1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
//! Command line module for one-shot service discovery

use actix::{Arbiter, System};
use actor::{MdnsActor, OneShot};
use clap::{App, Arg, ArgMatches, SubCommand};
use futures::Future;
use gu_base::{cli, Decorator, Module};
use service::{ServiceInstance, ServicesDescription};
use std::{collections::HashSet, net::Ipv4Addr};

fn format_addresses(addrs_v4: &Vec<Ipv4Addr>, ports: &Vec<u16>) -> String {
    let mut res = String::new();
    let addr = addrs_v4
        .first()
        .map(|ip| format!("{:?}", ip))
        .unwrap_or("<missing ip>".to_string());

    for port in ports {
        res.push_str(addr.as_ref());
        res.push(':');
        res.push_str(&format!("{}", &port));
    }

    res
}

pub fn format_instances_table(instances: &HashSet<ServiceInstance>) {
    cli::format_table(
        row!["Service type", "Host name", "Addresses", "Description"],
        || "No instances found",
        instances.iter().map(|instance| {
            row![
                instance.name,
                instance.host,
                format_addresses(&instance.addrs_v4, &instance.ports),
                instance.txt.join(""),
            ]
        }),
    )
}

fn run_client(instances: &String) {
    use actix::{self, SystemService};

    let sys = actix::System::new("gu-lan");
    let instances = instances.split(',').map(|s| s.to_string().into()).collect();

    let mdns_actor = MdnsActor::<OneShot>::from_registry();
    let query = ServicesDescription::new(instances);

    Arbiter::spawn(
        mdns_actor
            .send(query)
            .map_err(|e| error!("error! {}", e))
            .and_then(|r| r.map_err(|e| error!("error! {}", e)))
            .and_then(|r| Ok(format_instances_table(&r)))
            .map_err(|e| error!("error! {:?}", e))
            .then(|_| Ok(System::current().stop())),
    );

    let _ = sys.run();
}

enum LanCommand {
    None,
    List(String),
}

pub struct LanModule {
    command: LanCommand,
}

impl LanModule {
    pub fn module() -> LanModule {
        Self {
            command: LanCommand::None,
        }
    }
}

impl Module for LanModule {
    fn args_declare<'a, 'b>(&self, app: App<'a, 'b>) -> App<'a, 'b> {
        let instance = Arg::with_name("instance")
            .short("I")
            .help("queries mDNS server about some instance")
            .default_value("gu-hub,gu-provider");

        app.subcommand(
            SubCommand::with_name("lan")
                .subcommand(
                    SubCommand::with_name("list")
                        .about("Lists available instances")
                        .arg(instance),
                ).about("Lan services"),
        )
    }

    fn args_consume(&mut self, matches: &ArgMatches) -> bool {
        if let Some(m) = matches.subcommand_matches("lan") {
            self.command = match m.subcommand() {
                ("list", Some(m)) => {
                    let instance = m
                        .value_of("instance")
                        .expect("Lack of required `instance` argument")
                        .to_string();
                    LanCommand::List(instance)
                }
                _ => return false,
            };
            true
        } else {
            false
        }
    }

    fn run<D: Decorator + Clone + 'static>(&self, _decorator: D) {
        match self.command {
            LanCommand::List(ref s) => run_client(s),
            _ => (),
        }
    }
}