<commit_before>// Copyright 2017 The Exonum Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#![allow(missing_debug_implementations)]

//! This module implement all core commands.
use toml::Value;

use std::fs;
use std::path::Path;
use std::net::SocketAddr;
use std::collections::BTreeMap;

use blockchain::GenesisConfig;
use helpers::generate_testnet_config;
use helpers::config::ConfigFile;
use node::{NodeConfig, NodeApiConfig};
use storage::Database;
use crypto;

use blockchain::config::ValidatorKeys;

use super::internal::{Command, Feedback};
use super::{Argument, Context, CommandName};

use super::shared::{AbstractConfig, NodePublicConfig, SharedConfig, NodePrivateConfig,
                    CommonConfigTemplate};
use super::DEFAULT_EXONUM_LISTEN_PORT;

/// Run command.
pub struct Run;

impl Run {
    /// Returns the name of the `Run` command.
    pub fn name() -> CommandName {
        "run"
    }

    /// Returns created database instance.
    pub fn db_helper(ctx: &Context) -> Box<Database> {
        use storage::{RocksDB, RocksDBOptions};

        let path = ctx.arg::<String>("ROCKSDB_PATH").expect(
            "ROCKSDB_PATH not found.",
        );
        let mut options = RocksDBOptions::default();
        options.create_if_missing(true);
        Box::new(RocksDB::open(Path::new(&path), &options).unwrap())
    }

    fn node_config(ctx: &Context) -> NodeConfig {
        let path = ctx.arg::<String>("NODE_CONFIG_PATH").expect(
            "NODE_CONFIG_PATH not found.",
        );
        ConfigFile::load(path).unwrap()
    }

    fn public_api_address(ctx: &Context) -> Option<SocketAddr> {
        ctx.arg("PUBLIC_API_ADDRESS").ok()
    }

    fn private_api_address(ctx: &Context) -> Option<SocketAddr> {
        ctx.arg("PRIVATE_API_ADDRESS").ok()
    }
}

impl Command for Run {
    fn args(&self) -> Vec<Argument> {
        vec![
            Argument::new_named(
                "NODE_CONFIG_PATH",
                true,
                "Path to node configuration file.",
                "c",
                "node-config",
                false
            ),
            Argument::new_named(
                "ROCKSDB_PATH",
                true,
                "Use rocksdb database with the given path.",
                "d",
                "rocksdb",
                false
            ),
            Argument::new_named(
                "PUBLIC_API_ADDRESS",
                false,
                "Listen address for public api.",
                None,
                "public-api-address",
                false
            ),
            Argument::new_named(
                "PRIVATE_API_ADDRESS",
                false,
                "Listen address for private api.",
                None,
                "private-api-address",
                false
            ),
        ]
    }

    fn name(&self) -> CommandName {
        Self::name()
    }

    fn about(&self) -> &str {
        "Run application"
    }

    fn execute(&self, mut context: Context, exts: &Fn(Context) -> Context) -> Feedback {
        let config = Self::node_config(&context);
        let public_addr = Self::public_api_address(&context);
        let private_addr = Self::private_api_address(&context);

        context.set("node_config", config);
        let mut new_context = exts(context);
        let mut config: NodeConfig = new_context.get("node_config").expect(
            "cant load node_config",
        );
        // Override api options

        config.api.public_api_address = public_addr;

        config.api.private_api_address = private_addr;

        new_context.set("node_config", config);

        Feedback::RunNode(new_context)
    }
}

/// Command for the template generation.
pub struct GenerateCommonConfig;

impl GenerateCommonConfig {
    /// Returns the name of the `GenerateCommonConfig` command.
    pub fn name() -> CommandName {
        "generate-template"
    }
}

impl Command for GenerateCommonConfig {
    fn args(&self) -> Vec<Argument> {
        vec![Argument::new_positional("COMMON_CONFIG", true, "Path to common config.")]
    }

    fn name(&self) -> CommandName {
        Self::name()
    }

    fn about(&self) -> &str {
        "Generate basic config template."
    }

    fn execute(&self, mut context: Context, exts: &Fn(Context) -> Context) -> Feedback {
        let template_path = context.arg::<String>("COMMON_CONFIG").expect(
            "COMMON_CONFIG not found",
        );

        context.set("services_config", AbstractConfig::default());
        let new_context = exts(context);
        let services_config = new_context.get("services_config").unwrap_or_default();

        let template = CommonConfigTemplate {
            services_config,
            ..CommonConfigTemplate::default()
        };

        ConfigFile::save(&template, template_path).expect("Could not write template file.");
        Feedback::None
    }
}

/// Command for the node configuration generation.
pub struct GenerateNodeConfig;

impl GenerateNodeConfig {
    /// Returns the name of the `GenerateNodeConfig` command.
    pub fn name() -> CommandName {
        "generate-config"
    }

    fn addr(context: &Context) -> (SocketAddr, SocketAddr) {
        let addr = context.arg::<String>("PEER_ADDR").unwrap_or_default();

        let mut addr_parts = addr.split(':');
        let ip = addr_parts.next().expect("Expected ip address");
        if ip.len() < 8 {
            panic!("Expected ip address in PEER_ADDR.")
        }
        let port = addr_parts.next().map_or(DEFAULT_EXONUM_LISTEN_PORT, |s| {
            s.parse().expect("could not parse port")
        });
        let external_addr = format!("{}:{}", ip, port);
        let listen_addr = format!("0.0.0.0:{}", port);
        (external_addr.parse().unwrap(), listen_addr.parse().unwrap())
    }
}

impl Command for GenerateNodeConfig {
    fn args(&self) -> Vec<Argument> {
        vec![
            Argument::new_positional("COMMON_CONFIG", true, "Path to common config."),
            Argument::new_positional("PUB_CONFIG", true, "Path where save public config."),
            Argument::new_positional("SEC_CONFIG", true, "Path where save private config."),
            Argument::new_named(
                "PEER_ADDR",
                true,
                "Remote peer address",
                "a",
                "peer-addr",
                false
            ),
        ]
    }

    fn name(&self) -> CommandName {
        Self::name()
    }

    fn about(&self) -> &str {
        "Generate node secret and public configs."
    }

    fn execute(&self, mut context: Context, exts: &Fn(Context) -> Context) -> Feedback {
        let common_config_path = context.arg::<String>("COMMON_CONFIG").expect(
            "expected common config path",
        );
        let pub_config_path = context.arg::<String>("PUB_CONFIG").expect(
            "expected public config path",
        );
        let priv_config_path = context.arg::<String>("SEC_CONFIG").expect(
            "expected secret config path",
        );

        let addr = Self::addr(&context);
        let common: CommonConfigTemplate =
            ConfigFile::load(&common_config_path).expect("Could not load common config");
        context.set("common_config", common.clone());
        context.set(
            "services_public_configs",
            BTreeMap::<String, Value>::default(),
        );
        context.set(
            "services_secret_configs",
            BTreeMap::<String, Value>::default(),
        );
        let new_context = exts(context);
        let services_public_configs = new_context.get("services_public_configs").unwrap();
        let services_secret_configs = new_context.get("services_secret_configs");

        let (consensus_public_key, consensus_secret_key) = crypto::gen_keypair();
        let (service_public_key, service_secret_key) = crypto::gen_keypair();

        let validator_keys = ValidatorKeys {
            consensus_key: consensus_public_key,
            service_key: service_public_key,
        };
        let node_pub_config = NodePublicConfig {
            addr: addr.0,
            validator_keys,
            services_public_configs,
        };
        let shared_config = SharedConfig {
            node: node_pub_config,
            common: common,
        };
        // Save public config separately.
        ConfigFile::save(&shared_config, &pub_config_path).expect(
            "Could not write public config file.",
        );

        let priv_config = NodePrivateConfig {
            listen_addr: addr.1,
            consensus_public_key,
            consensus_secret_key,
            service_public_key,
            service_secret_key,
            services_secret_configs: services_secret_configs.expect(
                "services_secret_configs not found after exts call",
            ),
        };

        ConfigFile::save(&priv_config, priv_config_path).expect(
            "Could not write secret config file.",
        );
        Feedback::None
    }
}

/// Finalize command.
pub struct Finalize;

impl Finalize {
    /// Returns the name of the `Finalize` command.
    pub fn name() -> CommandName {
        "finalize"
    }

    /// Returns `GenesisConfig` from the template.
    fn genesis_from_template(
        template: CommonConfigTemplate,
        configs: &[NodePublicConfig],
    ) -> GenesisConfig {
        GenesisConfig::new_with_consensus(
            template.consensus_config,
            configs.iter().map(|c| c.validator_keys),
        )
    }

    fn reduce_configs(
        public_configs: Vec<SharedConfig>,
        our_config: &NodePrivateConfig,
    ) -> (CommonConfigTemplate, Vec<NodePublicConfig>, Option<NodePublicConfig>) {
        let mut map = BTreeMap::new();
        let mut config_iter = public_configs.into_iter();
        let first = config_iter.next().expect(
            "Expected at least one config in PUBLIC_CONFIGS",
        );
        let common = first.common;
        map.insert(first.node.validator_keys.consensus_key, first.node);

        for config in config_iter {
            if common != config.common {
                panic!("Found config with different common part.");
            };
            if map.insert(config.node.validator_keys.consensus_key, config.node)
                .is_some()
            {
                panic!("Found duplicate consenus keys in PUBLIC_CONFIGS");
            }

        }
        (
            common,
            map.iter().map(|(_, c)| c.clone()).collect(),
            map.get(&our_config.consensus_public_key).cloned(),
        )
    }
}

impl Command for Finalize {
    fn args(&self) -> Vec<Argument> {
        vec![
            Argument::new_named(
                "PUBLIC_CONFIGS",
                true,
                "Path to validators public configs",
                "p",
                "public-configs",
                true
            ),
            Argument::new_named(
                "PUBLIC_API_ADDRESS",
                false,
                "Listen address for public api.",
                None,
                "public-api-address",
                false
            ),
            Argument::new_named(
                "PRIVATE_API_ADDRESS",
                false,
                "Listen address for private api.",
                None,
                "private-api-address",
                false
            ),
            Argument::new_positional("SECRET_CONFIG", true, "Path to our secret config."),
            Argument::new_positional("OUTPUT_CONFIG_PATH", true, "Path to output node config."),
        ]
    }

    fn name(&self) -> CommandName {
        Self::name()
    }

    fn about(&self) -> &str {
        "Collect public and secret configs into node config."
    }

    fn execute(&self, mut context: Context, exts: &Fn(Context) -> Context) -> Feedback {
        let public_configs_path = context.arg_multiple::<String>("PUBLIC_CONFIGS").expect(
            "keychain path not found",
        );
        let secret_config_path = context.arg::<String>("SECRET_CONFIG").expect(
            "config path not found",
        );
        let output_config_path = context.arg::<String>("OUTPUT_CONFIG_PATH").expect(
            "config path not found",
        );

        let public_addr = Run::public_api_address(&context);
        let private_addr = Run::private_api_address(&context);

        let secret_config: NodePrivateConfig =
            ConfigFile::load(secret_config_path).expect("Failed to load key config.");
        let public_configs: Vec<SharedConfig> = public_configs_path
            .into_iter()
            .map(|path| {
                ConfigFile::load(path).expect("Failed to load validator public config.")
            })
            .collect();
        let (common, list, our) = Self::reduce_configs(public_configs, &secret_config);

        context.set("auditor_mode", our.is_none());

        let peers = list.iter().map(|c| c.addr).collect();

        let genesis = Self::genesis_from_template(common.clone(), &list);

        let config = {
            NodeConfig {
                listen_address: secret_config.listen_addr,
                external_address: our.map(|o| o.addr),
                network: Default::default(),
                whitelist: Default::default(),
                peers: peers,
                consensus_public_key: secret_config.consensus_public_key,
                consensus_secret_key: secret_config.consensus_secret_key,
                service_public_key: secret_config.service_public_key,
                service_secret_key: secret_config.service_secret_key,
                genesis: genesis,
                api: NodeApiConfig {
                    public_api_address: public_addr,
                    private_api_address: private_addr,
                    ..Default::default()
                },
                mempool: Default::default(),
                services_configs: Default::default(),
            }
        };

        context.set("public_config_list", list);
        context.set("node_config", config);
        context.set("common_config", common);
        context.set(
            "services_secret_configs",
            secret_config.services_secret_configs,
        );

        let new_context = exts(context);

        let config: NodeConfig = new_context.get("node_config").expect(
            "Could not create config from template, services return error",
        );
        ConfigFile::save(&config, output_config_path).expect("Could not write config file.");

        Feedback::None
    }
}

/// Command for the testnet generation.
pub struct GenerateTestnet;

impl GenerateTestnet {
    /// Returns the name of the `GenerateTestnet` command.
    pub fn name() -> CommandName {
        "generate-testnet"
    }
}

impl Command for GenerateTestnet {
    fn args(&self) -> Vec<Argument> {
        vec![
            Argument::new_named(
                "OUTPUT_DIR",
                true,
                "Path to directory where save configs.",
                "o",
                "output_dir",
                false
            ),
            Argument::new_named(
                "START_PORT",
                false,
                "Port number started from which should validators listen.",
                "p",
                "start",
                false
            ),
            Argument::new_positional("COUNT", true, "Count of validators in testnet."),
        ]
    }

    fn name(&self) -> CommandName {
        Self::name()
    }

    fn about(&self) -> &str {
        "Generates genesis configuration for testnet"
    }

    fn execute(&self, mut context: Context, exts: &Fn(Context) -> Context) -> Feedback {

        let dir = context.arg::<String>("OUTPUT_DIR").expect("output dir");
        let count: u8 = context.arg("COUNT").expect("count as int");
        let start_port = context.arg::<u16>("START_PORT").unwrap_or(
            DEFAULT_EXONUM_LISTEN_PORT,
        );

        if count == 0 {
            panic!("Can't generate testnet with zero nodes count.");
        }

        let dir = Path::new(&dir);
        let dir = dir.join("validators");
        if !dir.exists() {
            fs::create_dir_all(&dir).unwrap();
        }

        let configs = generate_testnet_config(count, start_port);
        context.set("configs", configs);
        let new_context = exts(context);
        let configs: Vec<NodeConfig> = new_context.get("configs").expect(
            "Couldn't read testnet configs after exts call.",
        );

        for (idx, cfg) in configs.into_iter().enumerate() {
            let file_name = format!("{}.toml", idx);
            ConfigFile::save(&cfg, &dir.join(file_name)).unwrap();
        }

        Feedback::None
    }
}
<commit_msg>Fixed api listener config<commit_after>// Copyright 2017 The Exonum Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#![allow(missing_debug_implementations)]

//! This module implement all core commands.
use toml::Value;

use std::fs;
use std::path::Path;
use std::net::SocketAddr;
use std::collections::BTreeMap;

use blockchain::GenesisConfig;
use helpers::generate_testnet_config;
use helpers::config::ConfigFile;
use node::{NodeConfig, NodeApiConfig};
use storage::Database;
use crypto;

use blockchain::config::ValidatorKeys;

use super::internal::{Command, Feedback};
use super::{Argument, Context, CommandName};

use super::shared::{AbstractConfig, NodePublicConfig, SharedConfig, NodePrivateConfig,
                    CommonConfigTemplate};
use super::DEFAULT_EXONUM_LISTEN_PORT;

/// Run command.
pub struct Run;

impl Run {
    /// Returns the name of the `Run` command.
    pub fn name() -> CommandName {
        "run"
    }

    /// Returns created database instance.
    pub fn db_helper(ctx: &Context) -> Box<Database> {
        use storage::{RocksDB, RocksDBOptions};

        let path = ctx.arg::<String>("ROCKSDB_PATH").expect(
            "ROCKSDB_PATH not found.",
        );
        let mut options = RocksDBOptions::default();
        options.create_if_missing(true);
        Box::new(RocksDB::open(Path::new(&path), &options).unwrap())
    }

    fn node_config(ctx: &Context) -> NodeConfig {
        let path = ctx.arg::<String>("NODE_CONFIG_PATH").expect(
            "NODE_CONFIG_PATH not found.",
        );
        ConfigFile::load(path).unwrap()
    }

    fn public_api_address(ctx: &Context) -> Option<SocketAddr> {
        ctx.arg("PUBLIC_API_ADDRESS").ok()
    }

    fn private_api_address(ctx: &Context) -> Option<SocketAddr> {
        ctx.arg("PRIVATE_API_ADDRESS").ok()
    }
}

impl Command for Run {
    fn args(&self) -> Vec<Argument> {
        vec![
            Argument::new_named(
                "NODE_CONFIG_PATH",
                true,
                "Path to node configuration file.",
                "c",
                "node-config",
                false
            ),
            Argument::new_named(
                "ROCKSDB_PATH",
                true,
                "Use rocksdb database with the given path.",
                "d",
                "rocksdb",
                false
            ),
            Argument::new_named(
                "PUBLIC_API_ADDRESS",
                false,
                "Listen address for public api.",
                None,
                "public-api-address",
                false
            ),
            Argument::new_named(
                "PRIVATE_API_ADDRESS",
                false,
                "Listen address for private api.",
                None,
                "private-api-address",
                false
            ),
        ]
    }

    fn name(&self) -> CommandName {
        Self::name()
    }

    fn about(&self) -> &str {
        "Run application"
    }

    fn execute(&self, mut context: Context, exts: &Fn(Context) -> Context) -> Feedback {
        let config = Self::node_config(&context);
        let public_addr = Self::public_api_address(&context);
        let private_addr = Self::private_api_address(&context);

        context.set("node_config", config);
        let mut new_context = exts(context);
        let mut config: NodeConfig = new_context.get("node_config").expect(
            "cant load node_config",
        );
        // Override api options
        if let Some(public_addr) = public_addr {
            config.api.public_api_address = Some(public_addr);
        }

        if let Some(private_api_address) = private_addr {
            config.api.private_api_address = Some(private_api_address);
        }

        new_context.set("node_config", config);

        Feedback::RunNode(new_context)
    }
}

/// Command for the template generation.
pub struct GenerateCommonConfig;

impl GenerateCommonConfig {
    /// Returns the name of the `GenerateCommonConfig` command.
    pub fn name() -> CommandName {
        "generate-template"
    }
}

impl Command for GenerateCommonConfig {
    fn args(&self) -> Vec<Argument> {
        vec![Argument::new_positional("COMMON_CONFIG", true, "Path to common config.")]
    }

    fn name(&self) -> CommandName {
        Self::name()
    }

    fn about(&self) -> &str {
        "Generate basic config template."
    }

    fn execute(&self, mut context: Context, exts: &Fn(Context) -> Context) -> Feedback {
        let template_path = context.arg::<String>("COMMON_CONFIG").expect(
            "COMMON_CONFIG not found",
        );

        context.set("services_config", AbstractConfig::default());
        let new_context = exts(context);
        let services_config = new_context.get("services_config").unwrap_or_default();

        let template = CommonConfigTemplate {
            services_config,
            ..CommonConfigTemplate::default()
        };

        ConfigFile::save(&template, template_path).expect("Could not write template file.");
        Feedback::None
    }
}

/// Command for the node configuration generation.
pub struct GenerateNodeConfig;

impl GenerateNodeConfig {
    /// Returns the name of the `GenerateNodeConfig` command.
    pub fn name() -> CommandName {
        "generate-config"
    }

    fn addr(context: &Context) -> (SocketAddr, SocketAddr) {
        let addr = context.arg::<String>("PEER_ADDR").unwrap_or_default();

        let mut addr_parts = addr.split(':');
        let ip = addr_parts.next().expect("Expected ip address");
        if ip.len() < 8 {
            panic!("Expected ip address in PEER_ADDR.")
        }
        let port = addr_parts.next().map_or(DEFAULT_EXONUM_LISTEN_PORT, |s| {
            s.parse().expect("could not parse port")
        });
        let external_addr = format!("{}:{}", ip, port);
        let listen_addr = format!("0.0.0.0:{}", port);
        (external_addr.parse().unwrap(), listen_addr.parse().unwrap())
    }
}

impl Command for GenerateNodeConfig {
    fn args(&self) -> Vec<Argument> {
        vec![
            Argument::new_positional("COMMON_CONFIG", true, "Path to common config."),
            Argument::new_positional("PUB_CONFIG", true, "Path where save public config."),
            Argument::new_positional("SEC_CONFIG", true, "Path where save private config."),
            Argument::new_named(
                "PEER_ADDR",
                true,
                "Remote peer address",
                "a",
                "peer-addr",
                false
            ),
        ]
    }

    fn name(&self) -> CommandName {
        Self::name()
    }

    fn about(&self) -> &str {
        "Generate node secret and public configs."
    }

    fn execute(&self, mut context: Context, exts: &Fn(Context) -> Context) -> Feedback {
        let common_config_path = context.arg::<String>("COMMON_CONFIG").expect(
            "expected common config path",
        );
        let pub_config_path = context.arg::<String>("PUB_CONFIG").expect(
            "expected public config path",
        );
        let priv_config_path = context.arg::<String>("SEC_CONFIG").expect(
            "expected secret config path",
        );

        let addr = Self::addr(&context);
        let common: CommonConfigTemplate =
            ConfigFile::load(&common_config_path).expect("Could not load common config");
        context.set("common_config", common.clone());
        context.set(
            "services_public_configs",
            BTreeMap::<String, Value>::default(),
        );
        context.set(
            "services_secret_configs",
            BTreeMap::<String, Value>::default(),
        );
        let new_context = exts(context);
        let services_public_configs = new_context.get("services_public_configs").unwrap();
        let services_secret_configs = new_context.get("services_secret_configs");

        let (consensus_public_key, consensus_secret_key) = crypto::gen_keypair();
        let (service_public_key, service_secret_key) = crypto::gen_keypair();

        let validator_keys = ValidatorKeys {
            consensus_key: consensus_public_key,
            service_key: service_public_key,
        };
        let node_pub_config = NodePublicConfig {
            addr: addr.0,
            validator_keys,
            services_public_configs,
        };
        let shared_config = SharedConfig {
            node: node_pub_config,
            common: common,
        };
        // Save public config separately.
        ConfigFile::save(&shared_config, &pub_config_path).expect(
            "Could not write public config file.",
        );

        let priv_config = NodePrivateConfig {
            listen_addr: addr.1,
            consensus_public_key,
            consensus_secret_key,
            service_public_key,
            service_secret_key,
            services_secret_configs: services_secret_configs.expect(
                "services_secret_configs not found after exts call",
            ),
        };

        ConfigFile::save(&priv_config, priv_config_path).expect(
            "Could not write secret config file.",
        );
        Feedback::None
    }
}

/// Finalize command.
pub struct Finalize;

impl Finalize {
    /// Returns the name of the `Finalize` command.
    pub fn name() -> CommandName {
        "finalize"
    }

    /// Returns `GenesisConfig` from the template.
    fn genesis_from_template(
        template: CommonConfigTemplate,
        configs: &[NodePublicConfig],
    ) -> GenesisConfig {
        GenesisConfig::new_with_consensus(
            template.consensus_config,
            configs.iter().map(|c| c.validator_keys),
        )
    }

    fn reduce_configs(
        public_configs: Vec<SharedConfig>,
        our_config: &NodePrivateConfig,
    ) -> (CommonConfigTemplate, Vec<NodePublicConfig>, Option<NodePublicConfig>) {
        let mut map = BTreeMap::new();
        let mut config_iter = public_configs.into_iter();
        let first = config_iter.next().expect(
            "Expected at least one config in PUBLIC_CONFIGS",
        );
        let common = first.common;
        map.insert(first.node.validator_keys.consensus_key, first.node);

        for config in config_iter {
            if common != config.common {
                panic!("Found config with different common part.");
            };
            if map.insert(config.node.validator_keys.consensus_key, config.node)
                .is_some()
            {
                panic!("Found duplicate consenus keys in PUBLIC_CONFIGS");
            }

        }
        (
            common,
            map.iter().map(|(_, c)| c.clone()).collect(),
            map.get(&our_config.consensus_public_key).cloned(),
        )
    }
}

impl Command for Finalize {
    fn args(&self) -> Vec<Argument> {
        vec![
            Argument::new_named(
                "PUBLIC_CONFIGS",
                true,
                "Path to validators public configs",
                "p",
                "public-configs",
                true
            ),
            Argument::new_named(
                "PUBLIC_API_ADDRESS",
                false,
                "Listen address for public api.",
                None,
                "public-api-address",
                false
            ),
            Argument::new_named(
                "PRIVATE_API_ADDRESS",
                false,
                "Listen address for private api.",
                None,
                "private-api-address",
                false
            ),
            Argument::new_positional("SECRET_CONFIG", true, "Path to our secret config."),
            Argument::new_positional("OUTPUT_CONFIG_PATH", true, "Path to output node config."),
        ]
    }

    fn name(&self) -> CommandName {
        Self::name()
    }

    fn about(&self) -> &str {
        "Collect public and secret configs into node config."
    }

    fn execute(&self, mut context: Context, exts: &Fn(Context) -> Context) -> Feedback {
        let public_configs_path = context.arg_multiple::<String>("PUBLIC_CONFIGS").expect(
            "keychain path not found",
        );
        let secret_config_path = context.arg::<String>("SECRET_CONFIG").expect(
            "config path not found",
        );
        let output_config_path = context.arg::<String>("OUTPUT_CONFIG_PATH").expect(
            "config path not found",
        );

        let public_addr = Run::public_api_address(&context);
        let private_addr = Run::private_api_address(&context);

        let secret_config: NodePrivateConfig =
            ConfigFile::load(secret_config_path).expect("Failed to load key config.");
        let public_configs: Vec<SharedConfig> = public_configs_path
            .into_iter()
            .map(|path| {
                ConfigFile::load(path).expect("Failed to load validator public config.")
            })
            .collect();
        let (common, list, our) = Self::reduce_configs(public_configs, &secret_config);

        context.set("auditor_mode", our.is_none());

        let peers = list.iter().map(|c| c.addr).collect();

        let genesis = Self::genesis_from_template(common.clone(), &list);

        let config = {
            NodeConfig {
                listen_address: secret_config.listen_addr,
                external_address: our.map(|o| o.addr),
                network: Default::default(),
                whitelist: Default::default(),
                peers: peers,
                consensus_public_key: secret_config.consensus_public_key,
                consensus_secret_key: secret_config.consensus_secret_key,
                service_public_key: secret_config.service_public_key,
                service_secret_key: secret_config.service_secret_key,
                genesis: genesis,
                api: NodeApiConfig {
                    public_api_address: public_addr,
                    private_api_address: private_addr,
                    ..Default::default()
                },
                mempool: Default::default(),
                services_configs: Default::default(),
            }
        };

        context.set("public_config_list", list);
        context.set("node_config", config);
        context.set("common_config", common);
        context.set(
            "services_secret_configs",
            secret_config.services_secret_configs,
        );

        let new_context = exts(context);

        let config: NodeConfig = new_context.get("node_config").expect(
            "Could not create config from template, services return error",
        );
        ConfigFile::save(&config, output_config_path).expect("Could not write config file.");

        Feedback::None
    }
}

/// Command for the testnet generation.
pub struct GenerateTestnet;

impl GenerateTestnet {
    /// Returns the name of the `GenerateTestnet` command.
    pub fn name() -> CommandName {
        "generate-testnet"
    }
}

impl Command for GenerateTestnet {
    fn args(&self) -> Vec<Argument> {
        vec![
            Argument::new_named(
                "OUTPUT_DIR",
                true,
                "Path to directory where save configs.",
                "o",
                "output_dir",
                false
            ),
            Argument::new_named(
                "START_PORT",
                false,
                "Port number started from which should validators listen.",
                "p",
                "start",
                false
            ),
            Argument::new_positional("COUNT", true, "Count of validators in testnet."),
        ]
    }

    fn name(&self) -> CommandName {
        Self::name()
    }

    fn about(&self) -> &str {
        "Generates genesis configuration for testnet"
    }

    fn execute(&self, mut context: Context, exts: &Fn(Context) -> Context) -> Feedback {

        let dir = context.arg::<String>("OUTPUT_DIR").expect("output dir");
        let count: u8 = context.arg("COUNT").expect("count as int");
        let start_port = context.arg::<u16>("START_PORT").unwrap_or(
            DEFAULT_EXONUM_LISTEN_PORT,
        );

        if count == 0 {
            panic!("Can't generate testnet with zero nodes count.");
        }

        let dir = Path::new(&dir);
        let dir = dir.join("validators");
        if !dir.exists() {
            fs::create_dir_all(&dir).unwrap();
        }

        let configs = generate_testnet_config(count, start_port);
        context.set("configs", configs);
        let new_context = exts(context);
        let configs: Vec<NodeConfig> = new_context.get("configs").expect(
            "Couldn't read testnet configs after exts call.",
        );

        for (idx, cfg) in configs.into_iter().enumerate() {
            let file_name = format!("{}.toml", idx);
            ConfigFile::save(&cfg, &dir.join(file_name)).unwrap();
        }

        Feedback::None
    }
}
<|endoftext|>


<commit_before>// Traits can have associated types:
#![feature(collections)]

use std::fmt::Debug;
//use std::string;

// I thought that associated types could be used to encode existential
// types, as attempted below.  The trait T has associated type t.  I
// define two implementations for this trait, with different
// definitions of t.  Then, I try to create a packed up pair where the
// first and second components of the pair look "the same" (both just
// mention trait T, and each leaves t abstract, via an (implicit)
// existential quantifier.

// Apparently, this is not allowed:

// examples/assoctypes.rs:24:16: 24:17 error: the value of the associated type `t` (from the trait `T`) must be specified [E0191]
//    examples/assoctypes.rs:24     two : (Box<T>,Box<T>)
//
// examples/assoctypes.rs:24:23: 24:24 error: the value of the associated type `t` (from the trait `T`) must be specified [E0191]
//    examples/assoctypes.rs:24     two : (Box<T>,Box<T>)

trait T : Debug {
    type t;
    fn get (self:&Self) -> Self::t ;
    fn foo (self:&Self, Self::t ) -> String ;
}

#[derive(Debug)]
pub enum Foo { Foo }

#[derive(Debug)]
pub enum Bar { Bar }

impl T for Foo {
    type t = String;
    fn get (self:&Self) -> String { String::from_str("foo") }
    fn foo (self:&Self, s:Self::t) -> String { s }
}

impl T for Bar {
    type t = u32;
    fn get (self:&Self) -> u32 { 0 }
    fn foo (self:&Self, _s:Self::t) -> String { String::from_str("bar") }
}

#[derive(Debug)]
struct Packed {
    two : (Box<T<t=String>>,Box<T<t=u32>>)
}

pub fn main () {
    let a = Packed { two : (Box::new(Foo::Foo),
                            Box::new(Bar::Bar)) } ;
     println!("{:?}", a);
}
<commit_msg>some notes about associated types and their lack of abstract-ness<commit_after>// Traits can have associated types:
#![feature(collections)]

use std::fmt::Debug;
//use std::string;

// I thought that associated types could be used to encode existential
// types, as attempted below.  The trait T has associated type t.  I
// define two implementations for this trait, with different
// definitions of t.  Then, I try to create a packed up pair where the
// first and second components of the pair look "the same" (both just
// mention trait T, and each leaves t abstract, via an (implicit)
// existential quantifier.

// The only way I can get rustc to accept this code is when I leave
// these associated types "translucent", and expose their identities
// in terms of concrete types (i.e., String and u32).  This is exactly
// what I want to avoid.  In particular, the tuple pattern that works
// below doesn't generalize to the case where I have an array or a
// list.

// This post may provide a path forward:
// http://stackoverflow.com/questions/28003773/implementing-iterator-using-an-underlying-iterator

trait T : Debug {
    type t;
    fn get (self:&Self) -> Self::t ;
    fn foo (self:&Self, Self::t ) -> String ;
}

#[derive(Debug)]
pub enum Foo { Foo }

#[derive(Debug)]
pub enum Bar { Bar }

impl T for Foo {
    type t = String;
    fn get (self:&Self) -> String { String::from_str("foo") }
    fn foo (self:&Self, s:Self::t) -> String { s }
}

impl T for Bar {
    type t = u32;
    fn get (self:&Self) -> u32 { 0 }
    fn foo (self:&Self, _s:Self::t) -> String { String::from_str("bar") }
}

#[derive(Debug)]
struct Packed {
    two : (Box<T<t=String>>,Box<T<t=u32>>)
}

pub fn main () {
    let a = Packed { two : (Box::new(Foo::Foo),
                            Box::new(Bar::Bar)) } ;
     println!("{:?}", a);
}
<|endoftext|>


<commit_before>extern crate env_logger;
extern crate handlebars;
extern crate serde_json;

use std::env;
use std::io::{self, Write};
use std::process;
use std::str::FromStr;

use serde_json::value::Value as Json;

use handlebars::Handlebars;

fn usage() -> ! {
    writeln!(
        &mut io::stderr(),
        "{}",
        r#"Usage: ./render-cli template.hbs '{"json": "data"}'"#
    ).ok();
    process::exit(1);
}

fn parse_json(text: &str) -> Json {
    match Json::from_str(text) {
        Ok(json) => json,
        Err(_) => usage(),
    }
}

fn main() {
    env_logger::init();

    let mut args = env::args();
    args.next(); // skip own filename
    let (filename, json) = match (args.next(), args.next()) {
        (Some(filename), Some(json)) => (filename, json),
        _ => usage(),
    };
    let data = parse_json(&json);

    let mut handlebars = Handlebars::new();

    handlebars
        .register_template_file(&filename, &filename)
        .ok()
        .unwrap();
    match handlebars.render(&filename, &data) {
        Ok(data) => {
            println!("{}", data);
        }
        Err(e) => {
            println!("Error rendering {}: {}", filename, e);
            process::exit(2);
        }
    }
}
<commit_msg>(feat) added json file support for render-cli<commit_after>extern crate env_logger;
extern crate handlebars;
extern crate serde_json;

use std::env;
use std::fs;
use std::io::{self, Write};
use std::process;
use std::str::FromStr;

use serde_json::value::Value as Json;

use handlebars::Handlebars;

fn usage() -> ! {
    writeln!(
        &mut io::stderr(),
        "{}",
        r#"Usage: ./render-cli template.hbs '{"json": "data"}'"#
    ).ok();
    process::exit(1);
}

fn parse_json(text: &str) -> Json {
    let text = if text.starts_with("@") {
        fs::read_to_string(&text[1..]).unwrap()
    } else {
        text.to_owned()
    };
    match Json::from_str(&text) {
        Ok(json) => json,
        Err(_) => usage(),
    }
}

fn main() {
    env_logger::init();

    let mut args = env::args();
    args.next(); // skip own filename
    let (filename, json) = match (args.next(), args.next()) {
        (Some(filename), Some(json)) => (filename, json),
        _ => usage(),
    };
    let data = parse_json(&json);

    let mut handlebars = Handlebars::new();

    handlebars
        .register_template_file(&filename, &filename)
        .ok()
        .unwrap();
    match handlebars.render(&filename, &data) {
        Ok(data) => {
            println!("{}", data);
        }
        Err(e) => {
            println!("Error rendering {}: {}", filename, e);
            process::exit(2);
        }
    }
}
<|endoftext|>


<commit_before>extern crate ffmpeg;
extern crate flv_toolbox_rs;

use flv_toolbox_rs::lib::{ FLVTagRead, FLVTag, FLVTagType, FLVTagWrite, format_seconds_ms, AudioSpecificConfig };

use std::fs::File;
use std::io::{ Read, Write };
use std::slice;
use std::collections::BTreeMap;

#[derive(Debug, Clone)]
pub struct FlvTimeInfo {
    pub ref_video_frame_duration: u64,
    pub ref_audio_frame_duration: u64,
    pub real_video_frame_duration: i64,
    pub real_audio_frame_duration: i64,
    pub timestamps: Vec<TagTimestamp>,
}

#[derive(Debug, Clone)]
pub enum TagTimestamp {
    Video(i64, i64),
    Audio(i64, i64, i64),
}

fn get_info(path: &str) -> FlvTimeInfo {

    let mut file = File::open(path).unwrap();
    let file_info = file.metadata().unwrap();
    let file_len = file_info.len();
    let mut parser = FLVTagRead::new(&mut file);

    let mut ref_video_frame_duration: u64 = 0;
    let mut ref_audio_frame_duration: u64 = 0;
    let mut real_video_frame_duration: i64 = 0;
    let mut real_audio_frame_duration: i64 = 0;

    let mut last_video_tag: Option<FLVTag> = None;
    let mut video_duration_map: BTreeMap<i64, u64> = BTreeMap::new();

    let mut last_audio_tag: Option<FLVTag> = None;
    let mut audio_duration_map: BTreeMap<i64, u64> = BTreeMap::new();
    let mut last_audio_decode_duration: i64 = 0;

    let mut timestamps: Vec<TagTimestamp> = vec![];

    // ffmpeg
    ffmpeg::init().unwrap();
    let codec = ffmpeg::decoder::find(ffmpeg::codec::id::Id::AAC).unwrap();
    let context = ffmpeg::codec::Context::new();
    let opened = context.decoder().open_as(codec).unwrap();
    let mut decoder = opened.audio().unwrap();
    // asc
    let mut asc: Option<AudioSpecificConfig> = None;

    loop {
        let position = parser.get_position();
        print!("progress: {: >3.0}%\r", position as f64 / file_len as f64 * 100.);
        let nxt: Option<FLVTag> = parser.next();
        if nxt.is_none() {
            break;
        }
        let tag: FLVTag = nxt.unwrap();

        match tag.get_tag_type() {
            FLVTagType::TAG_TYPE_VIDEO => {

                if last_video_tag.is_some() {
                    let time_delta = tag.get_timestamp() as i64 - last_video_tag.as_ref().unwrap().get_timestamp() as i64;
                    timestamps.push(TagTimestamp::Video(last_video_tag.as_ref().unwrap().get_timestamp() as i64, time_delta));
                    *(video_duration_map.entry(time_delta).or_insert(0)) += 1;
                }

                match tag.get_frame_type() {
                    1 => {
                        match tag.get_avc_packet_type() {
                            0 => { // AVC_PACKET_TYPE_SEQUENCE_HEADER

                            },
                            _ => { // normal keyframes
                                last_video_tag = Some(tag);
                            }
                        };
                    }
                    _ => { // normal frames
                        last_video_tag = Some(tag);
                    }
                };
            },
            FLVTagType::TAG_TYPE_AUDIO => {

                if last_audio_tag.is_some() {
                    let time_delta = tag.get_timestamp() as i64 - last_audio_tag.as_ref().unwrap().get_timestamp() as i64;
                    timestamps.push(TagTimestamp::Audio(last_audio_tag.as_ref().unwrap().get_timestamp() as i64, time_delta, last_audio_decode_duration));
                    *(audio_duration_map.entry(time_delta).or_insert(0)) += 1;
                }

                if tag.is_acc_sequence_header() { // acc sequence header
                    asc = Some(tag.get_sound_audio_specific_config());
                } else { // normal frames
                    // decode audio frame samples by ffmpeg
                    let mut audio_buffer: Vec<u8> = Vec::with_capacity(tag.get_sound_data_size() as usize + 7);
                    audio_buffer.write(&FLVTag::get_sound_adts_header_data(asc.as_ref().unwrap(), tag.get_sound_data_size() + 7));
                    audio_buffer.write(&tag.get_sound_data());
                    // println!("{:?}", audio_buffer);
                    // break;

                    let packet = ffmpeg::codec::packet::Packet::borrow(&audio_buffer);
                    let mut frame = ffmpeg::util::frame::Audio::empty();
                    let result = decoder.decode(&packet, &mut frame).unwrap();
                    let duration = 1000. * frame.samples() as f64 / frame.rate() as f64;
                    last_audio_decode_duration = duration as i64;
                    // println!("{:?}", (frame.channels(), frame.rate(), frame.samples(), duration));

                    last_audio_tag = Some(tag);
                }
            },
            FLVTagType::TAG_TYPE_SCRIPTDATAOBJECT => {

            }
        };
    }
    println!("complete!\r");

    real_video_frame_duration = top_duration(&video_duration_map);
    real_audio_frame_duration = top_duration(&audio_duration_map);

    return FlvTimeInfo {
        ref_video_frame_duration  ,
        ref_audio_frame_duration  ,
        real_video_frame_duration ,
        real_audio_frame_duration ,
        timestamps                ,
    };
}

fn top_duration(pairs: &BTreeMap<i64, u64>) -> i64 {
    let mut list: Vec<(&i64, &u64)> = pairs.iter().collect();
    match list.len() {
        0 => 0,
        1 => *(list[0].0),
        _ => {
            list.as_mut_slice().sort_by_key(|&(_, &count)| count );
            *(list.last().unwrap().0)
        },
    }
}

fn check_offset(info: &FlvTimeInfo) {
    let &FlvTimeInfo {
        ref ref_video_frame_duration  ,
        ref ref_audio_frame_duration  ,
        ref real_video_frame_duration ,
        ref real_audio_frame_duration ,
        ref timestamps                ,
    } = info;

    let mut video_time: i64 = 0;
    let mut audio_time: i64 = 0;
    let mut current_time = 0;

    for tm in timestamps.iter() {
        let mut changed = false;
        match tm {
            &TagTimestamp::Video(ref time, ref duration) => {
                // current_time = *time;
                if (*duration - *real_video_frame_duration).abs() > 1 {
                    // println!("v offset {}", video_time - *time);
                    // changed = true;
                    video_time += *real_video_frame_duration;
                } else {
                    video_time += *duration;
                }
            }
            &TagTimestamp::Audio(ref time, ref duration, ref decode_duration) => {
                current_time = *time;
                if (*duration - *decode_duration).abs() > 1 {
                    // println!("a offset {}", audio_time - *time);
                    changed = true;
                    audio_time += *decode_duration;
                } else {
                    audio_time += *decode_duration;
                }
            }
        };
        if changed {
            println!("{}: av offset: {} {} {}", format_seconds_ms(current_time as u64), audio_time - video_time, audio_time - current_time, video_time - current_time);
        }
    }
}

fn main() {
    let path = std::env::args().nth(1).unwrap();
    println!("checking flv: {}", path);
    let info = get_info(&path);
    // println!("{:?}", info);
    check_offset(&info);
}<commit_msg>final version of audio gap detecter<commit_after>extern crate ffmpeg;
extern crate flv_toolbox_rs;

use flv_toolbox_rs::lib::{ FLVTagRead, FLVTag, FLVTagType, FLVTagWrite, format_seconds_ms, AudioSpecificConfig };

use std::fs::File;
use std::io::{ Read, Write };
use std::slice;
use std::collections::BTreeMap;

#[derive(Debug, Clone)]
pub struct FlvTimeInfo {
    pub ref_video_frame_duration: u64,
    pub ref_audio_frame_duration: u64,
    pub real_video_frame_duration: i64,
    pub real_audio_frame_duration: i64,
    pub timestamps: Vec<TagTimestamp>,
}

#[derive(Debug, Clone)]
pub enum TagTimestamp {
    Video(i64, i64),
    Audio(i64, i64, i64),
}

fn get_info(path: &str) -> FlvTimeInfo {

    let mut file = File::open(path).unwrap();
    let file_info = file.metadata().unwrap();
    let file_len = file_info.len();
    let mut parser = FLVTagRead::new(&mut file);

    let mut ref_video_frame_duration: u64 = 0;
    let mut ref_audio_frame_duration: u64 = 0;
    let mut real_video_frame_duration: i64 = 0;
    let mut real_audio_frame_duration: i64 = 0;

    let mut last_video_tag: Option<FLVTag> = None;
    let mut video_duration_map: BTreeMap<i64, u64> = BTreeMap::new();

    let mut last_audio_tag: Option<FLVTag> = None;
    let mut audio_duration_map: BTreeMap<i64, u64> = BTreeMap::new();
    let mut last_audio_decode_duration: i64 = 0;

    let mut timestamps: Vec<TagTimestamp> = vec![];

    // ffmpeg
    ffmpeg::init().unwrap();
    let codec = ffmpeg::decoder::find(ffmpeg::codec::id::Id::AAC).unwrap();
    let context = ffmpeg::codec::Context::new();
    let opened = context.decoder().open_as(codec).unwrap();
    let mut decoder = opened.audio().unwrap();
    // asc
    let mut asc: Option<AudioSpecificConfig> = None;

    loop {
        let position = parser.get_position();
        print!("progress: {: >3.0}%\r", position as f64 / file_len as f64 * 100.);
        let nxt: Option<FLVTag> = parser.next();
        if nxt.is_none() {
            break;
        }
        let tag: FLVTag = nxt.unwrap();

        match tag.get_tag_type() {
            FLVTagType::TAG_TYPE_VIDEO => {

                if last_video_tag.is_some() {
                    let time_delta = tag.get_timestamp() as i64 - last_video_tag.as_ref().unwrap().get_timestamp() as i64;
                    timestamps.push(TagTimestamp::Video(last_video_tag.as_ref().unwrap().get_timestamp() as i64, time_delta));
                    *(video_duration_map.entry(time_delta).or_insert(0)) += 1;
                }

                match tag.get_frame_type() {
                    1 => {
                        match tag.get_avc_packet_type() {
                            0 => { // AVC_PACKET_TYPE_SEQUENCE_HEADER

                            },
                            _ => { // normal keyframes
                                last_video_tag = Some(tag);
                            }
                        };
                    }
                    _ => { // normal frames
                        last_video_tag = Some(tag);
                    }
                };
            },
            FLVTagType::TAG_TYPE_AUDIO => {

                if last_audio_tag.is_some() {
                    let time_delta = tag.get_timestamp() as i64 - last_audio_tag.as_ref().unwrap().get_timestamp() as i64;
                    timestamps.push(TagTimestamp::Audio(last_audio_tag.as_ref().unwrap().get_timestamp() as i64, time_delta, last_audio_decode_duration));
                    *(audio_duration_map.entry(time_delta).or_insert(0)) += 1;
                }

                if tag.is_acc_sequence_header() { // acc sequence header
                    asc = Some(tag.get_sound_audio_specific_config());
                } else { // normal frames
                    // decode audio frame samples by ffmpeg
                    let mut audio_buffer: Vec<u8> = Vec::with_capacity(tag.get_sound_data_size() as usize + 7);
                    audio_buffer.write(&FLVTag::get_sound_adts_header_data(asc.as_ref().unwrap(), tag.get_sound_data_size() + 7));
                    audio_buffer.write(&tag.get_sound_data());
                    // println!("{:?}", audio_buffer);
                    // break;

                    let packet = ffmpeg::codec::packet::Packet::borrow(&audio_buffer);
                    let mut frame = ffmpeg::util::frame::Audio::empty();
                    let result = decoder.decode(&packet, &mut frame).unwrap();
                    if result {
                        let duration = 1000. * frame.samples() as f64 / frame.rate() as f64;
                        last_audio_decode_duration = duration as i64;
                        // println!("{:?}", (frame.channels(), frame.rate(), frame.samples(), duration));

                        last_audio_tag = Some(tag);
                    }
                }
            },
            FLVTagType::TAG_TYPE_SCRIPTDATAOBJECT => {

            }
        };
    }
    println!("complete!\r");

    real_video_frame_duration = top_duration(&video_duration_map);
    real_audio_frame_duration = top_duration(&audio_duration_map);

    return FlvTimeInfo {
        ref_video_frame_duration  ,
        ref_audio_frame_duration  ,
        real_video_frame_duration ,
        real_audio_frame_duration ,
        timestamps                ,
    };
}

fn top_duration(pairs: &BTreeMap<i64, u64>) -> i64 {
    let mut list: Vec<(&i64, &u64)> = pairs.iter().collect();
    match list.len() {
        0 => 0,
        1 => *(list[0].0),
        _ => {
            list.as_mut_slice().sort_by_key(|&(_, &count)| count );
            *(list.last().unwrap().0)
        },
    }
}

fn check_offset(info: &FlvTimeInfo) {
    let &FlvTimeInfo {
        ref ref_video_frame_duration  ,
        ref ref_audio_frame_duration  ,
        ref real_video_frame_duration ,
        ref real_audio_frame_duration ,
        ref timestamps                ,
    } = info;

    let mut video_time: i64 = 0;
    let mut audio_time: i64 = 0;
    let mut current_time: i64 = 0;

    for tm in timestamps.iter() {
        let mut changed = false;
        match tm {
            &TagTimestamp::Video(ref time, ref duration) => {
                // current_time = *time;
                if (*duration - *real_video_frame_duration).abs() > 1 {
                    // println!("v offset {}", video_time - *time);
                    // changed = true;
                    video_time += *real_video_frame_duration;
                } else {
                    video_time += *duration;
                }
            }
            &TagTimestamp::Audio(ref time, ref duration, ref decode_duration) => {
                current_time = *time;
                if (*duration - *decode_duration).abs() > 1 {
                    // println!("a offset {}", audio_time - *time);
                    changed = true;
                }
                audio_time += *decode_duration;
            }
        };
        if changed {
            // println!("{}: av offset: {} {} {}", format_seconds_ms(current_time as u64), audio_time, current_time, video_time);
            println!("{}: av offset: {} {} {}", format_seconds_ms(current_time as u64), audio_time - video_time, audio_time - current_time, video_time - current_time);
        }
    }
}

fn main() {
    let path = std::env::args().nth(1).unwrap();
    println!("checking flv: {}", path);
    let info = get_info(&path);
    // println!("{:?}", info);
    check_offset(&info);
}<|endoftext|>


<commit_before>use nom::{self, IResult};

use std::str;

/// list-wildcards = "%" / "*"
pub fn list_wildcards(c: u8) -> bool {
    c == b'%' || c == b'*'
}

/// quoted-specials = DQUOTE / "\"
pub fn quoted_specials(c: u8) -> bool {
    c == b'"' || c == b'\\'
}

/// resp-specials = "]"
pub fn resp_specials(c: u8) -> bool {
    c == b']'
}

/// atom-specials = "(" / ")" / "{" / SP / CTL / list-wildcards / quoted-specials / resp-specials
pub fn atom_specials(c: u8) -> bool {
    c == b'('
        || c == b')'
        || c == b'{'
        || c == b' '
        || c < 32
        || list_wildcards(c)
        || quoted_specials(c)
        || resp_specials(c)
}

/// ATOM-CHAR = <any CHAR except atom-specials>
pub fn atom_char(c: u8) -> bool {
    !atom_specials(c)
}

/// ASTRING-CHAR = ATOM-CHAR / resp-specials
pub fn astring_char(c: u8) -> bool {
    atom_char(c) || resp_specials(c)
}

/// QUOTED-CHAR = <any TEXT-CHAR except quoted-specials> / "\" quoted-specials
pub fn quoted_data(i: &[u8]) -> IResult<&[u8], &[u8]> {
    // Ideally this should use nom's `escaped` macro, but it suffers from broken
    // type inference unless compiled with the verbose-errors feature enabled.
    let mut escape = false;
    let mut len = 0;
    for c in i {
        if *c == b'"' && !escape {
            break;
        }
        len += 1;
        if *c == b'\\' && !escape {
            escape = true
        } else if escape {
            escape = false;
        }
    }
    Ok((&i[len..], &i[..len]))
}

/// quoted = DQUOTE *QUOTED-CHAR DQUOTE
named!(pub quoted<&[u8]>, do_parse!(
    tag_s!("\"") >>
    data: quoted_data >>
    tag_s!("\"") >>
    (data)
));

/// literal = "{" number "}" CRLF *CHAR8
///            ; Number represents the number of CHAR8s
named!(pub literal<&[u8]>, do_parse!(
    tag_s!("{") >>
    len: number >>
    tag_s!("}") >>
    tag_s!("\r\n") >>
    data: take!(len) >>
    (data)
));

/// string = quoted / literal
named!(pub string<&[u8]>, alt!(quoted | literal));

/// nstring = string / nil
named!(pub nstring<Option<&[u8]>>, alt!(
    map!(tag_s!("NIL"), |_| None) |
    map!(string, |s| Some(s))
));

/// number          = 1*DIGIT
///                    ; Unsigned 32-bit integer
///                    ; (0 <= n < 4,294,967,296)
named!(pub number<u32>, map_res!(
    map_res!(nom::digit, str::from_utf8),
    str::parse
));

/// same as `number` but 64-bit
named!(pub number_64<u64>, map_res!(
    map_res!(nom::digit, str::from_utf8),
    str::parse
));

/// atom = 1*ATOM-CHAR
named!(pub atom<&str>, map_res!(take_while1_s!(atom_char),
    str::from_utf8
));

/// astring = 1*ASTRING-CHAR / string
named!(pub astring<&[u8]>, alt!(
    take_while1!(astring_char) |
    string
));

/// text = 1*TEXT-CHAR
named!(pub text<&str>, map_res!(take_while_s!(text_char),
    str::from_utf8
));

/// TEXT-CHAR = <any CHAR except CR and LF>
pub fn text_char(c: u8) -> bool {
    c != b'\r' && c != b'\n'
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_string_literal() {
        match string(b"{3}\r\nXYZ") {
            Ok((_, value)) => {
                assert_eq!(value, "XYZ".as_bytes());
            }
            rsp => panic!("unexpected response {:?}", rsp),
        }
    }

    #[test]
    fn test_astring() {
        match astring(b"text ") {
            Ok((_, value)) => {
                assert_eq!(value, "text".as_bytes());
            }
            rsp => panic!("unexpected response {:?}", rsp),
        }
    }
}
<commit_msg>Add nil tag<commit_after>use nom::{self, IResult};

use std::str;

/// list-wildcards = "%" / "*"
pub fn list_wildcards(c: u8) -> bool {
    c == b'%' || c == b'*'
}

/// quoted-specials = DQUOTE / "\"
pub fn quoted_specials(c: u8) -> bool {
    c == b'"' || c == b'\\'
}

/// resp-specials = "]"
pub fn resp_specials(c: u8) -> bool {
    c == b']'
}

/// atom-specials = "(" / ")" / "{" / SP / CTL / list-wildcards / quoted-specials / resp-specials
pub fn atom_specials(c: u8) -> bool {
    c == b'('
        || c == b')'
        || c == b'{'
        || c == b' '
        || c < 32
        || list_wildcards(c)
        || quoted_specials(c)
        || resp_specials(c)
}

/// ATOM-CHAR = <any CHAR except atom-specials>
pub fn atom_char(c: u8) -> bool {
    !atom_specials(c)
}

/// nil = "NIL"
named!(pub nil, tag_s!("NIL"));

/// ASTRING-CHAR = ATOM-CHAR / resp-specials
pub fn astring_char(c: u8) -> bool {
    atom_char(c) || resp_specials(c)
}

/// QUOTED-CHAR = <any TEXT-CHAR except quoted-specials> / "\" quoted-specials
pub fn quoted_data(i: &[u8]) -> IResult<&[u8], &[u8]> {
    // Ideally this should use nom's `escaped` macro, but it suffers from broken
    // type inference unless compiled with the verbose-errors feature enabled.
    let mut escape = false;
    let mut len = 0;
    for c in i {
        if *c == b'"' && !escape {
            break;
        }
        len += 1;
        if *c == b'\\' && !escape {
            escape = true
        } else if escape {
            escape = false;
        }
    }
    Ok((&i[len..], &i[..len]))
}

/// quoted = DQUOTE *QUOTED-CHAR DQUOTE
named!(pub quoted<&[u8]>, do_parse!(
    tag_s!("\"") >>
    data: quoted_data >>
    tag_s!("\"") >>
    (data)
));

/// literal = "{" number "}" CRLF *CHAR8
///            ; Number represents the number of CHAR8s
named!(pub literal<&[u8]>, do_parse!(
    tag_s!("{") >>
    len: number >>
    tag_s!("}") >>
    tag_s!("\r\n") >>
    data: take!(len) >>
    (data)
));

/// string = quoted / literal
named!(pub string<&[u8]>, alt!(quoted | literal));

/// nstring = string / nil
named!(pub nstring<Option<&[u8]>>, alt!(
    map!(tag_s!("NIL"), |_| None) |
    map!(string, |s| Some(s))
));

/// number          = 1*DIGIT
///                    ; Unsigned 32-bit integer
///                    ; (0 <= n < 4,294,967,296)
named!(pub number<u32>, map_res!(
    map_res!(nom::digit, str::from_utf8),
    str::parse
));

/// same as `number` but 64-bit
named!(pub number_64<u64>, map_res!(
    map_res!(nom::digit, str::from_utf8),
    str::parse
));

/// atom = 1*ATOM-CHAR
named!(pub atom<&str>, map_res!(take_while1_s!(atom_char),
    str::from_utf8
));

/// astring = 1*ASTRING-CHAR / string
named!(pub astring<&[u8]>, alt!(
    take_while1!(astring_char) |
    string
));

/// text = 1*TEXT-CHAR
named!(pub text<&str>, map_res!(take_while_s!(text_char),
    str::from_utf8
));

/// TEXT-CHAR = <any CHAR except CR and LF>
pub fn text_char(c: u8) -> bool {
    c != b'\r' && c != b'\n'
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_string_literal() {
        match string(b"{3}\r\nXYZ") {
            Ok((_, value)) => {
                assert_eq!(value, "XYZ".as_bytes());
            }
            rsp => panic!("unexpected response {:?}", rsp),
        }
    }

    #[test]
    fn test_astring() {
        match astring(b"text ") {
            Ok((_, value)) => {
                assert_eq!(value, "text".as_bytes());
            }
            rsp => panic!("unexpected response {:?}", rsp),
        }
    }
}
<|endoftext|>


<commit_before>use std::sync::Arc;

use salsa::Database;
use ra_db::{FilesDatabase, CrateGraph, SourceRootId};
use relative_path::RelativePath;
use test_utils::assert_eq_text;

use crate::{
    ItemMap, Resolution,
    db::HirDatabase,
    mock::MockDatabase,
    module_tree::ModuleId,
};

fn item_map(fixture: &str) -> (Arc<ItemMap>, ModuleId) {
    let (db, pos) = MockDatabase::with_position(fixture);
    let source_root = db.file_source_root(pos.file_id);
    let module = crate::source_binder::module_from_position(&db, pos)
        .unwrap()
        .unwrap();
    let module_id = module.def_id.loc(&db).module_id;
    (db.item_map(source_root).unwrap(), module_id)
}

fn check_module_item_map(map: &ItemMap, module_id: ModuleId, expected: &str) {
    let mut lines = map.per_module[&module_id]
        .items
        .iter()
        .map(|(name, res)| format!("{}: {}", name, dump_resolution(res)))
        .collect::<Vec<_>>();
    lines.sort();
    let actual = lines.join("\n");
    let expected = expected
        .trim()
        .lines()
        .map(|it| it.trim())
        .collect::<Vec<_>>()
        .join("\n");
    assert_eq_text!(&expected, &actual);

    fn dump_resolution(resolution: &Resolution) -> &'static str {
        match (
            resolution.def_id.types.is_some(),
            resolution.def_id.values.is_some(),
        ) {
            (true, true) => "t v",
            (true, false) => "t",
            (false, true) => "v",
            (false, false) => "_",
        }
    }
}

#[test]
fn item_map_smoke_test() {
    let (item_map, module_id) = item_map(
        "
        //- /lib.rs
        mod foo;

        use crate::foo::bar::Baz;
        <|>

        //- /foo/mod.rs
        pub mod bar;

        //- /foo/bar.rs
        pub struct Baz;
    ",
    );
    check_module_item_map(
        &item_map,
        module_id,
        "
            Baz: t v
            foo: t
        ",
    );
}

#[test]
fn use_trees() {
    let (item_map, module_id) = item_map(
        "
        //- /lib.rs
        mod foo;

        use crate::foo::bar::{Baz, Quux};
        <|>

        //- /foo/mod.rs
        pub mod bar;

        //- /foo/bar.rs
        pub struct Baz;
        pub enum Quux {};
    ",
    );
    check_module_item_map(
        &item_map,
        module_id,
        "
            Baz: t v
            Quux: t
            foo: t
        ",
    );
}

#[test]
fn re_exports() {
    let (item_map, module_id) = item_map(
        "
        //- /lib.rs
        mod foo;

        use self::foo::Baz;
        <|>

        //- /foo/mod.rs
        pub mod bar;

        pub use self::bar::Baz;

        //- /foo/bar.rs
        pub struct Baz;
    ",
    );
    check_module_item_map(
        &item_map,
        module_id,
        "
            Baz: t v
            foo: t
        ",
    );
}

#[test]
fn item_map_contains_items_from_expansions() {
    let (item_map, module_id) = item_map(
        "
        //- /lib.rs
        mod foo;

        use crate::foo::bar::Baz;
        <|>

        //- /foo/mod.rs
        pub mod bar;

        //- /foo/bar.rs
        salsa::query_group! {
            trait Baz {}
        }
    ",
    );
    check_module_item_map(
        &item_map,
        module_id,
        "
            Baz: t
            foo: t
        ",
    );
}

#[test]
fn item_map_using_self() {
    let (item_map, module_id) = item_map(
        "
            //- /lib.rs
            mod foo;
            use crate::foo::bar::Baz::{self};
            <|>
            //- /foo/mod.rs
            pub mod bar;
            //- /foo/bar.rs
            pub struct Baz;
        ",
    );
    check_module_item_map(
        &item_map,
        module_id,
        "
            Baz: t v
            foo: t
        ",
    );
}

#[test]
fn item_map_across_crates() {
    let (mut db, sr) = MockDatabase::with_files(
        "
        //- /main.rs
        use test_crate::Baz;

        //- /lib.rs
        pub struct Baz;
    ",
    );
    let main_id = sr.files[RelativePath::new("/main.rs")];
    let lib_id = sr.files[RelativePath::new("/lib.rs")];

    let mut crate_graph = CrateGraph::default();
    let main_crate = crate_graph.add_crate_root(main_id);
    let lib_crate = crate_graph.add_crate_root(lib_id);
    crate_graph.add_dep(main_crate, "test_crate".into(), lib_crate);

    db.set_crate_graph(crate_graph);

    let source_root = db.file_source_root(main_id);
    let module = crate::source_binder::module_from_file_id(&db, main_id)
        .unwrap()
        .unwrap();
    let module_id = module.def_id.loc(&db).module_id;
    let item_map = db.item_map(source_root).unwrap();

    check_module_item_map(
        &item_map,
        module_id,
        "
            Baz: t v
            test_crate: t
        ",
    );
}

#[test]
fn import_across_source_roots() {
    let (mut db, sr) = MockDatabase::with_files(
        "
        //- /lib.rs
        pub mod a {
            pub mod b {
                pub struct C;
            }
        }
    ",
    );
    let lib_id = sr.files[RelativePath::new("/lib.rs")];

    let source_root = SourceRootId(1);

    let (sr2, pos) = db.add_fixture(
        source_root,
        "
        //- /main.rs
        use test_crate::a::b::C;
    ",
    );
    assert!(pos.is_none());

    let main_id = sr2.files[RelativePath::new("/main.rs")];

    eprintln!("lib = {:?}, main = {:?}", lib_id, main_id);

    let mut crate_graph = CrateGraph::default();
    let main_crate = crate_graph.add_crate_root(main_id);
    let lib_crate = crate_graph.add_crate_root(lib_id);
    crate_graph.add_dep(main_crate, "test_crate".into(), lib_crate);

    db.set_crate_graph(crate_graph);

    let module = crate::source_binder::module_from_file_id(&db, main_id)
        .unwrap()
        .unwrap();
    let module_id = module.def_id.loc(&db).module_id;
    let item_map = db.item_map(source_root).unwrap();

    check_module_item_map(
        &item_map,
        module_id,
        "
            C: t v
            test_crate: t
        ",
    );
}

#[test]
fn reexport_across_crates() {
    let (mut db, sr) = MockDatabase::with_files(
        "
        //- /main.rs
        use test_crate::Baz;

        //- /lib.rs
        pub use foo::Baz;

        mod foo;

        //- /foo.rs
        pub struct Baz;
    ",
    );
    let main_id = sr.files[RelativePath::new("/main.rs")];
    let lib_id = sr.files[RelativePath::new("/lib.rs")];

    let mut crate_graph = CrateGraph::default();
    let main_crate = crate_graph.add_crate_root(main_id);
    let lib_crate = crate_graph.add_crate_root(lib_id);
    crate_graph.add_dep(main_crate, "test_crate".into(), lib_crate);

    db.set_crate_graph(crate_graph);

    let source_root = db.file_source_root(main_id);
    let module = crate::source_binder::module_from_file_id(&db, main_id)
        .unwrap()
        .unwrap();
    let module_id = module.def_id.loc(&db).module_id;
    let item_map = db.item_map(source_root).unwrap();

    check_module_item_map(
        &item_map,
        module_id,
        "
            Baz: t v
            test_crate: t
        ",
    );
}

fn check_item_map_is_not_recomputed(initial: &str, file_change: &str) {
    let (mut db, pos) = MockDatabase::with_position(initial);
    let source_root = db.file_source_root(pos.file_id);
    {
        let events = db.log_executed(|| {
            db.item_map(source_root).unwrap();
        });
        assert!(format!("{:?}", events).contains("item_map"))
    }
    db.query_mut(ra_db::FileTextQuery)
        .set(pos.file_id, Arc::new(file_change.to_string()));

    {
        let events = db.log_executed(|| {
            db.item_map(source_root).unwrap();
        });
        assert!(
            !format!("{:?}", events).contains("item_map"),
            "{:#?}",
            events
        )
    }
}

#[test]
fn typing_inside_a_function_should_not_invalidate_item_map() {
    check_item_map_is_not_recomputed(
        "
        //- /lib.rs
        mod foo;

        use crate::foo::bar::Baz;

        //- /foo/mod.rs
        pub mod bar;

        //- /foo/bar.rs
        <|>
        salsa::query_group! {
            trait Baz {
                fn foo() -> i32 { 1 + 1 }
            }
        }
        ",
        "
        salsa::query_group! {
            trait Baz {
                fn foo() -> i32 { 92 }
            }
        }
        ",
    );
}

#[test]
fn typing_inside_a_function_inside_a_macro_should_not_invalidate_item_map() {
    check_item_map_is_not_recomputed(
        "
        //- /lib.rs
        mod foo;<|>

        use crate::foo::bar::Baz;

        fn foo() -> i32 {
            1 + 1
        }
        //- /foo/mod.rs
        pub mod bar;

        //- /foo/bar.rs
        pub struct Baz;
        ",
        "
        mod foo;

        use crate::foo::bar::Baz;

        fn foo() -> i32 { 92 }
        ",
    );
}
<commit_msg>fix test naming<commit_after>use std::sync::Arc;

use salsa::Database;
use ra_db::{FilesDatabase, CrateGraph, SourceRootId};
use relative_path::RelativePath;
use test_utils::assert_eq_text;

use crate::{
    ItemMap, Resolution,
    db::HirDatabase,
    mock::MockDatabase,
    module_tree::ModuleId,
};

fn item_map(fixture: &str) -> (Arc<ItemMap>, ModuleId) {
    let (db, pos) = MockDatabase::with_position(fixture);
    let source_root = db.file_source_root(pos.file_id);
    let module = crate::source_binder::module_from_position(&db, pos)
        .unwrap()
        .unwrap();
    let module_id = module.def_id.loc(&db).module_id;
    (db.item_map(source_root).unwrap(), module_id)
}

fn check_module_item_map(map: &ItemMap, module_id: ModuleId, expected: &str) {
    let mut lines = map.per_module[&module_id]
        .items
        .iter()
        .map(|(name, res)| format!("{}: {}", name, dump_resolution(res)))
        .collect::<Vec<_>>();
    lines.sort();
    let actual = lines.join("\n");
    let expected = expected
        .trim()
        .lines()
        .map(|it| it.trim())
        .collect::<Vec<_>>()
        .join("\n");
    assert_eq_text!(&expected, &actual);

    fn dump_resolution(resolution: &Resolution) -> &'static str {
        match (
            resolution.def_id.types.is_some(),
            resolution.def_id.values.is_some(),
        ) {
            (true, true) => "t v",
            (true, false) => "t",
            (false, true) => "v",
            (false, false) => "_",
        }
    }
}

#[test]
fn item_map_smoke_test() {
    let (item_map, module_id) = item_map(
        "
        //- /lib.rs
        mod foo;

        use crate::foo::bar::Baz;
        <|>

        //- /foo/mod.rs
        pub mod bar;

        //- /foo/bar.rs
        pub struct Baz;
    ",
    );
    check_module_item_map(
        &item_map,
        module_id,
        "
            Baz: t v
            foo: t
        ",
    );
}

#[test]
fn use_trees() {
    let (item_map, module_id) = item_map(
        "
        //- /lib.rs
        mod foo;

        use crate::foo::bar::{Baz, Quux};
        <|>

        //- /foo/mod.rs
        pub mod bar;

        //- /foo/bar.rs
        pub struct Baz;
        pub enum Quux {};
    ",
    );
    check_module_item_map(
        &item_map,
        module_id,
        "
            Baz: t v
            Quux: t
            foo: t
        ",
    );
}

#[test]
fn re_exports() {
    let (item_map, module_id) = item_map(
        "
        //- /lib.rs
        mod foo;

        use self::foo::Baz;
        <|>

        //- /foo/mod.rs
        pub mod bar;

        pub use self::bar::Baz;

        //- /foo/bar.rs
        pub struct Baz;
    ",
    );
    check_module_item_map(
        &item_map,
        module_id,
        "
            Baz: t v
            foo: t
        ",
    );
}

#[test]
fn item_map_contains_items_from_expansions() {
    let (item_map, module_id) = item_map(
        "
        //- /lib.rs
        mod foo;

        use crate::foo::bar::Baz;
        <|>

        //- /foo/mod.rs
        pub mod bar;

        //- /foo/bar.rs
        salsa::query_group! {
            trait Baz {}
        }
    ",
    );
    check_module_item_map(
        &item_map,
        module_id,
        "
            Baz: t
            foo: t
        ",
    );
}

#[test]
fn item_map_using_self() {
    let (item_map, module_id) = item_map(
        "
            //- /lib.rs
            mod foo;
            use crate::foo::bar::Baz::{self};
            <|>
            //- /foo/mod.rs
            pub mod bar;
            //- /foo/bar.rs
            pub struct Baz;
        ",
    );
    check_module_item_map(
        &item_map,
        module_id,
        "
            Baz: t v
            foo: t
        ",
    );
}

#[test]
fn item_map_across_crates() {
    let (mut db, sr) = MockDatabase::with_files(
        "
        //- /main.rs
        use test_crate::Baz;

        //- /lib.rs
        pub struct Baz;
    ",
    );
    let main_id = sr.files[RelativePath::new("/main.rs")];
    let lib_id = sr.files[RelativePath::new("/lib.rs")];

    let mut crate_graph = CrateGraph::default();
    let main_crate = crate_graph.add_crate_root(main_id);
    let lib_crate = crate_graph.add_crate_root(lib_id);
    crate_graph.add_dep(main_crate, "test_crate".into(), lib_crate);

    db.set_crate_graph(crate_graph);

    let source_root = db.file_source_root(main_id);
    let module = crate::source_binder::module_from_file_id(&db, main_id)
        .unwrap()
        .unwrap();
    let module_id = module.def_id.loc(&db).module_id;
    let item_map = db.item_map(source_root).unwrap();

    check_module_item_map(
        &item_map,
        module_id,
        "
            Baz: t v
            test_crate: t
        ",
    );
}

#[test]
fn import_across_source_roots() {
    let (mut db, sr) = MockDatabase::with_files(
        "
        //- /lib.rs
        pub mod a {
            pub mod b {
                pub struct C;
            }
        }
    ",
    );
    let lib_id = sr.files[RelativePath::new("/lib.rs")];

    let source_root = SourceRootId(1);

    let (sr2, pos) = db.add_fixture(
        source_root,
        "
        //- /main.rs
        use test_crate::a::b::C;
    ",
    );
    assert!(pos.is_none());

    let main_id = sr2.files[RelativePath::new("/main.rs")];

    eprintln!("lib = {:?}, main = {:?}", lib_id, main_id);

    let mut crate_graph = CrateGraph::default();
    let main_crate = crate_graph.add_crate_root(main_id);
    let lib_crate = crate_graph.add_crate_root(lib_id);
    crate_graph.add_dep(main_crate, "test_crate".into(), lib_crate);

    db.set_crate_graph(crate_graph);

    let module = crate::source_binder::module_from_file_id(&db, main_id)
        .unwrap()
        .unwrap();
    let module_id = module.def_id.loc(&db).module_id;
    let item_map = db.item_map(source_root).unwrap();

    check_module_item_map(
        &item_map,
        module_id,
        "
            C: t v
            test_crate: t
        ",
    );
}

#[test]
fn reexport_across_crates() {
    let (mut db, sr) = MockDatabase::with_files(
        "
        //- /main.rs
        use test_crate::Baz;

        //- /lib.rs
        pub use foo::Baz;

        mod foo;

        //- /foo.rs
        pub struct Baz;
    ",
    );
    let main_id = sr.files[RelativePath::new("/main.rs")];
    let lib_id = sr.files[RelativePath::new("/lib.rs")];

    let mut crate_graph = CrateGraph::default();
    let main_crate = crate_graph.add_crate_root(main_id);
    let lib_crate = crate_graph.add_crate_root(lib_id);
    crate_graph.add_dep(main_crate, "test_crate".into(), lib_crate);

    db.set_crate_graph(crate_graph);

    let source_root = db.file_source_root(main_id);
    let module = crate::source_binder::module_from_file_id(&db, main_id)
        .unwrap()
        .unwrap();
    let module_id = module.def_id.loc(&db).module_id;
    let item_map = db.item_map(source_root).unwrap();

    check_module_item_map(
        &item_map,
        module_id,
        "
            Baz: t v
            test_crate: t
        ",
    );
}

fn check_item_map_is_not_recomputed(initial: &str, file_change: &str) {
    let (mut db, pos) = MockDatabase::with_position(initial);
    let source_root = db.file_source_root(pos.file_id);
    {
        let events = db.log_executed(|| {
            db.item_map(source_root).unwrap();
        });
        assert!(format!("{:?}", events).contains("item_map"))
    }
    db.query_mut(ra_db::FileTextQuery)
        .set(pos.file_id, Arc::new(file_change.to_string()));

    {
        let events = db.log_executed(|| {
            db.item_map(source_root).unwrap();
        });
        assert!(
            !format!("{:?}", events).contains("item_map"),
            "{:#?}",
            events
        )
    }
}

#[test]
fn typing_inside_a_function_should_not_invalidate_item_map() {
    check_item_map_is_not_recomputed(
        "
        //- /lib.rs
        mod foo;<|>

        use crate::foo::bar::Baz;

        fn foo() -> i32 {
            1 + 1
        }
        //- /foo/mod.rs
        pub mod bar;

        //- /foo/bar.rs
        pub struct Baz;
        ",
        "
        mod foo;

        use crate::foo::bar::Baz;

        fn foo() -> i32 { 92 }
        ",
    );
}

#[test]
fn typing_inside_a_function_inside_a_macro_should_not_invalidate_item_map() {
    check_item_map_is_not_recomputed(
        "
        //- /lib.rs
        mod foo;

        use crate::foo::bar::Baz;

        //- /foo/mod.rs
        pub mod bar;

        //- /foo/bar.rs
        <|>
        salsa::query_group! {
            trait Baz {
                fn foo() -> i32 { 1 + 1 }
            }
        }
        ",
        "
        salsa::query_group! {
            trait Baz {
                fn foo() -> i32 { 92 }
            }
        }
        ",
    );
}
<|endoftext|>