~liz/yooper

426232ca66f90e04fdc05f14fd28a65d52347e97 — Ellie Frost 2 years ago 4467fc5
Handy dandy CLI
4 files changed, 168 insertions(+), 22 deletions(-)

M Cargo.lock
M yooper/Cargo.toml
M yooper/src/description.rs
M yooper/src/main.rs
M Cargo.lock => Cargo.lock +63 -0
@@ 1,6 1,24 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
 "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "autocfg"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 36,6 54,20 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "clap"
version = "2.33.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
 "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
 "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
 "vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "core-foundation"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 774,6 806,11 @@ dependencies = [
]

[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "syn"
version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 797,6 834,14 @@ dependencies = [
]

[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "thiserror"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 908,6 953,11 @@ dependencies = [
]

[[package]]
name = "unicode-width"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "unicode-xid"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 933,6 983,11 @@ version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "version_check"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 1083,6 1138,7 @@ name = "yooper"
version = "0.1.0"
dependencies = [
 "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)",
 "futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
 "indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
 "mac_address 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",


@@ 1108,6 1164,8 @@ dependencies = [
]

[metadata]
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
"checksum base64 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "53d1ccbaf7d9ec9537465a97bf19edc1a4e158ecb49fc16178202238c569cc42"
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"


@@ 1115,6 1173,7 @@ dependencies = [
"checksum bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1"
"checksum cc 1.0.54 (registry+https://github.com/rust-lang/crates.io-index)" = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311"
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
"checksum clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129"
"checksum core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171"
"checksum core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac"
"checksum dtoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3"


@@ 1198,8 1257,10 @@ dependencies = [
"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
"checksum smallvec 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4"
"checksum socket2 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)" = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918"
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
"checksum syn 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)" = "b5304cfdf27365b7585c25d4af91b35016ed21ef88f17ced89c7093b43dba8b6"
"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
"checksum thiserror 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "b13f926965ad00595dd129fa12823b04bbf866e9085ab0a5f2b05b850fbfc344"
"checksum thiserror-impl 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "893582086c2f98cde18f906265a65b5030a074b1046c674ae898be6519a7f479"
"checksum time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"


@@ 1212,10 1273,12 @@ dependencies = [
"checksum unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
"checksum unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4"
"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
"checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb"
"checksum uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11"
"checksum vcpkg 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c"
"checksum vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
"checksum version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
"checksum want 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"

M yooper/Cargo.toml => yooper/Cargo.toml +6 -1
@@ 6,8 6,9 @@ edition = "2018"
license = "BSD-3-Clause"

[features]
default = ["description"]
default = ["description", "cli"]
description = ["reqwest", "serde", "serde_with", "serde-xml-rs"]
cli = ["clap"]

[dependencies]
bytes = "0.5.4"


@@ 46,4 47,8 @@ optional = true

[dependencies.reqwest]
version = "0.10"
optional = true

[dependencies.clap]
version = "2.33"
optional = true
\ No newline at end of file

M yooper/src/description.rs => yooper/src/description.rs +8 -8
@@ 12,9 12,9 @@ use std::str::FromStr;
#[derive(PartialEq, Debug)]
pub struct DeviceType {
    /// Will be None for standard devices specified by the UPnP Forum.
    vendor_domain: Option<String>,
    device_type: String,
    version: String,
    pub vendor_domain: Option<String>,
    pub device_type: String,
    pub version: String,
}

impl ToString for DeviceType {


@@ 54,9 54,9 @@ impl FromStr for DeviceType {
#[derive(Debug, PartialEq)]
pub struct ServiceType {
    /// Will be None for standard services specified by the UPnP Forum.
    vendor_domain: Option<String>,
    service_type: String,
    version: String,
    pub vendor_domain: Option<String>,
    pub service_type: String,
    pub version: String,
}

impl FromStr for ServiceType {


@@ 246,8 246,8 @@ where

/// Retrieve and parse a device description.
/// See the location field from [discovery::Device](../discovery/struct.Device.html#structfield.location).
pub async fn describe(location: String) -> Result<Device, Error> {
    let body = reqwest::get(&location).await?.text().await?;
pub async fn describe(location: &str) -> Result<Device, Error> {
    let body = reqwest::get(location).await?.text().await?;

    let document: Description = serde_xml_rs::from_str(&body)?;
    Ok(document.device)

M yooper/src/main.rs => yooper/src/main.rs +91 -13
@@ 1,22 1,53 @@
use yooper::{discovery::Discovery, Error};
#![cfg(feature = "cli")]

// const VERSION: &'static str = env!("CARGO_PKG_VERSION");
// const OS: &'static str = "linux"; //TODO
use clap::{
    app_from_crate, crate_authors, crate_description, crate_name, crate_version, Arg, SubCommand,
};
use yooper::Error;

// M-SEARCH * HTTP/1.1
//     HOST: 239.255.255.250:1900
//     MAN: "ssdp:discover"
//     MX: seconds to delay response
//     ST: search target
//     USER-AGENT: OS/version UPnP/2.0 product/version
//     CPFN.UPNP.ORG: friendly name of the control point
//     CPUUID.UPNP.ORG: uuid of the control point
fn validate_secs(v: String) -> Result<(), String> {
    let msg = "Please specify a number between 1 and 5";
    let v: u8 = v.parse().map_err(|_| msg.to_owned())?;

    match v {
        1..=5 => Ok(()),
        _ => Err(msg.to_owned()),
    }
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    let mut discovery = Discovery::new().await?;
    let args = app_from_crate!()
        .setting(clap::AppSettings::SubcommandRequiredElseHelp)
        .subcommands(vec![
            SubCommand::with_name("discover")
                     .about("discover UPnP devices on network")
                     .arg(Arg::with_name("timeout")
                          .short("t")
                          .long("timeout")
                          .takes_value(true)
                          .default_value("5")
                          .value_name("TIMEOUT")
                          .help("How long to wait for devices on the network to respond. 1..5 seconds per the UPnP spec.")
                          .validator(validate_secs)),
            #[cfg(feature = "description")]
            SubCommand::with_name("describe")
                .about("Describe a UPnP device's capabilities")
                .arg(Arg::with_name("url").help("The URL to describe").required(true)),
        ]).get_matches();

    match args.subcommand() {
        ("discover", Some(sub_m)) => discover(sub_m.value_of("timeout").unwrap().parse()?).await,
        #[cfg(feature = "description")]
        ("describe", Some(sub_m)) => describe::run(sub_m.value_of("url").unwrap()).await,
        _ => unreachable!(),
    }
}

    for result in discovery.find(5).await? {
async fn discover(secs: u8) -> Result<(), Error> {
    let mut discovery = yooper::discovery::Discovery::new().await?;

    for result in discovery.find(secs).await? {
        println!("{} at {}", result.server, result.location);
        for service in result.services {
            println!("∟ {:?}", service.target)


@@ 24,3 55,50 @@ async fn main() -> Result<(), Error> {
    }
    Ok(())
}

#[cfg(feature = "description")]
mod describe {
    use yooper::description::{describe, Device};
    use yooper::Error;

    pub async fn run(url: &str) -> Result<(), Error> {
        describe(url).await.and_then(|d| {
            print_device(d, 0);
            Ok(())
        })
    }

    fn print_device(device: Device, indent: u8) {
        let prefix = if indent == 0 {
            "".into()
        } else {
            format!("{}∟ ", "".repeat((indent - 1).into()))
        };
        println!(
            "{}{}{}:{}",
            prefix,
            device
                .device_type
                .vendor_domain
                .map_or("".into(), |v| format!("{}: ", v)),
            device.device_type.device_type,
            device.device_type.version,
        );
        for svc in device.services {
            println!(
                "{}{}{}:{} -> {}",
                prefix,
                svc.service_type
                    .vendor_domain
                    .map_or("".into(), |v| format!("{}: ", v)),
                svc.service_type.service_type,
                svc.service_type.version,
                svc.control_url,
            )
        }

        for dvc in device.devices {
            print_device(dvc, indent + 1)
        }
    }
}