<commit_before>// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

use crate::{
    jellyfish_merkle_node::JellyfishMerkleNodeSchema, metrics::PRUNER_LEAST_READABLE_VERSION,
    pruner::db_pruner::DBPruner, stale_node_index::StaleNodeIndexSchema, OTHER_TIMERS_SECONDS,
};
use aptos_infallible::Mutex;
use aptos_jellyfish_merkle::StaleNodeIndex;
use aptos_logger::{error, warn};
use aptos_types::transaction::{AtomicVersion, Version};
use schemadb::{ReadOptions, SchemaBatch, SchemaIterator, DB};
use std::{
    iter::Peekable,
    sync::{atomic::Ordering, Arc},
    time::{Duration, Instant},
};

#[cfg(test)]
mod test;

pub const STATE_STORE_PRUNER_NAME: &str = "state store pruner";

pub struct StateStorePruner {
    db: Arc<DB>,
    index_min_nonpurged_version: AtomicVersion,
    index_purged_at: Mutex<Instant>,
    /// Keeps track of the target version that the pruner needs to achieve.
    target_version: AtomicVersion,
    min_readable_version: AtomicVersion,
}

impl DBPruner for StateStorePruner {
    fn name(&self) -> &'static str {
        STATE_STORE_PRUNER_NAME
    }

    fn prune(
        &self,
        _ledger_db_batch: &mut SchemaBatch,
        max_versions: u64,
    ) -> anyhow::Result<Version> {
        if !self.is_pruning_pending() {
            return Ok(self.min_readable_version());
        }
        let min_readable_version = self.min_readable_version.load(Ordering::Relaxed);
        let target_version = self.target_version();
        return match prune_state_store(
            &self.db,
            min_readable_version,
            target_version,
            max_versions as usize,
        ) {
            Ok(new_min_readable_version) => {
                self.record_progress(new_min_readable_version);
                // Try to purge the log.
                if let Err(e) = self.maybe_purge_index() {
                    warn!(
                        error = ?e,
                        "Failed purging state node index, ignored.",
                    );
                }
                Ok(new_min_readable_version)
            }
            Err(e) => {
                error!(
                    error = ?e,
                    "Error pruning stale state nodes.",
                );
                Err(e)
                // On error, stop retrying vigorously by making next recv() blocking.
            }
        };
    }

    fn initialize_min_readable_version(&self) -> anyhow::Result<Version> {
        let mut iter = self
            .db
            .iter::<StaleNodeIndexSchema>(ReadOptions::default())?;
        iter.seek_to_first();
        Ok(iter.next().transpose()?.map_or(0, |(index, _)| {
            index
                .stale_since_version
                .checked_sub(1)
                .expect("Nothing is stale since version 0.")
        }))
    }

    fn min_readable_version(&self) -> Version {
        self.min_readable_version.load(Ordering::Relaxed)
    }

    fn set_target_version(&self, target_version: Version) {
        self.target_version.store(target_version, Ordering::Relaxed);
    }

    fn target_version(&self) -> Version {
        self.target_version.load(Ordering::Relaxed)
    }

    fn record_progress(&self, min_readable_version: Version) {
        self.min_readable_version
            .store(min_readable_version, Ordering::Relaxed);
        PRUNER_LEAST_READABLE_VERSION
            .with_label_values(&["state_store"])
            .set(min_readable_version as i64);
    }
}

impl StateStorePruner {
    pub fn new(
        db: Arc<DB>,
        index_min_nonpurged_version: Version,
        index_purged_at: Instant,
    ) -> Self {
        let pruner = StateStorePruner {
            db,
            index_min_nonpurged_version: AtomicVersion::new(index_min_nonpurged_version),
            index_purged_at: Mutex::new(index_purged_at),
            target_version: AtomicVersion::new(0),
            min_readable_version: AtomicVersion::new(0),
        };
        pruner.initialize();
        pruner
    }

    /// Purge the stale node index so that after restart not too much already pruned stuff is dealt
    /// with again (although no harm is done deleting those then non-existent things.)
    ///
    /// We issue (range) deletes on the index only periodically instead of after every pruning batch
    /// to avoid sending too many deletions to the DB, which takes disk space and slows it down.
    fn maybe_purge_index(&self) -> anyhow::Result<()> {
        const MIN_INTERVAL: Duration = Duration::from_secs(10);
        const MIN_VERSIONS: u64 = 60000;

        // A deletion is issued at most once in one minute and when the pruner has progressed by at
        // least 60000 versions (assuming the pruner deletes as slow as 1000 versions per second,
        // this imposes at most one minute of work in vain after restarting.)
        let now = Instant::now();
        if now - *self.index_purged_at.lock() > MIN_INTERVAL
            && self.min_readable_version.load(Ordering::Relaxed)
                - self.index_min_nonpurged_version()
                + 1
                > MIN_VERSIONS
        {
            let new_min_non_purged_version = self.min_readable_version.load(Ordering::Relaxed) + 1;
            self.db.range_delete::<StaleNodeIndexSchema, Version>(
                &self.index_min_nonpurged_version(),
                &new_min_non_purged_version, // end is exclusive
            )?;
            self.index_min_nonpurged_version
                .store(new_min_non_purged_version, Ordering::Relaxed);
            *self.index_purged_at.lock() = now;
        }
        Ok(())
    }

    pub fn index_min_nonpurged_version(&self) -> Version {
        self.index_min_nonpurged_version.load(Ordering::Relaxed)
    }

    pub fn target_version(&self) -> Version {
        self.target_version.load(Ordering::Relaxed)
    }
}

pub fn prune_state_store(
    db: &DB,
    min_readable_version: Version,
    target_version: Version,
    max_versions: usize,
) -> anyhow::Result<Version> {
    let indices = StaleNodeIndicesByVersionIterator::new(db, min_readable_version, target_version)?
        .take(max_versions) // Iterator<Item = Result<Vec<StaleNodeIndex>>>
        .collect::<anyhow::Result<Vec<_>>>()? // now Vec<Vec<StaleNodeIndex>>
        .into_iter()
        .flatten()
        .collect::<Vec<_>>();

    if indices.is_empty() {
        Ok(min_readable_version)
    } else {
        let _timer = OTHER_TIMERS_SECONDS
            .with_label_values(&["pruner_commit"])
            .start_timer();
        let new_min_readable_version = indices.last().expect("Should exist.").stale_since_version;
        let mut batch = SchemaBatch::new();
        indices
            .into_iter()
            .try_for_each(|index| batch.delete::<JellyfishMerkleNodeSchema>(&index.node_key))?;
        db.write_schemas(batch)?;
        Ok(new_min_readable_version)
    }
}

struct StaleNodeIndicesByVersionIterator<'a> {
    inner: Peekable<SchemaIterator<'a, StaleNodeIndexSchema>>,
    target_min_readable_version: Version,
}

impl<'a> StaleNodeIndicesByVersionIterator<'a> {
    fn new(
        db: &'a DB,
        min_readable_version: Version,
        target_min_readable_version: Version,
    ) -> anyhow::Result<Self> {
        let mut iter = db.iter::<StaleNodeIndexSchema>(ReadOptions::default())?;
        iter.seek(&min_readable_version)?;

        Ok(Self {
            inner: iter.peekable(),
            target_min_readable_version,
        })
    }

    fn next_result(&mut self) -> anyhow::Result<Option<Vec<StaleNodeIndex>>> {
        match self.inner.next().transpose()? {
            None => Ok(None),
            Some((index, _)) => {
                let version = index.stale_since_version;
                if version > self.target_min_readable_version {
                    return Ok(None);
                }

                let mut indices = vec![index];
                while let Some(res) = self.inner.peek() {
                    if let Ok((index_ref, _)) = res {
                        if index_ref.stale_since_version != version {
                            break;
                        }
                    }

                    let (index, _) = self.inner.next().transpose()?.expect("Should be Some.");
                    indices.push(index);
                }

                Ok(Some(indices))
            }
        }
    }
}

impl<'a> Iterator for StaleNodeIndicesByVersionIterator<'a> {
    type Item = anyhow::Result<Vec<StaleNodeIndex>>;

    fn next(&mut self) -> Option<Self::Item> {
        self.next_result().transpose()
    }
}
<commit_msg>[Storage][Pruner] Ignore error in state index pruning<commit_after>// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

use crate::{
    jellyfish_merkle_node::JellyfishMerkleNodeSchema, metrics::PRUNER_LEAST_READABLE_VERSION,
    pruner::db_pruner::DBPruner, stale_node_index::StaleNodeIndexSchema, OTHER_TIMERS_SECONDS,
};
use aptos_infallible::Mutex;
use aptos_jellyfish_merkle::StaleNodeIndex;
use aptos_logger::{error, warn};
use aptos_types::transaction::{AtomicVersion, Version};
use schemadb::{ReadOptions, SchemaBatch, SchemaIterator, DB};
use std::{
    iter::Peekable,
    sync::{atomic::Ordering, Arc},
    time::{Duration, Instant},
};

#[cfg(test)]
mod test;

pub const STATE_STORE_PRUNER_NAME: &str = "state store pruner";

pub struct StateStorePruner {
    db: Arc<DB>,
    index_min_nonpurged_version: AtomicVersion,
    index_purged_at: Mutex<Instant>,
    /// Keeps track of the target version that the pruner needs to achieve.
    target_version: AtomicVersion,
    min_readable_version: AtomicVersion,
}

impl DBPruner for StateStorePruner {
    fn name(&self) -> &'static str {
        STATE_STORE_PRUNER_NAME
    }

    fn prune(
        &self,
        _ledger_db_batch: &mut SchemaBatch,
        max_versions: u64,
    ) -> anyhow::Result<Version> {
        if !self.is_pruning_pending() {
            return Ok(self.min_readable_version());
        }
        let min_readable_version = self.min_readable_version.load(Ordering::Relaxed);
        let target_version = self.target_version();
        return match prune_state_store(
            &self.db,
            min_readable_version,
            target_version,
            max_versions as usize,
        ) {
            Ok(new_min_readable_version) => {
                self.record_progress(new_min_readable_version);
                // Try to purge the log.
                if let Err(e) = self.maybe_purge_index() {
                    warn!(
                        error = ?e,
                        "Failed purging state node index, ignored.",
                    );
                }
                Ok(new_min_readable_version)
            }
            Err(e) => {
                error!(
                    error = ?e,
                    "Error pruning stale state nodes.",
                );
                Err(e)
                // On error, stop retrying vigorously by making next recv() blocking.
            }
        };
    }

    fn initialize_min_readable_version(&self) -> anyhow::Result<Version> {
        let mut iter = self
            .db
            .iter::<StaleNodeIndexSchema>(ReadOptions::default())?;
        iter.seek_to_first();
        Ok(iter.next().transpose()?.map_or(0, |(index, _)| {
            index
                .stale_since_version
                .checked_sub(1)
                .expect("Nothing is stale since version 0.")
        }))
    }

    fn min_readable_version(&self) -> Version {
        self.min_readable_version.load(Ordering::Relaxed)
    }

    fn set_target_version(&self, target_version: Version) {
        self.target_version.store(target_version, Ordering::Relaxed);
    }

    fn target_version(&self) -> Version {
        self.target_version.load(Ordering::Relaxed)
    }

    fn record_progress(&self, min_readable_version: Version) {
        self.min_readable_version
            .store(min_readable_version, Ordering::Relaxed);
        PRUNER_LEAST_READABLE_VERSION
            .with_label_values(&["state_store"])
            .set(min_readable_version as i64);
    }
}

impl StateStorePruner {
    pub fn new(
        db: Arc<DB>,
        index_min_nonpurged_version: Version,
        index_purged_at: Instant,
    ) -> Self {
        let pruner = StateStorePruner {
            db,
            index_min_nonpurged_version: AtomicVersion::new(index_min_nonpurged_version),
            index_purged_at: Mutex::new(index_purged_at),
            target_version: AtomicVersion::new(0),
            min_readable_version: AtomicVersion::new(0),
        };
        pruner.initialize();
        pruner
    }

    /// Purge the stale node index so that after restart not too much already pruned stuff is dealt
    /// with again (although no harm is done deleting those then non-existent things.)
    ///
    /// We issue (range) deletes on the index only periodically instead of after every pruning batch
    /// to avoid sending too many deletions to the DB, which takes disk space and slows it down.
    fn maybe_purge_index(&self) -> anyhow::Result<()> {
        const MIN_INTERVAL: Duration = Duration::from_secs(10);
        const MIN_VERSIONS: u64 = 60000;

        // A deletion is issued at most once in one minute and when the pruner has progressed by at
        // least 60000 versions (assuming the pruner deletes as slow as 1000 versions per second,
        // this imposes at most one minute of work in vain after restarting.)
        let now = Instant::now();
        if self.min_readable_version.load(Ordering::Relaxed) < self.index_min_nonpurged_version() {
            warn!("State pruner inconsistent, min_readable_version is {} and  index_min_non-purged_version is {}",
                self.min_readable_version.load(Ordering::Relaxed), self.index_min_nonpurged_version());
            return Ok(());
        }

        if now - *self.index_purged_at.lock() > MIN_INTERVAL
            && self.min_readable_version.load(Ordering::Relaxed)
                - self.index_min_nonpurged_version()
                + 1
                > MIN_VERSIONS
        {
            let new_min_non_purged_version = self.min_readable_version.load(Ordering::Relaxed) + 1;
            self.db.range_delete::<StaleNodeIndexSchema, Version>(
                &self.index_min_nonpurged_version(),
                &new_min_non_purged_version, // end is exclusive
            )?;
            self.index_min_nonpurged_version
                .store(new_min_non_purged_version, Ordering::Relaxed);
            *self.index_purged_at.lock() = now;
        }
        Ok(())
    }

    pub fn index_min_nonpurged_version(&self) -> Version {
        self.index_min_nonpurged_version.load(Ordering::Relaxed)
    }

    pub fn target_version(&self) -> Version {
        self.target_version.load(Ordering::Relaxed)
    }
}

pub fn prune_state_store(
    db: &DB,
    min_readable_version: Version,
    target_version: Version,
    max_versions: usize,
) -> anyhow::Result<Version> {
    let indices = StaleNodeIndicesByVersionIterator::new(db, min_readable_version, target_version)?
        .take(max_versions) // Iterator<Item = Result<Vec<StaleNodeIndex>>>
        .collect::<anyhow::Result<Vec<_>>>()? // now Vec<Vec<StaleNodeIndex>>
        .into_iter()
        .flatten()
        .collect::<Vec<_>>();

    if indices.is_empty() {
        Ok(min_readable_version)
    } else {
        let _timer = OTHER_TIMERS_SECONDS
            .with_label_values(&["pruner_commit"])
            .start_timer();
        let new_min_readable_version = indices.last().expect("Should exist.").stale_since_version;
        let mut batch = SchemaBatch::new();
        indices
            .into_iter()
            .try_for_each(|index| batch.delete::<JellyfishMerkleNodeSchema>(&index.node_key))?;
        db.write_schemas(batch)?;
        Ok(new_min_readable_version)
    }
}

struct StaleNodeIndicesByVersionIterator<'a> {
    inner: Peekable<SchemaIterator<'a, StaleNodeIndexSchema>>,
    target_min_readable_version: Version,
}

impl<'a> StaleNodeIndicesByVersionIterator<'a> {
    fn new(
        db: &'a DB,
        min_readable_version: Version,
        target_min_readable_version: Version,
    ) -> anyhow::Result<Self> {
        let mut iter = db.iter::<StaleNodeIndexSchema>(ReadOptions::default())?;
        iter.seek(&min_readable_version)?;

        Ok(Self {
            inner: iter.peekable(),
            target_min_readable_version,
        })
    }

    fn next_result(&mut self) -> anyhow::Result<Option<Vec<StaleNodeIndex>>> {
        match self.inner.next().transpose()? {
            None => Ok(None),
            Some((index, _)) => {
                let version = index.stale_since_version;
                if version > self.target_min_readable_version {
                    return Ok(None);
                }

                let mut indices = vec![index];
                while let Some(res) = self.inner.peek() {
                    if let Ok((index_ref, _)) = res {
                        if index_ref.stale_since_version != version {
                            break;
                        }
                    }

                    let (index, _) = self.inner.next().transpose()?.expect("Should be Some.");
                    indices.push(index);
                }

                Ok(Some(indices))
            }
        }
    }
}

impl<'a> Iterator for StaleNodeIndicesByVersionIterator<'a> {
    type Item = anyhow::Result<Vec<StaleNodeIndex>>;

    fn next(&mut self) -> Option<Self::Item> {
        self.next_result().transpose()
    }
}
<|endoftext|>


<commit_before>use crate::err::{self, Result};
use smallvec::{smallvec, SmallVec};
use snafu::ensure;
use std::convert::TryFrom;
use std::result;
use termion::event::Key;
use tui::style::{Color, Style};
use tui::widgets::Text;
use unicode_width::UnicodeWidthChar;

/// A prompt to enter commands in that provides suggestions.
pub struct CommandPrompt<'a> {
    buffer: String,
    hint_text: Option<&'a str>,
    width: usize,
}

impl<'a> CommandPrompt<'a> {
    /// Create a new `CommandPrompt`.
    pub fn new() -> Self {
        CommandPrompt {
            buffer: String::new(),
            hint_text: None,
            width: 0,
        }
    }

    /// Process a key for the `CommandPrompt`.
    ///
    /// Returns a `PromptResult` representing the state of the prompt based off of `key`.
    /// Example return values are given below:
    ///
    /// | `key`             | `PromptResult`             |
    /// | ----------------- | -------------------------- |
    /// | `Key::Char('a')`  | `PromptResult::NotDone`    |
    /// | `Key::Esc`        | `PromptResult::Done`       |
    /// | `Key::Char('\n')` | `PromptResult::Command(_)` |
    pub fn process_key(&mut self, key: Key) -> Result<PromptResult> {
        match key {
            Key::Char('\n') => {
                let command = Command::try_from(self.buffer.as_ref())?;
                self.buffer.clear();
                self.width = 0;
                return Ok(PromptResult::Command(command));
            }
            Key::Char('\t') => {
                if let Some(hint_text) = self.hint_text {
                    self.buffer.push_str(hint_text);
                    self.buffer.push(' ');
                    // Our hint text should always be ASCII, so we can skip getting the unicode length in this case
                    self.width += hint_text.len() + 1;
                    self.hint_text = None;
                }
            }
            Key::Char(ch) => {
                self.buffer.push(ch);
                self.width += UnicodeWidthChar::width(ch).unwrap_or(0);

                if let Some(matching_cmd) = Command::best_matching_name(&self.buffer) {
                    if self.buffer.len() <= matching_cmd.len() {
                        let visible_slice = &matching_cmd[self.buffer.len()..];
                        self.hint_text = Some(visible_slice);
                    }
                } else {
                    self.hint_text = None;
                }
            }
            Key::Backspace => {
                if let Some(popped) = self.buffer.pop() {
                    self.width -= UnicodeWidthChar::width(popped).unwrap_or(0);
                }

                self.hint_text = None;
            }
            Key::Esc => {
                self.buffer.clear();
                self.width = 0;
                return Ok(PromptResult::Done);
            }
            _ => (),
        }

        Ok(PromptResult::NotDone)
    }

    #[inline(always)]
    pub fn width(&self) -> usize {
        self.width
    }

    /// The items of the `CommandPrompt` in a form ready for drawing.
    pub fn draw_items<'b>(&'b self) -> SmallVec<[Text<'b>; 2]> {
        let mut text = smallvec![Text::raw(&self.buffer)];

        if let Some(hint_text) = self.hint_text {
            text.push(Text::styled(
                hint_text,
                Style::default().fg(Color::DarkGray),
            ));
        }

        text
    }
}

/// The result of processing a key in a `CommandPrompt`.
pub enum PromptResult {
    /// A successfully parsed command.
    Command(Command),
    /// Input is considered completed without a command.
    Done,
    /// More input is needed to reach a `Command(_)` or `Done` state.
    NotDone,
}

macro_rules! impl_command_matching {
    ($enum_name:ident, $num_cmds:expr, $($field:pat => { name: $name:expr, min_args: $min_args:expr, fn: $parse_fn:expr, },)+) => {
        impl $enum_name {
            const CMD_NAMES: [&'static str; $num_cmds] = [
                $($name,)+
            ];
        }

        impl TryFrom<&str> for $enum_name {
            type Error = err::Error;

            fn try_from(value: &str) -> result::Result<Self, Self::Error> {
                let fragments = value.split_whitespace().collect::<SmallVec<[&str; 3]>>();

                ensure!(!fragments.is_empty(), err::NoCommandSpecified);

                let name = fragments[0].to_ascii_lowercase();
                let args = if fragments.len() > 1 {
                    &fragments[1..]
                } else {
                    &[]
                };

                match name.as_ref() {
                    $($name => {
                        #[allow(unused_comparisons)]
                        let has_min_args = args.len() >= $min_args;

                        ensure!(
                            has_min_args,
                            err::NotEnoughArguments {
                                has: args.len(),
                                need: $min_args as usize,
                            }
                        );

                        $parse_fn(args)
                    },)+
                    _ => Err(err::Error::CommandNotFound {
                        command: value.into(),
                    }),
                }
            }
        }
    };
}

/// A parsed command with its arguments.
#[derive(Debug, Clone)]
pub enum Command {
    /// Remove the selected series from the program.
    Delete,
    /// Set the user's login token for a remote service.
    LoginToken(String),
    /// Specify the video player arguments for the selected season.
    PlayerArgs(Vec<String>),
    /// Increment / decrement the watched episodes of the selected season.
    Progress(ProgressDirection),
    /// Syncronize the selected season to the remote service.
    SyncFromRemote,
    /// Syncronize the selected season from the remote service.
    SyncToRemote,
    /// Rate the selected season.
    Score(String),
    /// Set the watch status of the selected season.
    Status(anime::remote::Status),
}

impl_command_matching!(Command, 8,
    Delete => {
        name: "delete",
        min_args: 0,
        fn: |_| Ok(Command::Delete),
    },
    LoginToken(_) => {
        name: "token",
        min_args: 1,
        fn: |args: &[&str]| {
            Ok(Command::LoginToken(args.join(" ")))
        },
    },
    PlayerArgs(_) => {
        name: "args",
        min_args: 0,
        fn: |args: &[&str]| {
            let args = args.iter()
                .map(|frag| frag.to_string())
                .collect();

            Ok(Command::PlayerArgs(args))
        },
    },
    Progress(_) => {
        name: "progress",
        min_args: 1,
        fn: |args: &[&str]| {
            let dir = ProgressDirection::try_from(args[0])?;
            Ok(Command::Progress(dir))
        },
    },
    SyncFromRemote => {
        name: "syncfromremote",
        min_args: 0,
        fn: |_| Ok(Command::SyncFromRemote),
    },
    SyncToRemote => {
        name: "synctoremote",
        min_args: 0,
        fn: |_| Ok(Command::SyncToRemote),
    },
    Score(_) => {
        name: "rate",
        min_args: 1,
        fn: |args: &[&str]| {
            let score = args[0].into();
            Ok(Command::Score(score))
        },
    },
    Status(_) => {
        name: "status",
        min_args: 1,
        fn: |args: &[&str]| {
            use anime::remote::Status;

            let status = match args[0].to_ascii_lowercase().as_ref() {
                "w" | "watching" => Status::Watching,
                "c" | "completed" => Status::Completed,
                "h" | "hold" => Status::OnHold,
                "d" | "drop" => Status::Dropped,
                "p" | "plan" => Status::PlanToWatch,
                "r" | "rewatch" => Status::Rewatching,
                _ => {
                    return Err(err::Error::UnknownCmdPromptArg {
                        value: args[0].into(),
                    })
                }
            };

            Ok(Command::Status(status))
        },
    },
);

impl Command {
    /// Returns the command most similar to `name`.
    ///
    /// `None` will be returned if `name` does not match a command with
    /// at least 70% similarity.
    fn best_matching_name(name: &str) -> Option<&'static str> {
        const MIN_CONFIDENCE: f32 = 0.7;

        detect::closest_str_match(
            &Command::CMD_NAMES,
            name,
            MIN_CONFIDENCE,
            strsim::jaro_winkler,
        )
    }
}

/// Indicates which way to advance the episode count of a season.
#[derive(Debug, Copy, Clone)]
pub enum ProgressDirection {
    /// Increase the episode count.
    Forwards,
    /// Decrease the episode count.
    Backwards,
}

impl TryFrom<&str> for ProgressDirection {
    type Error = err::Error;

    fn try_from(value: &str) -> result::Result<Self, Self::Error> {
        match value {
            "f" | "forward" => Ok(ProgressDirection::Forwards),
            "b" | "backward" => Ok(ProgressDirection::Backwards),
            _ => Err(err::Error::UnknownCmdPromptArg {
                value: value.into(),
            }),
        }
    }
}

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

    #[test]
    fn test_commands() {
        use anime::remote::Status;

        let mut prompt = CommandPrompt::new();

        let mut enter_command = |name: &str| {
            for ch in name.chars() {
                match prompt.process_key(Key::Char(ch)) {
                    Ok(PromptResult::NotDone) => (),
                    Ok(PromptResult::Done) => panic!("expected {} command, got nothing", name),
                    Ok(PromptResult::Command(cmd)) => return cmd,
                    Err(err) => panic!("error processing command: {}", err),
                }
            }

            panic!("unexpected finish when entering \"{}\"", name);
        };

        macro_rules! expected {
            ($got:expr, $wanted:expr) => {
                panic!("got unexpected command: {:?}, wanted  {:?}", $got, $wanted)
            };
        }

        macro_rules! test_command {
            ($name:expr, $should_eq:pat) => {
                match enter_command($name) {
                    $should_eq => (),
                    other => expected!(other, stringify!($should_eq)),
                }
            };
        }

        test_command!("synctoremote\n", Command::SyncToRemote);
        test_command!("syncfromremote\n", Command::SyncFromRemote);
        test_command!("status watching\n", Command::Status(Status::Watching));
        test_command!(
            "progress forward\n",
            Command::Progress(ProgressDirection::Forwards)
        );

        let expected_args: Vec<String> = vec!["arg1".into(), "arg2".into()];

        match enter_command("args arg1 arg2\n") {
            Command::PlayerArgs(args) => {
                if args.len() != expected_args.len() {
                    expected!(
                        Command::PlayerArgs(args),
                        Command::PlayerArgs(expected_args)
                    );
                }

                for (parsed, expected_arg) in args.iter().zip(&expected_args) {
                    if *parsed != *expected_arg {
                        expected!(
                            Command::PlayerArgs(args),
                            Command::PlayerArgs(expected_args)
                        );
                    }
                }
            }
            other => expected!(other, Command::PlayerArgs(expected_args)),
        }

        match enter_command("token inserttokenhere\n") {
            Command::LoginToken(token) if token == "inserttokenhere" => (),
            other => expected!(other, Command::LoginToken(String::from("inserttokenhere"))),
        }
    }
}
<commit_msg>anup: slightly refactor command testing<commit_after>use crate::err::{self, Result};
use smallvec::{smallvec, SmallVec};
use snafu::ensure;
use std::convert::TryFrom;
use std::result;
use termion::event::Key;
use tui::style::{Color, Style};
use tui::widgets::Text;
use unicode_width::UnicodeWidthChar;

/// A prompt to enter commands in that provides suggestions.
pub struct CommandPrompt<'a> {
    buffer: String,
    hint_text: Option<&'a str>,
    width: usize,
}

impl<'a> CommandPrompt<'a> {
    /// Create a new `CommandPrompt`.
    pub fn new() -> Self {
        CommandPrompt {
            buffer: String::new(),
            hint_text: None,
            width: 0,
        }
    }

    /// Process a key for the `CommandPrompt`.
    ///
    /// Returns a `PromptResult` representing the state of the prompt based off of `key`.
    /// Example return values are given below:
    ///
    /// | `key`             | `PromptResult`             |
    /// | ----------------- | -------------------------- |
    /// | `Key::Char('a')`  | `PromptResult::NotDone`    |
    /// | `Key::Esc`        | `PromptResult::Done`       |
    /// | `Key::Char('\n')` | `PromptResult::Command(_)` |
    pub fn process_key(&mut self, key: Key) -> Result<PromptResult> {
        match key {
            Key::Char('\n') => {
                let command = Command::try_from(self.buffer.as_ref())?;
                self.buffer.clear();
                self.width = 0;
                return Ok(PromptResult::Command(command));
            }
            Key::Char('\t') => {
                if let Some(hint_text) = self.hint_text {
                    self.buffer.push_str(hint_text);
                    self.buffer.push(' ');
                    // Our hint text should always be ASCII, so we can skip getting the unicode length in this case
                    self.width += hint_text.len() + 1;
                    self.hint_text = None;
                }
            }
            Key::Char(ch) => {
                self.buffer.push(ch);
                self.width += UnicodeWidthChar::width(ch).unwrap_or(0);

                if let Some(matching_cmd) = Command::best_matching_name(&self.buffer) {
                    if self.buffer.len() <= matching_cmd.len() {
                        let visible_slice = &matching_cmd[self.buffer.len()..];
                        self.hint_text = Some(visible_slice);
                    }
                } else {
                    self.hint_text = None;
                }
            }
            Key::Backspace => {
                if let Some(popped) = self.buffer.pop() {
                    self.width -= UnicodeWidthChar::width(popped).unwrap_or(0);
                }

                self.hint_text = None;
            }
            Key::Esc => {
                self.buffer.clear();
                self.width = 0;
                return Ok(PromptResult::Done);
            }
            _ => (),
        }

        Ok(PromptResult::NotDone)
    }

    #[inline(always)]
    pub fn width(&self) -> usize {
        self.width
    }

    /// The items of the `CommandPrompt` in a form ready for drawing.
    pub fn draw_items<'b>(&'b self) -> SmallVec<[Text<'b>; 2]> {
        let mut text = smallvec![Text::raw(&self.buffer)];

        if let Some(hint_text) = self.hint_text {
            text.push(Text::styled(
                hint_text,
                Style::default().fg(Color::DarkGray),
            ));
        }

        text
    }
}

/// The result of processing a key in a `CommandPrompt`.
pub enum PromptResult {
    /// A successfully parsed command.
    Command(Command),
    /// Input is considered completed without a command.
    Done,
    /// More input is needed to reach a `Command(_)` or `Done` state.
    NotDone,
}

macro_rules! impl_command_matching {
    ($enum_name:ident, $num_cmds:expr, $($field:pat => { name: $name:expr, min_args: $min_args:expr, fn: $parse_fn:expr, },)+) => {
        impl $enum_name {
            const CMD_NAMES: [&'static str; $num_cmds] = [
                $($name,)+
            ];
        }

        impl TryFrom<&str> for $enum_name {
            type Error = err::Error;

            fn try_from(value: &str) -> result::Result<Self, Self::Error> {
                let fragments = value.split_whitespace().collect::<SmallVec<[&str; 3]>>();

                ensure!(!fragments.is_empty(), err::NoCommandSpecified);

                let name = fragments[0].to_ascii_lowercase();
                let args = if fragments.len() > 1 {
                    &fragments[1..]
                } else {
                    &[]
                };

                match name.as_ref() {
                    $($name => {
                        #[allow(unused_comparisons)]
                        let has_min_args = args.len() >= $min_args;

                        ensure!(
                            has_min_args,
                            err::NotEnoughArguments {
                                has: args.len(),
                                need: $min_args as usize,
                            }
                        );

                        $parse_fn(args)
                    },)+
                    _ => Err(err::Error::CommandNotFound {
                        command: value.into(),
                    }),
                }
            }
        }
    };
}

/// A parsed command with its arguments.
#[derive(Debug, Clone)]
pub enum Command {
    /// Remove the selected series from the program.
    Delete,
    /// Set the user's login token for a remote service.
    LoginToken(String),
    /// Specify the video player arguments for the selected season.
    PlayerArgs(Vec<String>),
    /// Increment / decrement the watched episodes of the selected season.
    Progress(ProgressDirection),
    /// Syncronize the selected season to the remote service.
    SyncFromRemote,
    /// Syncronize the selected season from the remote service.
    SyncToRemote,
    /// Rate the selected season.
    Score(String),
    /// Set the watch status of the selected season.
    Status(anime::remote::Status),
}

impl_command_matching!(Command, 8,
    Delete => {
        name: "delete",
        min_args: 0,
        fn: |_| Ok(Command::Delete),
    },
    LoginToken(_) => {
        name: "token",
        min_args: 1,
        fn: |args: &[&str]| {
            Ok(Command::LoginToken(args.join(" ")))
        },
    },
    PlayerArgs(_) => {
        name: "args",
        min_args: 0,
        fn: |args: &[&str]| {
            let args = args.iter()
                .map(|frag| frag.to_string())
                .collect();

            Ok(Command::PlayerArgs(args))
        },
    },
    Progress(_) => {
        name: "progress",
        min_args: 1,
        fn: |args: &[&str]| {
            let dir = ProgressDirection::try_from(args[0])?;
            Ok(Command::Progress(dir))
        },
    },
    SyncFromRemote => {
        name: "syncfromremote",
        min_args: 0,
        fn: |_| Ok(Command::SyncFromRemote),
    },
    SyncToRemote => {
        name: "synctoremote",
        min_args: 0,
        fn: |_| Ok(Command::SyncToRemote),
    },
    Score(_) => {
        name: "rate",
        min_args: 1,
        fn: |args: &[&str]| {
            let score = args[0].into();
            Ok(Command::Score(score))
        },
    },
    Status(_) => {
        name: "status",
        min_args: 1,
        fn: |args: &[&str]| {
            use anime::remote::Status;

            let status = match args[0].to_ascii_lowercase().as_ref() {
                "w" | "watching" => Status::Watching,
                "c" | "completed" => Status::Completed,
                "h" | "hold" => Status::OnHold,
                "d" | "drop" => Status::Dropped,
                "p" | "plan" => Status::PlanToWatch,
                "r" | "rewatch" => Status::Rewatching,
                _ => {
                    return Err(err::Error::UnknownCmdPromptArg {
                        value: args[0].into(),
                    })
                }
            };

            Ok(Command::Status(status))
        },
    },
);

impl Command {
    /// Returns the command most similar to `name`.
    ///
    /// `None` will be returned if `name` does not match a command with
    /// at least 70% similarity.
    fn best_matching_name(name: &str) -> Option<&'static str> {
        const MIN_CONFIDENCE: f32 = 0.7;

        detect::closest_str_match(
            &Command::CMD_NAMES,
            name,
            MIN_CONFIDENCE,
            strsim::jaro_winkler,
        )
    }
}

/// Indicates which way to advance the episode count of a season.
#[derive(Debug, Copy, Clone)]
pub enum ProgressDirection {
    /// Increase the episode count.
    Forwards,
    /// Decrease the episode count.
    Backwards,
}

impl TryFrom<&str> for ProgressDirection {
    type Error = err::Error;

    fn try_from(value: &str) -> result::Result<Self, Self::Error> {
        match value {
            "f" | "forward" => Ok(ProgressDirection::Forwards),
            "b" | "backward" => Ok(ProgressDirection::Backwards),
            _ => Err(err::Error::UnknownCmdPromptArg {
                value: value.into(),
            }),
        }
    }
}

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

    #[test]
    fn test_commands() {
        use anime::remote::Status;

        let mut prompt = CommandPrompt::new();

        let mut enter_command = |name: &str| {
            for ch in name.chars() {
                match prompt.process_key(Key::Char(ch)) {
                    Ok(PromptResult::NotDone) => (),
                    Ok(PromptResult::Done) => panic!("expected {} command, got nothing", name),
                    Ok(PromptResult::Command(cmd)) => return cmd,
                    Err(err) => panic!("error processing command: {}", err),
                }
            }

            panic!("unexpected finish when entering \"{}\"", name);
        };

        macro_rules! expected {
            ($got:expr, $wanted:expr) => {
                panic!("got unexpected command: {:?}, wanted  {:?}", $got, $wanted)
            };
        }

        macro_rules! test_command {
            ($name:expr, $should_eq:pat) => {
                match enter_command($name) {
                    $should_eq => (),
                    other => expected!(other, stringify!($should_eq)),
                }
            };
        }

        test_command!("delete\n", Command::Delete);

        match enter_command("token inserttokenhere\n") {
            Command::LoginToken(token) if token == "inserttokenhere" => (),
            other => expected!(other, Command::LoginToken(String::from("inserttokenhere"))),
        }

        let expected_args: Vec<String> = vec!["arg1".into(), "arg2".into()];

        match enter_command("args arg1 arg2\n") {
            Command::PlayerArgs(args) => {
                if args.len() != expected_args.len() {
                    expected!(
                        Command::PlayerArgs(args),
                        Command::PlayerArgs(expected_args)
                    );
                }

                for (parsed, expected_arg) in args.iter().zip(&expected_args) {
                    if *parsed != *expected_arg {
                        expected!(
                            Command::PlayerArgs(args),
                            Command::PlayerArgs(expected_args)
                        );
                    }
                }
            }
            other => expected!(other, Command::PlayerArgs(expected_args)),
        }

        test_command!(
            "progress forward\n",
            Command::Progress(ProgressDirection::Forwards)
        );

        test_command!("status watching\n", Command::Status(Status::Watching));
    }
}
<|endoftext|>


<commit_before>use std::collections::HashSet;
use super::bls::{
    AggregateSignature,
    AggregatePublicKey,
};
use super::db::ClientDB;
use super::db::stores::{
    ValidatorStore,
    ValidatorStoreError,
};
use super::types::Bitfield;

#[derive(Debug, PartialEq)]
pub enum SignatureVerificationError {
    BadValidatorIndex,
    PublicKeyCorrupt,
    NoPublicKeyForValidator,
    DBError(String),
}

/// Verify an aggregate signature across the supplied message.
///
/// The public keys used for verification are collected by mapping
/// each true bitfield bit to canonical ValidatorRecord index through
/// the attestation_indicies map.
///
/// Each public key is loaded from the store on-demand.
pub fn verify_aggregate_signature_for_indices<T>(
    message: &[u8],
    agg_sig: &AggregateSignature,
    attestation_indices: &[usize],
    bitfield: &Bitfield,
    validator_store: &ValidatorStore<T>)
    -> Result<Option<HashSet<usize>>, SignatureVerificationError>
    where T: ClientDB + Sized
{
    let mut voters = HashSet::new();
    let mut agg_pub_key = AggregatePublicKey::new();

    for i in 0..attestation_indices.len() {
        let voted = bitfield.get_bit(i);
        if voted {
            /*
             * De-reference the attestation index into a canonical ValidatorRecord index.
             */
            let validator = *attestation_indices.get(i)
                .ok_or(SignatureVerificationError::BadValidatorIndex)?;
            /*
             * Load the validators public key from our store.
             */
            let pub_key = validator_store
                .get_public_key_by_index(validator)?
                .ok_or(SignatureVerificationError::NoPublicKeyForValidator)?;
            /*
             * Add the validators public key to the aggregate public key.
             */
            agg_pub_key.add(&pub_key);
            /*
             * Add to the validator to the set of voters for this attestation record.
             */
            voters.insert(validator);
        }
    }
    /*
     * Verify the aggregate public key against the aggregate signature.
     *
     * This verification will only succeed if the exact set of public keys
     * were added to the aggregate public key as those that signed the aggregate signature.
     */
    if agg_sig.verify(&message, &agg_pub_key) {
        Ok(Some(voters))
    } else {
        Ok(None)
    }
}

impl From<ValidatorStoreError> for SignatureVerificationError {
    fn from(error: ValidatorStoreError) -> Self {
        match error {
            ValidatorStoreError::DBError(s) =>
                SignatureVerificationError::DBError(s),
            ValidatorStoreError::DecodeError =>
                SignatureVerificationError::PublicKeyCorrupt,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use super::super::bls::{
        Keypair,
        Signature,
    };
    use super::super::db::MemoryDB;
    use std::sync::Arc;

    /*
     * Cases that still need testing:
     *
     * - No signatures.
     * - Database failure.
     * - Unknown validator index.
     * - Extra validator on signature.
     */

    #[test]
    fn test_signature_verification() {
        let message = "cats".as_bytes();
        let signing_keypairs = vec![
            Keypair::random(),
            Keypair::random(),
            Keypair::random(),
            Keypair::random(),
            Keypair::random(),
            Keypair::random(),
        ];
        let non_signing_keypairs = vec![
            Keypair::random(),
            Keypair::random(),
            Keypair::random(),
            Keypair::random(),
            Keypair::random(),
            Keypair::random(),
        ];
        /*
         * Signing keypairs first, then non-signing
         */
        let mut all_keypairs = signing_keypairs.clone();
        all_keypairs.append(&mut non_signing_keypairs.clone());

        let attestation_indices: Vec<usize> = (0..all_keypairs.len())
            .collect();
        let mut bitfield = Bitfield::new();
        for i in 0..signing_keypairs.len() {
            bitfield.set_bit(i, true);
        }

        let db = Arc::new(MemoryDB::open());
        let store = ValidatorStore::new(db);

        for (i, keypair) in all_keypairs.iter().enumerate() {
            store.put_public_key_by_index(i, &keypair.pk).unwrap();
        }

        let mut agg_sig = AggregateSignature::new();
        for keypair in &signing_keypairs {
            let sig = Signature::new(&message, &keypair.sk);
            agg_sig.add(&sig);
        }

        /*
         * Test using all valid parameters.
         */
        let voters = verify_aggregate_signature_for_indices(
            &message,
            &agg_sig,
            &attestation_indices,
            &bitfield,
            &store).unwrap();

        let voters = voters.unwrap();
        (0..signing_keypairs.len())
            .for_each(|i| assert!(voters.contains(&i)));
        (signing_keypairs.len()..non_signing_keypairs.len())
            .for_each(|i| assert!(!voters.contains(&i)));

        /*
         * Add another validator to the bitfield, run validation will all other
         * parameters the same and assert that it fails.
         */
        bitfield.set_bit(signing_keypairs.len() + 1, true);
        let voters = verify_aggregate_signature_for_indices(
            &message,
            &agg_sig,
            &attestation_indices,
            &bitfield,
            &store).unwrap();

        assert_eq!(voters, None);
    }
}
<commit_msg>Fix a type in sig. ver. comment<commit_after>use std::collections::HashSet;
use super::bls::{
    AggregateSignature,
    AggregatePublicKey,
};
use super::db::ClientDB;
use super::db::stores::{
    ValidatorStore,
    ValidatorStoreError,
};
use super::types::Bitfield;

#[derive(Debug, PartialEq)]
pub enum SignatureVerificationError {
    BadValidatorIndex,
    PublicKeyCorrupt,
    NoPublicKeyForValidator,
    DBError(String),
}

/// Verify an aggregate signature across the supplied message.
///
/// The public keys used for verification are collected by mapping
/// each true bitfield bit to canonical ValidatorRecord index through
/// the attestation_indicies map.
///
/// Each public key is loaded from the store on-demand.
pub fn verify_aggregate_signature_for_indices<T>(
    message: &[u8],
    agg_sig: &AggregateSignature,
    attestation_indices: &[usize],
    bitfield: &Bitfield,
    validator_store: &ValidatorStore<T>)
    -> Result<Option<HashSet<usize>>, SignatureVerificationError>
    where T: ClientDB + Sized
{
    let mut voters = HashSet::new();
    let mut agg_pub_key = AggregatePublicKey::new();

    for i in 0..attestation_indices.len() {
        let voted = bitfield.get_bit(i);
        if voted {
            /*
             * De-reference the attestation index into a canonical ValidatorRecord index.
             */
            let validator = *attestation_indices.get(i)
                .ok_or(SignatureVerificationError::BadValidatorIndex)?;
            /*
             * Load the validators public key from our store.
             */
            let pub_key = validator_store
                .get_public_key_by_index(validator)?
                .ok_or(SignatureVerificationError::NoPublicKeyForValidator)?;
            /*
             * Add the validators public key to the aggregate public key.
             */
            agg_pub_key.add(&pub_key);
            /*
             * Add validator to the set of voters for this attestation record.
             */
            voters.insert(validator);
        }
    }
    /*
     * Verify the aggregate public key against the aggregate signature.
     *
     * This verification will only succeed if the exact set of public keys
     * were added to the aggregate public key as those that signed the aggregate signature.
     */
    if agg_sig.verify(&message, &agg_pub_key) {
        Ok(Some(voters))
    } else {
        Ok(None)
    }
}

impl From<ValidatorStoreError> for SignatureVerificationError {
    fn from(error: ValidatorStoreError) -> Self {
        match error {
            ValidatorStoreError::DBError(s) =>
                SignatureVerificationError::DBError(s),
            ValidatorStoreError::DecodeError =>
                SignatureVerificationError::PublicKeyCorrupt,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use super::super::bls::{
        Keypair,
        Signature,
    };
    use super::super::db::MemoryDB;
    use std::sync::Arc;

    /*
     * Cases that still need testing:
     *
     * - No signatures.
     * - Database failure.
     * - Unknown validator index.
     * - Extra validator on signature.
     */

    #[test]
    fn test_signature_verification() {
        let message = "cats".as_bytes();
        let signing_keypairs = vec![
            Keypair::random(),
            Keypair::random(),
            Keypair::random(),
            Keypair::random(),
            Keypair::random(),
            Keypair::random(),
        ];
        let non_signing_keypairs = vec![
            Keypair::random(),
            Keypair::random(),
            Keypair::random(),
            Keypair::random(),
            Keypair::random(),
            Keypair::random(),
        ];
        /*
         * Signing keypairs first, then non-signing
         */
        let mut all_keypairs = signing_keypairs.clone();
        all_keypairs.append(&mut non_signing_keypairs.clone());

        let attestation_indices: Vec<usize> = (0..all_keypairs.len())
            .collect();
        let mut bitfield = Bitfield::new();
        for i in 0..signing_keypairs.len() {
            bitfield.set_bit(i, true);
        }

        let db = Arc::new(MemoryDB::open());
        let store = ValidatorStore::new(db);

        for (i, keypair) in all_keypairs.iter().enumerate() {
            store.put_public_key_by_index(i, &keypair.pk).unwrap();
        }

        let mut agg_sig = AggregateSignature::new();
        for keypair in &signing_keypairs {
            let sig = Signature::new(&message, &keypair.sk);
            agg_sig.add(&sig);
        }

        /*
         * Test using all valid parameters.
         */
        let voters = verify_aggregate_signature_for_indices(
            &message,
            &agg_sig,
            &attestation_indices,
            &bitfield,
            &store).unwrap();

        let voters = voters.unwrap();
        (0..signing_keypairs.len())
            .for_each(|i| assert!(voters.contains(&i)));
        (signing_keypairs.len()..non_signing_keypairs.len())
            .for_each(|i| assert!(!voters.contains(&i)));

        /*
         * Add another validator to the bitfield, run validation will all other
         * parameters the same and assert that it fails.
         */
        bitfield.set_bit(signing_keypairs.len() + 1, true);
        let voters = verify_aggregate_signature_for_indices(
            &message,
            &agg_sig,
            &attestation_indices,
            &bitfield,
            &store).unwrap();

        assert_eq!(voters, None);
    }
}
<|endoftext|>


<commit_before>//  Copyright (C) 2020 Éloïs SANCHEZ.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

use crate::*;
use dubp::{
    crypto::bases::BaseConversionError,
    documents::transaction::v10_gen::{TransactionDocV10ComplexGen, TxV10ComplexIssuer},
};
use duniter_dbs::smallvec::SmallVec;

struct TxIssuerTyped {
    script: WalletScriptV10,
    signers: SmallVec<[PublicKey; 1]>,
    codes: SmallVec<[String; 1]>,
    amount: i32,
}
impl TryFrom<TxIssuer> for TxIssuerTyped {
    type Error = async_graphql::Error;

    fn try_from(input: TxIssuer) -> async_graphql::Result<Self> {
        let codes = if let Some(codes) = input.codes {
            codes.into_iter().collect()
        } else {
            SmallVec::new()
        };
        let signers: SmallVec<[PublicKey; 1]> = input
            .signers
            .iter()
            .map(|s| PublicKey::from_base58(s))
            .collect::<Result<_, BaseConversionError>>()?;
        let script = if let Some(ref script_str) = input.script {
            dubp::documents_parser::wallet_script_from_str(script_str)?
        } else if signers.len() <= 3 && codes.is_empty() {
            match signers.len() {
                1 => WalletScriptV10::single(WalletConditionV10::Sig(signers[0])),
                2 => WalletScriptV10::and(
                    WalletConditionV10::Sig(signers[0]),
                    WalletConditionV10::Sig(signers[1]),
                ),
                3 => WalletScriptV10::and_and(
                    WalletConditionV10::Sig(signers[0]),
                    WalletConditionV10::Sig(signers[1]),
                    WalletConditionV10::Sig(signers[2]),
                ),
                _ => unreachable!(),
            }
        } else {
            return Err(async_graphql::Error::new("missing a issuer script"));
        };
        Ok(Self {
            script,
            signers,
            codes,
            amount: input.amount,
        })
    }
}
struct TxRecipientTyped {
    amount: i32,
    script: WalletScriptV10,
}
impl TryFrom<TxRecipient> for TxRecipientTyped {
    type Error = async_graphql::Error;

    fn try_from(input: TxRecipient) -> async_graphql::Result<Self> {
        let script = dubp::documents_parser::wallet_script_from_str(&input.script)?;
        Ok(Self {
            amount: input.amount,
            script,
        })
    }
}

#[derive(Default)]
pub(crate) struct GenTxsQuery;
#[async_graphql::Object]
impl GenTxsQuery {
    /// Generate simple transaction document
    async fn gen_tx(
        &self,
        ctx: &async_graphql::Context<'_>,
        #[graphql(desc = "Transaction amount", validator(IntGreaterThan(value = "0")))] amount: i32,
        #[graphql(desc = "Transaction comment", validator(TxCommentValidator))] comment: Option<
            String,
        >,
        #[graphql(
            desc = "Ed25519 public key on base 58 representation",
            validator(and(StringMinLength(length = "40"), StringMaxLength(length = "44")))
        )]
        issuer: String,
        #[graphql(
            desc = "Ed25519 public key on base 58 representation",
            validator(and(StringMinLength(length = "40"), StringMaxLength(length = "44")))
        )]
        recipient: String,
        #[graphql(desc = "Use mempool sources", default = false)] use_mempool_sources: bool,
    ) -> async_graphql::Result<Vec<String>> {
        let comment = comment.unwrap_or_default();
        let issuer = PublicKey::from_base58(&issuer)?;
        let recipient = PublicKey::from_base58(&recipient)?;

        let data = ctx.data::<GvaSchemaData>()?;
        let db_reader = data.dbs_reader();
        let currency = data.server_meta_data.currency.clone();

        let (current_block, (inputs, inputs_sum)) = data
            .dbs_pool
            .execute(move |dbs| {
                if let Some(current_block) = duniter_bc_reader::get_current_block_meta(&dbs.cm_db)?
                {
                    Ok((
                        current_block,
                        db_reader.find_inputs(
                            &dbs.bc_db_ro,
                            &dbs.txs_mp_db,
                            SourceAmount::new(amount as i64, current_block.unit_base as i64),
                            &WalletScriptV10::single(WalletConditionV10::Sig(issuer)),
                            use_mempool_sources,
                        )?,
                    ))
                } else {
                    Err(anyhow::Error::msg("no blockchain"))
                }
            })
            .await??;

        let amount = SourceAmount::new(amount as i64, current_block.unit_base as i64);

        if inputs_sum < amount {
            return Err(async_graphql::Error::new("insufficient balance"));
        }

        let current_blockstamp = Blockstamp {
            number: BlockNumber(current_block.number),
            hash: BlockHash(current_block.hash),
        };

        Ok(TransactionDocumentV10::generate_simple_txs(
            current_blockstamp,
            currency,
            (inputs, inputs_sum),
            issuer,
            recipient,
            (amount, comment),
            None,
        ))
    }
    /// Generate complex transaction document
    async fn gen_complex_tx(
        &self,
        ctx: &async_graphql::Context<'_>,
        #[graphql(desc = "Transaction issuers")] issuers: Vec<TxIssuer>,
        #[graphql(desc = "Transaction issuers")] recipients: Vec<TxRecipient>,
        #[graphql(desc = "Transaction comment", validator(TxCommentValidator))] comment: Option<
            String,
        >,
        #[graphql(desc = "Use mempool sources", default = false)] use_mempool_sources: bool,
    ) -> async_graphql::Result<RawTxOrChanges> {
        let comment = comment.unwrap_or_default();
        let issuers = issuers
            .into_iter()
            .map(TryFrom::try_from)
            .collect::<async_graphql::Result<Vec<TxIssuerTyped>>>()?;
        let recipients = recipients
            .into_iter()
            .map(TryFrom::try_from)
            .collect::<async_graphql::Result<Vec<TxRecipientTyped>>>()?;

        let issuers_sum: i32 = issuers.iter().map(|issuer| issuer.amount).sum();
        let recipients_sum: i32 = recipients.iter().map(|recipient| recipient.amount).sum();
        if issuers_sum != recipients_sum {
            return Err(async_graphql::Error::new(
            "The sum of the amounts of the issuers must be equal to the sum of the amounts of the recipients.",
        ));
        }

        let data = ctx.data::<GvaSchemaData>()?;
        let db_reader = data.dbs_reader();
        let currency = data.server_meta_data.currency.clone();

        let (current_block, issuers_inputs_with_sum) = data
            .dbs_pool
            .execute(move |dbs| {
                if let Some(current_block) = duniter_bc_reader::get_current_block_meta(&dbs.cm_db)?
                {
                    let mut issuers_inputs_with_sum = Vec::new();
                    for issuer in issuers {
                        issuers_inputs_with_sum.push((
                            db_reader.find_inputs(
                                &dbs.bc_db_ro,
                                &dbs.txs_mp_db,
                                SourceAmount::new(
                                    issuer.amount as i64,
                                    current_block.unit_base as i64,
                                ),
                                &issuer.script,
                                use_mempool_sources,
                            )?,
                            issuer,
                        ));
                    }
                    Ok((current_block, issuers_inputs_with_sum))
                } else {
                    Err(anyhow::Error::msg("no blockchain"))
                }
            })
            .await??;

        for ((_inputs, inputs_sum), issuer) in &issuers_inputs_with_sum {
            let amount = SourceAmount::new(issuer.amount as i64, current_block.unit_base as i64);
            if *inputs_sum < amount {
                return Err(async_graphql::Error::new(format!(
                    "Insufficient balance for issuer {}",
                    issuer.script.to_string()
                )));
            }
        }

        let current_blockstamp = Blockstamp {
            number: BlockNumber(current_block.number),
            hash: BlockHash(current_block.hash),
        };
        let base = current_block.unit_base as i64;

        let (final_tx_opt, changes_txs) = TransactionDocV10ComplexGen {
            blockstamp: current_blockstamp,
            currency,
            issuers: issuers_inputs_with_sum
                .into_iter()
                .map(|((inputs, inputs_sum), issuer)| TxV10ComplexIssuer {
                    amount: SourceAmount::new(issuer.amount as i64, base),
                    codes: issuer.codes,
                    inputs,
                    inputs_sum,
                    script: issuer.script,
                    signers: issuer.signers,
                })
                .collect(),
            recipients: recipients
                .into_iter()
                .map(|TxRecipientTyped { amount, script }| {
                    (SourceAmount::new(amount as i64, base), script)
                })
                .collect(),
            user_comment: comment,
        }
        .gen()?;

        if let Some(final_tx) = final_tx_opt {
            Ok(RawTxOrChanges::FinalTx(final_tx))
        } else {
            Ok(RawTxOrChanges::Changes(changes_txs))
        }
    }
}
<commit_msg>[feat] gva: genTx: add param cashBackAddress<commit_after>//  Copyright (C) 2020 Éloïs SANCHEZ.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

use crate::*;
use dubp::{
    crypto::bases::BaseConversionError,
    documents::transaction::v10_gen::{TransactionDocV10ComplexGen, TxV10ComplexIssuer},
};
use duniter_dbs::smallvec::SmallVec;

struct TxIssuerTyped {
    script: WalletScriptV10,
    signers: SmallVec<[PublicKey; 1]>,
    codes: SmallVec<[String; 1]>,
    amount: i32,
}
impl TryFrom<TxIssuer> for TxIssuerTyped {
    type Error = async_graphql::Error;

    fn try_from(input: TxIssuer) -> async_graphql::Result<Self> {
        let codes = if let Some(codes) = input.codes {
            codes.into_iter().collect()
        } else {
            SmallVec::new()
        };
        let signers: SmallVec<[PublicKey; 1]> = input
            .signers
            .iter()
            .map(|s| PublicKey::from_base58(s))
            .collect::<Result<_, BaseConversionError>>()?;
        let script = if let Some(ref script_str) = input.script {
            dubp::documents_parser::wallet_script_from_str(script_str)?
        } else if signers.len() <= 3 && codes.is_empty() {
            match signers.len() {
                1 => WalletScriptV10::single(WalletConditionV10::Sig(signers[0])),
                2 => WalletScriptV10::and(
                    WalletConditionV10::Sig(signers[0]),
                    WalletConditionV10::Sig(signers[1]),
                ),
                3 => WalletScriptV10::and_and(
                    WalletConditionV10::Sig(signers[0]),
                    WalletConditionV10::Sig(signers[1]),
                    WalletConditionV10::Sig(signers[2]),
                ),
                _ => unreachable!(),
            }
        } else {
            return Err(async_graphql::Error::new("missing a issuer script"));
        };
        Ok(Self {
            script,
            signers,
            codes,
            amount: input.amount,
        })
    }
}
struct TxRecipientTyped {
    amount: i32,
    script: WalletScriptV10,
}
impl TryFrom<TxRecipient> for TxRecipientTyped {
    type Error = async_graphql::Error;

    fn try_from(input: TxRecipient) -> async_graphql::Result<Self> {
        let script = dubp::documents_parser::wallet_script_from_str(&input.script)?;
        Ok(Self {
            amount: input.amount,
            script,
        })
    }
}

#[derive(Default)]
pub(crate) struct GenTxsQuery;
#[async_graphql::Object]
impl GenTxsQuery {
    #[allow(clippy::too_many_arguments)]
    /// Generate simple transaction document
    async fn gen_tx(
        &self,
        ctx: &async_graphql::Context<'_>,
        #[graphql(desc = "Transaction amount", validator(IntGreaterThan(value = "0")))] amount: i32,
        #[graphql(
            desc = "Cash back address, equal to issuer address by default (Ed25519 public key on base 58 representation)",
            validator(and(StringMinLength(length = "40"), StringMaxLength(length = "44")))
        )]
        cash_back_address: Option<String>,
        #[graphql(desc = "Transaction comment", validator(TxCommentValidator))] comment: Option<
            String,
        >,
        #[graphql(
            desc = "Issuer address (Ed25519 public key on base 58 representation)",
            validator(and(StringMinLength(length = "40"), StringMaxLength(length = "44")))
        )]
        issuer: String,
        #[graphql(
            desc = "Recipient address (Ed25519 public key on base 58 representation)",
            validator(and(StringMinLength(length = "40"), StringMaxLength(length = "44")))
        )]
        recipient: String,
        #[graphql(desc = "Use mempool sources", default = false)] use_mempool_sources: bool,
    ) -> async_graphql::Result<Vec<String>> {
        let cash_back_pubkey = if let Some(cash_back_address) = cash_back_address {
            Some(PublicKey::from_base58(&cash_back_address)?)
        } else {
            None
        };
        let comment = comment.unwrap_or_default();
        let issuer = PublicKey::from_base58(&issuer)?;
        let recipient = PublicKey::from_base58(&recipient)?;

        let data = ctx.data::<GvaSchemaData>()?;
        let db_reader = data.dbs_reader();
        let currency = data.server_meta_data.currency.clone();

        let (current_block, (inputs, inputs_sum)) = data
            .dbs_pool
            .execute(move |dbs| {
                if let Some(current_block) = duniter_bc_reader::get_current_block_meta(&dbs.cm_db)?
                {
                    Ok((
                        current_block,
                        db_reader.find_inputs(
                            &dbs.bc_db_ro,
                            &dbs.txs_mp_db,
                            SourceAmount::new(amount as i64, current_block.unit_base as i64),
                            &WalletScriptV10::single(WalletConditionV10::Sig(issuer)),
                            use_mempool_sources,
                        )?,
                    ))
                } else {
                    Err(anyhow::Error::msg("no blockchain"))
                }
            })
            .await??;

        let amount = SourceAmount::new(amount as i64, current_block.unit_base as i64);

        if inputs_sum < amount {
            return Err(async_graphql::Error::new("insufficient balance"));
        }

        let current_blockstamp = Blockstamp {
            number: BlockNumber(current_block.number),
            hash: BlockHash(current_block.hash),
        };

        Ok(TransactionDocumentV10::generate_simple_txs(
            current_blockstamp,
            currency,
            (inputs, inputs_sum),
            issuer,
            recipient,
            (amount, comment),
            cash_back_pubkey,
        ))
    }
    /// Generate complex transaction document
    async fn gen_complex_tx(
        &self,
        ctx: &async_graphql::Context<'_>,
        #[graphql(desc = "Transaction issuers")] issuers: Vec<TxIssuer>,
        #[graphql(desc = "Transaction issuers")] recipients: Vec<TxRecipient>,
        #[graphql(desc = "Transaction comment", validator(TxCommentValidator))] comment: Option<
            String,
        >,
        #[graphql(desc = "Use mempool sources", default = false)] use_mempool_sources: bool,
    ) -> async_graphql::Result<RawTxOrChanges> {
        let comment = comment.unwrap_or_default();
        let issuers = issuers
            .into_iter()
            .map(TryFrom::try_from)
            .collect::<async_graphql::Result<Vec<TxIssuerTyped>>>()?;
        let recipients = recipients
            .into_iter()
            .map(TryFrom::try_from)
            .collect::<async_graphql::Result<Vec<TxRecipientTyped>>>()?;

        let issuers_sum: i32 = issuers.iter().map(|issuer| issuer.amount).sum();
        let recipients_sum: i32 = recipients.iter().map(|recipient| recipient.amount).sum();
        if issuers_sum != recipients_sum {
            return Err(async_graphql::Error::new(
            "The sum of the amounts of the issuers must be equal to the sum of the amounts of the recipients.",
        ));
        }

        let data = ctx.data::<GvaSchemaData>()?;
        let db_reader = data.dbs_reader();
        let currency = data.server_meta_data.currency.clone();

        let (current_block, issuers_inputs_with_sum) = data
            .dbs_pool
            .execute(move |dbs| {
                if let Some(current_block) = duniter_bc_reader::get_current_block_meta(&dbs.cm_db)?
                {
                    let mut issuers_inputs_with_sum = Vec::new();
                    for issuer in issuers {
                        issuers_inputs_with_sum.push((
                            db_reader.find_inputs(
                                &dbs.bc_db_ro,
                                &dbs.txs_mp_db,
                                SourceAmount::new(
                                    issuer.amount as i64,
                                    current_block.unit_base as i64,
                                ),
                                &issuer.script,
                                use_mempool_sources,
                            )?,
                            issuer,
                        ));
                    }
                    Ok((current_block, issuers_inputs_with_sum))
                } else {
                    Err(anyhow::Error::msg("no blockchain"))
                }
            })
            .await??;

        for ((_inputs, inputs_sum), issuer) in &issuers_inputs_with_sum {
            let amount = SourceAmount::new(issuer.amount as i64, current_block.unit_base as i64);
            if *inputs_sum < amount {
                return Err(async_graphql::Error::new(format!(
                    "Insufficient balance for issuer {}",
                    issuer.script.to_string()
                )));
            }
        }

        let current_blockstamp = Blockstamp {
            number: BlockNumber(current_block.number),
            hash: BlockHash(current_block.hash),
        };
        let base = current_block.unit_base as i64;

        let (final_tx_opt, changes_txs) = TransactionDocV10ComplexGen {
            blockstamp: current_blockstamp,
            currency,
            issuers: issuers_inputs_with_sum
                .into_iter()
                .map(|((inputs, inputs_sum), issuer)| TxV10ComplexIssuer {
                    amount: SourceAmount::new(issuer.amount as i64, base),
                    codes: issuer.codes,
                    inputs,
                    inputs_sum,
                    script: issuer.script,
                    signers: issuer.signers,
                })
                .collect(),
            recipients: recipients
                .into_iter()
                .map(|TxRecipientTyped { amount, script }| {
                    (SourceAmount::new(amount as i64, base), script)
                })
                .collect(),
            user_comment: comment,
        }
        .gen()?;

        if let Some(final_tx) = final_tx_opt {
            Ok(RawTxOrChanges::FinalTx(final_tx))
        } else {
            Ok(RawTxOrChanges::Changes(changes_txs))
        }
    }
}
<|endoftext|>


<commit_before>// compile-flags: -C no-prepopulate-passes

#![crate_type = "lib"]
#![feature(const_eval_select)]

use std::intrinsics::const_eval_select;

const fn foo(_: (i32,)) -> i32 { 1 }

#[no_mangle]
pub fn hi((n,): (i32,)) -> i32 { n }

#[no_mangle]
pub unsafe fn hey() {
    // CHECK: call i32 @hi(i32
    const_eval_select((42,), foo, hi);
}
<commit_msg>fix codegen test<commit_after>// compile-flags: -C no-prepopulate-passes

#![crate_type = "lib"]
#![feature(const_eval_select)]

use std::intrinsics::const_eval_select;

const fn foo(_: i32) -> i32 { 1 }

#[no_mangle]
pub fn hi(n: i32) -> i32 { n }

#[no_mangle]
pub unsafe fn hey() {
    // CHECK: call i32 @hi(i32
    const_eval_select((42,), foo, hi);
}
<|endoftext|>


<commit_before>use std::ptr::null;
use std::str::raw;
use libc::{c_int, c_char};
use sync::mutex::{StaticMutex, MUTEX_INIT};

#[link(name="gdal")]
extern {
    fn OGRRegisterAll();
    fn OGROpen(pszName: *c_char, bUpdate: c_int, pahDriverList: *()) -> *();
    fn OGR_DS_GetLayerCount(hDS: *()) -> c_int;
    fn OGR_DS_Destroy(hDataSource: *());
    fn OGR_DS_GetLayer(hDS: *(), iLayer: c_int) -> *();
    fn OGR_L_GetNextFeature(hLayer: *()) -> *();
    fn OGR_F_GetFieldIndex(hFeat: *(), pszName: *c_char) -> c_int;
    fn OGR_F_GetFieldAsString(hFeat: *(), iField: c_int) -> *c_char;
    fn OGR_F_GetGeometryRef(hFeat: *()) -> *();
    fn OGR_F_Destroy(hFeat: *());
    fn OGR_G_ExportToWkt(hGeom: *(), ppszSrcText: **c_char) -> c_int;
    fn OGRFree(ptr: *());
}

static mut LOCK: StaticMutex = MUTEX_INIT;
static mut registered_drivers: bool = false;

fn register_drivers() {
    unsafe {
        let _g = LOCK.lock();
        if ! registered_drivers {
            OGRRegisterAll();
            registered_drivers = true;
        }
    }
}

pub struct VectorDataset {
    c_dataset: *(),
}

impl VectorDataset {
    pub fn get_layer_count(&self) -> int {
        return unsafe { OGR_DS_GetLayerCount(self.c_dataset) } as int;
    }

    pub fn get_layer<'a>(&'a self, idx: int) -> Option<Layer<'a>> {
        let c_layer = unsafe { OGR_DS_GetLayer(self.c_dataset, idx as c_int) };
        return match c_layer.is_null() {
            true  => None,
            false => Some(Layer{vector_dataset: self, c_layer: c_layer}),
        };
    }
}

impl Drop for VectorDataset {
    fn drop(&mut self) {
        unsafe { OGR_DS_Destroy(self.c_dataset); }
    }
}

pub struct Layer<'a> {
    vector_dataset: &'a VectorDataset,
    c_layer: *(),
}

impl<'a> Layer<'a> {
    pub fn features<'a>(&'a self) -> FeatureIterator<'a> {
        return FeatureIterator{layer: self};
    }
}

pub struct FeatureIterator<'a> {
    layer: &'a Layer<'a>,
}

impl<'a> Iterator<Feature<'a>> for FeatureIterator<'a> {
    #[inline]
    fn next(&mut self) -> Option<Feature<'a>> {
        let c_feature = unsafe { OGR_L_GetNextFeature(self.layer.c_layer) };
        return match c_feature.is_null() {
            true  => None,
            false => Some(Feature{layer: self.layer, c_feature: c_feature}),
        };
    }
}

pub struct Feature<'a> {
    layer: &'a Layer<'a>,
    c_feature: *(),
}

impl<'a> Feature<'a> {
    pub fn get_field(&self, name: String) -> String {
        return name.with_c_str(|c_name| {
            unsafe {
                let field_id = OGR_F_GetFieldIndex(self.c_feature, c_name);
                let c_value = OGR_F_GetFieldAsString(self.c_feature, field_id);
                return raw::from_c_str(c_value);
            }
        });
    }

    pub fn get_wkt(&self) -> String {
        unsafe {
            let c_geom = OGR_F_GetGeometryRef(self.c_feature);
            let c_wkt: *c_char = null();
            OGR_G_ExportToWkt(c_geom, &c_wkt);
            let wkt = raw::from_c_str(c_wkt);
            OGRFree(c_wkt as *());
            return wkt;
        }
    }
}

#[unsafe_destructor]
impl<'a> Drop for Feature<'a> {
    fn drop(&mut self) {
        unsafe { OGR_F_Destroy(self.c_feature); }
    }
}

pub fn open(path: &Path) -> Option<VectorDataset> {
    register_drivers();
    let filename = path.as_str().unwrap();
    let c_dataset = filename.with_c_str(|c_filename| {
        return unsafe { OGROpen(c_filename, 0, null()) };
    });
    return match c_dataset.is_null() {
        true  => None,
        false => Some(VectorDataset{c_dataset: c_dataset}),
    };
}

#[cfg(test)]
mod test {
    use std::os::getenv;
    use std::path::Path;
    use super::{Feature, open};

    fn fixture_path(name: &str) -> Path {
        let envvar = "RUST_GDAL_TEST_FIXTURES";
        let fixtures = match getenv(envvar) {
            Some(p) => Path::new(p),
            None => fail!("Environment variable {} not set", envvar)
        };
        let rv = fixtures.join(name);
        return rv;
    }

    #[test]
    fn test_layer_count() {
        let ds = open(&fixture_path("roads.geojson")).unwrap();
        assert_eq!(ds.get_layer_count(), 1);
    }

    #[test]
    fn test_iterate_features() {
        let ds = open(&fixture_path("roads.geojson")).unwrap();
        let layer = ds.get_layer(0).unwrap();
        let features: Vec<Feature> = layer.features().collect();
        assert_eq!(features.len(), 21);
    }

    #[test]
    fn test_get_field() {
        let ds = open(&fixture_path("roads.geojson")).unwrap();
        let layer = ds.get_layer(0).unwrap();
        let feature = layer.features().next().unwrap();
        assert_eq!(feature.get_field("highway".to_string()),
                   "footway".to_string());
        assert_eq!(
            layer.features()
                 .count(|f|
                    f.get_field("highway".to_string()) ==
                    "residential".to_string()),
            2);
    }

    #[test]
    fn test_get_wkt() {
        let ds = open(&fixture_path("roads.geojson")).unwrap();
        let layer = ds.get_layer(0).unwrap();
        let feature = layer.features().next().unwrap();
        let wkt = feature.get_wkt();
        assert_eq!(wkt, "LINESTRING (26.1019276 44.4302748,26.1019382 44.4303191,26.1020002 44.4304202)".to_string());
    }
}
<commit_msg>get float field value<commit_after>use std::ptr::null;
use std::str::raw;
use libc::{c_int, c_char, c_double};
use sync::mutex::{StaticMutex, MUTEX_INIT};

#[link(name="gdal")]
extern {
    fn OGRRegisterAll();
    fn OGROpen(pszName: *c_char, bUpdate: c_int, pahDriverList: *()) -> *();
    fn OGR_DS_GetLayerCount(hDS: *()) -> c_int;
    fn OGR_DS_Destroy(hDataSource: *());
    fn OGR_DS_GetLayer(hDS: *(), iLayer: c_int) -> *();
    fn OGR_L_GetNextFeature(hLayer: *()) -> *();
    fn OGR_F_GetFieldIndex(hFeat: *(), pszName: *c_char) -> c_int;
    fn OGR_F_GetFieldAsString(hFeat: *(), iField: c_int) -> *c_char;
    fn OGR_F_GetFieldAsDouble(hFeat: *(), iField: c_int) -> c_double;
    fn OGR_F_GetGeometryRef(hFeat: *()) -> *();
    fn OGR_F_Destroy(hFeat: *());
    fn OGR_G_ExportToWkt(hGeom: *(), ppszSrcText: **c_char) -> c_int;
    fn OGRFree(ptr: *());
}

static mut LOCK: StaticMutex = MUTEX_INIT;
static mut registered_drivers: bool = false;

fn register_drivers() {
    unsafe {
        let _g = LOCK.lock();
        if ! registered_drivers {
            OGRRegisterAll();
            registered_drivers = true;
        }
    }
}

pub struct VectorDataset {
    c_dataset: *(),
}

impl VectorDataset {
    pub fn get_layer_count(&self) -> int {
        return unsafe { OGR_DS_GetLayerCount(self.c_dataset) } as int;
    }

    pub fn get_layer<'a>(&'a self, idx: int) -> Option<Layer<'a>> {
        let c_layer = unsafe { OGR_DS_GetLayer(self.c_dataset, idx as c_int) };
        return match c_layer.is_null() {
            true  => None,
            false => Some(Layer{vector_dataset: self, c_layer: c_layer}),
        };
    }
}

impl Drop for VectorDataset {
    fn drop(&mut self) {
        unsafe { OGR_DS_Destroy(self.c_dataset); }
    }
}

pub struct Layer<'a> {
    vector_dataset: &'a VectorDataset,
    c_layer: *(),
}

impl<'a> Layer<'a> {
    pub fn features<'a>(&'a self) -> FeatureIterator<'a> {
        return FeatureIterator{layer: self};
    }
}

pub struct FeatureIterator<'a> {
    layer: &'a Layer<'a>,
}

impl<'a> Iterator<Feature<'a>> for FeatureIterator<'a> {
    #[inline]
    fn next(&mut self) -> Option<Feature<'a>> {
        let c_feature = unsafe { OGR_L_GetNextFeature(self.layer.c_layer) };
        return match c_feature.is_null() {
            true  => None,
            false => Some(Feature{layer: self.layer, c_feature: c_feature}),
        };
    }
}

pub struct Feature<'a> {
    layer: &'a Layer<'a>,
    c_feature: *(),
}

impl<'a> Feature<'a> {
    pub fn get_string_field(&self, name: String) -> String {
        return name.with_c_str(|c_name| {
            unsafe {
                let field_id = OGR_F_GetFieldIndex(self.c_feature, c_name);
                let c_value = OGR_F_GetFieldAsString(self.c_feature, field_id);
                return raw::from_c_str(c_value);
            }
        });
    }

    pub fn get_f64_field(&self, name: String) -> f64 {
        return name.with_c_str(|c_name| {
            unsafe {
                let field_id = OGR_F_GetFieldIndex(self.c_feature, c_name);
                let c_value = OGR_F_GetFieldAsDouble(self.c_feature, field_id);
                return c_value as f64;
            }
        });
    }

    pub fn get_wkt(&self) -> String {
        unsafe {
            let c_geom = OGR_F_GetGeometryRef(self.c_feature);
            let c_wkt: *c_char = null();
            OGR_G_ExportToWkt(c_geom, &c_wkt);
            let wkt = raw::from_c_str(c_wkt);
            OGRFree(c_wkt as *());
            return wkt;
        }
    }
}

#[unsafe_destructor]
impl<'a> Drop for Feature<'a> {
    fn drop(&mut self) {
        unsafe { OGR_F_Destroy(self.c_feature); }
    }
}

pub fn open(path: &Path) -> Option<VectorDataset> {
    register_drivers();
    let filename = path.as_str().unwrap();
    let c_dataset = filename.with_c_str(|c_filename| {
        return unsafe { OGROpen(c_filename, 0, null()) };
    });
    return match c_dataset.is_null() {
        true  => None,
        false => Some(VectorDataset{c_dataset: c_dataset}),
    };
}

#[cfg(test)]
mod test {
    use std::os::getenv;
    use std::path::Path;
    use super::{Feature, open};

    fn fixture_path(name: &str) -> Path {
        let envvar = "RUST_GDAL_TEST_FIXTURES";
        let fixtures = match getenv(envvar) {
            Some(p) => Path::new(p),
            None => fail!("Environment variable {} not set", envvar)
        };
        let rv = fixtures.join(name);
        return rv;
    }

    fn assert_almost_eq(a: f64, b: f64) {
        let f: f64 = a / b;
        assert!(f < 1.00001);
        assert!(f > 0.99999);
    }

    #[test]
    fn test_layer_count() {
        let ds = open(&fixture_path("roads.geojson")).unwrap();
        assert_eq!(ds.get_layer_count(), 1);
    }

    #[test]
    fn test_iterate_features() {
        let ds = open(&fixture_path("roads.geojson")).unwrap();
        let layer = ds.get_layer(0).unwrap();
        let features: Vec<Feature> = layer.features().collect();
        assert_eq!(features.len(), 21);
    }

    #[test]
    fn test_get_string_field() {
        let ds = open(&fixture_path("roads.geojson")).unwrap();
        let layer = ds.get_layer(0).unwrap();
        let feature = layer.features().next().unwrap();
        assert_eq!(feature.get_string_field("highway".to_string()),
                   "footway".to_string());
        assert_eq!(
            layer.features()
                 .count(|f|
                    f.get_string_field("highway".to_string()) ==
                    "residential".to_string()),
            2);
    }

    #[test]
    fn test_get_float_field() {
        let ds = open(&fixture_path("roads.geojson")).unwrap();
        let layer = ds.get_layer(0).unwrap();
        let feature = layer.features().next().unwrap();
        assert_almost_eq(feature.get_f64_field("sort_key".to_string()), -9.0);
    }

    #[test]
    fn test_get_wkt() {
        let ds = open(&fixture_path("roads.geojson")).unwrap();
        let layer = ds.get_layer(0).unwrap();
        let feature = layer.features().next().unwrap();
        let wkt = feature.get_wkt();
        assert_eq!(wkt, "LINESTRING (26.1019276 44.4302748,26.1019382 44.4303191,26.1020002 44.4304202)".to_string());
    }
}
<|endoftext|>


<commit_before>use std::str::FromStr;
use std::fmt;

pub enum Operator
{
    Plus,
    Minus,
    Asterisk,
    Slash,
    Percent
}

pub enum Token
{
    Operator(Operator),
    Literal(isize)
}

pub enum Expression
{
    Literal(isize),
    Expression
        (
            Operator,
            Box<Expression>,
            Box<Expression>
        )
}

impl fmt::Debug for Operator
{
    fn  fmt(&self, f: &mut fmt::Formatter) -> fmt::Result
    {
        write!
            (
                f,
                "{}",
                match *self
                {
                    Operator::Plus      =>  "+",
                    Operator::Minus     =>  "-",
                    Operator::Asterisk  =>  "*",
                    Operator::Slash     =>  "/",
                    Operator::Percent   =>  "%"
                }
            )
    }
}

impl fmt::Debug for Token
{
    fn  fmt(&self, f: &mut fmt::Formatter) -> fmt::Result
    {
        write!
            (
                f,
                "{}",
                match *self
                {
                    Token::Operator(ref op)     =>  format!("Operator({:?})", op),
                    Token::Literal(num)     =>  format!("Literal({})", num)
                }
            )
    }
}

pub fn assign_token(input: &str) -> Result<Token, String>
{
    match input
    {
        "+"         =>  Ok(Token::Operator(Operator::Plus)),
        "-"         =>  Ok(Token::Operator(Operator::Minus)),
        "*"         =>  Ok(Token::Operator(Operator::Asterisk)),
        "/"         =>  Ok(Token::Operator(Operator::Slash)),
        "%"         =>  Ok(Token::Operator(Operator::Percent)),
        input       =>  match FromStr::from_str(input)
        {
            Ok(number)      =>  Ok(Token::Literal(number)),
            Err(_)          =>  Err(format!("Error: unassignable token ({})", input))
        }
    }
}
<commit_msg>color test<commit_after>use std::str::FromStr;
use std::fmt;

pub enum Operator {
    Plus,
    Minus,
    Asterisk,
    Slash,
    Percent }

pub enum Token {
    Operator(Operator),
    Literal(isize) }

pub enum Expression {
    Literal(isize),
    Expression (
            Operator,
            Box<Expression>,
            Box<Expression> )}

impl fmt::Debug for Operator {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
                f,
                "{}",
                match *self {
                    Operator::Plus      =>  "+",
                    Operator::Minus     =>  "-",
                    Operator::Asterisk  =>  "*",
                    Operator::Slash     =>  "/",
                    Operator::Percent   =>  "%" })}}

impl fmt::Debug for Token {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
                f,
                "{}",
                match *self {
                    Token::Operator(ref op)     =>  format!("Operator({:?})", op),
                    Token::Literal(num)     =>  format!("Literal({})", num) })}}

pub fn assign_token(input: &str) -> Result<Token, String> {
    match input {
        "+"         =>  Ok(Token::Operator(Operator::Plus)),
        "-"         =>  Ok(Token::Operator(Operator::Minus)),
        "*"         =>  Ok(Token::Operator(Operator::Asterisk)),
        "/"         =>  Ok(Token::Operator(Operator::Slash)),
        "%"         =>  Ok(Token::Operator(Operator::Percent)),
        input       =>  match FromStr::from_str(input) {
            Ok(number)      =>  Ok(Token::Literal(number)),
            Err(_)          =>  Err(format!("Error: unassignable token ({})", input)) }}}
<|endoftext|>


<commit_before>#![allow(dead_code)]
use std::{cmp, result, io};
use std::error;
use protobuf::error::ProtobufError;

quick_error! {
    #[derive(Debug)]
    pub enum Error {
        Io(err: io::Error) {
            from()
            cause(err)
            description(err.description())
        }
        Store(err: StorageError) {
            from()
            cause(err)
            description(err.description())
        }
        StepLocalMsg {
            description("raft: cannot step raft local message")
        }
        StepPeerNotFound {
            description("raft: cannot step as peer not found")
        }
        ConfigInvalid(desc: String) {
            description(desc)
        }
        Other(err: Box<error::Error + Sync + Send>) {
            cause(err.as_ref())
            description(err.description())
        }
    }
}

impl Error {
    pub fn other<T: error::Error + Sync + Send + 'static>(err: T) -> Error {
        Error::Other(Box::new(err))
    }
}

impl cmp::PartialEq for Error {
    fn eq(&self, other: &Error) -> bool {
        match (self, other) {
            (&Error::StepPeerNotFound, &Error::StepPeerNotFound) => true,
            (&Error::Store(ref e1), &Error::Store(ref e2)) => e1 == e2,
            (&Error::Io(ref e1), &Error::Io(ref e2)) => e1.kind() == e2.kind(),
            (&Error::StepLocalMsg, &Error::StepLocalMsg) => true,
            (&Error::ConfigInvalid(ref e1), &Error::ConfigInvalid(ref e2)) => e1 == e2,
            _ => false,
        }
    }
}

impl From<ProtobufError> for Error {
    fn from(e: ProtobufError) -> Error {
        Error::other(e)
    }
}

quick_error! {
    #[derive(Debug)]
    pub enum StorageError {
        Compacted {
            description("log compacted")
        }
        Unavailable {
            description("log unavailable")
        }
        SnapshotOutOfDate {
            description("snapshot out of date")
        }
        SnapshotTemporarilyUnavailable {
            description("snapshot is temporarily unavailable")
        }
        Other(err: Box<error::Error + Sync + Send>) {
            cause(err.as_ref())
            description(err.description())
        }
    }
}

impl StorageError {
    pub fn other<T: error::Error + Sync + Send + 'static>(err: T) -> StorageError {
        StorageError::Other(Box::new(err))
    }
}

impl cmp::PartialEq for StorageError {
    fn eq(&self, other: &StorageError) -> bool {
        match (self, other) {
            (&StorageError::Compacted, &StorageError::Compacted) => true,
            (&StorageError::Unavailable, &StorageError::Unavailable) => true,
            (&StorageError::SnapshotOutOfDate,
             &StorageError::SnapshotOutOfDate) => true,
            (&StorageError::SnapshotTemporarilyUnavailable,
             &StorageError::SnapshotTemporarilyUnavailable) => true,
            _ => false,
        }
    }
}

pub type Result<T> = result::Result<T, Error>;

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

    #[test]
    fn test_error_equal() {
        assert_eq!(Error::StepPeerNotFound, Error::StepPeerNotFound);
        assert!(Error::StepPeerNotFound != Error::Store(StorageError::Compacted));
        assert_eq!(Error::Store(StorageError::Compacted),
                   Error::Store(StorageError::Compacted));
        assert_eq!(Error::StepLocalMsg, Error::StepLocalMsg);
        assert_eq!(Error::from(io::Error::new(io::ErrorKind::Other, "oh no!")),
                   Error::from(io::Error::new(io::ErrorKind::Other, "oh yes!")));
    }

    #[test]
    fn test_storage_error_equal() {
        assert_eq!(StorageError::Compacted, StorageError::Compacted);
        assert!(StorageError::Compacted != StorageError::Unavailable);
        assert_eq!(StorageError::Unavailable, StorageError::Unavailable);
        assert_eq!(StorageError::SnapshotOutOfDate,
                   StorageError::SnapshotOutOfDate);
        assert_eq!(StorageError::SnapshotTemporarilyUnavailable,
                   StorageError::SnapshotTemporarilyUnavailable);
        assert!(StorageError::other(Error::StepLocalMsg) != StorageError::Unavailable);
    }
}
<commit_msg>raft: let error other accept more types.<commit_after>#![allow(dead_code)]
use std::{cmp, result, io};
use std::error;
use protobuf::error::ProtobufError;

quick_error! {
    #[derive(Debug)]
    pub enum Error {
        Io(err: io::Error) {
            from()
            cause(err)
            description(err.description())
        }
        Store(err: StorageError) {
            from()
            cause(err)
            description(err.description())
        }
        StepLocalMsg {
            description("raft: cannot step raft local message")
        }
        StepPeerNotFound {
            description("raft: cannot step as peer not found")
        }
        ConfigInvalid(desc: String) {
            description(desc)
        }
        Other(err: Box<error::Error + Sync + Send>) {
            cause(err.as_ref())
            description(err.description())
        }
    }
}

impl Error {
    pub fn other<T>(err: T) -> Error
        where T: Into<Box<error::Error + Sync + Send + 'static>>
    {
        Error::Other(err.into())
    }
}

impl cmp::PartialEq for Error {
    fn eq(&self, other: &Error) -> bool {
        match (self, other) {
            (&Error::StepPeerNotFound, &Error::StepPeerNotFound) => true,
            (&Error::Store(ref e1), &Error::Store(ref e2)) => e1 == e2,
            (&Error::Io(ref e1), &Error::Io(ref e2)) => e1.kind() == e2.kind(),
            (&Error::StepLocalMsg, &Error::StepLocalMsg) => true,
            (&Error::ConfigInvalid(ref e1), &Error::ConfigInvalid(ref e2)) => e1 == e2,
            _ => false,
        }
    }
}

impl From<ProtobufError> for Error {
    fn from(e: ProtobufError) -> Error {
        Error::other(e)
    }
}

quick_error! {
    #[derive(Debug)]
    pub enum StorageError {
        Compacted {
            description("log compacted")
        }
        Unavailable {
            description("log unavailable")
        }
        SnapshotOutOfDate {
            description("snapshot out of date")
        }
        SnapshotTemporarilyUnavailable {
            description("snapshot is temporarily unavailable")
        }
        Other(err: Box<error::Error + Sync + Send>) {
            cause(err.as_ref())
            description(err.description())
        }
    }
}

impl StorageError {
    pub fn other<T>(err: T) -> StorageError
        where T: Into<Box<error::Error + Sync + Send + 'static>>
    {
        StorageError::Other(err.into())
    }
}

impl cmp::PartialEq for StorageError {
    fn eq(&self, other: &StorageError) -> bool {
        match (self, other) {
            (&StorageError::Compacted, &StorageError::Compacted) => true,
            (&StorageError::Unavailable, &StorageError::Unavailable) => true,
            (&StorageError::SnapshotOutOfDate,
             &StorageError::SnapshotOutOfDate) => true,
            (&StorageError::SnapshotTemporarilyUnavailable,
             &StorageError::SnapshotTemporarilyUnavailable) => true,
            _ => false,
        }
    }
}

pub type Result<T> = result::Result<T, Error>;

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

    #[test]
    fn test_error_equal() {
        assert_eq!(Error::StepPeerNotFound, Error::StepPeerNotFound);
        assert!(Error::StepPeerNotFound != Error::Store(StorageError::Compacted));
        assert_eq!(Error::Store(StorageError::Compacted),
                   Error::Store(StorageError::Compacted));
        assert_eq!(Error::StepLocalMsg, Error::StepLocalMsg);
        assert_eq!(Error::from(io::Error::new(io::ErrorKind::Other, "oh no!")),
                   Error::from(io::Error::new(io::ErrorKind::Other, "oh yes!")));
    }

    #[test]
    fn test_storage_error_equal() {
        assert_eq!(StorageError::Compacted, StorageError::Compacted);
        assert!(StorageError::Compacted != StorageError::Unavailable);
        assert_eq!(StorageError::Unavailable, StorageError::Unavailable);
        assert_eq!(StorageError::SnapshotOutOfDate,
                   StorageError::SnapshotOutOfDate);
        assert_eq!(StorageError::SnapshotTemporarilyUnavailable,
                   StorageError::SnapshotTemporarilyUnavailable);
        assert!(StorageError::other(Error::StepLocalMsg) != StorageError::Unavailable);
    }

    #[test]
    fn test_other() {
        // just for check compile.
        Error::other("a simple string error");
        StorageError::other("a simple string error");
        Error::other(io::Error::new(io::ErrorKind::Other, "hello io"));
        StorageError::other(io::Error::new(io::ErrorKind::Other, "hello io"));
    }
}
<|endoftext|>