diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4231c347..71ade0b2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -194,6 +194,7 @@ jobs: run: | cargo test -p=playdate-build-utils --all-features cargo test -p=playdate-build --all-features + cargo test -p=playdate-device cargo test -p=playdate-tool --all-features tool: diff --git a/Cargo.lock b/Cargo.lock index 5c42d894..748e9736 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -160,7 +160,7 @@ checksum = "136d4d23bcc79e27423727b36823d86233aad06dfea531837b038394d11e9928" dependencies = [ "concurrent-queue", "event-listener 5.3.0", - "event-listener-strategy 0.5.1", + "event-listener-strategy 0.5.2", "futures-core", "pin-project-lite", ] @@ -173,7 +173,7 @@ checksum = "b10202063978b3351199d68f8b22c4e47e4b1b822f8d43fd862d5ea8c006b29a" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.0.2", + "fastrand 2.1.0", "futures-lite 2.3.0", "slab", ] @@ -339,9 +339,9 @@ dependencies = [ [[package]] name = "async-task" -version = "4.7.0" +version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" @@ -523,18 +523,16 @@ dependencies = [ [[package]] name = "blocking" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" +checksum = "495f7104e962b7356f0aeb34247aca1fe7d2e783b346582db7f2904cb5717e88" dependencies = [ "async-channel 2.2.1", "async-lock 3.3.0", "async-task", - "fastrand 2.0.2", "futures-io", "futures-lite 2.3.0", "piper", - "tracing", ] [[package]] @@ -743,7 +741,7 @@ dependencies = [ [[package]] name = "cargo-playdate" -version = "0.4.7" +version = "0.4.8" dependencies = [ "anstyle", "anyhow", @@ -775,7 +773,7 @@ dependencies = [ "toml_edit 0.22.12", "try-lazy-init", "walkdir", - "zip 1.1.1", + "zip 1.1.2", ] [[package]] @@ -973,9 +971,9 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] @@ -1232,7 +1230,7 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "socket2 0.5.6", + "socket2 0.5.7", "windows-sys 0.52.0", ] @@ -1554,9 +1552,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "332f51cb23d20b0de8458b86580878211da09bcd4503cb579c225b3d124cabb3" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ "event-listener 5.3.0", "pin-project-lite", @@ -1597,9 +1595,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "ff" @@ -1631,9 +1629,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "libz-ng-sys", @@ -1733,7 +1731,7 @@ checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" dependencies = [ "futures-core", "lock_api", - "parking_lot 0.12.1", + "parking_lot 0.12.2", ] [[package]] @@ -1763,7 +1761,7 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ - "fastrand 2.0.2", + "fastrand 2.1.0", "futures-core", "futures-io", "parking", @@ -1920,7 +1918,7 @@ dependencies = [ "gix-validate", "gix-worktree", "once_cell", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "prodash", "smallvec", "thiserror", @@ -2106,7 +2104,7 @@ dependencies = [ "gix-trace", "libc", "once_cell", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "prodash", "sha1_smol", "thiserror", @@ -2172,8 +2170,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ddf80e16f3c19ac06ce415a38b8591993d3f73aede049cb561becb5b3a8e242" dependencies = [ "gix-hash", - "hashbrown 0.14.3", - "parking_lot 0.12.1", + "hashbrown 0.14.5", + "parking_lot 0.12.2", ] [[package]] @@ -2284,7 +2282,7 @@ dependencies = [ "gix-pack", "gix-path", "gix-quote", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "tempfile", "thiserror", ] @@ -2304,7 +2302,7 @@ dependencies = [ "gix-path", "gix-tempfile", "memmap2", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "smallvec", "thiserror", ] @@ -2369,7 +2367,7 @@ checksum = "f5325eb17ce7b5e5d25dec5c2315d642a09d55b9888b3bf46b7d72e1621a55d8" dependencies = [ "gix-command", "gix-config-value", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "rustix 0.38.34", "thiserror", ] @@ -2505,7 +2503,7 @@ dependencies = [ "gix-fs", "libc", "once_cell", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "tempfile", ] @@ -2570,7 +2568,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35192df7fd0fa112263bad8021e2df7167df4cc2a6e6d15892e1e55621d3d4dc" dependencies = [ - "fastrand 2.0.2", + "fastrand 2.1.0", "unicode-normalization", ] @@ -2682,9 +2680,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", "allocator-api2", @@ -2696,7 +2694,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -2860,7 +2858,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.6", + "socket2 0.5.7", "tokio", "tower-service", "tracing", @@ -2942,7 +2940,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -3291,9 +3289,9 @@ checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -3791,12 +3789,12 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" dependencies = [ "lock_api", - "parking_lot_core 0.9.9", + "parking_lot_core 0.9.10", ] [[package]] @@ -3815,15 +3813,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.4.1", + "redox_syscall 0.5.1", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] @@ -4024,7 +4022,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" dependencies = [ "atomic-waker", - "fastrand 2.0.2", + "fastrand 2.1.0", "futures-io", ] @@ -4138,7 +4136,7 @@ dependencies = [ [[package]] name = "playdate-device" -version = "0.2.4" +version = "0.2.8" dependencies = [ "async-std", "clap", @@ -4279,7 +4277,7 @@ dependencies = [ [[package]] name = "playdate-tool" -version = "0.3.2" +version = "0.3.3" dependencies = [ "clap", "console-subscriber", @@ -4417,7 +4415,7 @@ version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79" dependencies = [ - "parking_lot 0.12.1", + "parking_lot 0.12.2", ] [[package]] @@ -4563,6 +4561,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags 2.5.0", +] + [[package]] name = "redox_users" version = "0.4.5" @@ -4797,9 +4804,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.198" +version = "1.0.199" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" +checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a" dependencies = [ "serde_derive", ] @@ -4826,9 +4833,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.198" +version = "1.0.199" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" +checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc" dependencies = [ "proc-macro2", "quote", @@ -5034,9 +5041,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -5201,7 +5208,7 @@ checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" dependencies = [ "new_debug_unreachable", "once_cell", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "phf_shared 0.10.0", "precomputed-hash", "serde", @@ -5302,7 +5309,7 @@ dependencies = [ "nom", "nom-supreme", "once_cell", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "pdb-addr2line", "regex", "scroll", @@ -5402,7 +5409,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", - "fastrand 2.0.2", + "fastrand 2.1.0", "rustix 0.38.34", "windows-sys 0.52.0", ] @@ -5528,10 +5535,10 @@ dependencies = [ "libc", "mio", "num_cpus", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.6", + "socket2 0.5.7", "tokio-macros", "tracing", "windows-sys 0.48.0", @@ -5640,7 +5647,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.6", + "winnow 0.6.7", ] [[package]] @@ -5865,9 +5872,9 @@ checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" [[package]] name = "unicode-xid" @@ -5900,9 +5907,9 @@ checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" [[package]] name = "usb-ids" -version = "1.2024.2" +version = "1.2024.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f552b841f639685a742820252cd3d0d04ae03ddae685c49a16a09f4a8424d6" +checksum = "ccaea350f6514f904c435feea0ea5a6cecdcb47aaf377745423c1694a01f5785" dependencies = [ "nom", "phf 0.11.2", @@ -6137,9 +6144,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134306a13c5647ad6453e8deaec55d3a44d6021970129e6188735e74bf546697" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ "windows-sys 0.52.0", ] @@ -6406,9 +6413,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352" +checksum = "14b9415ee827af173ebb3f15f9083df5a122eb93572ec28741fb153356ea2578" dependencies = [ "memchr", ] @@ -6452,9 +6459,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63381fa6624bf92130a6b87c0d07380116f80b565c42cf0d754136f0238359ef" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" [[package]] name = "zip" @@ -6470,9 +6477,9 @@ dependencies = [ [[package]] name = "zip" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2655979068a1f8fa91cb9e8e5b9d3ee54d18e0ddc358f2f4a395afc0929a84b" +checksum = "2a23468b4a7a4e9e1e62812f8d1dd614f67f148e2b3faa72f4843bf00b573fd7" dependencies = [ "aes", "arbitrary", diff --git a/cargo/Cargo.toml b/cargo/Cargo.toml index 8df9403f..9ed04c64 100644 --- a/cargo/Cargo.toml +++ b/cargo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-playdate" -version = "0.4.7" +version = "0.4.8" readme = "README.md" description = "Build tool for neat yellow console." keywords = ["playdate", "build", "cargo", "plugin", "cargo-subcommand"] diff --git a/support/device/Cargo.toml b/support/device/Cargo.toml index a103cc7b..3089be14 100644 --- a/support/device/Cargo.toml +++ b/support/device/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "playdate-device" -version = "0.2.4" +version = "0.2.8" readme = "README.md" description = "Cross-platform interface Playdate device, async & blocking." keywords = ["playdate", "usb", "serial"] @@ -95,8 +95,8 @@ features = [ [features] default = ["tokio", "tokio-serial"] tokio-serial = ["futures", "dep:tokio-serial", "tokio?/io-util", "tokio?/rt"] -tokio = ["futures", "dep:tokio", "async-std?/tokio1"] # use async-std reactor -async-std = ["futures", "dep:async-std"] # use async-std reactor and features +tokio = ["futures", "dep:tokio", "async-std?/tokio1"] # use tokio reactor +async-std = ["futures", "dep:async-std"] # use async-std reactor [package.metadata.docs.rs] diff --git a/support/device/src/device/serial.rs b/support/device/src/device/serial.rs index cc663a5e..4457762a 100644 --- a/support/device/src/device/serial.rs +++ b/support/device/src/device/serial.rs @@ -34,6 +34,9 @@ impl SerialNumber { s } } + + + pub fn as_str(&self) -> &str { &self.0 } } impl FromStr for SerialNumber { @@ -67,10 +70,18 @@ impl PartialEq for SerialNumber { impl> PartialEq for SerialNumber { fn eq(&self, other: &T) -> bool { let other = other.as_ref().to_uppercase(); - self.0.contains(&other) || other.contains(&self.0) + other.len() >= 3 && (self.0.contains(&other) || other.contains(&self.0)) } } +// Commutative pares fore above +impl PartialEq for &str { + fn eq(&self, sn: &SerialNumber) -> bool { sn.eq(self) } +} +impl PartialEq for String { + fn eq(&self, sn: &SerialNumber) -> bool { sn.eq(self) } +} + impl std::fmt::Debug for SerialNumber { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_tuple("Serial").field(&self.0).finish() @@ -112,3 +123,70 @@ pub mod error { fn from(value: &str) -> Self { Self::new(value.to_owned()) } } } + + +#[cfg(test)] +mod tests { + use super::*; + + const SN: &str = "PDU0-X000042"; + const SN_UNDERSCORE: &str = "PDU0_X000042"; + const SN_FORMS: &[&str] = &[SN, SN_UNDERSCORE]; + + const PATHS: &[&str] = &["/dev/cu.usbmodem", "other/path/", "/", ""]; + + #[test] + fn from_str() { + let sn = SerialNumber::from_str(SN).unwrap(); + let sn_ = SerialNumber::from_str(SN_UNDERSCORE).unwrap(); + assert_eq!(sn, sn_); + assert_eq!(sn.0, sn_.0); + assert_eq!(sn.as_str(), sn_.as_str()); + } + + #[test] + fn from_port_path() { + const SUFFIX: &[Option<&str>] = &[None, Some("0"), Some("1"), Some("2"), Some("42")]; + + for sn in SN_FORMS { + for suffix in SUFFIX { + let suffix = suffix.unwrap_or_default(); + for path in PATHS { + let path = format!("{path}{sn}{suffix}"); + println!("parsing {path}"); + let parsed = SerialNumber::from_str(&path).unwrap(); + assert!(parsed == SN); + assert!(SN == parsed); + } + } + } + } + + #[test] + fn from_port_path_nq() { + const SUFFIX: &[Option<&str>] = &[None, Some("0"), Some("1"), Some("2"), Some("42")]; + let sn_forms: &[String] = &[SN.replace("42", "11"), SN_UNDERSCORE.replace("42", "11")]; + + for sn in sn_forms { + for suffix in SUFFIX { + let suffix = suffix.unwrap_or_default(); + for path in PATHS { + let path = format!("{path}{sn}{suffix}"); + println!("parsing {path}"); + let parsed = SerialNumber::from_str(&path).unwrap(); + assert!(parsed != SN); + assert!(SN != parsed); + } + } + } + } + + #[test] + fn invalid() { + assert!(SerialNumber::from_str("").is_err()); + assert!(SerialNumber::from_str("PDU").is_err()); + assert!(SerialNumber::from_str("001").is_err()); + assert!(SerialNumber::from_str("001-00000").is_err()); + assert!(SerialNumber::from_str("PDU0--AAAAAAA").is_err()); + } +} diff --git a/support/device/src/install.rs b/support/device/src/install.rs index e64a5970..d953f55d 100644 --- a/support/device/src/install.rs +++ b/support/device/src/install.rs @@ -1,6 +1,5 @@ use std::ffi::OsStr; use std::path::{Path, PathBuf}; -use std::time::Duration; use futures::{FutureExt, Stream, StreamExt, TryFutureExt}; @@ -8,7 +7,6 @@ use crate::device::query::Query; use crate::error::Error; use crate::mount::MountedDevice; use crate::mount; -use crate::retry::Retries; type Result = std::result::Result; @@ -73,8 +71,8 @@ pub async fn install<'dev>(drive: &'dev MountedDevice, use std::process::Command; - let retry = Retries::new(Duration::from_millis(500), Duration::from_secs(60)); - mount::wait_fs_available(drive, retry).await?; + let retry = mount::fs_available_wait_time(); + mount::wait_fs_available_with_user(drive, retry).await?; validate_host_package(path).await?; trace!( diff --git a/support/device/src/mount/mac.rs b/support/device/src/mount/mac.rs index 498e984f..4b135bf4 100644 --- a/support/device/src/mount/mac.rs +++ b/support/device/src/mount/mac.rs @@ -166,13 +166,31 @@ fn spusb(filter: F) let output = Command::new("system_profiler").args(["-json", "SPUSBDataType"]) .output()?; output.status.exit_ok()?; + parse_spusb(filter, &output.stdout) +} + - let data: SystemProfilerResponse = serde_json::from_reader(&output.stdout[..])?; +fn parse_spusb( + filter: F, + data: &[u8]) + -> Result>>>, Error> + where F: FnMut(&DeviceInfo) -> bool +{ + let data: SystemProfilerResponse = serde_json::from_slice(data)?; let result = data.data .into_iter() .filter_map(|c| c.items) .flatten() + .filter_map(|item| { + match item { + AnyDeviceInfo::Known(info) => Some(info), + AnyDeviceInfo::Other { name, .. } => { + trace!("Skip {name}"); + None + }, + } + }) .filter(|item| item.vendor_id == VENDOR_ID_ENC) .filter(filter) .filter_map(|item| { @@ -188,11 +206,16 @@ fn spusb(filter: F) trace!("found mount-point: {}", path.display()); Some(futures_lite::future::ready(Ok(path)).left_future()) } else { - let path = Path::new("/Volumes").join(&par.name); - if path.exists() { - trace!("existing, by name: {}", path.display()); - Some(futures_lite::future::ready(Ok(path)).left_future()) - } else if par.volume_uuid.is_some() { + // This is ok for just one connected PD, + // Otherwise, it can be mount of other PD, but not this PD. + // Just commented for future and maybe could be configurable later. + // Issue: #332 + // let path = Path::new("/Volumes").join(&par.name); + // if !par.name.trim().is_empty() && path.exists() { + // trace!("existing, by name: {}", path.display()); + // Some(futures_lite::future::ready(Ok(path)).left_future()) + // } else + if par.volume_uuid.is_some() { trace!("not mounted yet, create resolver fut"); Some(mount_point_for_partition(par).right_future()) } else { @@ -218,16 +241,20 @@ async fn mount_point_for_partition(par: MediaPartitionInfo) -> Result Result { + let info: DiskUtilResponse = plist::from_bytes(data)?; + info.mount_point + .filter(|s| !s.trim().is_empty()) + .ok_or(Error::MountNotFound(format!("{} {}", par.name, par.bsd_name))) + .map(PathBuf::from) +} + #[derive(Deserialize, Debug)] struct DiskUtilResponse { @@ -246,7 +273,22 @@ struct SystemProfilerResponse { #[derive(Deserialize, Debug)] struct ControllerInfo { #[serde(rename = "_items")] - items: Option>, + items: Option>, +} + + +/// Flatten untagged enum, +/// represents normal `DeviceInfo` +/// and any other not-complete `DeviceInfo`, +/// e.g. without serial-number. +#[derive(Deserialize, Debug)] +#[serde(untagged)] +enum AnyDeviceInfo { + Known(DeviceInfo), + Other { + #[serde(rename = "_name")] + name: String, + }, } #[derive(Deserialize, Debug)] @@ -275,3 +317,242 @@ pub struct MediaPartitionInfo { volume_uuid: Option, mount_point: Option, } + + +#[cfg(test)] +mod tests { + use std::path::Path; + + use futures::FutureExt; + use super::MediaPartitionInfo; + use super::parse_spusb; + use super::parse_diskutil_info; + + + #[test] + fn parse_spusb_not_mount() { + let data = r#" + { + "SPUSBDataType" : [ + { + "_items" : [ + { + "_name" : "Playdate", + "serial_num" : "PDU1-Y000042", + "vendor_id" : "0x1331" + } + ] + } + ] + } + "#; + + let res = parse_spusb(|_| true, data.as_bytes()).unwrap().count(); + assert_eq!(0, res); + } + + + #[test] + fn parse_spusb_mount_complete() { + let data = r#" + { + "SPUSBDataType" : [ + { + "_items" : [ + { + "_name" : "Playdate", + "Media" : [ + { + "volumes" : [ + { + "_name" : "PLAYDATE", + "bsd_name" : "disk9s1", + "mount_point" : "/Volumes/PLAYDATE", + "volume_uuid" : "1AA11111-111A-311A-11A1-1AA111A1A1A1" + } + ] + } + ], + "serial_num" : "PDU1-Y000042", + "vendor_id" : "0x1331" + } + ] + } + ] + } + "#; + + let dev = { + let mut devs: Vec<_> = parse_spusb(|_| true, data.as_bytes()).unwrap().collect(); + assert_eq!(1, devs.len()); + devs.pop().unwrap() + }; + + assert_eq!(dev.name, "Playdate"); + assert_eq!(dev.serial, "PDU1-Y000042"); + + let vol = dev.volume.now_or_never().unwrap().unwrap(); + assert_eq!("/Volumes/PLAYDATE", vol.to_string_lossy()); + } + + /// Tests parsing doc with multiple devices with one "dev of interest" + /// that with serial number. + #[test] + fn parse_spusb_mount_others() { + let data = r#" + { + "SPUSBDataType" : [ + { + "_items" : [ + { + "_name" : "with-sn", + "Media" : [ + { + "volumes" : [ + { + "_name" : "PLAYDATE", + "bsd_name" : "disk9s1", + "mount_point" : "/Volumes/PLAYDATE", + "volume_uuid" : "1AA11111-111A-311A-11A1-1AA111A1A1A1" + } + ] + } + ], + "serial_num" : "PDU1-Y000042", + "vendor_id" : "0x1331" + }, + { + "_name" : "with-sn-no-media", + "serial_num" : "PDU1-Y000042", + "vendor_id" : "0x1331" + }, + { + "_name" : "no-sn", + "Media" : [ + { + "volumes" : [ + { + "_name" : "PLAYDATE", + "bsd_name" : "disk9s1", + "mount_point" : "/Volumes/PLAYDATE", + "volume_uuid" : "1AA11111-111A-311A-11A1-1AA111A1A1A1" + } + ] + } + ], + "vendor_id" : "0x1331" + }, + { + "_name" : "no-sn", + "vendor_id" : "0x1331" + } + ] + } + ] + } + "#; + + let dev = { + let mut devs: Vec<_> = parse_spusb(|_| true, data.as_bytes()).unwrap().collect(); + assert_eq!(1, devs.len()); + devs.pop().unwrap() + }; + + assert_eq!(dev.name, "with-sn"); + assert_eq!(dev.serial, "PDU1-Y000042"); + + let vol = dev.volume.now_or_never().unwrap().unwrap(); + assert_eq!("/Volumes/PLAYDATE", vol.to_string_lossy()); + } + + + #[test] + fn parse_spusb_mount_incomplete() { + let data = r#" + { + "SPUSBDataType" : [ + { + "_items" : [ + { + "_name" : "Playdate", + "Media" : [ + { + "volumes" : [ + { + "_name" : "PLAYDATE", + "bsd_name" : "disk9s1", + "file_system" : "MS-DOS FAT32", + "iocontent" : "Windows_FAT_32", + "size" : "3.66 GB", + "size_in_bytes" : 3663724032, + "volume_uuid" : "1AA11111-111A-311A-11A1-1AA111A1A1A1" + } + ] + } + ], + "serial_num" : "PDU1-Y000042", + "vendor_id" : "0x1331" + } + ] + } + ] + } + "#; + + let dev = { + let mut devs: Vec<_> = parse_spusb(|_| true, data.as_bytes()).unwrap().collect(); + assert_eq!(1, devs.len()); + devs.pop().unwrap() + }; + + assert_eq!(dev.name, "Playdate"); + assert_eq!(dev.serial, "PDU1-Y000042"); + + let vol = dev.volume.now_or_never(); + assert!(matches!(vol, Some(Err(_)))); + } + + + #[test] + fn parse_diskutil_info_complete() { + let data = r#" + + + + + MountPoint + /Vols/NAME + + + "#; + + let partition = MediaPartitionInfo { name: "name".to_owned(), + bsd_name: "bsd_name".to_owned(), + volume_uuid: None, + mount_point: None }; + let path = parse_diskutil_info(&partition, data.as_bytes()).unwrap(); + assert_eq!(Path::new("/Vols/NAME"), path); + } + + + #[test] + fn parse_diskutil_info_incomplete() { + let data = r#" + + + + + MountPoint + + + + "#; + + let partition = MediaPartitionInfo { name: "name".to_owned(), + bsd_name: "bsd_name".to_owned(), + volume_uuid: None, + mount_point: None }; + let res = parse_diskutil_info(&partition, data.as_bytes()); + assert!(res.is_err()) + } +} diff --git a/support/device/src/mount/methods.rs b/support/device/src/mount/methods.rs index 24a802bd..8c33d3d3 100644 --- a/support/device/src/mount/methods.rs +++ b/support/device/src/mount/methods.rs @@ -97,6 +97,58 @@ pub async fn wait_fs_available(mount: &MountedDevice, retry: Retries) -> R } +/// Double wait time and notify user in between of halfs. +pub async fn wait_fs_available_with_user(mount: &MountedDevice, retry: Retries) -> Result + where T: Clone + std::fmt::Debug + IterTime { + match wait_fs_available(mount, retry).await { + Ok(_) => (), + Err(err) => { + error!("{err}"); + warn!( + "Still waiting mounted device at {}.", + mount.handle.volume.path().display() + ); + + let last_chance = fs_available_wait_time(); + wait_fs_available(mount, last_chance).await? + }, + } + + Ok(()) +} + + +pub const FS_AVAILABLE_AWAIT_ENV: &str = "PD_MOUNT_TIMEOUT"; +const FS_AVAILABLE_AWAIT_TIMEOUT: Duration = Duration::from_secs(60); + +pub fn fs_available_wait_time() -> Retries { + let t = match std::env::var_os(FS_AVAILABLE_AWAIT_ENV).map(|s| s.to_string_lossy().trim().parse::()) { + Some(Ok(v)) => Duration::from_millis(v as _), + Some(Err(err)) => { + error!("Invalid ms value of {FS_AVAILABLE_AWAIT_ENV}: {err}, using default timeout."); + FS_AVAILABLE_AWAIT_TIMEOUT + }, + None => FS_AVAILABLE_AWAIT_TIMEOUT, + }; + Retries::new(Duration::from_millis(200), t) +} + +pub const UNMOUNT_AWAIT_ENV: &str = "PD_UNMOUNT_TIMEOUT"; +const UNMOUNT_AWAIT_TIMEOUT: Duration = Duration::from_secs(60); + +pub fn unmount_wait_time() -> Retries { + let t = match std::env::var_os(UNMOUNT_AWAIT_ENV).map(|s| s.to_string_lossy().trim().parse::()) { + Some(Ok(v)) => Duration::from_millis(v as _), + Some(Err(err)) => { + error!("Invalid ms value of '{UNMOUNT_AWAIT_ENV}': {err}, using default timeout."); + UNMOUNT_AWAIT_TIMEOUT + }, + None => UNMOUNT_AWAIT_TIMEOUT, + }; + Retries::new(Duration::from_millis(100), t) +} + + #[cfg_attr(feature = "tracing", tracing::instrument())] pub async fn mount(query: Query) -> Result>> { match query.value { @@ -119,22 +171,20 @@ pub async fn mount(query: Query) -> Result Result>> { - let fut = - mount(query).await?.flat_map(move |res| { - async move { - match res { - Ok(drive) => { - if wait { - let retry = - Retries::new(Duration::from_millis(500), Duration::from_secs(60)); - wait_fs_available(&drive, retry).await? - } - Ok(drive) - }, - Err(err) => Err(err), - } - }.into_stream() - }); + let fut = mount(query).await?.flat_map(move |res| { + async move { + match res { + Ok(drive) => { + if wait { + let retry = fs_available_wait_time(); + wait_fs_available_with_user(&drive, retry).await? + } + Ok(drive) + }, + Err(err) => Err(err), + } + }.into_stream() + }); Ok(fut) } @@ -359,7 +409,7 @@ pub async fn unmount_and_wait(query: Query, retry: Retries) -> Result Result>> { let results = if wait { - let retry = Retries::::default(); + let retry = unmount_wait_time(); unmount_and_wait(query, retry).await?.left_stream() } else { unmount(query).await? diff --git a/support/device/src/serial/mod.rs b/support/device/src/serial/mod.rs index 4d0dfd14..dbca5121 100644 --- a/support/device/src/serial/mod.rs +++ b/support/device/src/serial/mod.rs @@ -1,8 +1,6 @@ use std::borrow::Cow; use std::cell::RefCell; -use crate::error::Error; - pub mod discover; mod blocking; @@ -72,7 +70,7 @@ impl Interface { pub fn set_port(&mut self, port: Port) { self.port = Some(RefCell::new(port)); } #[cfg_attr(feature = "tracing", tracing::instrument)] - pub fn open(&mut self) -> Result<(), Error> { + pub fn open(&mut self) -> Result<(), serialport::Error> { if self.port.is_some() { Ok(()) } else { @@ -89,7 +87,7 @@ impl Interface { #[cfg_attr(feature = "tracing", tracing::instrument)] -pub fn open<'a, S: Into>>(port_name: S) -> Result +pub fn open<'a, S: Into>>(port_name: S) -> Result where S: std::fmt::Debug { trace!("opening port {port_name:?}"); let builder = port_builder(port_name); diff --git a/support/device/src/usb/mod.rs b/support/device/src/usb/mod.rs index 98261a90..9601fe39 100644 --- a/support/device/src/usb/mod.rs +++ b/support/device/src/usb/mod.rs @@ -144,10 +144,11 @@ impl Device { // Special case: if we already have an interface, mostly possible serial: if self.interface.is_some() { - return match self.interface_mut()? { - crate::interface::Interface::Serial(i) => i.open(), - _ => Ok(()), + match self.interface_mut()? { + crate::interface::Interface::Serial(i) => i.open()?, + _ => {}, }; + return Ok(()); } if self.have_data_interface() { diff --git a/support/tool/Cargo.toml b/support/tool/Cargo.toml index 1f109b91..4de1ba3e 100644 --- a/support/tool/Cargo.toml +++ b/support/tool/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "playdate-tool" -version = "0.3.2" +version = "0.3.3" readme = "README.md" description = "Tool for interaction with Playdate device and sim." keywords = ["playdate", "usb", "utility"]