<commit_before>// partially (c) 2016 Google Inc. (Lewin Bormann, lewinb@google.com)
//
// See project root for licensing information.
//
use crate::types::Token;

use std::cell::RefCell;
use std::collections::BTreeMap;
use std::io;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::sync::Mutex;

use serde::{Deserialize, Serialize};

// The storage layer allows retrieving tokens for scopes that have been
// previously granted tokens. One wrinkle is that a token granted for a set
// of scopes X is also valid for any subset of X's scopes. So when retrieving a
// token for a set of scopes provided by the caller it's beneficial to compare
// that set to all previously stored tokens to see if it is a subset of any
// existing set. To do this efficiently we store a bloom filter along with each
// token that represents the set of scopes the token is associated with. The
// bloom filter allows for efficiently skipping any entries that are
// definitively not a superset.
// The current implementation uses a 64bit bloom filter with 4 hash functions.

/// ScopeHash is a hash value derived from a list of scopes. The hash value
/// represents a fingerprint of the set of scopes *independent* of the ordering.
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
struct ScopeHash(u64);

/// ScopeFilter represents a filter for a set of scopes. It can definitively
/// prove that a given list of scopes is not a subset of another.
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
struct ScopeFilter(u64);

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum FilterResponse {
    Maybe,
    No,
}

impl ScopeFilter {
    /// Determine if this ScopeFilter could be a subset of the provided filter.
    fn is_subset_of(self, filter: ScopeFilter) -> FilterResponse {
        if self.0 & filter.0 == self.0 {
            FilterResponse::Maybe
        } else {
            FilterResponse::No
        }
    }
}

#[derive(Debug)]
pub(crate) struct ScopeSet<'a, T> {
    hash: ScopeHash,
    filter: ScopeFilter,
    scopes: &'a [T],
}

// Implement Clone manually. Auto derive fails to work correctly because we want
// Clone to be implemented regardless of whether T is Clone or not.
impl<'a, T> Clone for ScopeSet<'a, T> {
    fn clone(&self) -> Self {
        ScopeSet {
            hash: self.hash,
            filter: self.filter,
            scopes: self.scopes,
        }
    }
}
impl<'a, T> Copy for ScopeSet<'a, T> {}

impl<'a, T> ScopeSet<'a, T>
where
    T: AsRef<str>,
{
    // implement an inherent from method even though From is implemented. This
    // is because passing an array ref like &[&str; 1] (&["foo"]) will be auto
    // deref'd to a slice on function boundaries, but it will not implement the
    // From trait. This inherent method just serves to auto deref from array
    // refs to slices and proxy to the From impl.
    pub fn from(scopes: &'a [T]) -> Self {
        let (hash, filter) = scopes.iter().fold(
            (ScopeHash(0), ScopeFilter(0)),
            |(mut scope_hash, mut scope_filter), scope| {
                let h = seahash::hash(scope.as_ref().as_bytes());

                // Use the first 4 6-bit chunks of the seahash as the 4 hash values
                // in the bloom filter.
                for i in 0..4 {
                    // h is a hash derived value in the range 0..64
                    let h = (h >> (6 * i)) & 0b11_1111;
                    scope_filter.0 |= 1 << h;
                }

                // xor the hashes together to get an order independent fingerprint.
                scope_hash.0 ^= h;
                (scope_hash, scope_filter)
            },
        );
        ScopeSet {
            hash,
            filter,
            scopes,
        }
    }
}

pub(crate) enum Storage {
    Memory { tokens: Mutex<JSONTokens> },
    Disk(DiskStorage),
}

impl Storage {
    pub(crate) async fn set<T>(&self, scopes: ScopeSet<'_, T>, token: Token)
    where
        T: AsRef<str>,
    {
        match self {
            Storage::Memory { tokens } => tokens.lock().unwrap().set(scopes, token),
            Storage::Disk(disk_storage) => disk_storage.set(scopes, token).await,
        }
    }

    pub(crate) fn get<T>(&self, scopes: ScopeSet<T>) -> Option<Token>
    where
        T: AsRef<str>,
    {
        match self {
            Storage::Memory { tokens } => tokens.lock().unwrap().get(scopes),
            Storage::Disk(disk_storage) => disk_storage.get(scopes),
        }
    }
}

/// A single stored token.

#[derive(Debug, Clone)]
struct JSONToken {
    scopes: Vec<String>,
    token: Token,
    hash: ScopeHash,
    filter: ScopeFilter,
}

impl<'de> Deserialize<'de> for JSONToken {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        #[derive(Deserialize)]
        struct RawJSONToken {
            scopes: Vec<String>,
            token: Token,
        }
        let RawJSONToken { scopes, token } = RawJSONToken::deserialize(deserializer)?;
        let ScopeSet { hash, filter, .. } = ScopeSet::from(&scopes);
        Ok(JSONToken {
            scopes,
            token,
            hash,
            filter,
        })
    }
}

impl Serialize for JSONToken {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        #[derive(Serialize)]
        struct RawJSONToken<'a> {
            scopes: &'a [String],
            token: &'a Token,
        }
        RawJSONToken {
            scopes: &self.scopes,
            token: &self.token,
        }
        .serialize(serializer)
    }
}

/// List of tokens in a JSON object
#[derive(Debug, Clone)]
pub(crate) struct JSONTokens {
    token_map: BTreeMap<ScopeHash, Rc<RefCell<JSONToken>>>,
    tokens: Vec<Rc<RefCell<JSONToken>>>,
}

impl JSONTokens {
    pub(crate) fn new() -> Self {
        JSONTokens {
            token_map: BTreeMap::new(),
            tokens: Vec::new(),
        }
    }

    pub(crate) async fn load_from_file(filename: &Path) -> Result<Self, io::Error> {
        let contents = tokio::fs::read(filename).await?;
        let tokens: Vec<Rc<RefCell<JSONToken>>> =
            serde_json::from_slice::<Vec<JSONToken>>(&contents)
                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
                .into_iter()
                .map(|json_token| Rc::new(RefCell::new(json_token)))
                .collect();
        let mut token_map: BTreeMap<ScopeHash, Rc<RefCell<JSONToken>>> = BTreeMap::new();
        for token in tokens.iter().cloned() {
            let hash = token.borrow().hash;
            token_map.insert(hash, token);
        }
        Ok(JSONTokens { token_map, tokens })
    }

    fn get<T>(
        &self,
        ScopeSet {
            hash,
            filter,
            scopes,
        }: ScopeSet<T>,
    ) -> Option<Token>
    where
        T: AsRef<str>,
    {
        if let Some(json_token) = self.token_map.get(&hash) {
            return Some(json_token.borrow().token.clone());
        }

        let requested_scopes_are_subset_of = |other_scopes: &[String]| {
            scopes
                .iter()
                .all(|s| other_scopes.iter().any(|t| t.as_str() == s.as_ref()))
        };
        // No exact match for the scopes provided. Search for any tokens that
        // exist for a superset of the scopes requested.
        self.tokens
            .iter()
            .filter(|json_token| {
                filter.is_subset_of(json_token.borrow().filter) == FilterResponse::Maybe
            })
            .find(|v: &&Rc<RefCell<JSONToken>>| requested_scopes_are_subset_of(&v.borrow().scopes))
            .map(|t: &Rc<RefCell<JSONToken>>| t.borrow().token.clone())
    }

    fn set<T>(
        &mut self,
        ScopeSet {
            hash,
            filter,
            scopes,
        }: ScopeSet<T>,
        token: Token,
    ) where
        T: AsRef<str>,
    {
        use std::collections::btree_map::Entry;
        match self.token_map.entry(hash) {
            Entry::Occupied(entry) => {
                entry.get().borrow_mut().token = token;
            }
            Entry::Vacant(entry) => {
                let json_token = Rc::new(RefCell::new(JSONToken {
                    scopes: scopes.iter().map(|x| x.as_ref().to_owned()).collect(),
                    token,
                    hash,
                    filter,
                }));
                entry.insert(json_token.clone());
                self.tokens.push(json_token);
            }
        }
    }

    fn all_tokens(&self) -> Vec<JSONToken> {
        self.tokens
            .iter()
            .map(|t: &Rc<RefCell<JSONToken>>| t.borrow().clone())
            .collect()
    }
}

pub(crate) struct DiskStorage {
    tokens: Mutex<JSONTokens>,
    write_tx: tokio::sync::mpsc::Sender<Vec<JSONToken>>,
}

impl DiskStorage {
    pub(crate) async fn new(path: PathBuf) -> Result<Self, io::Error> {
        let tokens = match JSONTokens::load_from_file(&path).await {
            Ok(tokens) => tokens,
            Err(e) if e.kind() == io::ErrorKind::NotFound => JSONTokens::new(),
            Err(e) => return Err(e),
        };

        // Writing to disk will happen in a separate task. This means in the
        // common case returning a token to the user will not be required to
        // wait for disk i/o. We communicate with a dedicated writer task via a
        // buffered channel. This ensures that the writes happen in the order
        // received, and if writes fall too far behind we will block GetToken
        // requests until disk i/o completes.
        let (write_tx, mut write_rx) = tokio::sync::mpsc::channel::<Vec<JSONToken>>(2);
        tokio::spawn(async move {
            while let Some(tokens) = write_rx.recv().await {
                match serde_json::to_string(&tokens) {
                    Err(e) => log::error!("Failed to serialize tokens: {}", e),
                    Ok(ser) => {
                        if let Err(e) = tokio::fs::write(path.clone(), &ser).await {
                            log::error!("Failed to write tokens to disk: {}", e);
                        }
                    }
                }
            }
        });
        Ok(DiskStorage {
            tokens: Mutex::new(tokens),
            write_tx,
        })
    }

    async fn set<T>(&self, scopes: ScopeSet<'_, T>, token: Token)
    where
        T: AsRef<str>,
    {
        let cloned_tokens = {
            let mut tokens = self.tokens.lock().unwrap();
            tokens.set(scopes, token);
            tokens.all_tokens()
        };
        self.write_tx
            .clone()
            .send(cloned_tokens)
            .await
            .expect("disk storage task not running");
    }

    pub(crate) fn get<T>(&self, scopes: ScopeSet<T>) -> Option<Token>
    where
        T: AsRef<str>,
    {
        self.tokens.lock().unwrap().get(scopes)
    }
}

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

    #[test]
    fn test_scope_filter() {
        let foo = ScopeSet::from(&["foo"]).filter;
        let bar = ScopeSet::from(&["bar"]).filter;
        let foobar = ScopeSet::from(&["foo", "bar"]).filter;

        // foo and bar are both subsets of foobar. This condition should hold no
        // matter what changes are made to the bloom filter implementation.
        assert!(foo.is_subset_of(foobar) == FilterResponse::Maybe);
        assert!(bar.is_subset_of(foobar) == FilterResponse::Maybe);

        // These conditions hold under the current bloom filter implementation
        // because "foo" and "bar" don't collide, but if the bloom filter
        // implementations change it could be valid for them to return Maybe.
        assert!(foo.is_subset_of(bar) == FilterResponse::No);
        assert!(bar.is_subset_of(foo) == FilterResponse::No);
        assert!(foobar.is_subset_of(foo) == FilterResponse::No);
        assert!(foobar.is_subset_of(bar) == FilterResponse::No);
    }
}
<commit_msg>Use Arc<Mutex<T>> rather than Rc<RefCell<T>> in DiskStorage.<commit_after>// partially (c) 2016 Google Inc. (Lewin Bormann, lewinb@google.com)
//
// See project root for licensing information.
//
use crate::types::Token;

use std::collections::BTreeMap;
use std::io;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};

use serde::{Deserialize, Serialize};

// The storage layer allows retrieving tokens for scopes that have been
// previously granted tokens. One wrinkle is that a token granted for a set
// of scopes X is also valid for any subset of X's scopes. So when retrieving a
// token for a set of scopes provided by the caller it's beneficial to compare
// that set to all previously stored tokens to see if it is a subset of any
// existing set. To do this efficiently we store a bloom filter along with each
// token that represents the set of scopes the token is associated with. The
// bloom filter allows for efficiently skipping any entries that are
// definitively not a superset.
// The current implementation uses a 64bit bloom filter with 4 hash functions.

/// ScopeHash is a hash value derived from a list of scopes. The hash value
/// represents a fingerprint of the set of scopes *independent* of the ordering.
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
struct ScopeHash(u64);

/// ScopeFilter represents a filter for a set of scopes. It can definitively
/// prove that a given list of scopes is not a subset of another.
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
struct ScopeFilter(u64);

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum FilterResponse {
    Maybe,
    No,
}

impl ScopeFilter {
    /// Determine if this ScopeFilter could be a subset of the provided filter.
    fn is_subset_of(self, filter: ScopeFilter) -> FilterResponse {
        if self.0 & filter.0 == self.0 {
            FilterResponse::Maybe
        } else {
            FilterResponse::No
        }
    }
}

#[derive(Debug)]
pub(crate) struct ScopeSet<'a, T> {
    hash: ScopeHash,
    filter: ScopeFilter,
    scopes: &'a [T],
}

// Implement Clone manually. Auto derive fails to work correctly because we want
// Clone to be implemented regardless of whether T is Clone or not.
impl<'a, T> Clone for ScopeSet<'a, T> {
    fn clone(&self) -> Self {
        ScopeSet {
            hash: self.hash,
            filter: self.filter,
            scopes: self.scopes,
        }
    }
}
impl<'a, T> Copy for ScopeSet<'a, T> {}

impl<'a, T> ScopeSet<'a, T>
where
    T: AsRef<str>,
{
    // implement an inherent from method even though From is implemented. This
    // is because passing an array ref like &[&str; 1] (&["foo"]) will be auto
    // deref'd to a slice on function boundaries, but it will not implement the
    // From trait. This inherent method just serves to auto deref from array
    // refs to slices and proxy to the From impl.
    pub fn from(scopes: &'a [T]) -> Self {
        let (hash, filter) = scopes.iter().fold(
            (ScopeHash(0), ScopeFilter(0)),
            |(mut scope_hash, mut scope_filter), scope| {
                let h = seahash::hash(scope.as_ref().as_bytes());

                // Use the first 4 6-bit chunks of the seahash as the 4 hash values
                // in the bloom filter.
                for i in 0..4 {
                    // h is a hash derived value in the range 0..64
                    let h = (h >> (6 * i)) & 0b11_1111;
                    scope_filter.0 |= 1 << h;
                }

                // xor the hashes together to get an order independent fingerprint.
                scope_hash.0 ^= h;
                (scope_hash, scope_filter)
            },
        );
        ScopeSet {
            hash,
            filter,
            scopes,
        }
    }
}

pub(crate) enum Storage {
    Memory { tokens: Mutex<JSONTokens> },
    Disk(DiskStorage),
}

impl Storage {
    pub(crate) async fn set<T>(&self, scopes: ScopeSet<'_, T>, token: Token)
    where
        T: AsRef<str>,
    {
        match self {
            Storage::Memory { tokens } => tokens.lock().unwrap().set(scopes, token),
            Storage::Disk(disk_storage) => disk_storage.set(scopes, token).await,
        }
    }

    pub(crate) fn get<T>(&self, scopes: ScopeSet<T>) -> Option<Token>
    where
        T: AsRef<str>,
    {
        match self {
            Storage::Memory { tokens } => tokens.lock().unwrap().get(scopes),
            Storage::Disk(disk_storage) => disk_storage.get(scopes),
        }
    }
}

/// A single stored token.

#[derive(Debug, Clone)]
struct JSONToken {
    scopes: Vec<String>,
    token: Token,
    hash: ScopeHash,
    filter: ScopeFilter,
}

impl<'de> Deserialize<'de> for JSONToken {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        #[derive(Deserialize)]
        struct RawJSONToken {
            scopes: Vec<String>,
            token: Token,
        }
        let RawJSONToken { scopes, token } = RawJSONToken::deserialize(deserializer)?;
        let ScopeSet { hash, filter, .. } = ScopeSet::from(&scopes);
        Ok(JSONToken {
            scopes,
            token,
            hash,
            filter,
        })
    }
}

impl Serialize for JSONToken {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        #[derive(Serialize)]
        struct RawJSONToken<'a> {
            scopes: &'a [String],
            token: &'a Token,
        }
        RawJSONToken {
            scopes: &self.scopes,
            token: &self.token,
        }
        .serialize(serializer)
    }
}

/// List of tokens in a JSON object
#[derive(Debug, Clone)]
pub(crate) struct JSONTokens {
    token_map: BTreeMap<ScopeHash, Arc<Mutex<JSONToken>>>,
    tokens: Vec<Arc<Mutex<JSONToken>>>,
}

impl JSONTokens {
    pub(crate) fn new() -> Self {
        JSONTokens {
            token_map: BTreeMap::new(),
            tokens: Vec::new(),
        }
    }

    pub(crate) async fn load_from_file(filename: &Path) -> Result<Self, io::Error> {
        let contents = tokio::fs::read(filename).await?;
        let tokens: Vec<Arc<Mutex<JSONToken>>> =
            serde_json::from_slice::<Vec<JSONToken>>(&contents)
                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
                .into_iter()
                .map(|json_token| Arc::new(Mutex::new(json_token)))
                .collect();
        let mut token_map: BTreeMap<ScopeHash, Arc<Mutex<JSONToken>>> = BTreeMap::new();
        for token in tokens.iter().cloned() {
            let hash = token.lock().unwrap().hash;
            token_map.insert(hash, token);
        }
        Ok(JSONTokens { token_map, tokens })
    }

    fn get<T>(
        &self,
        ScopeSet {
            hash,
            filter,
            scopes,
        }: ScopeSet<T>,
    ) -> Option<Token>
    where
        T: AsRef<str>,
    {
        if let Some(json_token) = self.token_map.get(&hash) {
            return Some(json_token.lock().unwrap().token.clone());
        }

        let requested_scopes_are_subset_of = |other_scopes: &[String]| {
            scopes
                .iter()
                .all(|s| other_scopes.iter().any(|t| t.as_str() == s.as_ref()))
        };
        // No exact match for the scopes provided. Search for any tokens that
        // exist for a superset of the scopes requested.
        self.tokens
            .iter()
            .filter(|json_token| {
                filter.is_subset_of(json_token.lock().unwrap().filter) == FilterResponse::Maybe
            })
            .find(|v: &&Arc<Mutex<JSONToken>>| requested_scopes_are_subset_of(&v.lock().unwrap().scopes))
            .map(|t: &Arc<Mutex<JSONToken>>| t.lock().unwrap().token.clone())
    }

    fn set<T>(
        &mut self,
        ScopeSet {
            hash,
            filter,
            scopes,
        }: ScopeSet<T>,
        token: Token,
    ) where
        T: AsRef<str>,
    {
        use std::collections::btree_map::Entry;
        match self.token_map.entry(hash) {
            Entry::Occupied(entry) => {
                entry.get().lock().unwrap().token = token;
            }
            Entry::Vacant(entry) => {
                let json_token = Arc::new(Mutex::new(JSONToken {
                    scopes: scopes.iter().map(|x| x.as_ref().to_owned()).collect(),
                    token,
                    hash,
                    filter,
                }));
                entry.insert(json_token.clone());
                self.tokens.push(json_token);
            }
        }
    }

    fn all_tokens(&self) -> Vec<JSONToken> {
        self.tokens
            .iter()
            .map(|t: &Arc<Mutex<JSONToken>>| t.lock().unwrap().clone())
            .collect()
    }
}

pub(crate) struct DiskStorage {
    tokens: Mutex<JSONTokens>,
    write_tx: tokio::sync::mpsc::Sender<Vec<JSONToken>>,
}

fn is_send<T: Send>() {}

impl DiskStorage {
    pub(crate) async fn new(path: PathBuf) -> Result<Self, io::Error> {
        is_send::<JSONTokens>();
        let tokens = match JSONTokens::load_from_file(&path).await {
            Ok(tokens) => tokens,
            Err(e) if e.kind() == io::ErrorKind::NotFound => JSONTokens::new(),
            Err(e) => return Err(e),
        };

        // Writing to disk will happen in a separate task. This means in the
        // common case returning a token to the user will not be required to
        // wait for disk i/o. We communicate with a dedicated writer task via a
        // buffered channel. This ensures that the writes happen in the order
        // received, and if writes fall too far behind we will block GetToken
        // requests until disk i/o completes.
        let (write_tx, mut write_rx) = tokio::sync::mpsc::channel::<Vec<JSONToken>>(2);
        tokio::spawn(async move {
            while let Some(tokens) = write_rx.recv().await {
                match serde_json::to_string(&tokens) {
                    Err(e) => log::error!("Failed to serialize tokens: {}", e),
                    Ok(ser) => {
                        if let Err(e) = tokio::fs::write(path.clone(), &ser).await {
                            log::error!("Failed to write tokens to disk: {}", e);
                        }
                    }
                }
            }
        });
        Ok(DiskStorage {
            tokens: Mutex::new(tokens),
            write_tx,
        })
    }

    async fn set<T>(&self, scopes: ScopeSet<'_, T>, token: Token)
    where
        T: AsRef<str>,
    {
        let cloned_tokens = {
            let mut tokens = self.tokens.lock().unwrap();
            tokens.set(scopes, token);
            tokens.all_tokens()
        };
        self.write_tx
            .clone()
            .send(cloned_tokens)
            .await
            .expect("disk storage task not running");
    }

    pub(crate) fn get<T>(&self, scopes: ScopeSet<T>) -> Option<Token>
    where
        T: AsRef<str>,
    {
        self.tokens.lock().unwrap().get(scopes)
    }
}

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

    #[test]
    fn test_scope_filter() {
        let foo = ScopeSet::from(&["foo"]).filter;
        let bar = ScopeSet::from(&["bar"]).filter;
        let foobar = ScopeSet::from(&["foo", "bar"]).filter;

        // foo and bar are both subsets of foobar. This condition should hold no
        // matter what changes are made to the bloom filter implementation.
        assert!(foo.is_subset_of(foobar) == FilterResponse::Maybe);
        assert!(bar.is_subset_of(foobar) == FilterResponse::Maybe);

        // These conditions hold under the current bloom filter implementation
        // because "foo" and "bar" don't collide, but if the bloom filter
        // implementations change it could be valid for them to return Maybe.
        assert!(foo.is_subset_of(bar) == FilterResponse::No);
        assert!(bar.is_subset_of(foo) == FilterResponse::No);
        assert!(foobar.is_subset_of(foo) == FilterResponse::No);
        assert!(foobar.is_subset_of(bar) == FilterResponse::No);
    }
}
<|endoftext|>


<commit_before>use std::result::Result;

use env::load_var;
use lodestone::NewsItem;
use redis::{Client, Commands, Connection, RedisError};

pub fn connect_redis() -> Connection {
    let env = load_var("REDIS_URL", "redis://127.0.0.1:6379");
    let url = env.as_str();
    let client = match Client::open(url) {
        Ok(client) => client,
        Err(err) => panic!("Failed to open redis connection!\n{}", err)
    };

    let conn = match client.get_connection() {
        Ok(conn) => conn,
        Err(err) => panic!("Redis connection could not open!\n{}", err)
    };

    conn
}

pub fn push_news(locale: &'static str, topics: &Vec<NewsItem>, conn: &Connection) -> Result<(), RedisError> {
    let json: Vec<String> = topics.iter().map(|ref t| t.to_json()).collect();

    conn.rpush(format!("news:{}", locale), json)
}

pub fn pull_news(locale: &'static str, conn: &Connection) -> Vec<NewsItem> {
    let vec: Vec<String> = match conn.lrange(format!("news:{}", locale), 0, 19) {
        Ok(vec) => vec,
        Err(err) => panic!("Couldn't get JSON from redis!\n{}", err)
    };

    vec.iter().map(|s| NewsItem::from_json(s)).collect()
}
<commit_msg>refactor(storage): Correct LRANGE args<commit_after>use std::result::Result;

use env::load_var;
use lodestone::NewsItem;
use redis::{Client, Commands, Connection, RedisError};

pub fn connect_redis() -> Connection {
    let env = load_var("REDIS_URL", "redis://127.0.0.1:6379");
    let url = env.as_str();
    let client = match Client::open(url) {
        Ok(client) => client,
        Err(err) => panic!("Failed to open redis connection!\n{}", err)
    };

    let conn = match client.get_connection() {
        Ok(conn) => conn,
        Err(err) => panic!("Redis connection could not open!\n{}", err)
    };

    conn
}

pub fn push_news(locale: &'static str, topics: &Vec<NewsItem>, conn: &Connection) -> Result<(), RedisError> {
    let json: Vec<String> = topics.iter().map(|ref t| t.to_json()).collect();

    conn.rpush(format!("news:{}", locale), json)
}

pub fn pull_news(locale: &'static str, conn: &Connection) -> Vec<NewsItem> {
    let vec: Vec<String> = match conn.lrange(format!("news:{}", locale), -20, -1) {
        Ok(vec) => vec,
        Err(err) => panic!("Couldn't get JSON from redis!\n{}", err)
    };

    vec.iter().map(|s| NewsItem::from_json(s)).collect()
}
<|endoftext|>


<commit_before>extern crate rustbox;
extern crate chrono;

use rustbox::*;
use chrono::*;

use screen::common::*;
use utility::string::*;
use model::ShowItem;
use reply_model::*;

pub struct Show<'a> {
    rustbox: &'a rustbox::RustBox,
    scrollY: usize,
}

impl<'a> Show<'a> {
    pub fn new(rustbox: &'a rustbox::RustBox) -> Self {
        Show {
            rustbox: &rustbox,
            scrollY: 0,
        }
    }
    pub fn print(&mut self, title: &str, item: &ShowItem) {

        print_header(&self.rustbox,
                     self.rustbox.width(),
                     &format!("{} - {} [{}/{}]",
                              item.title,
                              title,
                              item.page,
                              item.max_page));
        print_body(&self.rustbox,
                   self.body_width(),
                   2,
                   self.body_height(),
                   &item,
                   self.scrollY);
    }

    pub fn resetY(&mut self) {
        self.scrollY = 0;
    }

    pub fn scrollUp(&mut self, value: usize) -> bool {
        let tmp = self.scrollY;
        if tmp > value {
            self.scrollY = tmp - value;
            true
        } else if tmp != 0 {
            self.scrollY = 0;
            true
        } else {
            false
        }
    }

    pub fn scrollDown(&mut self, value: usize) -> bool {
        let tmp = self.scrollY;
        if tmp < 10000 {
            self.scrollY = tmp + value;
            return true;
        }
        false
    }

    pub fn body_height(&self) -> usize {
        if self.rustbox.height() >= 3 {
            self.rustbox.height() - 3
        } else {
            0
        }
    }

    pub fn body_width(&self) -> usize {
        if self.rustbox.width() >= 2 {
            self.rustbox.width() - 2
        } else {
            0
        }
    }
}

fn print_header(rustbox: &rustbox::RustBox, width: usize, text: &str) {
    let title_len = jks_len(text);
    let padding = (if width >= title_len {
        width - title_len
    } else {
        0
    }) / 2;

    let header_bottom = (0..width).map(|_| "─").collect::<Vec<_>>().join("");

    clearline(&rustbox, width, 0, 0);
    rustbox.print(padding,
                  0,
                  rustbox::RB_BOLD,
                  Color::White,
                  Color::Black,
                  text);
    rustbox.print(0,
                  1,
                  rustbox::RB_BOLD,
                  Color::Yellow,
                  Color::Black,
                  &header_bottom);
}

fn print_default(rustbox: &rustbox::RustBox, x: usize, y: usize, s: String) {
    rustbox.print(0, y, rustbox::RB_NORMAL, Color::White, Color::Black, &s);
}

fn print_reply(vec: &Vec<NodeType>,
               depth: u8,
               scrollY: usize,
               y: usize,
               rustbox: &rustbox::RustBox)
               -> usize {
    let padding = (0..depth).map(|_| "├─").collect::<Vec<_>>().join("");
    let mut m = 0;
    let mut recursive_offset = 0;
    let mut total_y = 0;
    let mut line = String::new();

    // clean up lines
    let vec2 = {
        let vec_length = vec.len();
        let vec_check_cleanup = vec.clone();

        // check if last 4 elements match the EMPTY PATTERN
        let is_last4_empty = vec_check_cleanup.iter()
                                              .rev()
                                              .take(4)
                                              .enumerate()
                                              .all(|(j, node)| match node.clone() {
                                                  NodeType::Br(n) => j == 1 || j == 2 || j == 3,
                                                  NodeType::Text(n) => j == 0 && n.data.is_empty(),
                                                  _ => false,
                                              });

        let vec_short_length = if vec_length > 4 && is_last4_empty {
            vec_length - 4
        } else {
            vec_length
        };

        vec.iter().take(vec_short_length)
    };

    for (j, node) in vec2.enumerate() {
        total_y = y + m + recursive_offset;
        if scrollY + 1 < total_y {
            let node2 = node.clone();
            match node2 {
                NodeType::Text(n) => {
                    if n.data != "" {
                        line = format!("{}{}", line, n.data);
                    }
                }
                NodeType::Image(n) => {
                    if n.data != "" {
                        line = format!("{}[img {}]", line, n.data);
                    }
                }
                NodeType::BlockQuote(n) => {
                    print_default(rustbox,
                                  0,
                                  total_y - scrollY,
                                  format!("{}[{}] = BlockQuote", padding, m));
                    m += 1;
                    recursive_offset += print_reply(&n.data,
                                                    depth + 1,
                                                    scrollY,
                                                    total_y + 1,
                                                    &rustbox);
                }
                NodeType::Br(n) => {
                    if !line.is_empty() {
                        print_default(rustbox,
                                      0,
                                      total_y - scrollY,
                                      format!(" {}{}", padding, line));
                        line = String::new();
                    }
                    m += 1;
                }
            }
        }
    }

    if !line.is_empty() {
        total_y = y + m + recursive_offset;
        print_default(rustbox,
                      0,
                      total_y - scrollY,
                      format!(" {}{}  ", padding, line));
        line = String::new();
        m += 1;
    }

    m + recursive_offset
}

fn print_body(rustbox: &rustbox::RustBox,
              width: usize,
              offset_y: usize,
              rows: usize,
              item: &ShowItem,
              scrollY: usize) {

    let mut y = offset_y;
    let replier_max_width = 14;
    let time_max_width = 5;
    let now = Local::now();

    let separator_width = if rustbox.width() >= 2 {
        rustbox.width() - 2
    } else {
        0
    };
    let separator_padding_width = if rustbox.width() > separator_width {
        rustbox.width() - separator_width
    } else {
        0
    } / 2;

    let separator_padding = (0..separator_padding_width).map(|_| " ").collect::<Vec<_>>().join("");

    let separator_bottom = make_separator_bottom(separator_width, &separator_padding);

    for (i, reply) in item.replies.iter().take(rows).enumerate() {

        let mut m = print_reply(&reply.body, 0, scrollY, y, &rustbox);

        if scrollY + 1 < y + m {

            let replier_name = reply.username.clone();

            let published_at = reply.published_at.clone();

            let published_at_dt = match Local.datetime_from_str(&published_at, "%d/%m/%Y %H:%M") {
                Ok(v) => v,
                Err(e) => now,
            };
            let time = published_at_format(&(now - published_at_dt));

            let separator_top = make_separator_top(separator_width,
                                                   &separator_padding,
                                                   replier_max_width,
                                                   &replier_name,
                                                   time_max_width,
                                                   &time);

            rustbox.print(0,
                          m + y - scrollY,
                          rustbox::RB_NORMAL,
                          Color::Green,
                          Color::Black,
                          &separator_top);
        }
        m += 1;

        if scrollY + 1 < y + m {
            rustbox.print(0,
                          m + y - scrollY,
                          rustbox::RB_NORMAL,
                          Color::Green,
                          Color::Black,
                          &separator_bottom);
        }
        m += 1;
        y += m;
    }
}

fn make_separator_replier_name(separator_width: usize,
                               separator_padding: &str,
                               replier_max_width: usize,
                               replier_name: &str)
                               -> String {
    let replier_name_len = jks_len(&replier_name);
    let replier_name_spacing_width = replier_max_width - replier_name_len;
    let is_replier_name_spacing_width_odd = replier_name_spacing_width & 1 == 1;
    let replier_name_right_spacing_width = replier_name_spacing_width / 2;
    let replier_name_left_spacing_width = if is_replier_name_spacing_width_odd {
        replier_name_right_spacing_width + 1
    } else {
        replier_name_right_spacing_width
    };

    let replier_name_left_spacing = (0..replier_name_left_spacing_width)
                                        .map(|_| "─")
                                        .collect::<Vec<_>>()
                                        .join("");

    let replier_name_right_spacing = (0..replier_name_right_spacing_width)
                                         .map(|_| "─")
                                         .collect::<Vec<_>>()
                                         .join("");

    let separator_replier = format!("{}{}{}{}{}",
                                    "╭",
                                    replier_name_left_spacing,
                                    replier_name,
                                    replier_name_right_spacing,
                                    "");

    return separator_replier;
}

fn make_separator_time(separator_width: usize,
                       separator_padding: &str,
                       time_max_width: usize,
                       time: &str)
                       -> String {
    let time_len = jks_len(&time);
    let time_spacing_width = if time_max_width > time_len {
        time_max_width - time_len
    } else {
        0
    };

    let is_time_spacing_width_odd = time_spacing_width & 1 == 1;
    let time_right_spacing_width = time_spacing_width / 2;
    let time_left_spacing_width = if is_time_spacing_width_odd {
        time_right_spacing_width + 1
    } else {
        time_right_spacing_width
    };

    let time_left_spacing = (0..time_left_spacing_width)
                                .map(|_| "─")
                                .collect::<Vec<_>>()
                                .join("");

    let time_right_spacing = (0..time_right_spacing_width)
                                 .map(|_| "─")
                                 .collect::<Vec<_>>()
                                 .join("");

    let separator_time = format!("{}{}{}{}{}",
                                 "",
                                 time_left_spacing,
                                 time,
                                 time_right_spacing,
                                 "╮");

    return separator_time;
}

fn make_separator_top(separator_width: usize,
                      separator_padding: &str,
                      replier_max_width: usize,
                      replier_name: &str,
                      time_max_width: usize,
                      time: &str)
                      -> String {

    let separator_replier = make_separator_replier_name(separator_width,
                                                        &separator_padding,
                                                        replier_max_width,
                                                        &replier_name);

    let separator_replier_width = jks_len(&separator_replier);

    let separator_time = make_separator_time(separator_width,
                                             &separator_padding,
                                             time_max_width,
                                             &time);

    let separator_time_width = jks_len(&separator_time);

    let separator_top_middle_width = if separator_width >=
                                        (separator_replier_width + separator_time_width) {
        separator_width - separator_replier_width - separator_time_width
    } else {
        0
    };

    let separator_top_middle = (0..separator_top_middle_width)
                                   .map(|_| " ")
                                   .collect::<Vec<_>>()
                                   .join("");

    let separator_top = format!("{}{}{}{}{}",
                                separator_padding,
                                separator_top_middle,
                                separator_replier,
                                separator_time,
                                separator_padding);
    return separator_top;
}

fn make_separator_bottom(separator_width: usize, separator_padding: &str) -> String {
    let style_box_width = 1;
    let separator_bottom_middle_width = if separator_width > style_box_width {
        separator_width - style_box_width
    } else {
        0
    };
    let separator_bottom_middle = (0..separator_bottom_middle_width)
                                      .map(|_| "─")
                                      .collect::<Vec<_>>()
                                      .join("");
    let separator_bottom = format!("{}{}{}{}",
                                   separator_padding,
                                   separator_bottom_middle,
                                   "╯",
                                   separator_padding);
    return separator_bottom;
}

fn published_at_format(duration: &Duration) -> String {
    let weeks = duration.num_weeks();
    let days = duration.num_days();
    let hours = duration.num_hours();
    let minutes = duration.num_minutes();

    if weeks > 0 {
        format!("{}w", weeks)
    } else if days > 0 {
        format!("{}d", days)
    } else if hours > 0 {
        format!("{}h", hours)
    } else if minutes > 0 {
        format!("{}m", minutes)
    } else {
        String::from("1m")
    }
}
<commit_msg>show - print reply - adjust line breaking with blockqoutes<commit_after>extern crate rustbox;
extern crate chrono;

use rustbox::*;
use chrono::*;

use screen::common::*;
use utility::string::*;
use model::ShowItem;
use reply_model::*;

pub struct Show<'a> {
    rustbox: &'a rustbox::RustBox,
    scrollY: usize,
}

impl<'a> Show<'a> {
    pub fn new(rustbox: &'a rustbox::RustBox) -> Self {
        Show {
            rustbox: &rustbox,
            scrollY: 0,
        }
    }
    pub fn print(&mut self, title: &str, item: &ShowItem) {

        print_header(&self.rustbox,
                     self.rustbox.width(),
                     &format!("{} - {} [{}/{}]",
                              item.title,
                              title,
                              item.page,
                              item.max_page));
        print_body(&self.rustbox,
                   self.body_width(),
                   2,
                   self.body_height(),
                   &item,
                   self.scrollY);
    }

    pub fn resetY(&mut self) {
        self.scrollY = 0;
    }

    pub fn scrollUp(&mut self, value: usize) -> bool {
        let tmp = self.scrollY;
        if tmp > value {
            self.scrollY = tmp - value;
            true
        } else if tmp != 0 {
            self.scrollY = 0;
            true
        } else {
            false
        }
    }

    pub fn scrollDown(&mut self, value: usize) -> bool {
        let tmp = self.scrollY;
        if tmp < 10000 {
            self.scrollY = tmp + value;
            return true;
        }
        false
    }

    pub fn body_height(&self) -> usize {
        if self.rustbox.height() >= 3 {
            self.rustbox.height() - 3
        } else {
            0
        }
    }

    pub fn body_width(&self) -> usize {
        if self.rustbox.width() >= 2 {
            self.rustbox.width() - 2
        } else {
            0
        }
    }
}

fn print_header(rustbox: &rustbox::RustBox, width: usize, text: &str) {
    let title_len = jks_len(text);
    let padding = (if width >= title_len {
        width - title_len
    } else {
        0
    }) / 2;

    let header_bottom = (0..width).map(|_| "─").collect::<Vec<_>>().join("");

    clearline(&rustbox, width, 0, 0);
    rustbox.print(padding,
                  0,
                  rustbox::RB_BOLD,
                  Color::White,
                  Color::Black,
                  text);
    rustbox.print(0,
                  1,
                  rustbox::RB_BOLD,
                  Color::Yellow,
                  Color::Black,
                  &header_bottom);
}

fn print_default(rustbox: &rustbox::RustBox, x: usize, y: usize, s: String) {
    rustbox.print(0, y, rustbox::RB_NORMAL, Color::White, Color::Black, &s);
}

fn print_reply(vec: &Vec<NodeType>,
               depth: u8,
               scrollY: usize,
               y: usize,
               rustbox: &rustbox::RustBox)
               -> usize {
    let padding = (0..depth).map(|_| "├─").collect::<Vec<_>>().join("");
    let mut m = 0;
    let mut recursive_offset = 0;
    let mut total_y = 0;
    let mut line = String::new();

    // clean up lines
    let vec2 = {
        let vec_length = vec.len();
        let vec_check_cleanup = vec.clone();

        // check if last 4 elements match the EMPTY PATTERN
        let is_last4_empty = vec_check_cleanup.iter()
                                              .rev()
                                              .take(4)
                                              .enumerate()
                                              .all(|(j, node)| match node.clone() {
                                                  NodeType::Br(n) => j == 1 || j == 2 || j == 3,
                                                  NodeType::Text(n) => j == 0 && n.data.is_empty(),
                                                  _ => false,
                                              });

        let vec_short_length = if vec_length > 4 && is_last4_empty {
            vec_length - 4
        } else {
            vec_length
        };

        vec.iter().take(vec_short_length)
    };

    for (j, node) in vec2.enumerate() {
        total_y = y + m + recursive_offset;
        if scrollY + 1 < total_y {
            let node2 = node.clone();
            match node2 {
                NodeType::Text(n) => {
                    if n.data != "" {
                        line = format!("{}{}", line, n.data);
                    }
                }
                NodeType::Image(n) => {
                    if n.data != "" {
                        line = format!("{}[img {}]", line, n.data);
                    }
                }
                NodeType::BlockQuote(n) => {
                    recursive_offset += print_reply(&n.data,
                                                    depth + 1,
                                                    scrollY,
                                                    total_y,
                                                    &rustbox);
                }
                NodeType::Br(n) => {
                    if !line.is_empty() {
                        print_default(rustbox,
                                      0,
                                      total_y - scrollY,
                                      format!(" {}{}", padding, line));
                        line = String::new();
                    }
                    m += 1;
                }
            }
        }
    }

    if !line.is_empty() {
        total_y = y + m + recursive_offset;
        print_default(rustbox,
                      0,
                      total_y - scrollY,
                      format!(" {}{}  ", padding, line));
        line = String::new();
        if depth == 0 {
            m += 1;
        }
    }

    m + recursive_offset
}

fn print_body(rustbox: &rustbox::RustBox,
              width: usize,
              offset_y: usize,
              rows: usize,
              item: &ShowItem,
              scrollY: usize) {

    let mut y = offset_y;
    let replier_max_width = 14;
    let time_max_width = 5;
    let now = Local::now();

    let separator_width = if rustbox.width() >= 2 {
        rustbox.width() - 2
    } else {
        0
    };
    let separator_padding_width = if rustbox.width() > separator_width {
        rustbox.width() - separator_width
    } else {
        0
    } / 2;

    let separator_padding = (0..separator_padding_width).map(|_| " ").collect::<Vec<_>>().join("");

    let separator_bottom = make_separator_bottom(separator_width, &separator_padding);

    for (i, reply) in item.replies.iter().take(rows).enumerate() {

        let mut m = print_reply(&reply.body, 0, scrollY, y, &rustbox);

        if scrollY + 1 < y + m {

            let replier_name = reply.username.clone();

            let published_at = reply.published_at.clone();

            let published_at_dt = match Local.datetime_from_str(&published_at, "%d/%m/%Y %H:%M") {
                Ok(v) => v,
                Err(e) => now,
            };
            let time = published_at_format(&(now - published_at_dt));

            let separator_top = make_separator_top(separator_width,
                                                   &separator_padding,
                                                   replier_max_width,
                                                   &replier_name,
                                                   time_max_width,
                                                   &time);

            rustbox.print(0,
                          m + y - scrollY,
                          rustbox::RB_NORMAL,
                          Color::Green,
                          Color::Black,
                          &separator_top);
        }
        m += 1;

        if scrollY + 1 < y + m {
            rustbox.print(0,
                          m + y - scrollY,
                          rustbox::RB_NORMAL,
                          Color::Green,
                          Color::Black,
                          &separator_bottom);
        }
        m += 1;
        y += m;
    }
}

fn make_separator_replier_name(separator_width: usize,
                               separator_padding: &str,
                               replier_max_width: usize,
                               replier_name: &str)
                               -> String {
    let replier_name_len = jks_len(&replier_name);
    let replier_name_spacing_width = replier_max_width - replier_name_len;
    let is_replier_name_spacing_width_odd = replier_name_spacing_width & 1 == 1;
    let replier_name_right_spacing_width = replier_name_spacing_width / 2;
    let replier_name_left_spacing_width = if is_replier_name_spacing_width_odd {
        replier_name_right_spacing_width + 1
    } else {
        replier_name_right_spacing_width
    };

    let replier_name_left_spacing = (0..replier_name_left_spacing_width)
                                        .map(|_| "─")
                                        .collect::<Vec<_>>()
                                        .join("");

    let replier_name_right_spacing = (0..replier_name_right_spacing_width)
                                         .map(|_| "─")
                                         .collect::<Vec<_>>()
                                         .join("");

    let separator_replier = format!("{}{}{}{}{}",
                                    "╭",
                                    replier_name_left_spacing,
                                    replier_name,
                                    replier_name_right_spacing,
                                    "");

    return separator_replier;
}

fn make_separator_time(separator_width: usize,
                       separator_padding: &str,
                       time_max_width: usize,
                       time: &str)
                       -> String {
    let time_len = jks_len(&time);
    let time_spacing_width = if time_max_width > time_len {
        time_max_width - time_len
    } else {
        0
    };

    let is_time_spacing_width_odd = time_spacing_width & 1 == 1;
    let time_right_spacing_width = time_spacing_width / 2;
    let time_left_spacing_width = if is_time_spacing_width_odd {
        time_right_spacing_width + 1
    } else {
        time_right_spacing_width
    };

    let time_left_spacing = (0..time_left_spacing_width)
                                .map(|_| "─")
                                .collect::<Vec<_>>()
                                .join("");

    let time_right_spacing = (0..time_right_spacing_width)
                                 .map(|_| "─")
                                 .collect::<Vec<_>>()
                                 .join("");

    let separator_time = format!("{}{}{}{}{}",
                                 "",
                                 time_left_spacing,
                                 time,
                                 time_right_spacing,
                                 "╮");

    return separator_time;
}

fn make_separator_top(separator_width: usize,
                      separator_padding: &str,
                      replier_max_width: usize,
                      replier_name: &str,
                      time_max_width: usize,
                      time: &str)
                      -> String {

    let separator_replier = make_separator_replier_name(separator_width,
                                                        &separator_padding,
                                                        replier_max_width,
                                                        &replier_name);

    let separator_replier_width = jks_len(&separator_replier);

    let separator_time = make_separator_time(separator_width,
                                             &separator_padding,
                                             time_max_width,
                                             &time);

    let separator_time_width = jks_len(&separator_time);

    let separator_top_middle_width = if separator_width >=
                                        (separator_replier_width + separator_time_width) {
        separator_width - separator_replier_width - separator_time_width
    } else {
        0
    };

    let separator_top_middle = (0..separator_top_middle_width)
                                   .map(|_| " ")
                                   .collect::<Vec<_>>()
                                   .join("");

    let separator_top = format!("{}{}{}{}{}",
                                separator_padding,
                                separator_top_middle,
                                separator_replier,
                                separator_time,
                                separator_padding);
    return separator_top;
}

fn make_separator_bottom(separator_width: usize, separator_padding: &str) -> String {
    let style_box_width = 1;
    let separator_bottom_middle_width = if separator_width > style_box_width {
        separator_width - style_box_width
    } else {
        0
    };
    let separator_bottom_middle = (0..separator_bottom_middle_width)
                                      .map(|_| "─")
                                      .collect::<Vec<_>>()
                                      .join("");
    let separator_bottom = format!("{}{}{}{}",
                                   separator_padding,
                                   separator_bottom_middle,
                                   "╯",
                                   separator_padding);
    return separator_bottom;
}

fn published_at_format(duration: &Duration) -> String {
    let weeks = duration.num_weeks();
    let days = duration.num_days();
    let hours = duration.num_hours();
    let minutes = duration.num_minutes();

    if weeks > 0 {
        format!("{}w", weeks)
    } else if days > 0 {
        format!("{}d", days)
    } else if hours > 0 {
        format!("{}h", hours)
    } else if minutes > 0 {
        format!("{}m", minutes)
    } else {
        String::from("1m")
    }
}
<|endoftext|>


<commit_before>use Decimal;
use num::FromPrimitive;
use serde;
use serde::de::Unexpected;
use std::fmt;
use std::str::FromStr;

impl<'de> serde::Deserialize<'de> for Decimal {
    fn deserialize<D>(deserializer: D) -> Result<Decimal, D::Error>
    where
        D: serde::de::Deserializer<'de>, {
        deserializer.deserialize_str(DecimalVisitor)
    }
}

struct DecimalVisitor;

impl<'de> serde::de::Visitor<'de> for DecimalVisitor {
    type Value = Decimal;

    fn visit_f64<E>(self, value: f64) -> Result<Decimal, E>
    where
        E: serde::de::Error, {
        Decimal::from_str(&value.to_string()).map_err(|_| E::invalid_value(Unexpected::Float(value), &self))
    }

    fn visit_i64<E>(self, value: i64) -> Result<Decimal, E>
    where
        E: serde::de::Error, {
        match Decimal::from_i64(value) {
            Some(s) => Ok(s),
            None => Err(E::invalid_value(Unexpected::Signed(value), &self)),
        }
    }

    fn visit_u64<E>(self, value: u64) -> Result<Decimal, E>
    where
        E: serde::de::Error, {
        match Decimal::from_u64(value) {
            Some(s) => Ok(s),
            None => Err(E::invalid_value(Unexpected::Unsigned(value), &self)),
        }
    }

    fn visit_str<E>(self, value: &str) -> Result<Decimal, E>
    where
        E: serde::de::Error, {
        Decimal::from_str(value).map_err(|_| E::invalid_value(Unexpected::Str(value), &self))
    }

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        write!(
            formatter,
            "a Decimal type representing a fixed-point number"
        )
    }
}

impl serde::Serialize for Decimal {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer, {
        serializer.serialize_str(&self.to_string())
    }
}

#[cfg(test)]
mod test {
    extern crate serde_json;

    use super::*;

    #[derive(Serialize, Deserialize, Debug)]
    struct Record {
        amount: Decimal,
    }

    #[test]
    fn deserialize_valid_decimal() {
        let data = [
            ("{\"amount\":\"1.234\"}", "1.234"),
            ("{\"amount\":1234}", "1234"),
        ];
        for &(serialized, value) in data.iter() {
            let result = serde_json::from_str(serialized);
            assert_eq!(
                true,
                result.is_ok(),
                "expected successful deseralization for {}. Error: {:?}",
                serialized,
                result.err().unwrap()
            );
            let record: Record = result.unwrap();
            assert_eq!(
                value,
                record.amount.to_string(),
                "expected: {}, actual: {}",
                value,
                record.amount.to_string()
            );
        }
    }

    #[test]
    #[should_panic]
    fn deserialize_invalid_decimal() {
        let serialized = "{\"amount\":\"foo\"}";
        let _: Record = serde_json::from_str(serialized).unwrap();
    }

    #[test]
    fn serialize_decimal() {
        let record = Record {
            amount: Decimal::new(1234, 3),
        };
        let serialized = serde_json::to_string(&record).unwrap();
        assert_eq!("{\"amount\":\"1.234\"}", serialized);
    }
}
<commit_msg>Added test for float deserialization<commit_after>use Decimal;
use num::FromPrimitive;
use serde;
use serde::de::Unexpected;
use std::fmt;
use std::str::FromStr;

impl<'de> serde::Deserialize<'de> for Decimal {
    fn deserialize<D>(deserializer: D) -> Result<Decimal, D::Error>
    where
        D: serde::de::Deserializer<'de>, {
        deserializer.deserialize_str(DecimalVisitor)
    }
}

struct DecimalVisitor;

impl<'de> serde::de::Visitor<'de> for DecimalVisitor {
    type Value = Decimal;

    fn visit_f64<E>(self, value: f64) -> Result<Decimal, E>
    where
        E: serde::de::Error, {
        Decimal::from_str(&value.to_string()).map_err(|_| E::invalid_value(Unexpected::Float(value), &self))
    }

    fn visit_i64<E>(self, value: i64) -> Result<Decimal, E>
    where
        E: serde::de::Error, {
        match Decimal::from_i64(value) {
            Some(s) => Ok(s),
            None => Err(E::invalid_value(Unexpected::Signed(value), &self)),
        }
    }

    fn visit_u64<E>(self, value: u64) -> Result<Decimal, E>
    where
        E: serde::de::Error, {
        match Decimal::from_u64(value) {
            Some(s) => Ok(s),
            None => Err(E::invalid_value(Unexpected::Unsigned(value), &self)),
        }
    }

    fn visit_str<E>(self, value: &str) -> Result<Decimal, E>
    where
        E: serde::de::Error, {
        Decimal::from_str(value).map_err(|_| E::invalid_value(Unexpected::Str(value), &self))
    }

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        write!(
            formatter,
            "a Decimal type representing a fixed-point number"
        )
    }
}

impl serde::Serialize for Decimal {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer, {
        serializer.serialize_str(&self.to_string())
    }
}

#[cfg(test)]
mod test {
    extern crate serde_json;

    use super::*;

    #[derive(Serialize, Deserialize, Debug)]
    struct Record {
        amount: Decimal,
    }

    #[test]
    fn deserialize_valid_decimal() {
        let data = [
            ("{\"amount\":\"1.234\"}", "1.234"),
            ("{\"amount\":1234}", "1234"),
            ("{\"amount\":1234.56}", "1234.56"),
        ];
        for &(serialized, value) in data.iter() {
            let result = serde_json::from_str(serialized);
            assert_eq!(
                true,
                result.is_ok(),
                "expected successful deseralization for {}. Error: {:?}",
                serialized,
                result.err().unwrap()
            );
            let record: Record = result.unwrap();
            assert_eq!(
                value,
                record.amount.to_string(),
                "expected: {}, actual: {}",
                value,
                record.amount.to_string()
            );
        }
    }

    #[test]
    #[should_panic]
    fn deserialize_invalid_decimal() {
        let serialized = "{\"amount\":\"foo\"}";
        let _: Record = serde_json::from_str(serialized).unwrap();
    }

    #[test]
    fn serialize_decimal() {
        let record = Record {
            amount: Decimal::new(1234, 3),
        };
        let serialized = serde_json::to_string(&record).unwrap();
        assert_eq!("{\"amount\":\"1.234\"}", serialized);
    }
}
<|endoftext|>


<commit_before>use futures::{SinkExt, StreamExt};
use log::*;
use std::net::SocketAddr;
use tokio::net::{TcpListener, TcpStream};
use tokio_tungstenite::accept_async;

async fn accept_connection(peer: SocketAddr, stream: TcpStream) {
    let mut ws_stream = accept_async(stream).await.expect("Failed to accept");

    info!("New WebSocket connection: {}", peer);

    while let Some(msg) = ws_stream.next().await {
        let msg = msg.expect("Failed to get request");
        if msg.is_text() || msg.is_binary() {
            ws_stream.send(msg).await.expect("Failed to send response");
        }
    }
}

#[tokio::main]
async fn main() {
    env_logger::init();

    let addr = "127.0.0.1:9002";
    let mut listener = TcpListener::bind(&addr).await.unwrap();
    info!("Listening on: {}", addr);

    while let Ok((stream, _)) = listener.accept().await {
        let peer = stream
            .peer_addr()
            .expect("connected streams should have a peer address");
        info!("Peer address: {}", peer);

        tokio::spawn(accept_connection(peer, stream));
    }
}
<commit_msg>autobahn: don't panic in server<commit_after>use futures::{SinkExt, StreamExt};
use log::*;
use std::net::SocketAddr;
use tokio::net::{TcpListener, TcpStream};
use tokio_tungstenite::accept_async;
use tungstenite::Result;

async fn accept_connection(peer: SocketAddr, stream: TcpStream) {
    if let Err(err) = handle_connection(peer, stream).await {
        error!("Error processing connection: {}", err);
    }
}

async fn handle_connection(peer: SocketAddr, stream: TcpStream) -> Result<()> {
    let mut ws_stream = accept_async(stream).await.expect("Failed to accept");

    info!("New WebSocket connection: {}", peer);

    while let Some(msg) = ws_stream.next().await {
        let msg = msg?;
        if msg.is_text() || msg.is_binary() {
            ws_stream.send(msg).await?;
        }
    }

    Ok(())
}

#[tokio::main]
async fn main() {
    env_logger::init();

    let addr = "127.0.0.1:9002";
    let mut listener = TcpListener::bind(&addr).await.expect("Can't listen");
    info!("Listening on: {}", addr);

    while let Ok((stream, _)) = listener.accept().await {
        let peer = stream
            .peer_addr()
            .expect("connected streams should have a peer address");
        info!("Peer address: {}", peer);

        tokio::spawn(accept_connection(peer, stream));
    }
}
<|endoftext|>


<commit_before>//! Online backup API
use std::path::{Path};

use super::ffi;
use {SqliteError, SqliteResult, SqliteConnection};

impl SqliteConnection {
    /// Open or create a database file named `dest_path`.  Transfer the
    /// content of local database `name` into the `dest_path` database.
    pub fn backup<P: AsRef<Path>>(&self, name: &str, dest_path: &P) -> SqliteResult<()> {
        let dest = try!(SqliteConnection::open(dest_path)); // "cannot open target database: "

        let mut dest_db = dest.db.borrow_mut();
        let dest_name = try!(super::str_to_cstring("main"));
        let src_db = self.db.borrow_mut();
        let src_name = try!(super::str_to_cstring(name));
        let backup = unsafe { ffi::sqlite3_backup_init(dest_db.db(), dest_name.as_ptr(), src_db.db(), src_name.as_ptr()) };
        if backup.is_null() {
            let msg = unsafe { super::errmsg_to_string(ffi::sqlite3_errmsg(dest_db.db())) };
            let err = Err(SqliteError{code: unsafe { ffi::sqlite3_errcode(dest_db.db()) }, message: "backup failed: ".to_string() + &msg });
            let _ = dest_db.close();
            return err;
        }

        let mut rc;
        loop {
            rc = unsafe { ffi::sqlite3_backup_step(backup, 100) };
            if rc != ffi::SQLITE_OK {
                break;
            }
        }
        unsafe { ffi::sqlite3_backup_finish(backup) };
        let result = if rc == ffi::SQLITE_DONE {
            Ok(())
        } else {
            let msg = unsafe { super::errmsg_to_string(ffi::sqlite3_errmsg(dest_db.db())) };
            Err(SqliteError{code: rc, message: "backup failed: ".to_string() + &msg })
        };
        let _ = dest_db.close();
        result
    }

    /// Open a database file named `src_path`.  Transfer the content
    /// of `src_path` into the local database `name`.
    pub fn restore<P: AsRef<Path>>(&self, name: &str, src_path: &P) -> SqliteResult<()> {
        let src = try!(SqliteConnection::open_with_flags(src_path, super::SQLITE_OPEN_READ_ONLY | super::SQLITE_OPEN_URI)); // "cannot open source database: "

        let dest_db = self.db.borrow_mut();
        let dest_name = try!(super::str_to_cstring(name));
        let mut src_db = src.db.borrow_mut();
        let src_name = try!(super::str_to_cstring("main"));
        let backup = unsafe { ffi::sqlite3_backup_init(dest_db.db(), dest_name.as_ptr(), src_db.db(), src_name.as_ptr()) };
        if backup.is_null() {
            let msg = unsafe { super::errmsg_to_string(ffi::sqlite3_errmsg(dest_db.db())) };
            let err = Err(SqliteError{code: unsafe { ffi::sqlite3_errcode(dest_db.db()) }, message: "restore failed: ".to_string() + &msg });
            let _ = src_db.close();
            return err;
        }
        let mut rc;
        let mut n_timeout = 0;
        loop {
            rc = unsafe { ffi::sqlite3_backup_step(backup, 100) };
            if rc == ffi::SQLITE_OK {
            } else if rc == ffi::SQLITE_BUSY {
                n_timeout += 1;
                if n_timeout >= 3 {
                    break;
                }
                unsafe { ffi::sqlite3_sleep(100) };
            } else {
                break;
            }
        }
        unsafe { ffi::sqlite3_backup_finish(backup) };
        let result = if rc == ffi::SQLITE_DONE {
            Ok(())
        } else if rc == ffi::SQLITE_BUSY || rc == ffi::SQLITE_LOCKED {
            Err(SqliteError{code: rc, message: "restore failed: source database busy".to_string() })
        } else {
            let msg = unsafe { super::errmsg_to_string(ffi::sqlite3_errmsg(dest_db.db())) };
            Err(SqliteError{code: rc, message: "restore failed: ".to_string() + &msg })
        };
        let _ = src_db.close();
        result
    }
}

#[cfg(test)]
mod test {
    use SqliteConnection;
    use {SQLITE_OPEN_URI, SQLITE_OPEN_CREATE, SQLITE_OPEN_READ_WRITE};

    #[test]
    fn test_backup() {
        let db = SqliteConnection::open_in_memory().unwrap();
        let sql = "BEGIN;
                CREATE TABLE foo(x INTEGER);
                INSERT INTO foo VALUES(42);
                END;";
        db.execute_batch(sql).unwrap();
        db.backup("main", &":memory:").unwrap();
        assert!(db.close().is_ok());
    }

    #[test]
    fn test_restore() {
        let src_path = "file:dummy.db?mode=memory&cache=shared";
        let src = SqliteConnection::open_with_flags(&src_path, SQLITE_OPEN_URI | SQLITE_OPEN_CREATE | SQLITE_OPEN_READ_WRITE).unwrap();
        let sql = "BEGIN;
               CREATE TABLE foo(x INTEGER);
               INSERT INTO foo VALUES(42);
               END;";
        src.execute_batch(sql).unwrap();

        let db = SqliteConnection::open_in_memory().unwrap();
        db.restore("main", &src_path).unwrap();

        let the_answer = db.query_row("SELECT x FROM foo",
                                           &[],
                                           |r| r.get::<i64>(0));
        assert_eq!(42i64, the_answer.unwrap());

        assert!(db.close().is_ok());
        assert!(src.close().is_ok());
    }
}
<commit_msg>Fix extra spaces in documentation.<commit_after>//! Online backup API
use std::path::{Path};

use super::ffi;
use {SqliteError, SqliteResult, SqliteConnection};

impl SqliteConnection {
    /// Open or create a database file named `dest_path`. Transfer the
    /// content of local database `name` into the `dest_path` database.
    pub fn backup<P: AsRef<Path>>(&self, name: &str, dest_path: &P) -> SqliteResult<()> {
        let dest = try!(SqliteConnection::open(dest_path)); // "cannot open target database: "

        let mut dest_db = dest.db.borrow_mut();
        let dest_name = try!(super::str_to_cstring("main"));
        let src_db = self.db.borrow_mut();
        let src_name = try!(super::str_to_cstring(name));
        let backup = unsafe { ffi::sqlite3_backup_init(dest_db.db(), dest_name.as_ptr(), src_db.db(), src_name.as_ptr()) };
        if backup.is_null() {
            let msg = unsafe { super::errmsg_to_string(ffi::sqlite3_errmsg(dest_db.db())) };
            let err = Err(SqliteError{code: unsafe { ffi::sqlite3_errcode(dest_db.db()) }, message: "backup failed: ".to_string() + &msg });
            let _ = dest_db.close();
            return err;
        }

        let mut rc;
        loop {
            rc = unsafe { ffi::sqlite3_backup_step(backup, 100) };
            if rc != ffi::SQLITE_OK {
                break;
            }
        }
        unsafe { ffi::sqlite3_backup_finish(backup) };
        let result = if rc == ffi::SQLITE_DONE {
            Ok(())
        } else {
            let msg = unsafe { super::errmsg_to_string(ffi::sqlite3_errmsg(dest_db.db())) };
            Err(SqliteError{code: rc, message: "backup failed: ".to_string() + &msg })
        };
        let _ = dest_db.close();
        result
    }

    /// Open a database file named `src_path`. Transfer the content
    /// of `src_path` into the local database `name`.
    pub fn restore<P: AsRef<Path>>(&self, name: &str, src_path: &P) -> SqliteResult<()> {
        let src = try!(SqliteConnection::open_with_flags(src_path, super::SQLITE_OPEN_READ_ONLY | super::SQLITE_OPEN_URI)); // "cannot open source database: "

        let dest_db = self.db.borrow_mut();
        let dest_name = try!(super::str_to_cstring(name));
        let mut src_db = src.db.borrow_mut();
        let src_name = try!(super::str_to_cstring("main"));
        let backup = unsafe { ffi::sqlite3_backup_init(dest_db.db(), dest_name.as_ptr(), src_db.db(), src_name.as_ptr()) };
        if backup.is_null() {
            let msg = unsafe { super::errmsg_to_string(ffi::sqlite3_errmsg(dest_db.db())) };
            let err = Err(SqliteError{code: unsafe { ffi::sqlite3_errcode(dest_db.db()) }, message: "restore failed: ".to_string() + &msg });
            let _ = src_db.close();
            return err;
        }
        let mut rc;
        let mut n_timeout = 0;
        loop {
            rc = unsafe { ffi::sqlite3_backup_step(backup, 100) };
            if rc == ffi::SQLITE_OK {
            } else if rc == ffi::SQLITE_BUSY {
                n_timeout += 1;
                if n_timeout >= 3 {
                    break;
                }
                unsafe { ffi::sqlite3_sleep(100) };
            } else {
                break;
            }
        }
        unsafe { ffi::sqlite3_backup_finish(backup) };
        let result = if rc == ffi::SQLITE_DONE {
            Ok(())
        } else if rc == ffi::SQLITE_BUSY || rc == ffi::SQLITE_LOCKED {
            Err(SqliteError{code: rc, message: "restore failed: source database busy".to_string() })
        } else {
            let msg = unsafe { super::errmsg_to_string(ffi::sqlite3_errmsg(dest_db.db())) };
            Err(SqliteError{code: rc, message: "restore failed: ".to_string() + &msg })
        };
        let _ = src_db.close();
        result
    }
}

#[cfg(test)]
mod test {
    use SqliteConnection;
    use {SQLITE_OPEN_URI, SQLITE_OPEN_CREATE, SQLITE_OPEN_READ_WRITE};

    #[test]
    fn test_backup() {
        let db = SqliteConnection::open_in_memory().unwrap();
        let sql = "BEGIN;
                CREATE TABLE foo(x INTEGER);
                INSERT INTO foo VALUES(42);
                END;";
        db.execute_batch(sql).unwrap();
        db.backup("main", &":memory:").unwrap();
        assert!(db.close().is_ok());
    }

    #[test]
    fn test_restore() {
        let src_path = "file:dummy.db?mode=memory&cache=shared";
        let src = SqliteConnection::open_with_flags(&src_path, SQLITE_OPEN_URI | SQLITE_OPEN_CREATE | SQLITE_OPEN_READ_WRITE).unwrap();
        let sql = "BEGIN;
               CREATE TABLE foo(x INTEGER);
               INSERT INTO foo VALUES(42);
               END;";
        src.execute_batch(sql).unwrap();

        let db = SqliteConnection::open_in_memory().unwrap();
        db.restore("main", &src_path).unwrap();

        let the_answer = db.query_row("SELECT x FROM foo",
                                           &[],
                                           |r| r.get::<i64>(0));
        assert_eq!(42i64, the_answer.unwrap());

        assert!(db.close().is_ok());
        assert!(src.close().is_ok());
    }
}
<|endoftext|>


<commit_before>/// Éclairement énergétique solaire d'un point, en watts par mètre carré,
/// en fonction de la masse d'air devant être traversée par les rayons du
/// Soleil.
pub fn irradiance(air_mass: f64) -> f64 {
    // `powf` est une contraction de "power" et "float", car on élève
    // un nombre à une puissance réelle, et "float" signifie "nombre
    // à virgule flottante" en anglais.
    1.353 * 0.7f64.powf(air_mass.powf(0.678)) * 1000.0
}

/// Masse d'air devant être traversée par les rayons du Soleil en fonction de
/// l'angle zénithal.
pub fn air_mass(zenith_angle: f64) -> f64 {
    // `assert!` sert à vérifier qu'une condition est remplie.
    // En l'occurrence, la fonction `air_mass` est définie pour x ∈ [0; 90].
    assert!(0.0 <= zenith_angle && zenith_angle <= 90.0);

    // Les fonctions trigonométriques intégrées au langage Rust, ainsi qu'à
    // nombre d'autres langages de programmation, font usage de radians et non
    // de degrés. Il est donc nécessaire d'effectuer une conversion explicite.
    1.0 / zenith_angle.to_radians().cos()
}

/// Angle zénithal en fonction de l'angle d'élévation.
pub fn zenith_angle(elevation_angle: f64) -> f64 {
    assert!(0.0 <= elevation_angle && elevation_angle <= 90.0);
    90.0 - elevation_angle
}

/// Angle d'élévation en fonction de l'angle de déclinaison, de la latitude et
/// de l'angle horaire.
pub fn elevation_angle(declination_angle: f64,
                       latitude: f64,
                       hour_angle: f64)
                       -> f64 {
    assert!(-23.45 <= declination_angle && declination_angle <= 23.45);
    assert!(-90.0 <= latitude && latitude <= 90.0);
    assert!(-180.0 <= hour_angle && hour_angle <= 180.0);
    (declination_angle.to_radians().sin() * latitude.to_radians().sin() +
     declination_angle.to_radians().cos() * latitude.to_radians().cos() *
     hour_angle.to_radians().cos())
        .asin()
        .to_degrees()
}

/// Angle de déclinaison en fonction du jour de l'année.
pub fn declination_angle(day_of_year: u32) -> f64 {
    // Certaines années sont bissextiles et comportent 366 jours.
    assert!(1 <= day_of_year && day_of_year <= 366);

    (23.45f64.to_radians().sin() *
     ((360.0 / 365.0) * (day_of_year - 81) as f64).to_radians().sin())
        .asin()
        .to_degrees()
}

/// Angle horaire, en degrés, en fonction de l'heure solaire.
pub fn hour_angle(solar_time: f64) -> f64 {
    assert!(0.0 <= solar_time && solar_time <= 24.0);
    15.0 * (solar_time - 12.0)
}

/// Heure solaire en fonction de l'heure locale et du facteur de correction de
/// l'heure.
pub fn solar_time(local_time: f64, time_correction_factor: f64) -> f64 {
    local_time + time_correction_factor / 60.0
}

/// Facteur de correction de l'heure, en minutes, en fonction de la longitude
/// du point étudié, de la longitude du méridien standard local et de
/// l'équation du temps.
pub fn time_correction_factor(longitude: f64,
                              local_standard_meridian_longitude: f64,
                              equation_of_time: f64)
                              -> f64 {
    assert!(-15.0 <= equation_of_time && equation_of_time <= 20.0);
    4.0 * (longitude - local_standard_meridian_longitude) + equation_of_time
}

/// Longitude du méridien standard local en fonction du décalage horaire par
/// rapport au fuseau horaire GMT.
pub fn local_standard_meridian_longitude(gmt_offset: f64) -> f64 {
    15.0 * gmt_offset
}

/// Équation du temps en fonction du jour de l'année, en minutes.
pub fn equation_of_time(day_of_year: u32) -> f64 {
    let b = (360.0 / 365.0) * (day_of_year - 81) as f64;
    9.87 * (2.0 * b).to_radians().sin() - 7.53 * b.to_radians().cos() -
    1.5 * b.to_radians().sin()
}
<commit_msg>remove unicode char<commit_after>/// Éclairement énergétique solaire d'un point, en watts par mètre carré,
/// en fonction de la masse d'air devant être traversée par les rayons du
/// Soleil.
pub fn irradiance(air_mass: f64) -> f64 {
    // `powf` est une contraction de "power" et "float", car on élève
    // un nombre à une puissance réelle, et "float" signifie "nombre
    // à virgule flottante" en anglais.
    1.353 * 0.7f64.powf(air_mass.powf(0.678)) * 1000.0
}

/// Masse d'air devant être traversée par les rayons du Soleil en fonction de
/// l'angle zénithal.
pub fn air_mass(zenith_angle: f64) -> f64 {
    // `assert!` sert à vérifier qu'une condition est remplie.
    // En l'occurrence, la fonction `air_mass` est définie pour x
    // appartenant à [0; 90].
    assert!(0.0 <= zenith_angle && zenith_angle <= 90.0);

    // Les fonctions trigonométriques intégrées au langage Rust, ainsi qu'à
    // nombre d'autres langages de programmation, font usage de radians et non
    // de degrés. Il est donc nécessaire d'effectuer une conversion explicite.
    1.0 / zenith_angle.to_radians().cos()
}

/// Angle zénithal en fonction de l'angle d'élévation.
pub fn zenith_angle(elevation_angle: f64) -> f64 {
    assert!(0.0 <= elevation_angle && elevation_angle <= 90.0);
    90.0 - elevation_angle
}

/// Angle d'élévation en fonction de l'angle de déclinaison, de la latitude et
/// de l'angle horaire.
pub fn elevation_angle(declination_angle: f64,
                       latitude: f64,
                       hour_angle: f64)
                       -> f64 {
    assert!(-23.45 <= declination_angle && declination_angle <= 23.45);
    assert!(-90.0 <= latitude && latitude <= 90.0);
    assert!(-180.0 <= hour_angle && hour_angle <= 180.0);
    (declination_angle.to_radians().sin() * latitude.to_radians().sin() +
     declination_angle.to_radians().cos() * latitude.to_radians().cos() *
     hour_angle.to_radians().cos())
        .asin()
        .to_degrees()
}

/// Angle de déclinaison en fonction du jour de l'année.
pub fn declination_angle(day_of_year: u32) -> f64 {
    // Certaines années sont bissextiles et comportent 366 jours.
    assert!(1 <= day_of_year && day_of_year <= 366);

    (23.45f64.to_radians().sin() *
     ((360.0 / 365.0) * (day_of_year - 81) as f64).to_radians().sin())
        .asin()
        .to_degrees()
}

/// Angle horaire, en degrés, en fonction de l'heure solaire.
pub fn hour_angle(solar_time: f64) -> f64 {
    assert!(0.0 <= solar_time && solar_time <= 24.0);
    15.0 * (solar_time - 12.0)
}

/// Heure solaire en fonction de l'heure locale et du facteur de correction de
/// l'heure.
pub fn solar_time(local_time: f64, time_correction_factor: f64) -> f64 {
    local_time + time_correction_factor / 60.0
}

/// Facteur de correction de l'heure, en minutes, en fonction de la longitude
/// du point étudié, de la longitude du méridien standard local et de
/// l'équation du temps.
pub fn time_correction_factor(longitude: f64,
                              local_standard_meridian_longitude: f64,
                              equation_of_time: f64)
                              -> f64 {
    assert!(-15.0 <= equation_of_time && equation_of_time <= 20.0);
    4.0 * (longitude - local_standard_meridian_longitude) + equation_of_time
}

/// Longitude du méridien standard local en fonction du décalage horaire par
/// rapport au fuseau horaire GMT.
pub fn local_standard_meridian_longitude(gmt_offset: f64) -> f64 {
    15.0 * gmt_offset
}

/// Équation du temps en fonction du jour de l'année, en minutes.
pub fn equation_of_time(day_of_year: u32) -> f64 {
    let b = (360.0 / 365.0) * (day_of_year - 81) as f64;
    9.87 * (2.0 * b).to_radians().sin() - 7.53 * b.to_radians().cos() -
    1.5 * b.to_radians().sin()
}
<|endoftext|>


<commit_before>#![deny(warnings)]

extern crate extra;

use std::env;
use std::fs;
use std::path::Path;
use std::io::{stdout, stderr, Stderr, Write};
use std::os::unix::fs::MetadataExt;
use extra::option::OptionalExt;
use std::process::exit;

const MAN_PAGE: &'static str = /* @MANSTART{ls} */ r#"
NAME
    ls - list directory contents

SYNOPSIS
    ls [ -h | --help | -l ][FILE]...

DESCRIPTION
    List information about the FILE(s), or the current directory

OPTIONS
    -h
        with -l, print human readable sizes
    --help
        display this help and exit
    -l
        use a long listing format
"#; /* @MANEND */

fn list_dir(path: &str, long_format: bool, human_readable: bool, string: &mut String, stderr: &mut Stderr) {
    let metadata = fs::metadata(path).try(stderr);
    if metadata.is_dir() {
        let read_dir = Path::new(path).read_dir().try(stderr);

        let mut entries: Vec<String> = read_dir.filter_map(|x| x.ok()).map(|dir| {
                let mut file_name = dir.file_name().to_string_lossy().into_owned();
                if dir.file_type().try(stderr).is_dir() {
                    file_name.push('/');
                }
                file_name
            })
            .collect();

        entries.sort();

        for entry in entries.iter() {
            if long_format {
                let mut entry_path = path.to_owned();
                if !entry_path.ends_with('/') {
                    entry_path.push('/');
                }
                entry_path.push_str(&entry);

                let metadata = fs::metadata(entry_path).try(stderr);
                if human_readable {
                    string.push_str(&format!("{:>7o} {:>5} {:>5} {} ",
                                             metadata.mode(),
                                             metadata.uid(),
                                             metadata.gid(),
                                             to_human_readable_string(metadata.size())));
                } else {
                    string.push_str(&format!("{:>7o} {:>5} {:>5} {:>8} ",
                                             metadata.mode(),
                                             metadata.uid(),
                                             metadata.gid(),
                                             metadata.size()));
                }
            }
            string.push_str(entry);
            string.push('\n');
        }
    } else {
        if long_format {
            if human_readable {
                string.push_str(&format!("{:>7o} {:>5} {:>5} {} ",
                                         metadata.mode(),
                                         metadata.uid(),
                                         metadata.gid(),
                                         to_human_readable_string(metadata.size())));
            } else {
                string.push_str(&format!("{:>7o} {:>5} {:>5} {:>8} ",
                                         metadata.mode(),
                                         metadata.uid(),
                                         metadata.gid(),
                                         metadata.size()));
            }
        }
        string.push_str(path);
        string.push('\n');
    }
}

fn to_human_readable_string(size: u64) -> String {
    if size < 1024 {
        return format!("{}", size);
    }

    static UNITS: [&'static str; 7] = ["", "K", "M", "G", "T", "P", "E"];

    let digit_groups = ((64 - size.leading_zeros()) / 10) as i32;
    format!("{:.1}{}",
            size as f64 / 1024f64.powi(digit_groups),
            UNITS[digit_groups as usize])
}

fn main() {
    let stdout = stdout();
    let mut stdout = stdout.lock();
    let mut stderr = stderr();

    let mut long_format = false;
    let mut human_readable = false;
    let mut args = Vec::new();
    for arg in env::args().skip(1) {
        if arg.as_str() == "--help" {
            stdout.write(MAN_PAGE.as_bytes()).try(&mut stderr);
            stdout.flush().try(&mut stderr);
            exit(0);
        } else if arg.as_str() == "-l" {
            long_format = true;
        } else if arg.as_str() == "-h" {
            human_readable = true;
        } else {
            args.push(arg);
        }
    }

    let mut string = String::new();
    if args.is_empty() {
        list_dir(".", long_format, human_readable, &mut string, &mut stderr);
    } else {
        for dir in args {
            list_dir(&dir, long_format, human_readable, &mut string, &mut stderr);
        }
    }
    stdout.write(string.as_bytes()).try(&mut stderr);
}

#[test]
fn test_human_readable() {
    assert_eq!(to_human_readable_string(0), "0");
    assert_eq!(to_human_readable_string(1023), "1023");
    assert_eq!(to_human_readable_string(1024), "1.0K");
    assert_eq!(to_human_readable_string(1024 + 100), "1.1K");
    assert_eq!(to_human_readable_string(1024u64.pow(2) * 2), "2.0M");
    assert_eq!(to_human_readable_string(1024u64.pow(3) * 3), "3.0G");
    assert_eq!(to_human_readable_string(1024u64.pow(4) * 4), "4.0T");
    assert_eq!(to_human_readable_string(1024u64.pow(5) * 5), "5.0P");
    assert_eq!(to_human_readable_string(1024u64.pow(6) * 6), "6.0E");
}
<commit_msg>openlibm doesn't have powidf2<commit_after>#![deny(warnings)]

extern crate extra;

use std::env;
use std::fs;
use std::path::Path;
use std::io::{stdout, stderr, Stderr, Write};
use std::os::unix::fs::MetadataExt;
use extra::option::OptionalExt;
use std::process::exit;

const MAN_PAGE: &'static str = /* @MANSTART{ls} */ r#"
NAME
    ls - list directory contents

SYNOPSIS
    ls [ -h | --help | -l ][FILE]...

DESCRIPTION
    List information about the FILE(s), or the current directory

OPTIONS
    -h
        with -l, print human readable sizes
    --help
        display this help and exit
    -l
        use a long listing format
"#; /* @MANEND */

fn list_dir(path: &str, long_format: bool, human_readable: bool, string: &mut String, stderr: &mut Stderr) {
    let metadata = fs::metadata(path).try(stderr);
    if metadata.is_dir() {
        let read_dir = Path::new(path).read_dir().try(stderr);

        let mut entries: Vec<String> = read_dir.filter_map(|x| x.ok()).map(|dir| {
                let mut file_name = dir.file_name().to_string_lossy().into_owned();
                if dir.file_type().try(stderr).is_dir() {
                    file_name.push('/');
                }
                file_name
            })
            .collect();

        entries.sort();

        for entry in entries.iter() {
            if long_format {
                let mut entry_path = path.to_owned();
                if !entry_path.ends_with('/') {
                    entry_path.push('/');
                }
                entry_path.push_str(&entry);

                let metadata = fs::metadata(entry_path).try(stderr);
                if human_readable {
                    string.push_str(&format!("{:>7o} {:>5} {:>5} {} ",
                                             metadata.mode(),
                                             metadata.uid(),
                                             metadata.gid(),
                                             to_human_readable_string(metadata.size())));
                } else {
                    string.push_str(&format!("{:>7o} {:>5} {:>5} {:>8} ",
                                             metadata.mode(),
                                             metadata.uid(),
                                             metadata.gid(),
                                             metadata.size()));
                }
            }
            string.push_str(entry);
            string.push('\n');
        }
    } else {
        if long_format {
            if human_readable {
                string.push_str(&format!("{:>7o} {:>5} {:>5} {} ",
                                         metadata.mode(),
                                         metadata.uid(),
                                         metadata.gid(),
                                         to_human_readable_string(metadata.size())));
            } else {
                string.push_str(&format!("{:>7o} {:>5} {:>5} {:>8} ",
                                         metadata.mode(),
                                         metadata.uid(),
                                         metadata.gid(),
                                         metadata.size()));
            }
        }
        string.push_str(path);
        string.push('\n');
    }
}

fn to_human_readable_string(size: u64) -> String {
    if size < 1024 {
        return format!("{}", size);
    }

    static UNITS: [&'static str; 7] = ["", "K", "M", "G", "T", "P", "E"];

    let digit_groups = ((64 - size.leading_zeros()) / 10) as i32;
    format!("{:.1}{}",
            size as f64 / 1024f64.powf(digit_groups as f64),
            UNITS[digit_groups as usize])
}

fn main() {
    let stdout = stdout();
    let mut stdout = stdout.lock();
    let mut stderr = stderr();

    let mut long_format = false;
    let mut human_readable = false;
    let mut args = Vec::new();
    for arg in env::args().skip(1) {
        if arg.as_str() == "--help" {
            stdout.write(MAN_PAGE.as_bytes()).try(&mut stderr);
            stdout.flush().try(&mut stderr);
            exit(0);
        } else if arg.as_str() == "-l" {
            long_format = true;
        } else if arg.as_str() == "-h" {
            human_readable = true;
        } else {
            args.push(arg);
        }
    }

    let mut string = String::new();
    if args.is_empty() {
        list_dir(".", long_format, human_readable, &mut string, &mut stderr);
    } else {
        for dir in args {
            list_dir(&dir, long_format, human_readable, &mut string, &mut stderr);
        }
    }
    stdout.write(string.as_bytes()).try(&mut stderr);
}

#[test]
fn test_human_readable() {
    assert_eq!(to_human_readable_string(0), "0");
    assert_eq!(to_human_readable_string(1023), "1023");
    assert_eq!(to_human_readable_string(1024), "1.0K");
    assert_eq!(to_human_readable_string(1024 + 100), "1.1K");
    assert_eq!(to_human_readable_string(1024u64.pow(2) * 2), "2.0M");
    assert_eq!(to_human_readable_string(1024u64.pow(3) * 3), "3.0G");
    assert_eq!(to_human_readable_string(1024u64.pow(4) * 4), "4.0T");
    assert_eq!(to_human_readable_string(1024u64.pow(5) * 5), "5.0P");
    assert_eq!(to_human_readable_string(1024u64.pow(6) * 6), "6.0E");
}
<|endoftext|>