<commit_before>//! # urlshortener
//!
//! An easy library for retrieving short urls.
//!
//! ## Installation
//!
//! Add the following dependency to your project's `Cargo.toml`:
//!
//! ```toml
//! [dependencies]
//! urlshortener = "0.4"
//! ```
//!
//! And add this to your root file:
//!
//! ```no_run
//! extern crate urlshortener;
//! ```
//!
//! ## Usage
//!
//! Creating a short URL via a specified provider is very simple:
//!
//! ```no_run
//! use urlshortener::{Provider, UrlShortener};
//!
//! let us = UrlShortener::new();
//! let short_url = us.generate("https://my-long-url.com", Provider::IsGd);
//! assert!(short_url.is_ok());
//! ```
//!
//! Or attempting all URL shorteners until one is successfully generated:
//!
//! ```no_run
//! use urlshortener::UrlShortener;
//!
//! let us = UrlShortener::new();
//! let short_url = us.try_generate("https://my-long-url.com");
//! assert!(short_url.is_ok());
//! ```

#[macro_use]
extern crate log;
extern crate hyper;

pub mod providers;

pub use providers::{Provider, providers};

use providers::{parse, request};
use hyper::Client;
use std::io::{ErrorKind, Error, Read};
use std::time::Duration;

/// Url shortener - the way to retrieve a short url.
pub struct UrlShortener {
    client: Client,
}

impl UrlShortener {
    /// Creates new `UrlShortener` with default (3 seconds) timeout.
    pub fn new() -> UrlShortener {
        UrlShortener::with_timeout(3)
    }

    /// Creates new `UrlShortener` with custom read timeout.
    pub fn with_timeout(seconds: u64) -> UrlShortener {
        let mut client = hyper::Client::new();
        client.set_read_timeout(Some(Duration::from_secs(seconds)));

        UrlShortener {
            client: client,
        }
    }

    /// Try to generate a short URL from each provider, iterating over each
    /// provider until a short URL is successfully generated.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use urlshortener::UrlShortener;
    ///
    /// let us = UrlShortener::new();
    /// let long_url = "https://rust-lang.org";
    /// let _short_url = us.try_generate(long_url);
    /// ```
    ///
    /// # Errors
    ///
    /// Returns an `Error<ErrorKind::Other>` if there is an error generating a
    /// short URL from all providers.
    pub fn try_generate<S: Into<String>>(&self,
                                         url: S) -> Result<String, Error> {
        let url = &url.into()[..];
        let mut providers = providers();

        let x = 0usize;

        loop {
            if providers.len() == 0 {
                break
            }

            // This would normally have the potential to panic, except that a
            // check to ensure there is an element at this index is performed.
            let provider = providers.remove(x);
            let res = self.generate(url, provider);

            if let Ok(s) = res {
                return Ok(s)
            } else {
                warn!("Failed to get short link from service: {}",
                      res.unwrap_err());
            }
        }
        error!("Failed to get short link from any service");
        Err(Error::new(ErrorKind::Other, "Failed to get short link from any service"))
    }

    /// Attempts to get a short URL using the specified provider.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use urlshortener::{Provider, UrlShortener};
    ///
    /// let us = UrlShortener::new();
    /// let long_url = "http://rust-lang.org";
    /// let short_url = us.generate(long_url, Provider::IsGd);
    /// ```
    ///
    /// # Errors
    ///
    /// Returns an `std::io::Error` if there is an error generating a
    /// short URL from the given provider due to either:
    ///
    /// a. a decode error (ErrorKind::Other);
    /// b. the service being unavailable (ErrorKind::ConnectionAborted)
    pub fn generate<S: Into<String>>(&self,
                                     url: S,
                                     provider: Provider) -> Result<String, Error> {
        let response_opt = request(&url.into(), &self.client, provider);

        if let Some(mut response) = response_opt {
            if response.status.is_success() {
                let mut short_url = String::new();
                if try!(response.read_to_string(&mut short_url)) > 0 {
                    if let Some(s) = parse(&short_url, provider) {
                        return Ok(s)
                    } else {
                        return Err(Error::new(ErrorKind::Other, "Decode error"))
                    }
                }
            }
        }
        Err(Error::new(ErrorKind::ConnectionAborted, "Service is unavailable"))
    }
}

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

    #[test]
    fn providers() {
        let us = ::UrlShortener::with_timeout(5);
        let url = "http://stackoverflow.com";

        for provider in ::providers() {
            if let Some(err) = us.generate(url, provider).err() {
                assert!(err.kind() == ErrorKind::ConnectionAborted);
            }
        }
    }
}
<commit_msg>Don't publicly export the providers mod<commit_after>//! # urlshortener
//!
//! An easy library for retrieving short urls.
//!
//! ## Installation
//!
//! Add the following dependency to your project's `Cargo.toml`:
//!
//! ```toml
//! [dependencies]
//! urlshortener = "0.4"
//! ```
//!
//! And add this to your root file:
//!
//! ```no_run
//! extern crate urlshortener;
//! ```
//!
//! ## Usage
//!
//! Creating a short URL via a specified provider is very simple:
//!
//! ```no_run
//! use urlshortener::{Provider, UrlShortener};
//!
//! let us = UrlShortener::new();
//! let short_url = us.generate("https://my-long-url.com", Provider::IsGd);
//! assert!(short_url.is_ok());
//! ```
//!
//! Or attempting all URL shorteners until one is successfully generated:
//!
//! ```no_run
//! use urlshortener::UrlShortener;
//!
//! let us = UrlShortener::new();
//! let short_url = us.try_generate("https://my-long-url.com");
//! assert!(short_url.is_ok());
//! ```

#[macro_use]
extern crate log;
extern crate hyper;

mod providers;

pub use providers::{Provider, providers};

use providers::{parse, request};
use hyper::Client;
use std::io::{ErrorKind, Error, Read};
use std::time::Duration;

/// Url shortener - the way to retrieve a short url.
pub struct UrlShortener {
    client: Client,
}

impl UrlShortener {
    /// Creates new `UrlShortener` with default (3 seconds) timeout.
    pub fn new() -> UrlShortener {
        UrlShortener::with_timeout(3)
    }

    /// Creates new `UrlShortener` with custom read timeout.
    pub fn with_timeout(seconds: u64) -> UrlShortener {
        let mut client = hyper::Client::new();
        client.set_read_timeout(Some(Duration::from_secs(seconds)));

        UrlShortener {
            client: client,
        }
    }

    /// Try to generate a short URL from each provider, iterating over each
    /// provider until a short URL is successfully generated.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use urlshortener::UrlShortener;
    ///
    /// let us = UrlShortener::new();
    /// let long_url = "https://rust-lang.org";
    /// let _short_url = us.try_generate(long_url);
    /// ```
    ///
    /// # Errors
    ///
    /// Returns an `Error<ErrorKind::Other>` if there is an error generating a
    /// short URL from all providers.
    pub fn try_generate<S: Into<String>>(&self,
                                         url: S) -> Result<String, Error> {
        let url = &url.into()[..];
        let mut providers = providers();

        let x = 0usize;

        loop {
            if providers.len() == 0 {
                break
            }

            // This would normally have the potential to panic, except that a
            // check to ensure there is an element at this index is performed.
            let provider = providers.remove(x);
            let res = self.generate(url, provider);

            if let Ok(s) = res {
                return Ok(s)
            } else {
                warn!("Failed to get short link from service: {}",
                      res.unwrap_err());
            }
        }
        error!("Failed to get short link from any service");
        Err(Error::new(ErrorKind::Other, "Failed to get short link from any service"))
    }

    /// Attempts to get a short URL using the specified provider.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use urlshortener::{Provider, UrlShortener};
    ///
    /// let us = UrlShortener::new();
    /// let long_url = "http://rust-lang.org";
    /// let short_url = us.generate(long_url, Provider::IsGd);
    /// ```
    ///
    /// # Errors
    ///
    /// Returns an `std::io::Error` if there is an error generating a
    /// short URL from the given provider due to either:
    ///
    /// a. a decode error (ErrorKind::Other);
    /// b. the service being unavailable (ErrorKind::ConnectionAborted)
    pub fn generate<S: Into<String>>(&self,
                                     url: S,
                                     provider: Provider) -> Result<String, Error> {
        let response_opt = request(&url.into(), &self.client, provider);

        if let Some(mut response) = response_opt {
            if response.status.is_success() {
                let mut short_url = String::new();
                if try!(response.read_to_string(&mut short_url)) > 0 {
                    if let Some(s) = parse(&short_url, provider) {
                        return Ok(s)
                    } else {
                        return Err(Error::new(ErrorKind::Other, "Decode error"))
                    }
                }
            }
        }
        Err(Error::new(ErrorKind::ConnectionAborted, "Service is unavailable"))
    }
}

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

    #[test]
    fn providers() {
        let us = ::UrlShortener::with_timeout(5);
        let url = "http://stackoverflow.com";

        for provider in ::providers() {
            if let Some(err) = us.generate(url, provider).err() {
                assert!(err.kind() == ErrorKind::ConnectionAborted);
            }
        }
    }
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate log;

use std::io::{self, Read, Write};
use std::ops::Add;
use std::convert::From;

const SOH: u8 = 0x01;
const STX: u8 = 0x02;
const EOT: u8 = 0x04;
const ACK: u8 = 0x06;
const DLE: u8 = 0x10;
const NAK: u8 = 0x15;
const CAN: u8 = 0x18;
const CRC: u8 = 0x67;

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

pub enum Error {
    Io(io::Error),
    ExhaustedRetries,
    Canceled,
}

impl From<io::Error> for Error {
    fn from(err: io::Error) -> Error {
        Error::Io(err)
    }
}

enum Checksum {
    Standard,
    CRC16,
}

pub fn xmodem_send<D: Read + Write, R: Read>(dev: &mut D, stream: &mut R, max_retries: u32) -> Result<()> {
    debug!("Starting XMODEM transfer");
    let checksum_mode = try!(xmodem_send_start(dev, max_retries));
    debug!("First byte received. Sending stream.");
    try!(xmodem_send_stream(dev, stream, max_retries));
    debug!("Sending EOT");
    try!(xmodem_finish_send(dev, max_retries));

    Ok(())
}

fn xmodem_send_start<D: Read + Write>(dev: &mut D, max_retries: u32) -> Result<Checksum> {
    let mut errors = 0u32;
    let mut cancels = 0u32;
    loop {
        match get_byte(dev) {
            Ok(c) => {
                match c {
                    NAK => {
                        debug!("Standard checksum requested");
                        return Ok(Checksum::Standard);
                    }
                    CRC => {
                        debug!("16-bit CRC requested");
                        return Ok(Checksum::CRC16);
                    }
                    CAN => {
                        warn!("Cancel (CAN) byte received");
                        cancels += 1;
                    },
                    c => warn!("Unknown byte received at start of XMODEM transfer: {}", c),
                }
            },
            Err(err) => {
                if err.kind() == io::ErrorKind::TimedOut {
                    warn!("Timed out waiting for start of XMODEM transfer.");
                } else {
                    return Err(From::from(err));
                }
            },
        }

        errors += 1;

        if cancels >= 2 {
            error!("Transmission canceled: received two cancel (CAN) bytes \
                    at start of XMODEM transfer");
            return Err(Error::Canceled);
        }

        if errors >= max_retries {
            error!("Exhausted max retries ({}) at start of XMODEM transfer.", max_retries);
            // TODO send CAN in this case.
            return Err(Error::ExhaustedRetries);
        }
    }
}

fn xmodem_send_stream<D: Read + Write, R: Read>(dev: &mut D, stream: &mut R, max_retries: u32) -> Result<()> {
    let mut errors = 0u32;
    let mut block_num = 0u32;
    let pad = 0x1a;
    let block_size = 128;
    loop {
        let mut buff = vec![pad; block_size + 3];
        let n = try!(stream.read(&mut buff[3..]));
        if n == 0 {
            debug!("Reached EOF");
            return Ok(());
        }

        block_num += 1;
        buff[0] = SOH;
        buff[1] = (block_num & 0xFF) as u8;
        buff[2] = 0xFF - buff[1];
        // TODO support 16-bit CRC.
        let checksum = calc_checksum(&buff);
        buff.push(checksum);

        debug!("Sending block {}", block_num);
        try!(dev.write_all(&buff));

        match get_byte(dev) {
            Ok(c) => {
                if c == ACK {
                    debug!("Received ACK for block {}", block_num);
                    continue
                } else {
                    warn!("Expected ACK, got {}", c);
                }
                // TODO handle CAN bytes
            },
            Err(err) => {
                if err.kind() == io::ErrorKind::TimedOut {
                    warn!("Timeout waiting for ACK for block {}", block_num);
                } else {
                    return Err(From::from(err));
                }
            },
        }

        errors += 1;

        if errors >= max_retries {
            error!("Exhausted max retries ({}) while sending block {} in XMODEM transfer",
                   max_retries, block_num);
            return Err(Error::ExhaustedRetries);
        }
    }
}

fn xmodem_finish_send<D: Read + Write>(dev: &mut D, max_retries: u32) -> Result<()> {
    let mut errors = 0u32;
    loop {
        try!(dev.write_all(&[EOT]));

        match get_byte(dev) {
            Ok(c) => {
                if c == ACK {
                    info!("XMODEM transmission successful");
                    return Ok(());
                } else {
                    warn!("Expected ACK, got {}", c);
                }
            },
            Err(err) => {
                if err.kind() == io::ErrorKind::TimedOut {
                    warn!("Timeout waiting for ACK for EOT");
                } else {
                    return Err(From::from(err));
                }
            },
        }

        errors += 1;

        if errors >= max_retries {
            error!("Exhausted max retries ({}) while waiting for ACK for EOT", max_retries);
            return Err(Error::ExhaustedRetries);
        }
    }
}

fn calc_checksum(data: &[u8]) -> u8 {
    data.iter().fold(0, Add::add)
}

fn get_byte<R: Read>(reader: &mut R) -> std::io::Result<u8> {
    let mut buff = [0];
    try!(reader.read_exact(&mut buff));
    Ok(buff[0])
}
<commit_msg>Moved XMODEM send logic into struct.<commit_after>#[macro_use]
extern crate log;

use std::io::{self, Read, Write};
use std::ops::Add;
use std::convert::From;

// TODO: Send CAN byte after too many errors
// TODO: Handle CAN bytes while sending
// TODO: Use CRC-16.
// TODO pull config into struct with builder pattern

const SOH: u8 = 0x01;
const STX: u8 = 0x02;
const EOT: u8 = 0x04;
const ACK: u8 = 0x06;
const DLE: u8 = 0x10;
const NAK: u8 = 0x15;
const CAN: u8 = 0x18;
const CRC: u8 = 0x67;

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

pub enum Error {
    Io(io::Error),
    ExhaustedRetries,
    Canceled,
}

impl From<io::Error> for Error {
    fn from(err: io::Error) -> Error {
        Error::Io(err)
    }
}

#[derive(Copy, Clone, Debug)]
enum Checksum {
    Standard,
    CRC16,
}

#[derive(Copy, Clone, Debug)]
pub enum BlockLength {
    Standard = 128,
    OneK = 1024,
}

pub struct Xmodem {
    max_errors: u32,
    errors: u32,
    pad_byte: u8,
    block_length: BlockLength,
    checksum_mode: Checksum,
}

impl Xmodem {
    pub fn new() -> Self {
        Xmodem {
            max_errors: 16,
            errors: 0,
            pad_byte: 0x1a,
            block_length: BlockLength::Standard,
            checksum_mode: Checksum::Standard,
        }
    }

    pub fn block_length<'a>(&'a mut self, block_length: BlockLength) -> &'a mut Self {
        self.block_length = block_length;
        self
    }

    pub fn max_errors<'a>(&'a mut self, max_errors: u32) -> &'a mut Self {
        self.max_errors = max_errors;
        self
    }

    pub fn pad_byte<'a>(&'a mut self, pad_byte: u8) -> &'a mut Self {
        self.pad_byte = pad_byte;
        self
    }

    pub fn send<D: Read + Write, R: Read>(&mut self, dev: &mut D, stream: &mut R) -> Result<()> {
        self.errors = 0;

        debug!("Starting XMODEM transfer");
        try!(self.start_send(dev));
        debug!("First byte received. Sending stream.");
        try!(self.send_stream(dev, stream));
        debug!("Sending EOT");
        try!(self.finish_send(dev));

        Ok(())
    }

    pub fn start_send<D: Read + Write>(&mut self, dev: &mut D) -> Result<()> {
        let mut cancels = 0u32;
        loop {
            match get_byte(dev) {
                Ok(c) => {
                    match c {
                        NAK => {
                            debug!("Standard checksum requested");
                            self.checksum_mode = Checksum::Standard;
                            return Ok(());
                        }
                        CRC => {
                            debug!("16-bit CRC requested");
                            self.checksum_mode = Checksum::CRC16;
                            return Ok(());
                        }
                        CAN => {
                            warn!("Cancel (CAN) byte received");
                            cancels += 1;
                        },
                        c => warn!("Unknown byte received at start of XMODEM transfer: {}", c),
                    }
                },
                Err(err) => {
                    if err.kind() == io::ErrorKind::TimedOut {
                        warn!("Timed out waiting for start of XMODEM transfer.");
                    } else {
                        return Err(From::from(err));
                    }
                },
            }

            self.errors += 1;

            if cancels >= 2 {
                error!("Transmission canceled: received two cancel (CAN) bytes \
                        at start of XMODEM transfer");
                return Err(Error::Canceled);
            }

            if self.errors >= self.max_errors {
                error!("Exhausted max retries ({}) at start of XMODEM transfer.", self.max_errors);
                if let Err(err) = dev.write_all(&[CAN]) {
                    warn!("Error sending CAN byte: {}", err);
                }
                return Err(Error::ExhaustedRetries);
            }
        }
    }

    fn send_stream<D: Read + Write, R: Read>(&mut self, dev: &mut D, stream: &mut R) -> Result<()> {
        let mut block_num = 0u32;
        loop {
            let mut buff = vec![self.pad_byte; self.block_length as usize + 3];
            let n = try!(stream.read(&mut buff[3..]));
            if n == 0 {
                debug!("Reached EOF");
                return Ok(());
            }

            block_num += 1;
            buff[0] = SOH;
            buff[1] = (block_num & 0xFF) as u8;
            buff[2] = 0xFF - buff[1];
            // TODO support 16-bit CRC.
            let checksum = calc_checksum(&buff);
            buff.push(checksum);

            debug!("Sending block {}", block_num);
            try!(dev.write_all(&buff));

            match get_byte(dev) {
                Ok(c) => {
                    if c == ACK {
                        debug!("Received ACK for block {}", block_num);
                        continue
                    } else {
                        warn!("Expected ACK, got {}", c);
                    }
                    // TODO handle CAN bytes
                },
                Err(err) => {
                    if err.kind() == io::ErrorKind::TimedOut {
                        warn!("Timeout waiting for ACK for block {}", block_num);
                    } else {
                        return Err(From::from(err));
                    }
                },
            }

            self.errors += 1;

            if self.errors >= self.max_errors {
                error!("Exhausted max retries ({}) while sending block {} in XMODEM transfer",
                       self.max_errors, block_num);
                return Err(Error::ExhaustedRetries);
            }
        }
    }

    fn finish_send<D: Read + Write>(&mut self, dev: &mut D) -> Result<()> {
        loop {
            try!(dev.write_all(&[EOT]));

            match get_byte(dev) {
                Ok(c) => {
                    if c == ACK {
                        info!("XMODEM transmission successful");
                        return Ok(());
                    } else {
                        warn!("Expected ACK, got {}", c);
                    }
                },
                Err(err) => {
                    if err.kind() == io::ErrorKind::TimedOut {
                        warn!("Timeout waiting for ACK for EOT");
                    } else {
                        return Err(From::from(err));
                    }
                },
            }

            self.errors += 1;

            if self.errors >= self.max_errors {
                error!("Exhausted max retries ({}) while waiting for ACK for EOT", self.max_errors);
                return Err(Error::ExhaustedRetries);
            }
        }
    }
}

fn calc_checksum(data: &[u8]) -> u8 {
    data.iter().fold(0, Add::add)
}

fn get_byte<R: Read>(reader: &mut R) -> std::io::Result<u8> {
    let mut buff = [0];
    try!(reader.read_exact(&mut buff));
    Ok(buff[0])
}
<|endoftext|>