<commit_before>use std::env;
use std::fs;
use std::io::{Error as IoError, Read, Write};
use std::path::PathBuf;
use std::time::Duration;

use ansi_term::{Color, Style};
use app_dirs::{get_app_root, AppDataType};
use log::debug;
use serde_derive::{Deserialize, Serialize};

use crate::error::TealdeerError::{self, ConfigError};
use crate::types::PathSource;

pub const CONFIG_FILE_NAME: &str = "config.toml";
pub const MAX_CACHE_AGE: Duration = Duration::from_secs(2_592_000); // 30 days
const DEFAULT_UPDATE_INTERVAL_HOURS: u64 = MAX_CACHE_AGE.as_secs() / 3600; // 30 days

fn default_underline() -> bool {
    false
}

fn default_bold() -> bool {
    false
}

#[serde(rename_all = "lowercase")]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum RawColor {
    Black,
    Red,
    Green,
    Yellow,
    Blue,
    Purple,
    Cyan,
    White,
    Ansi(u8),
    Rgb { r: u8, g: u8, b: u8 },
}

impl From<RawColor> for Color {
    fn from(raw_color: RawColor) -> Self {
        match raw_color {
            RawColor::Black => Self::Black,
            RawColor::Red => Self::Red,
            RawColor::Green => Self::Green,
            RawColor::Yellow => Self::Yellow,
            RawColor::Blue => Self::Blue,
            RawColor::Purple => Self::Purple,
            RawColor::Cyan => Self::Cyan,
            RawColor::White => Self::White,
            RawColor::Ansi(num) => Self::Fixed(num),
            RawColor::Rgb { r, g, b } => Self::RGB(r, g, b),
        }
    }
}

#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
struct RawStyle {
    pub foreground: Option<RawColor>,
    pub background: Option<RawColor>,
    #[serde(default = "default_underline")]
    pub underline: bool,
    #[serde(default = "default_bold")]
    pub bold: bool,
}

impl Default for RawStyle {
    fn default() -> Self {
        Self {
            foreground: None,
            background: None,
            underline: false,
            bold: false,
        }
    }
} // impl RawStyle

impl From<RawStyle> for Style {
    fn from(raw_style: RawStyle) -> Self {
        let mut style = Self::default();

        if let Some(foreground) = raw_style.foreground {
            style = style.fg(Color::from(foreground));
        }

        if let Some(background) = raw_style.background {
            style = style.on(Color::from(background));
        }

        if raw_style.underline {
            style = style.underline();
        }

        if raw_style.bold {
            style = style.bold();
        }

        style
    }
}

#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
struct RawStyleConfig {
    #[serde(default)]
    pub description: RawStyle,
    #[serde(default)]
    pub command_name: RawStyle,
    #[serde(default)]
    pub example_text: RawStyle,
    #[serde(default)]
    pub example_code: RawStyle,
    #[serde(default)]
    pub example_variable: RawStyle,
}

#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
struct RawDisplayConfig {
    #[serde(default)]
    pub compact: bool,
    #[serde(default)]
    pub use_pager: bool,
}

/// Serde doesn't support default values yet (tracking issue:
/// <https://github.com/serde-rs/serde/issues/368>), so we need to wrap
/// `DEFAULT_UPDATE_INTERVAL_HOURS` in a function to be able to use
/// `#[serde(default = ...)]`
const fn default_auto_update_interval_hours() -> u64 {
    DEFAULT_UPDATE_INTERVAL_HOURS
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
struct RawUpdatesConfig {
    #[serde(default)]
    pub auto_update: bool,
    #[serde(default = "default_auto_update_interval_hours")]
    pub auto_update_interval_hours: u64,
}

impl Default for RawUpdatesConfig {
    fn default() -> Self {
        Self {
            auto_update: false,
            auto_update_interval_hours: DEFAULT_UPDATE_INTERVAL_HOURS,
        }
    }
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
struct RawDirectoriesConfig {
    #[serde(default)]
    pub custom_pages_dir: Option<PathBuf>,
}

impl Default for RawDirectoriesConfig {
    fn default() -> Self {
        Self {
            custom_pages_dir: get_app_root(AppDataType::UserData, &crate::APP_INFO)
                .map(|path| path.join("pages"))
                .ok(),
        }
    }
}

#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
struct RawConfig {
    #[serde(default)]
    style: RawStyleConfig,
    #[serde(default)]
    display: RawDisplayConfig,
    #[serde(default)]
    updates: RawUpdatesConfig,
    #[serde(default)]
    directories: RawDirectoriesConfig,
}

impl RawConfig {
    fn new() -> Self {
        let mut raw_config = Self::default();

        // Set default config
        raw_config.style.example_text.foreground = Some(RawColor::Green);
        raw_config.style.command_name.foreground = Some(RawColor::Cyan);
        raw_config.style.example_code.foreground = Some(RawColor::Cyan);
        raw_config.style.example_variable.foreground = Some(RawColor::Cyan);
        raw_config.style.example_variable.underline = true;

        raw_config
    }
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct StyleConfig {
    pub description: Style,
    pub command_name: Style,
    pub example_text: Style,
    pub example_code: Style,
    pub example_variable: Style,
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct DisplayConfig {
    pub compact: bool,
    pub use_pager: bool,
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct UpdatesConfig {
    pub auto_update: bool,
    pub auto_update_interval: Duration,
}

#[derive(Clone, Debug, PartialEq)]
pub struct DirectoriesConfig {
    pub custom_pages_dir: Option<PathBuf>,
}

#[derive(Clone, Debug, PartialEq)]
pub struct Config {
    pub style: StyleConfig,
    pub display: DisplayConfig,
    pub updates: UpdatesConfig,
    pub directories: DirectoriesConfig,
}

impl From<RawConfig> for Config {
    fn from(raw_config: RawConfig) -> Self {
        Self {
            style: StyleConfig {
                command_name: raw_config.style.command_name.into(),
                description: raw_config.style.description.into(),
                example_text: raw_config.style.example_text.into(),
                example_code: raw_config.style.example_code.into(),
                example_variable: raw_config.style.example_variable.into(),
            },
            display: DisplayConfig {
                compact: raw_config.display.compact,
                use_pager: raw_config.display.use_pager,
            },
            updates: UpdatesConfig {
                auto_update: raw_config.updates.auto_update,
                auto_update_interval: Duration::from_secs(
                    raw_config.updates.auto_update_interval_hours * 3600,
                ),
            },
            directories: DirectoriesConfig {
                custom_pages_dir: raw_config.directories.custom_pages_dir,
            },
        }
    }
}

#[allow(clippy::needless_pass_by_value)]
fn map_io_err_to_config_err(e: IoError) -> TealdeerError {
    ConfigError(format!("Io Error: {}", e))
}

impl Config {
    pub fn load(enable_styles: bool) -> Result<Self, TealdeerError> {
        debug!("Loading config");

        // Determine path
        let (config_file_path, _) = get_config_path()
            .map_err(|e| ConfigError(format!("Could not determine config path: {}", e)))?;

        // Load raw config
        let raw_config: RawConfig = if config_file_path.exists() && config_file_path.is_file() {
            let mut config_file =
                fs::File::open(config_file_path).map_err(map_io_err_to_config_err)?;
            let mut contents = String::new();
            let _ = config_file
                .read_to_string(&mut contents)
                .map_err(map_io_err_to_config_err)?;
            toml::from_str(&contents)
                .map_err(|err| ConfigError(format!("Failed to parse config file: {}", err)))?
        } else {
            RawConfig::new()
        };

        // Convert to config
        let mut config = Self::from(raw_config);

        // Potentially override styles
        if !enable_styles {
            config.style = StyleConfig {
                command_name: Style::default(),
                description: Style::default(),
                example_text: Style::default(),
                example_code: Style::default(),
                example_variable: Style::default(),
            };
        }

        Ok(config)
    }
}

/// Return the path to the config directory.
///
/// The config dir path can be overridden using the `TEALDEER_CONFIG_DIR` env
/// variable. Otherwise, the user config directory is returned.
///
/// Note that this function does not verify whether the directory at that
/// location exists, or is a directory.
pub fn get_config_dir() -> Result<(PathBuf, PathSource), TealdeerError> {
    // Allow overriding the config directory by setting the
    // $TEALDEER_CONFIG_DIR env variable.
    if let Ok(value) = env::var("TEALDEER_CONFIG_DIR") {
        return Ok((PathBuf::from(value), PathSource::EnvVar));
    };

    // Otherwise, fall back to the user config directory.
    match get_app_root(AppDataType::UserConfig, &crate::APP_INFO) {
        Ok(dirs) => Ok((dirs, PathSource::OsConvention)),
        Err(_) => Err(ConfigError(
            "Could not determine the user config directory.".into(),
        )),
    }
}

/// Return the path to the config file.
///
/// Note that this function does not verify whether the file at that location
/// exists, or is a file.
pub fn get_config_path() -> Result<(PathBuf, PathSource), TealdeerError> {
    let (config_dir, source) = get_config_dir()?;
    let config_file_path = config_dir.join(CONFIG_FILE_NAME);
    Ok((config_file_path, source))
}

/// Create default config file.
pub fn make_default_config() -> Result<PathBuf, TealdeerError> {
    let (config_dir, _) = get_config_dir()?;

    // Ensure that config directory exists
    if !config_dir.exists() {
        if let Err(e) = fs::create_dir_all(&config_dir) {
            return Err(ConfigError(format!(
                "Could not create config directory: {}",
                e
            )));
        }
    } else if !config_dir.is_dir() {
        return Err(ConfigError(format!(
            "Config directory could not be created: {} already exists but is not a directory",
            config_dir.to_string_lossy(),
        )));
    }

    // Ensure that a config file doesn't get overwritten
    let config_file_path = config_dir.join(CONFIG_FILE_NAME);
    if config_file_path.is_file() {
        return Err(ConfigError(format!(
            "A configuration file already exists at {}, no action was taken.",
            config_file_path.to_str().unwrap()
        )));
    }

    // Create default config
    let serialized_config = toml::to_string(&RawConfig::new())
        .map_err(|err| ConfigError(format!("Failed to serialize default config: {}", err)))?;

    // Write default config
    let mut config_file = fs::File::create(&config_file_path).map_err(map_io_err_to_config_err)?;
    let _wc = config_file
        .write(serialized_config.as_bytes())
        .map_err(map_io_err_to_config_err)?;

    Ok(config_file_path)
}

#[test]
fn test_serialize_deserialize() {
    let raw_config = RawConfig::new();
    let serialized = toml::to_string(&raw_config).unwrap();
    let deserialized: RawConfig = toml::from_str(&serialized).unwrap();
    assert_eq!(raw_config, deserialized);
}
<commit_msg>Fix order of derive helper attributes (#179)<commit_after>use std::env;
use std::fs;
use std::io::{Error as IoError, Read, Write};
use std::path::PathBuf;
use std::time::Duration;

use ansi_term::{Color, Style};
use app_dirs::{get_app_root, AppDataType};
use log::debug;
use serde_derive::{Deserialize, Serialize};

use crate::error::TealdeerError::{self, ConfigError};
use crate::types::PathSource;

pub const CONFIG_FILE_NAME: &str = "config.toml";
pub const MAX_CACHE_AGE: Duration = Duration::from_secs(2_592_000); // 30 days
const DEFAULT_UPDATE_INTERVAL_HOURS: u64 = MAX_CACHE_AGE.as_secs() / 3600; // 30 days

fn default_underline() -> bool {
    false
}

fn default_bold() -> bool {
    false
}

#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "lowercase")]
pub enum RawColor {
    Black,
    Red,
    Green,
    Yellow,
    Blue,
    Purple,
    Cyan,
    White,
    Ansi(u8),
    Rgb { r: u8, g: u8, b: u8 },
}

impl From<RawColor> for Color {
    fn from(raw_color: RawColor) -> Self {
        match raw_color {
            RawColor::Black => Self::Black,
            RawColor::Red => Self::Red,
            RawColor::Green => Self::Green,
            RawColor::Yellow => Self::Yellow,
            RawColor::Blue => Self::Blue,
            RawColor::Purple => Self::Purple,
            RawColor::Cyan => Self::Cyan,
            RawColor::White => Self::White,
            RawColor::Ansi(num) => Self::Fixed(num),
            RawColor::Rgb { r, g, b } => Self::RGB(r, g, b),
        }
    }
}

#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
struct RawStyle {
    pub foreground: Option<RawColor>,
    pub background: Option<RawColor>,
    #[serde(default = "default_underline")]
    pub underline: bool,
    #[serde(default = "default_bold")]
    pub bold: bool,
}

impl Default for RawStyle {
    fn default() -> Self {
        Self {
            foreground: None,
            background: None,
            underline: false,
            bold: false,
        }
    }
} // impl RawStyle

impl From<RawStyle> for Style {
    fn from(raw_style: RawStyle) -> Self {
        let mut style = Self::default();

        if let Some(foreground) = raw_style.foreground {
            style = style.fg(Color::from(foreground));
        }

        if let Some(background) = raw_style.background {
            style = style.on(Color::from(background));
        }

        if raw_style.underline {
            style = style.underline();
        }

        if raw_style.bold {
            style = style.bold();
        }

        style
    }
}

#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
struct RawStyleConfig {
    #[serde(default)]
    pub description: RawStyle,
    #[serde(default)]
    pub command_name: RawStyle,
    #[serde(default)]
    pub example_text: RawStyle,
    #[serde(default)]
    pub example_code: RawStyle,
    #[serde(default)]
    pub example_variable: RawStyle,
}

#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
struct RawDisplayConfig {
    #[serde(default)]
    pub compact: bool,
    #[serde(default)]
    pub use_pager: bool,
}

/// Serde doesn't support default values yet (tracking issue:
/// <https://github.com/serde-rs/serde/issues/368>), so we need to wrap
/// `DEFAULT_UPDATE_INTERVAL_HOURS` in a function to be able to use
/// `#[serde(default = ...)]`
const fn default_auto_update_interval_hours() -> u64 {
    DEFAULT_UPDATE_INTERVAL_HOURS
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
struct RawUpdatesConfig {
    #[serde(default)]
    pub auto_update: bool,
    #[serde(default = "default_auto_update_interval_hours")]
    pub auto_update_interval_hours: u64,
}

impl Default for RawUpdatesConfig {
    fn default() -> Self {
        Self {
            auto_update: false,
            auto_update_interval_hours: DEFAULT_UPDATE_INTERVAL_HOURS,
        }
    }
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
struct RawDirectoriesConfig {
    #[serde(default)]
    pub custom_pages_dir: Option<PathBuf>,
}

impl Default for RawDirectoriesConfig {
    fn default() -> Self {
        Self {
            custom_pages_dir: get_app_root(AppDataType::UserData, &crate::APP_INFO)
                .map(|path| path.join("pages"))
                .ok(),
        }
    }
}

#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
struct RawConfig {
    #[serde(default)]
    style: RawStyleConfig,
    #[serde(default)]
    display: RawDisplayConfig,
    #[serde(default)]
    updates: RawUpdatesConfig,
    #[serde(default)]
    directories: RawDirectoriesConfig,
}

impl RawConfig {
    fn new() -> Self {
        let mut raw_config = Self::default();

        // Set default config
        raw_config.style.example_text.foreground = Some(RawColor::Green);
        raw_config.style.command_name.foreground = Some(RawColor::Cyan);
        raw_config.style.example_code.foreground = Some(RawColor::Cyan);
        raw_config.style.example_variable.foreground = Some(RawColor::Cyan);
        raw_config.style.example_variable.underline = true;

        raw_config
    }
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct StyleConfig {
    pub description: Style,
    pub command_name: Style,
    pub example_text: Style,
    pub example_code: Style,
    pub example_variable: Style,
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct DisplayConfig {
    pub compact: bool,
    pub use_pager: bool,
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct UpdatesConfig {
    pub auto_update: bool,
    pub auto_update_interval: Duration,
}

#[derive(Clone, Debug, PartialEq)]
pub struct DirectoriesConfig {
    pub custom_pages_dir: Option<PathBuf>,
}

#[derive(Clone, Debug, PartialEq)]
pub struct Config {
    pub style: StyleConfig,
    pub display: DisplayConfig,
    pub updates: UpdatesConfig,
    pub directories: DirectoriesConfig,
}

impl From<RawConfig> for Config {
    fn from(raw_config: RawConfig) -> Self {
        Self {
            style: StyleConfig {
                command_name: raw_config.style.command_name.into(),
                description: raw_config.style.description.into(),
                example_text: raw_config.style.example_text.into(),
                example_code: raw_config.style.example_code.into(),
                example_variable: raw_config.style.example_variable.into(),
            },
            display: DisplayConfig {
                compact: raw_config.display.compact,
                use_pager: raw_config.display.use_pager,
            },
            updates: UpdatesConfig {
                auto_update: raw_config.updates.auto_update,
                auto_update_interval: Duration::from_secs(
                    raw_config.updates.auto_update_interval_hours * 3600,
                ),
            },
            directories: DirectoriesConfig {
                custom_pages_dir: raw_config.directories.custom_pages_dir,
            },
        }
    }
}

#[allow(clippy::needless_pass_by_value)]
fn map_io_err_to_config_err(e: IoError) -> TealdeerError {
    ConfigError(format!("Io Error: {}", e))
}

impl Config {
    pub fn load(enable_styles: bool) -> Result<Self, TealdeerError> {
        debug!("Loading config");

        // Determine path
        let (config_file_path, _) = get_config_path()
            .map_err(|e| ConfigError(format!("Could not determine config path: {}", e)))?;

        // Load raw config
        let raw_config: RawConfig = if config_file_path.exists() && config_file_path.is_file() {
            let mut config_file =
                fs::File::open(config_file_path).map_err(map_io_err_to_config_err)?;
            let mut contents = String::new();
            let _ = config_file
                .read_to_string(&mut contents)
                .map_err(map_io_err_to_config_err)?;
            toml::from_str(&contents)
                .map_err(|err| ConfigError(format!("Failed to parse config file: {}", err)))?
        } else {
            RawConfig::new()
        };

        // Convert to config
        let mut config = Self::from(raw_config);

        // Potentially override styles
        if !enable_styles {
            config.style = StyleConfig {
                command_name: Style::default(),
                description: Style::default(),
                example_text: Style::default(),
                example_code: Style::default(),
                example_variable: Style::default(),
            };
        }

        Ok(config)
    }
}

/// Return the path to the config directory.
///
/// The config dir path can be overridden using the `TEALDEER_CONFIG_DIR` env
/// variable. Otherwise, the user config directory is returned.
///
/// Note that this function does not verify whether the directory at that
/// location exists, or is a directory.
pub fn get_config_dir() -> Result<(PathBuf, PathSource), TealdeerError> {
    // Allow overriding the config directory by setting the
    // $TEALDEER_CONFIG_DIR env variable.
    if let Ok(value) = env::var("TEALDEER_CONFIG_DIR") {
        return Ok((PathBuf::from(value), PathSource::EnvVar));
    };

    // Otherwise, fall back to the user config directory.
    match get_app_root(AppDataType::UserConfig, &crate::APP_INFO) {
        Ok(dirs) => Ok((dirs, PathSource::OsConvention)),
        Err(_) => Err(ConfigError(
            "Could not determine the user config directory.".into(),
        )),
    }
}

/// Return the path to the config file.
///
/// Note that this function does not verify whether the file at that location
/// exists, or is a file.
pub fn get_config_path() -> Result<(PathBuf, PathSource), TealdeerError> {
    let (config_dir, source) = get_config_dir()?;
    let config_file_path = config_dir.join(CONFIG_FILE_NAME);
    Ok((config_file_path, source))
}

/// Create default config file.
pub fn make_default_config() -> Result<PathBuf, TealdeerError> {
    let (config_dir, _) = get_config_dir()?;

    // Ensure that config directory exists
    if !config_dir.exists() {
        if let Err(e) = fs::create_dir_all(&config_dir) {
            return Err(ConfigError(format!(
                "Could not create config directory: {}",
                e
            )));
        }
    } else if !config_dir.is_dir() {
        return Err(ConfigError(format!(
            "Config directory could not be created: {} already exists but is not a directory",
            config_dir.to_string_lossy(),
        )));
    }

    // Ensure that a config file doesn't get overwritten
    let config_file_path = config_dir.join(CONFIG_FILE_NAME);
    if config_file_path.is_file() {
        return Err(ConfigError(format!(
            "A configuration file already exists at {}, no action was taken.",
            config_file_path.to_str().unwrap()
        )));
    }

    // Create default config
    let serialized_config = toml::to_string(&RawConfig::new())
        .map_err(|err| ConfigError(format!("Failed to serialize default config: {}", err)))?;

    // Write default config
    let mut config_file = fs::File::create(&config_file_path).map_err(map_io_err_to_config_err)?;
    let _wc = config_file
        .write(serialized_config.as_bytes())
        .map_err(map_io_err_to_config_err)?;

    Ok(config_file_path)
}

#[test]
fn test_serialize_deserialize() {
    let raw_config = RawConfig::new();
    let serialized = toml::to_string(&raw_config).unwrap();
    let deserialized: RawConfig = toml::from_str(&serialized).unwrap();
    assert_eq!(raw_config, deserialized);
}
<|endoftext|>


<commit_before>use anyhow::*;
use diesel::r2d2::{self, ConnectionManager, PooledConnection};
use diesel::sqlite::SqliteConnection;
use diesel_migrations;
use log::info;
use std::path::Path;

mod schema;

pub use self::schema::*;

#[allow(dead_code)]
const DB_MIGRATIONS_PATH: &str = "migrations";
embed_migrations!("migrations");

#[derive(Clone)]
pub struct DB {
	pool: r2d2::Pool<ConnectionManager<SqliteConnection>>,
}

impl DB {
	pub fn new(path: &Path) -> Result<DB> {
		info!("Database file path: {}", path.to_string_lossy());
		let manager = ConnectionManager::<SqliteConnection>::new(path.to_string_lossy());
		let pool = r2d2::Pool::builder()
			.build(manager)
			.expect("Failed to create pool."); // TODO handle error

		let db = DB { pool: pool };
		db.migrate_up()?;
		Ok(db)
	}

	pub fn connect(&self) -> Result<PooledConnection<ConnectionManager<SqliteConnection>>> {
		self.pool.get().map_err(Error::new)
	}

	#[allow(dead_code)]
	fn migrate_down(&self) -> Result<()> {
		let connection = self.connect().unwrap();
		loop {
			match diesel_migrations::revert_latest_migration_in_directory(
				&connection,
				Path::new(DB_MIGRATIONS_PATH),
			) {
				Ok(_) => (),
				Err(diesel_migrations::RunMigrationsError::MigrationError(
					diesel_migrations::MigrationError::NoMigrationRun,
				)) => break,
				Err(e) => bail!(e),
			}
		}
		Ok(())
	}

	fn migrate_up(&self) -> Result<()> {
		let connection = self.connect().unwrap();
		embedded_migrations::run(&connection)?;
		Ok(())
	}
}

#[cfg(test)]
pub fn get_test_db(name: &str) -> DB {
	use crate::config;
	let config_path = Path::new("test/config.toml");
	let config = config::parse_toml_file(&config_path).unwrap();

	let mut db_path = std::path::PathBuf::new();
	db_path.push("test");
	db_path.push(name);
	if db_path.exists() {
		std::fs::remove_file(&db_path).unwrap();
	}

	let db = DB::new(&db_path).unwrap();
	config::reset(&db).unwrap();
	config::amend(&db, &config).unwrap();
	db
}

#[test]
fn test_migrations_up() {
	get_test_db("migrations_up.sqlite");
}

#[test]
fn test_migrations_down() {
	let db = get_test_db("migrations_down.sqlite");
	db.migrate_down().unwrap();
	db.migrate_up().unwrap();
}
<commit_msg>Database sanity settings<commit_after>use anyhow::*;
use diesel::r2d2::{self, ConnectionManager, PooledConnection};
use diesel::sqlite::SqliteConnection;
use diesel::RunQueryDsl;
use diesel_migrations;
use log::info;
use std::path::Path;

mod schema;

pub use self::schema::*;

#[allow(dead_code)]
const DB_MIGRATIONS_PATH: &str = "migrations";
embed_migrations!("migrations");

#[derive(Clone)]
pub struct DB {
	pool: r2d2::Pool<ConnectionManager<SqliteConnection>>,
}

#[derive(Debug)]
struct ConnectionCustomizer {}
impl diesel::r2d2::CustomizeConnection<SqliteConnection, diesel::r2d2::Error> for ConnectionCustomizer {
	fn on_acquire(&self, connection: &mut SqliteConnection) -> Result<(), diesel::r2d2::Error> {
		let query = diesel::sql_query(r#"
			PRAGMA busy_timeout = 60000;
			PRAGMA journal_mode = WAL;
			PRAGMA synchronous = NORMAL;
			PRAGMA foreign_keys = ON;
		"#);
		query.execute(connection)
			.map_err(|e| diesel::r2d2::Error::QueryError(e))?;
		Ok(())
	}
}

impl DB {
	pub fn new(path: &Path) -> Result<DB> {
		info!("Database file path: {}", path.to_string_lossy());
		let manager = ConnectionManager::<SqliteConnection>::new(path.to_string_lossy());
		let pool = diesel::r2d2::Pool::builder()
			.connection_customizer(Box::new(ConnectionCustomizer {}))
			.build(manager)?;
		let db = DB { pool: pool };
		db.migrate_up()?;
		Ok(db)
	}

	pub fn connect(&self) -> Result<PooledConnection<ConnectionManager<SqliteConnection>>> {
		self.pool.get().map_err(Error::new)
	}

	#[allow(dead_code)]
	fn migrate_down(&self) -> Result<()> {
		let connection = self.connect().unwrap();
		loop {
			match diesel_migrations::revert_latest_migration_in_directory(
				&connection,
				Path::new(DB_MIGRATIONS_PATH),
			) {
				Ok(_) => (),
				Err(diesel_migrations::RunMigrationsError::MigrationError(
					diesel_migrations::MigrationError::NoMigrationRun,
				)) => break,
				Err(e) => bail!(e),
			}
		}
		Ok(())
	}

	fn migrate_up(&self) -> Result<()> {
		let connection = self.connect().unwrap();
		embedded_migrations::run(&connection)?;
		Ok(())
	}
}

#[cfg(test)]
pub fn get_test_db(name: &str) -> DB {
	use crate::config;
	let config_path = Path::new("test/config.toml");
	let config = config::parse_toml_file(&config_path).unwrap();

	let mut db_path = std::path::PathBuf::new();
	db_path.push("test");
	db_path.push(name);
	if db_path.exists() {
		std::fs::remove_file(&db_path).unwrap();
	}

	let db = DB::new(&db_path).unwrap();
	config::reset(&db).unwrap();
	config::amend(&db, &config).unwrap();
	db
}

#[test]
fn test_migrations_up() {
	get_test_db("migrations_up.sqlite");
}

#[test]
fn test_migrations_down() {
	let db = get_test_db("migrations_down.sqlite");
	db.migrate_down().unwrap();
	db.migrate_up().unwrap();
}
<|endoftext|>


<commit_before>/// The Toml Delete extensions

use toml::Value;

use tokenizer::Token;
use tokenizer::tokenize_with_seperator;
use error::*;

pub trait TomlValueDeleteExt {

    /// Extension function for deleting a value in the current toml::Value document
    /// using a custom seperator.
    ///
    /// # Semantics
    ///
    /// The function does _not_ delete non-empty data structures. So deleting `array` from
    ///
    /// ```toml
    /// array = [ 1 ]
    /// ```
    ///
    /// does _not_ work.
    ///
    /// # Return value
    ///
    /// If the delete operation worked correctly, `Ok(Option<Value>)` is returned.
    ///
    /// The `Option<Value>` part is `None` if no value was actually removed as there was no value
    /// there. For example, if you're deleting `table.a` and the Table `table` has no key `a`, then
    /// `Ok(None)` is returned. Also, if you're deleting from an Array, but there is nothing in the
    /// array, or the array is shorter than the index you're deleting.
    /// If the delete operation actually removed something from the toml document, this value is
    /// returned as `Ok(Some(Value))`.
    ///
    /// On failure, `Err(e)` is returned
    ///
    fn delete_with_seperator(&mut self, query: &String, sep: char) -> Result<Option<Value>>;

    /// Extension function for inserting a value from the current toml::Value document
    ///
    /// See documentation of `TomlValueinsertExt::insert_with_seperator`
    fn delete(&mut self, query: &String) -> Result<Option<Value>> {
        self.delete_with_seperator(query, '.')
    }

}

impl TomlValueDeleteExt for Value {

    fn delete_with_seperator(&mut self, query: &String, sep: char) -> Result<Option<Value>> {
        use resolver::mut_resolver::resolve;

        Ok(None)
    }

}

#[cfg(test)]
mod test {
    use super::*;
    use error::*;
    use toml::Value;
    use toml::from_str as toml_from_str;

    #[test]
    fn test_delete_from_empty_document() {
        let mut toml : Value = toml_from_str("").unwrap();

        let res = toml.delete_with_seperator(&String::from("a"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_none());
    }

    #[test]
    fn test_delete_from_empty_table() {
        let mut toml : Value = toml_from_str(r#"
        [table]
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("table.a"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_none());
    }

    #[test]
    fn test_delete_integer() {
        let mut toml : Value = toml_from_str(r#"
        value = 1
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("value"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_some());
        let res = res.unwrap();
        assert!(is_match!(res, Value::Integer(1)));
    }

    #[test]
    fn test_delete_string() {
        let mut toml : Value = toml_from_str(r#"
        value = "foo"
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("value"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_some());
        let res = res.unwrap();
        assert!(is_match!(res, Value::String(_)));
        match res {
            Value::String(ref s) => assert_eq!("foo", s),
            _ => panic!("What just happened?"),
        }
    }

    #[test]
    fn test_delete_empty_table() {
        let mut toml : Value = toml_from_str(r#"
        [table]
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("table"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_some());
        let res = res.unwrap();
        assert!(is_match!(res, Value::Table(_)));
        match res {
            Value::Table(ref t) => assert!(t.is_empty()),
            _ => panic!("What just happened?"),
        }
    }

    #[test]
    fn test_delete_empty_array() {
        let mut toml : Value = toml_from_str(r#"
        array = []
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("array"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_some());
        let res = res.unwrap();
        assert!(is_match!(res, Value::Array(_)));
        match res {
            Value::Array(ref a) => assert!(a.is_empty()),
            _ => panic!("What just happened?"),
        }
    }

    #[test]
    fn test_delete_nonempty_table() {
        let mut toml : Value = toml_from_str(r#"
        [table]
        a = 1
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("table"), '.');

        assert!(res.is_err());

        let res = res.unwrap_err();
        assert!(is_match!(res.kind(), &ErrorKind::CannotDeleteNonEmptyTable(_)));
    }

    #[test]
    fn test_delete_nonempty_array() {
        let mut toml : Value = toml_from_str(r#"
        array = [ 1 ]
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("array.[0]"), '.');

        assert!(res.is_err());

        let res = res.unwrap_err();
        assert!(is_match!(res.kind(), &ErrorKind::CannotDeleteNonEmptyArray(_)));
    }

}

<commit_msg>Impl Value::delete_with_seperator to fix test_delete_string<commit_after>/// The Toml Delete extensions

use toml::Value;

use tokenizer::Token;
use tokenizer::tokenize_with_seperator;
use error::*;

pub trait TomlValueDeleteExt {

    /// Extension function for deleting a value in the current toml::Value document
    /// using a custom seperator.
    ///
    /// # Semantics
    ///
    /// The function does _not_ delete non-empty data structures. So deleting `array` from
    ///
    /// ```toml
    /// array = [ 1 ]
    /// ```
    ///
    /// does _not_ work.
    ///
    /// # Return value
    ///
    /// If the delete operation worked correctly, `Ok(Option<Value>)` is returned.
    ///
    /// The `Option<Value>` part is `None` if no value was actually removed as there was no value
    /// there. For example, if you're deleting `table.a` and the Table `table` has no key `a`, then
    /// `Ok(None)` is returned. Also, if you're deleting from an Array, but there is nothing in the
    /// array, or the array is shorter than the index you're deleting.
    /// If the delete operation actually removed something from the toml document, this value is
    /// returned as `Ok(Some(Value))`.
    ///
    /// On failure, `Err(e)` is returned
    ///
    fn delete_with_seperator(&mut self, query: &String, sep: char) -> Result<Option<Value>>;

    /// Extension function for inserting a value from the current toml::Value document
    ///
    /// See documentation of `TomlValueinsertExt::insert_with_seperator`
    fn delete(&mut self, query: &String) -> Result<Option<Value>> {
        self.delete_with_seperator(query, '.')
    }

}

impl TomlValueDeleteExt for Value {

    fn delete_with_seperator(&mut self, query: &String, sep: char) -> Result<Option<Value>> {
        use resolver::mut_resolver::resolve;

        let mut tokens = try!(tokenize_with_seperator(query, sep));
        let last_token = tokens.pop_last();

        if last_token.is_none() {
            match self {
                &mut Value::Table(ref mut tab) => {
                    match tokens {
                        Token::Identifier { ident, .. } => {
                            println!("Removing {} from {:?}", ident, tab);
                            Ok(tab.remove(&ident))
                        },
                        _ => Ok(None)
                    }
                },
                _ => unimplemented!()
            }
        } else {
            let mut val = try!(resolve(self, &tokens));
            Ok(None)
        }
    }

}

#[cfg(test)]
mod test {
    use super::*;
    use error::*;
    use toml::Value;
    use toml::from_str as toml_from_str;

    #[test]
    fn test_delete_from_empty_document() {
        let mut toml : Value = toml_from_str("").unwrap();

        let res = toml.delete_with_seperator(&String::from("a"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_none());
    }

    #[test]
    fn test_delete_from_empty_table() {
        let mut toml : Value = toml_from_str(r#"
        [table]
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("table.a"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_none());
    }

    #[test]
    fn test_delete_integer() {
        let mut toml : Value = toml_from_str(r#"
        value = 1
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("value"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_some());
        let res = res.unwrap();
        assert!(is_match!(res, Value::Integer(1)));
    }

    #[test]
    fn test_delete_string() {
        let mut toml : Value = toml_from_str(r#"
        value = "foo"
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("value"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_some());
        let res = res.unwrap();
        assert!(is_match!(res, Value::String(_)));
        match res {
            Value::String(ref s) => assert_eq!("foo", s),
            _ => panic!("What just happened?"),
        }
    }

    #[test]
    fn test_delete_empty_table() {
        let mut toml : Value = toml_from_str(r#"
        [table]
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("table"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_some());
        let res = res.unwrap();
        assert!(is_match!(res, Value::Table(_)));
        match res {
            Value::Table(ref t) => assert!(t.is_empty()),
            _ => panic!("What just happened?"),
        }
    }

    #[test]
    fn test_delete_empty_array() {
        let mut toml : Value = toml_from_str(r#"
        array = []
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("array"), '.');

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_some());
        let res = res.unwrap();
        assert!(is_match!(res, Value::Array(_)));
        match res {
            Value::Array(ref a) => assert!(a.is_empty()),
            _ => panic!("What just happened?"),
        }
    }

    #[test]
    fn test_delete_nonempty_table() {
        let mut toml : Value = toml_from_str(r#"
        [table]
        a = 1
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("table"), '.');

        assert!(res.is_err());

        let res = res.unwrap_err();
        assert!(is_match!(res.kind(), &ErrorKind::CannotDeleteNonEmptyTable(_)));
    }

    #[test]
    fn test_delete_nonempty_array() {
        let mut toml : Value = toml_from_str(r#"
        array = [ 1 ]
        "#).unwrap();

        let res = toml.delete_with_seperator(&String::from("array.[0]"), '.');

        assert!(res.is_err());

        let res = res.unwrap_err();
        assert!(is_match!(res.kind(), &ErrorKind::CannotDeleteNonEmptyArray(_)));
    }

}

<|endoftext|>


<commit_before>use cocoa::base::{id, nil};
use cocoa::foundation::{NSUInteger, NSString};
use objc::runtime::{BOOL, NO, YES};
use objc::runtime::{Class, Object};
use sys::{MTLCreateSystemDefaultDevice, MTLDevice};
use std::borrow::Cow;
use std::error::Error;
use std::fmt::{self, Display, Formatter};
use std::ffi::CStr;
use {Size};

pub trait ReadOnlyDevice {
    fn is_headless(&self) -> bool;
    fn is_low_power(&self) -> bool;
    fn max_threads_per_group(&self) -> bool;
    fn get_name(&self) -> Cow<str>;
    fn supports_texture_sample_count(&self, sample_count: usize) -> bool;
}

pub struct Device(id);

impl Device {
    pub fn system_default_device() -> Result<Self, DeviceError> {
        let device = unsafe { MTLCreateSystemDefaultDevice() };
        if device != nil {
            Ok(Device(device))
        } else {
            Err(DeviceError::ConstructionFailed)
        }
    }

    pub unsafe fn get_raw(&self) -> id {self.0}

    pub unsafe fn into_raw(self) -> id {
        self.0
    }

    pub unsafe fn from_raw_unchecked(device_ptr: id) -> Self {
        Device(device_ptr)
    }

    pub fn from_raw(device_ptr: id) -> Result<Self, DeviceError> {
        #[link(name = "Foundation", kind = "framework")]
        extern {
            fn NSProtocolFromString(namestr: id) -> id;
        }

        let mtl_device_protocol_str = unsafe { NSString::alloc(nil).init_str("MTLDevice") };
        let mtl_device_protocol = unsafe { NSProtocolFromString(mtl_device_protocol_str) };

        let conforms_to_protocol: BOOL = unsafe { msg_send![device_ptr, conformsToProtocol:mtl_device_protocol] };

        if device_ptr == nil {
            Err(DeviceError::ConstructedFromNil)
        } else if conforms_to_protocol == NO {
            Err(DeviceError::ConstructedFromWrongPointerType)
        } else {
            Ok(Device(device_ptr))
        }
    }

    pub fn is_headless(&self) -> bool {
        unsafe { self.0.headless() != 0 }
    }

    pub fn is_low_power(&self) -> bool {
        unsafe { self.0.lowPower() != 0 }
    }

    pub fn max_threads_per_group(&self) -> Size {
        unsafe { self.0.maxThreadsPerGroup().into() }
    }

    pub fn get_name(&self) -> Cow<str> {
        unsafe { CStr::from_ptr(self.0.name().UTF8String()).to_string_lossy() }
    }

    pub fn supports_texture_sample_count(&self, sample_count: usize) -> bool {
        unsafe { self.0.supportsTextureSampleCount(sample_count as NSUInteger) != 0 }
    }
}

/// Internal utility function to create a Device without exposing internals.
/// Not exported publicly from this crate.
#[doc(hidden)]
pub unsafe fn _make_device(device: id) -> Device {
    Device(device)
}

#[derive(Clone, Debug)]
pub enum DeviceError {
    ConstructionFailed,
    ConstructedFromNil,
    ConstructedFromWrongPointerType,
    OtherRuntimeError
}

impl Display for DeviceError {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        let descr = match *self {
            DeviceError::ConstructionFailed => "DeviceError::ConstructionFailed",
            DeviceError::ConstructedFromNil => "DeviceError::ConstructedFromNil",
            DeviceError::ConstructedFromWrongPointerType => "DeviceError::ConstructedFromWrongPointerType",
            DeviceError::OtherRuntimeError => "DeviceError::OtherRuntimeError"
        };
        write!(f, "{}", descr)
    }
}

impl Error for DeviceError {
    fn description(&self) -> &str {
        match *self {
            DeviceError::ConstructionFailed =>
                "Could not create a default device. Please ensure that you are using at least OSX 10.11 or iOS 8.0",
            DeviceError::ConstructedFromNil => "Attempted to create a device from a nil pointer",
            DeviceError::ConstructedFromWrongPointerType =>
                "Attempted to create a device from a pointer which does not implement MTLDevice",
            DeviceError::OtherRuntimeError => "An error occured in the Objective-C runtime"
        }
    }
}
<commit_msg>Compare functions which return a BOOL to YES instead of numeric literals<commit_after>use cocoa::base::{id, nil};
use cocoa::foundation::{NSUInteger, NSString};
use objc::runtime::{BOOL, NO, YES};
use objc::runtime::{Class, Object};
use sys::{MTLCreateSystemDefaultDevice, MTLDevice};
use std::borrow::Cow;
use std::error::Error;
use std::fmt::{self, Display, Formatter};
use std::ffi::CStr;
use {Size};

pub trait ReadOnlyDevice {
    fn is_headless(&self) -> bool;
    fn is_low_power(&self) -> bool;
    fn max_threads_per_group(&self) -> bool;
    fn get_name(&self) -> Cow<str>;
    fn supports_texture_sample_count(&self, sample_count: usize) -> bool;
}

pub struct Device(id);

impl Device {
    pub fn system_default_device() -> Result<Self, DeviceError> {
        let device = unsafe { MTLCreateSystemDefaultDevice() };
        if device != nil {
            Ok(Device(device))
        } else {
            Err(DeviceError::ConstructionFailed)
        }
    }

    pub unsafe fn get_raw(&self) -> id {self.0}

    pub unsafe fn into_raw(self) -> id {
        self.0
    }

    pub unsafe fn from_raw_unchecked(device_ptr: id) -> Self {
        Device(device_ptr)
    }

    pub fn from_raw(device_ptr: id) -> Result<Self, DeviceError> {
        #[link(name = "Foundation", kind = "framework")]
        extern {
            fn NSProtocolFromString(namestr: id) -> id;
        }

        let mtl_device_protocol_str = unsafe { NSString::alloc(nil).init_str("MTLDevice") };
        let mtl_device_protocol = unsafe { NSProtocolFromString(mtl_device_protocol_str) };

        let conforms_to_protocol: BOOL = unsafe { msg_send![device_ptr, conformsToProtocol:mtl_device_protocol] };

        if device_ptr == nil {
            Err(DeviceError::ConstructedFromNil)
        } else if conforms_to_protocol == NO {
            Err(DeviceError::ConstructedFromWrongPointerType)
        } else {
            Ok(Device(device_ptr))
        }
    }

    pub fn is_headless(&self) -> bool {
        unsafe { self.0.headless() == YES }
    }

    pub fn is_low_power(&self) -> bool {
        unsafe { self.0.lowPower() == YES }
    }

    pub fn max_threads_per_group(&self) -> Size {
        unsafe { self.0.maxThreadsPerGroup().into() }
    }

    pub fn get_name(&self) -> Cow<str> {
        unsafe { CStr::from_ptr(self.0.name().UTF8String()).to_string_lossy() }
    }

    pub fn supports_texture_sample_count(&self, sample_count: usize) -> bool {
        unsafe { self.0.supportsTextureSampleCount(sample_count as NSUInteger) == YES }
    }
}

/// Internal utility function to create a Device without exposing internals.
/// Not exported publicly from this crate.
#[doc(hidden)]
pub unsafe fn _make_device(device: id) -> Device {
    Device(device)
}

#[derive(Clone, Debug)]
pub enum DeviceError {
    ConstructionFailed,
    ConstructedFromNil,
    ConstructedFromWrongPointerType,
    OtherRuntimeError
}

impl Display for DeviceError {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        let descr = match *self {
            DeviceError::ConstructionFailed => "DeviceError::ConstructionFailed",
            DeviceError::ConstructedFromNil => "DeviceError::ConstructedFromNil",
            DeviceError::ConstructedFromWrongPointerType => "DeviceError::ConstructedFromWrongPointerType",
            DeviceError::OtherRuntimeError => "DeviceError::OtherRuntimeError"
        };
        write!(f, "{}", descr)
    }
}

impl Error for DeviceError {
    fn description(&self) -> &str {
        match *self {
            DeviceError::ConstructionFailed =>
                "Could not create a default device. Please ensure that you are using at least OSX 10.11 or iOS 8.0",
            DeviceError::ConstructedFromNil => "Attempted to create a device from a nil pointer",
            DeviceError::ConstructedFromWrongPointerType =>
                "Attempted to create a device from a pointer which does not implement MTLDevice",
            DeviceError::OtherRuntimeError => "An error occured in the Objective-C runtime"
        }
    }
}
<|endoftext|>


<commit_before>use std::ptr;
use std::io::IoError;
use std::fmt;
use libc::dev_t;
use std::iter;
use std::time::Duration;

use libudev_c;
use util;
use iterator;

use udev::Udev;
use iterator::UdevIterator;

pub struct Device<'u> {
    pub udev: &'u Udev,
    dev: libudev_c::udev_device,
}
pub type Devnum = dev_t;
pub enum DeviceType {
    CharDev,
    BlockDev
}

// Crate Private
pub unsafe fn device<'u>(udev: &'u Udev, dev: libudev_c::udev_device) -> Device<'u> {
    Device { udev: udev, dev: dev }
}

pub unsafe fn device_get_dev(device: &Device) -> libudev_c::udev_device {
    device.dev
}

impl<'u> Device<'u> {
    pub fn parent(&self) -> Option<Device> {
        match util::check_errno(|| unsafe {
            libudev_c::udev_device_ref(libudev_c::udev_device_get_parent(self.dev))
        }) {
            Ok(Some(dev)) => Some(unsafe { device(self.udev, dev) }),
            _ => None
        }
    }

    pub fn parent_with_subsystem(&self, subsystem: &str) -> Option<Device> {
        match subsystem.with_c_str(|subsystem| util::check_errno(|| unsafe {
            libudev_c::udev_device_ref(
                libudev_c::udev_device_get_parent_with_subsystem_devtype(self.dev, subsystem, ptr::null()))
        })) {
            Ok(Some(dev)) => Some(unsafe { device(self.udev, dev) }),
            _ => None
        }
    }

    pub fn parent_with_subsystem_devtype(&self, subsystem: &str, devtype: &str) -> Option<Device> {
        match subsystem.with_c_str(|subsystem| devtype.with_c_str(|devtype| util::check_errno(|| unsafe {
            libudev_c::udev_device_ref(
                libudev_c::udev_device_get_parent_with_subsystem_devtype(self.dev, subsystem, devtype))
        }))) {
            Ok(Some(dev)) => Some(unsafe { device(self.udev, dev) }),
            _ => None
        }
    }

    pub fn attribute<'s>(&'s self, attr: &str) -> Option<&'s str> {
        attr.with_c_str(|cstr| unsafe {
            util::c_to_str(libudev_c::udev_device_get_sysattr_value(self.dev, cstr))
        })
    }

    pub fn set_attribute(&self, attr: &str, value: &str) -> Result<(), IoError> {
        attr.with_c_str(|attr| value.with_c_str(|value| match unsafe {
            libudev_c::udev_device_set_sysattr_value(self.dev, attr, value)
        } {
            0           => Ok(()),
            n if n < 0  => Err(IoError::from_errno(-n as uint, true)),
            _           => fail!("udev returned an invalid error")
        }))
    }

    pub fn devpath<'s>(&'s self) -> &'s str {
        unsafe {
            util::c_to_str(libudev_c::udev_device_get_devpath(self.dev)).unwrap()
        }
    }

    pub fn syspath<'s>(&'s self) -> Path {
        unsafe {
            Path::new(util::c_to_str(libudev_c::udev_device_get_syspath(self.dev)).unwrap())
        }
    }

    pub fn sysname<'s>(&'s self) -> &'s str {
        unsafe {
            util::c_to_str(libudev_c::udev_device_get_sysname(self.dev)).unwrap()
        }
    }

    pub fn subsystem<'s>(&'s self) -> Option<&'s str> {
        unsafe {
            util::c_to_str(libudev_c::udev_device_get_subsystem(self.dev))
        }
    }

    pub fn devtype<'s>(&'s self) -> Option<&'s str> {
        unsafe {
            util::c_to_str(libudev_c::udev_device_get_devtype(self.dev))
        }
    }

    pub fn sysnum(&self) -> Option<u64> {
        match unsafe {
            util::c_to_str(libudev_c::udev_device_get_sysnum(self.dev))
        } {
            Some(n) => from_str(n),
            None => None
        }
    }

    pub fn devnum(&self) -> Option<Devnum> {
        match unsafe {
            libudev_c::udev_device_get_devnum(self.dev)
        } {
            0 => None,
            n => Some(n)
        }
    }

    pub fn driver<'s>(&'s self) -> Option<&'s str> {
        unsafe {
            util::c_to_str(libudev_c::udev_device_get_driver(self.dev))
        }
    }

    pub fn devnode<'s>(&'s self) -> Option<&'s str> {
        unsafe {
            util::c_to_str(libudev_c::udev_device_get_devnode(self.dev))
        }
    }

    pub fn iter_devlinks(&self) -> iter::Map<(&Device,&str,Option<&str>),Path,UdevIterator<Device>> {
        unsafe {
            iterator::udev_iterator(self, libudev_c::udev_device_get_devlinks_list_entry(self.dev))
        }.map(|(_, key, _)| Path::new(key))
    }

    pub fn iter_tags(&self) -> iter::Map<(&Device,&str,Option<&str>),&str,UdevIterator<Device>> {
        unsafe {
            iterator::udev_iterator(self, libudev_c::udev_device_get_tags_list_entry(self.dev))
        }.map(|(_, key, _)| key)
    }

    pub fn iter_properties(&self) -> iter::Map<(&Device,&str,Option<&str>),(&str, Option<&str>),UdevIterator<Device>> {
        unsafe {
            iterator::udev_iterator(self, libudev_c::udev_device_get_properties_list_entry(self.dev))
        }.map(|(_, key, value)| (key, value))
    }

    pub fn iter_attributes(& self) -> iter::Map<(&Device,&str,Option<&str>),&str,UdevIterator<Device>> {
        unsafe {
            iterator::udev_iterator(self, libudev_c::udev_device_get_sysattr_list_entry(self.dev))
        }.map(|(_, key, _)| key)
    }

    pub fn time_since_initialized(&self) -> Option<Duration> {
        let usec = unsafe { libudev_c::udev_device_get_usec_since_initialized(self.dev) };
        if usec == 0 {
            None
        } else {
            // Note: I don't support machines that are online for over 292,471 years. Sorry.
            Some(Duration::microseconds(usec as i64))
        }
    }

    pub fn is_initialized(&self) -> bool {
        unsafe { libudev_c::udev_device_get_is_initialized(self.dev) != 0 }
    }

    pub fn has_tag(&self, tag: &str) -> bool {
        tag.with_c_str(|tag| unsafe {
            libudev_c::udev_device_has_tag(self.dev, tag) != 0
        })
    }
}

#[unsafe_destructor]
impl<'u> Drop for Device<'u> {
    fn drop(&mut self) {
        unsafe { libudev_c::udev_device_unref(self.dev) };
    }
}

impl<'u> fmt::Show for Device<'u> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.syspath().as_str().unwrap())
    }
}

impl DeviceType {
    pub fn to_char(self) -> i8 {
        match self {
            CharDev => 'c' as i8,
            BlockDev => 'b' as i8
        }
    }
}
<commit_msg>Return an error from attribute() on failure<commit_after>use std::ptr;
use std::io::{IoError, standard_error, FileNotFound};
use std::fmt;
use libc::dev_t;
use std::iter;
use std::time::Duration;

use libudev_c;
use util;
use iterator;

use udev::Udev;
use iterator::UdevIterator;

pub struct Device<'u> {
    pub udev: &'u Udev,
    dev: libudev_c::udev_device,
}
pub type Devnum = dev_t;
pub enum DeviceType {
    CharDev,
    BlockDev
}

// Crate Private
pub unsafe fn device<'u>(udev: &'u Udev, dev: libudev_c::udev_device) -> Device<'u> {
    Device { udev: udev, dev: dev }
}

pub unsafe fn device_get_dev(device: &Device) -> libudev_c::udev_device {
    device.dev
}

impl<'u> Device<'u> {
    pub fn parent(&self) -> Option<Device> {
        match util::check_errno(|| unsafe {
            libudev_c::udev_device_ref(libudev_c::udev_device_get_parent(self.dev))
        }) {
            Ok(Some(dev)) => Some(unsafe { device(self.udev, dev) }),
            _ => None
        }
    }

    pub fn parent_with_subsystem(&self, subsystem: &str) -> Option<Device> {
        match subsystem.with_c_str(|subsystem| util::check_errno(|| unsafe {
            libudev_c::udev_device_ref(
                libudev_c::udev_device_get_parent_with_subsystem_devtype(self.dev, subsystem, ptr::null()))
        })) {
            Ok(Some(dev)) => Some(unsafe { device(self.udev, dev) }),
            _ => None
        }
    }

    pub fn parent_with_subsystem_devtype(&self, subsystem: &str, devtype: &str) -> Option<Device> {
        match subsystem.with_c_str(|subsystem| devtype.with_c_str(|devtype| util::check_errno(|| unsafe {
            libudev_c::udev_device_ref(
                libudev_c::udev_device_get_parent_with_subsystem_devtype(self.dev, subsystem, devtype))
        }))) {
            Ok(Some(dev)) => Some(unsafe { device(self.udev, dev) }),
            _ => None
        }
    }

    pub fn attribute<'s>(&'s self, attr: &str) -> Result<&'s str, IoError> {
        match attr.with_c_str(|cstr| util::check_errno(|| unsafe {
            libudev_c::udev_device_get_sysattr_value(self.dev, cstr)
        })) {
            Ok(Some(val)) => Ok(unsafe { util::c_to_str(val) }.unwrap()),
            Ok(None) => Err(standard_error(FileNotFound)),
            Err(errno) => Err(IoError::from_errno(errno as uint, true)),
        }
    }

    pub fn set_attribute(&self, attr: &str, value: &str) -> Result<(), IoError> {
        attr.with_c_str(|attr| value.with_c_str(|value| match unsafe {
            libudev_c::udev_device_set_sysattr_value(self.dev, attr, value)
        } {
            0           => Ok(()),
            n if n < 0  => Err(IoError::from_errno(-n as uint, true)),
            _           => fail!("udev returned an invalid error")
        }))
    }

    pub fn devpath<'s>(&'s self) -> &'s str {
        unsafe {
            util::c_to_str(libudev_c::udev_device_get_devpath(self.dev)).unwrap()
        }
    }

    pub fn syspath<'s>(&'s self) -> Path {
        unsafe {
            Path::new(util::c_to_str(libudev_c::udev_device_get_syspath(self.dev)).unwrap())
        }
    }

    pub fn sysname<'s>(&'s self) -> &'s str {
        unsafe {
            util::c_to_str(libudev_c::udev_device_get_sysname(self.dev)).unwrap()
        }
    }

    pub fn subsystem<'s>(&'s self) -> Option<&'s str> {
        unsafe {
            util::c_to_str(libudev_c::udev_device_get_subsystem(self.dev))
        }
    }

    pub fn devtype<'s>(&'s self) -> Option<&'s str> {
        unsafe {
            util::c_to_str(libudev_c::udev_device_get_devtype(self.dev))
        }
    }

    pub fn sysnum(&self) -> Option<u64> {
        match unsafe {
            util::c_to_str(libudev_c::udev_device_get_sysnum(self.dev))
        } {
            Some(n) => from_str(n),
            None => None
        }
    }

    pub fn devnum(&self) -> Option<Devnum> {
        match unsafe {
            libudev_c::udev_device_get_devnum(self.dev)
        } {
            0 => None,
            n => Some(n)
        }
    }

    pub fn driver<'s>(&'s self) -> Option<&'s str> {
        unsafe {
            util::c_to_str(libudev_c::udev_device_get_driver(self.dev))
        }
    }

    pub fn devnode<'s>(&'s self) -> Option<&'s str> {
        unsafe {
            util::c_to_str(libudev_c::udev_device_get_devnode(self.dev))
        }
    }

    pub fn iter_devlinks(&self) -> iter::Map<(&Device,&str,Option<&str>),Path,UdevIterator<Device>> {
        unsafe {
            iterator::udev_iterator(self, libudev_c::udev_device_get_devlinks_list_entry(self.dev))
        }.map(|(_, key, _)| Path::new(key))
    }

    pub fn iter_tags(&self) -> iter::Map<(&Device,&str,Option<&str>),&str,UdevIterator<Device>> {
        unsafe {
            iterator::udev_iterator(self, libudev_c::udev_device_get_tags_list_entry(self.dev))
        }.map(|(_, key, _)| key)
    }

    pub fn iter_properties(&self) -> iter::Map<(&Device,&str,Option<&str>),(&str, Option<&str>),UdevIterator<Device>> {
        unsafe {
            iterator::udev_iterator(self, libudev_c::udev_device_get_properties_list_entry(self.dev))
        }.map(|(_, key, value)| (key, value))
    }

    pub fn iter_attributes(& self) -> iter::Map<(&Device,&str,Option<&str>),&str,UdevIterator<Device>> {
        unsafe {
            iterator::udev_iterator(self, libudev_c::udev_device_get_sysattr_list_entry(self.dev))
        }.map(|(_, key, _)| key)
    }

    pub fn time_since_initialized(&self) -> Option<Duration> {
        let usec = unsafe { libudev_c::udev_device_get_usec_since_initialized(self.dev) };
        if usec == 0 {
            None
        } else {
            // Note: I don't support machines that are online for over 292,471 years. Sorry.
            Some(Duration::microseconds(usec as i64))
        }
    }

    pub fn is_initialized(&self) -> bool {
        unsafe { libudev_c::udev_device_get_is_initialized(self.dev) != 0 }
    }

    pub fn has_tag(&self, tag: &str) -> bool {
        tag.with_c_str(|tag| unsafe {
            libudev_c::udev_device_has_tag(self.dev, tag) != 0
        })
    }
}

#[unsafe_destructor]
impl<'u> Drop for Device<'u> {
    fn drop(&mut self) {
        unsafe { libudev_c::udev_device_unref(self.dev) };
    }
}

impl<'u> fmt::Show for Device<'u> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.syspath().as_str().unwrap())
    }
}

impl DeviceType {
    pub fn to_char(self) -> i8 {
        match self {
            CharDev => 'c' as i8,
            BlockDev => 'b' as i8
        }
    }
}
<|endoftext|>


<commit_before>/// get three u8 characters and convert to 4 indexes of ALPHABET table
fn encode_block(block: &[u8]) -> [u8; 4] {
    let mut bitvec: u32 = 0xff_00_00_00;

    // first char place in second octet of bitvec
    // second char to third, etc.
    for (i, chr) in block.iter().enumerate() {
        let x: u32 = (*chr as u32) << (8 * (2 - i));
        bitvec |= x;
    }

    //                              <-----> six bits
    let mut mask: u32 = 0b0000_0000_1111_1100_0000_0000_0000_0000;
    let mut res: [u8; 4] = [0; 4];

    // divide three octets of bitvec (2, 3, 4) to four six-bits integers
    for i in 0..4 {
        res[i] = ((bitvec & mask) >> (6 * (3 - i))) as u8;
        mask = mask >> 6;
    }
    res
}

/// get translation table and str data, return encoded string
pub fn encode(table: &[u8], data: &[u8]) -> Vec<u8> {
    let mut data = data.iter();
    let mut block: [u8; 3] = [0; 3];
    let mut res = Vec::new();
    let mut done = false;
    let mut count;

    while !done {
        count = 0;

        // fill block with chars
        // count only those symbols that were actually added
        for i in 0..3 {
            block[i] = match data.next() {
                Some(chr) => {
                    count += 1;
                    *chr
                },
                None => {
                    done = true;
                    0
                },
            }
        }

        // in case of empty iterator
        if count == 0 {
            break
        }

        for idx in &encode_block(&block) {
            if count + 1 != 0 {
                res.push(table[*idx as usize]);
                count -= 1;
            } else {
                res.push('=' as u8);
            }
        }
    }
    res
}
<commit_msg>fix comment<commit_after>/// get three u8 characters and convert to 4 indexes of ALPHABET table
fn encode_block(block: &[u8]) -> [u8; 4] {
    let mut bitvec: u32 = 0xff_00_00_00;

    // first char place in second octet of bitvec
    // second char to third, etc.
    for (i, chr) in block.iter().enumerate() {
        let x: u32 = (*chr as u32) << (8 * (2 - i));
        bitvec |= x;
    }

    //                              <-----> six bits
    let mut mask: u32 = 0b0000_0000_1111_1100_0000_0000_0000_0000;
    let mut res: [u8; 4] = [0; 4];

    // divide three octets (2, 3, 4) of bitvec to four six-bits integers
    for i in 0..4 {
        res[i] = ((bitvec & mask) >> (6 * (3 - i))) as u8;
        mask = mask >> 6;
    }
    res
}

/// get translation table and str data, return encoded string
pub fn encode(table: &[u8], data: &[u8]) -> Vec<u8> {
    let mut data = data.iter();
    let mut block: [u8; 3] = [0; 3];
    let mut res = Vec::new();
    let mut done = false;
    let mut count;

    while !done {
        count = 0;

        // fill block with chars
        // count only those symbols that were actually added
        for i in 0..3 {
            block[i] = match data.next() {
                Some(chr) => {
                    count += 1;
                    *chr
                },
                None => {
                    done = true;
                    0
                },
            }
        }

        // in case of empty iterator
        if count == 0 {
            break
        }

        for idx in &encode_block(&block) {
            if count + 1 != 0 {
                res.push(table[*idx as usize]);
                count -= 1;
            } else {
                res.push('=' as u8);
            }
        }
    }
    res
}
<|endoftext|>


<commit_before>use crc32;
use constants::{NUL, CR, LF, ESCAPE, DEFAULT_LINE_SIZE};

use std::fs::File;
use std::io;
use std::io::{Read, Write, Seek, SeekFrom};

/// Options for encoding
#[derive(Debug)]
pub struct EncodeOptions {
    line_length: u8,
    parts: u32,
    part: u32,
    begin: u64,
    end: u64,
}

impl Default for EncodeOptions {
    /// Constructs a new EncodeOptions instance, with the following defaults:
    /// line_length = 128.
    /// parts = 1,
    /// part = begin = end = 0
    fn default() -> Self {
        EncodeOptions {
            line_length: DEFAULT_LINE_SIZE,
            parts: 1,
            part: 0,
            begin: 0,
            end: 0,
        }
    }
}

impl EncodeOptions {
    /// Sets the maximum line length.
    pub fn line_length(mut self, line_length: u8) -> EncodeOptions {
        self.line_length = line_length;
        self
    }

    /// Sets the number of parts (default=1).
    /// When the number of parts equals 1, no '=ypart' line will be written
    /// in the ouput.
    pub fn parts(mut self, parts: u32) -> EncodeOptions {
        self.parts = parts;
        self
    }

    /// Sets the part number.
    /// Only used when `parts > 1`.
    /// The part number count starts at 1.
    pub fn part(mut self, part: u32) -> EncodeOptions {
        self.part = part;
        self
    }

    /// Sets the begin (which is the file offset + 1).
    /// Only used when `parts > 1`.
    /// The size of the part is `end - begin + 1`.
    pub fn begin(mut self, begin: u64) -> EncodeOptions {
        self.begin = begin;
        self
    }

    /// Sets the end.
    /// Only used when `parts > 1`.
    /// The size of the part is `end - begin + 1`.
    /// `end` should be larger than `begin`, otherwise an overflow error occurrs.
    pub fn end(mut self, end: u64) -> EncodeOptions {
        self.end = end;
        self
    }
}

/// Encodes the input file in a new output file.
/// # Example
/// ```rust,no_run
/// let mut file = std::fs::File::open("test1.bin").unwrap();
/// let encode_options = yenc::EncodeOptions::new().parts(1);
/// let mut output_file = std::fs::File::create("test1.bin.yenc").unwrap();
/// yenc::yencode_file(&mut file, "test1.bin", &encode_options, &mut output_file);
/// ```
/// # Errors
/// - when the output file already exists
///
pub fn yencode_file(
    input_file: &mut File,
    input_filename: &str,
    encode_options: &EncodeOptions,
    output: &mut Write,
) -> Result<(), io::Error> {
    let mut checksum = crc32::Crc32::new();
    let mut buffer = [0u8; 8192];
    let mut col = 0;

    if encode_options.parts == 1 {
        output.write_all(
            format!(
                "=ybegin line={} size={} name={}\r\n",
                encode_options.line_length,
                input_file.metadata()?.len(),
                input_filename
            ).as_bytes(),
        )?;
    } else {
        output.write_all(
            format!(
                "=ybegin part={} line={} size={} name={}\r\n",
                encode_options.part,
                encode_options.line_length,
                input_file.metadata()?.len(),
                input_filename
            ).as_bytes(),
        )?;
    }

    if encode_options.parts > 1 {
        output.write_all(
            format!(
                "=ypart begin={} end={}\r\n",
                encode_options.begin,
                encode_options.end
            ).as_bytes(),
        )?;
    }

    input_file.seek(SeekFrom::Start(encode_options.begin - 1))?;

    let mut remainder = (encode_options.end - encode_options.begin + 1) as usize;
    while remainder > 0 {
        let bytes_to_read = if remainder > buffer.len() {
            buffer.len()
        } else {
            remainder
        };
        input_file.read_exact(&mut buffer[0..bytes_to_read])?;
        checksum.update_with_slice(&buffer[0..bytes_to_read]);
        output.write_all(
            yencode_buffer(
                &buffer[0..bytes_to_read],
                &mut col,
                encode_options.line_length,
            ).as_slice(),
        )?;
        remainder -= bytes_to_read;
    }

    if encode_options.parts > 1 {
        output.write_all(
            format!(
                "\r\n=yend size={} part={} pcrc32={:08x}\r\n",
                checksum.num_bytes,
                encode_options.part,
                checksum.crc
            ).as_bytes(),
        )?;
    } else {
        output.write_all(
            format!(
                "\r\n=yend size={} crc32={:08x}\r\n",
                checksum.num_bytes,
                checksum.crc
            ).as_bytes(),
        )?;
    }
    Ok(())
}

/// Encode the byte slice into a vector of yEncoded bytes.
///
/// Lines are wrapped with a maximum of `line_length` characters per line.
/// Does not include the header and footer lines. These are only produced
/// by `yencode_stream` and `yencode_file`.
pub fn yencode_buffer(input: &[u8], col: &mut u8, line_length: u8) -> Vec<u8> {
    let mut output = Vec::<u8>::with_capacity(input.len()); //TODO remove this heap alloc
    for &b in input {
        let mut encoded = [0u8; 2];
        let encoded_len = yencode_byte(b, &mut encoded);
        output.push(encoded[0]);
        if encoded_len > 1 {
            output.push(encoded[1]);
        }
        *col += encoded_len as u8;
        if *col >= line_length {
            output.push(CR);
            output.push(LF);
            *col = 0;
        }
    }
    output
}

#[inline]
fn yencode_byte(input_byte: u8, output: &mut [u8; 2]) -> usize {
    let mut idx = 0;
    let mut output_byte = input_byte.overflowing_add(42).0;
    match output_byte {
        NUL | CR | LF | ESCAPE => {
            output[idx] = ESCAPE;
            idx += 1;
            output_byte = output_byte.overflowing_add(64).0;
        }
        _ => {}
    };
    output[idx] = output_byte;
    idx + 1
}

#[cfg(test)]
mod tests {
    use super::{yencode_byte, yencode_buffer};
    use super::super::constants::{ESCAPE, CR, LF};

    #[test]
    fn escape_null() {
        let mut output = [0u8; 2];
        assert_eq!(2, yencode_byte(214, &mut output));
        assert_eq!(vec![ESCAPE, 0x40], output);
    }

    /*
    #[test]
    fn escape_tab() {
        let mut output = [0u8; 2];
        assert_eq!(2, yencode_byte(214 + TAB, &mut output));
        assert_eq!(vec![ESCAPE, 0x49], output);
    }
    */

    #[test]
    fn escape_lf() {
        let mut output = [0u8; 2];
        assert_eq!(2, yencode_byte(214 + LF, &mut output));
        assert_eq!(vec![ESCAPE, 0x4A], output);
    }

    #[test]
    fn escape_cr() {
        let mut output = [0u8; 2];
        assert_eq!(2, yencode_byte(214 + CR, &mut output));
        assert_eq!(vec![ESCAPE, 0x4D], output);
    }

    /*
    #[test]
    fn escape_space() {
        let mut output = [0u8; 2];
        assert_eq!(2, yencode_byte(214 + SPACE, &mut output));
        assert_eq!(vec![ESCAPE, 0x60], output);
    }
    */

    #[test]
    fn escape_equal_sign() {
        let mut output = [0u8; 2];
        assert_eq!(2, yencode_byte(ESCAPE - 42, &mut output));
        assert_eq!(vec![ESCAPE, 0x7D], output);
    }

    #[test]
    fn non_escaped() {
        let mut output = [0u8; 2];
        assert_eq!(1, yencode_byte(0, &mut output));
        assert_eq!(vec![42, 0], output);
    }
}
<commit_msg>Fix test case in documentation.<commit_after>use crc32;
use constants::{NUL, CR, LF, ESCAPE, DEFAULT_LINE_SIZE};

use std::fs::File;
use std::io;
use std::io::{Read, Write, Seek, SeekFrom};

/// Options for encoding
#[derive(Debug)]
pub struct EncodeOptions {
    line_length: u8,
    parts: u32,
    part: u32,
    begin: u64,
    end: u64,
}

impl Default for EncodeOptions {
    /// Constructs a new EncodeOptions instance, with the following defaults:
    /// line_length = 128.
    /// parts = 1,
    /// part = begin = end = 0
    fn default() -> Self {
        EncodeOptions {
            line_length: DEFAULT_LINE_SIZE,
            parts: 1,
            part: 0,
            begin: 0,
            end: 0,
        }
    }
}

impl EncodeOptions {
    /// Sets the maximum line length.
    pub fn line_length(mut self, line_length: u8) -> EncodeOptions {
        self.line_length = line_length;
        self
    }

    /// Sets the number of parts (default=1).
    /// When the number of parts equals 1, no '=ypart' line will be written
    /// in the ouput.
    pub fn parts(mut self, parts: u32) -> EncodeOptions {
        self.parts = parts;
        self
    }

    /// Sets the part number.
    /// Only used when `parts > 1`.
    /// The part number count starts at 1.
    pub fn part(mut self, part: u32) -> EncodeOptions {
        self.part = part;
        self
    }

    /// Sets the begin (which is the file offset + 1).
    /// Only used when `parts > 1`.
    /// The size of the part is `end - begin + 1`.
    pub fn begin(mut self, begin: u64) -> EncodeOptions {
        self.begin = begin;
        self
    }

    /// Sets the end.
    /// Only used when `parts > 1`.
    /// The size of the part is `end - begin + 1`.
    /// `end` should be larger than `begin`, otherwise an overflow error occurrs.
    pub fn end(mut self, end: u64) -> EncodeOptions {
        self.end = end;
        self
    }
}

/// Encodes the input file in a new output file.
/// # Example
/// ```rust,no_run
/// let mut file = std::fs::File::open("test1.bin").unwrap();
/// let encode_options = yenc::EncodeOptions::default().parts(1);
/// let mut output_file = std::fs::File::create("test1.bin.yenc").unwrap();
/// yenc::yencode_file(&mut file, "test1.bin", &encode_options, &mut output_file);
/// ```
/// # Errors
/// - when the output file already exists
///
pub fn yencode_file(
    input_file: &mut File,
    input_filename: &str,
    encode_options: &EncodeOptions,
    output: &mut Write,
) -> Result<(), io::Error> {
    let mut checksum = crc32::Crc32::new();
    let mut buffer = [0u8; 8192];
    let mut col = 0;

    if encode_options.parts == 1 {
        output.write_all(
            format!(
                "=ybegin line={} size={} name={}\r\n",
                encode_options.line_length,
                input_file.metadata()?.len(),
                input_filename
            ).as_bytes(),
        )?;
    } else {
        output.write_all(
            format!(
                "=ybegin part={} line={} size={} name={}\r\n",
                encode_options.part,
                encode_options.line_length,
                input_file.metadata()?.len(),
                input_filename
            ).as_bytes(),
        )?;
    }

    if encode_options.parts > 1 {
        output.write_all(
            format!(
                "=ypart begin={} end={}\r\n",
                encode_options.begin,
                encode_options.end
            ).as_bytes(),
        )?;
    }

    input_file.seek(SeekFrom::Start(encode_options.begin - 1))?;

    let mut remainder = (encode_options.end - encode_options.begin + 1) as usize;
    while remainder > 0 {
        let bytes_to_read = if remainder > buffer.len() {
            buffer.len()
        } else {
            remainder
        };
        input_file.read_exact(&mut buffer[0..bytes_to_read])?;
        checksum.update_with_slice(&buffer[0..bytes_to_read]);
        output.write_all(
            yencode_buffer(
                &buffer[0..bytes_to_read],
                &mut col,
                encode_options.line_length,
            ).as_slice(),
        )?;
        remainder -= bytes_to_read;
    }

    if encode_options.parts > 1 {
        output.write_all(
            format!(
                "\r\n=yend size={} part={} pcrc32={:08x}\r\n",
                checksum.num_bytes,
                encode_options.part,
                checksum.crc
            ).as_bytes(),
        )?;
    } else {
        output.write_all(
            format!(
                "\r\n=yend size={} crc32={:08x}\r\n",
                checksum.num_bytes,
                checksum.crc
            ).as_bytes(),
        )?;
    }
    Ok(())
}

/// Encode the byte slice into a vector of yEncoded bytes.
///
/// Lines are wrapped with a maximum of `line_length` characters per line.
/// Does not include the header and footer lines. These are only produced
/// by `yencode_stream` and `yencode_file`.
pub fn yencode_buffer(input: &[u8], col: &mut u8, line_length: u8) -> Vec<u8> {
    let mut output = Vec::<u8>::with_capacity(input.len()); //TODO remove this heap alloc
    for &b in input {
        let mut encoded = [0u8; 2];
        let encoded_len = yencode_byte(b, &mut encoded);
        output.push(encoded[0]);
        if encoded_len > 1 {
            output.push(encoded[1]);
        }
        *col += encoded_len as u8;
        if *col >= line_length {
            output.push(CR);
            output.push(LF);
            *col = 0;
        }
    }
    output
}

#[inline]
fn yencode_byte(input_byte: u8, output: &mut [u8; 2]) -> usize {
    let mut idx = 0;
    let mut output_byte = input_byte.overflowing_add(42).0;
    match output_byte {
        NUL | CR | LF | ESCAPE => {
            output[idx] = ESCAPE;
            idx += 1;
            output_byte = output_byte.overflowing_add(64).0;
        }
        _ => {}
    };
    output[idx] = output_byte;
    idx + 1
}

#[cfg(test)]
mod tests {
    use super::{yencode_byte, yencode_buffer};
    use super::super::constants::{ESCAPE, CR, LF};

    #[test]
    fn escape_null() {
        let mut output = [0u8; 2];
        assert_eq!(2, yencode_byte(214, &mut output));
        assert_eq!(vec![ESCAPE, 0x40], output);
    }

    /*
    #[test]
    fn escape_tab() {
        let mut output = [0u8; 2];
        assert_eq!(2, yencode_byte(214 + TAB, &mut output));
        assert_eq!(vec![ESCAPE, 0x49], output);
    }
    */

    #[test]
    fn escape_lf() {
        let mut output = [0u8; 2];
        assert_eq!(2, yencode_byte(214 + LF, &mut output));
        assert_eq!(vec![ESCAPE, 0x4A], output);
    }

    #[test]
    fn escape_cr() {
        let mut output = [0u8; 2];
        assert_eq!(2, yencode_byte(214 + CR, &mut output));
        assert_eq!(vec![ESCAPE, 0x4D], output);
    }

    /*
    #[test]
    fn escape_space() {
        let mut output = [0u8; 2];
        assert_eq!(2, yencode_byte(214 + SPACE, &mut output));
        assert_eq!(vec![ESCAPE, 0x60], output);
    }
    */

    #[test]
    fn escape_equal_sign() {
        let mut output = [0u8; 2];
        assert_eq!(2, yencode_byte(ESCAPE - 42, &mut output));
        assert_eq!(vec![ESCAPE, 0x7D], output);
    }

    #[test]
    fn non_escaped() {
        let mut output = [0u8; 2];
        assert_eq!(1, yencode_byte(0, &mut output));
        assert_eq!(vec![42, 0], output);
    }
}
<|endoftext|>