<commit_before>// In order to use YAML to define your CLI you must compile clap with the "yaml" feature becasue
// it's **not** included by default.
//
// In order to do this, ensure your Cargo.toml looks like one of the following:
//
// [dependencies.clap]
// features = ["yaml"]
//
// __OR__
//
// [dependencies]
// clap = { features = ["yaml"] }

// Using yaml requires calling a clap macro `load_yaml!()` so we must use the '#[macro_use]'
// directive
#[macro_use]
extern crate clap;

#[cfg(feature = "yaml")]
fn main() {
    use clap::App;

    // To load a yaml file containing our CLI definition such as the example '17_yaml.yml' we can
    // use the convenience macro which loads the file at compile relative to the current file
    // similiar to how modules are found.
    //
    // Then we pass that yaml object to App to build the CLI.
    //
    // Finally we call get_matches() to start the parsing process. We use the matches just as we
    // normally would
    let yml = load_yaml!("17_yaml.yml");
    let m = App::from_yaml(yml).get_matches();

    // Because the example 17_yaml.yml is rather large we'll just look a single arg so you can
    // see that it works...
    if let Some(mode) = m.value_of("mode") {
        match mode {
            "vi" => println!("You are using vi"),
            "emacs" => println!("You are using emacs..."),
            _ => unreachable!(),
        }
    } else {
        println!("--mode <MODE> wasn't used...");
    }
}

#[cfg(not(feature = "yaml"))]
fn main() {
    // As stated above, if clap is not compiled with the YAML feature, it is disabled.
    println!("YAML feature is disabled.");
    println!("Pass --features yaml to cargo when trying this example.");
}
<commit_msg>fix unused import warning<commit_after>// In order to use YAML to define your CLI you must compile clap with the "yaml" feature becasue
// it's **not** included by default.
//
// In order to do this, ensure your Cargo.toml looks like one of the following:
//
// [dependencies.clap]
// features = ["yaml"]
//
// __OR__
//
// [dependencies]
// clap = { features = ["yaml"] }

// Using yaml requires calling a clap macro `load_yaml!()` so we must use the '#[macro_use]'
// directive
#[cfg(feature = "yaml")]
#[macro_use]
extern crate clap;

#[cfg(feature = "yaml")]
fn main() {
    use clap::App;

    // To load a yaml file containing our CLI definition such as the example '17_yaml.yml' we can
    // use the convenience macro which loads the file at compile relative to the current file
    // similiar to how modules are found.
    //
    // Then we pass that yaml object to App to build the CLI.
    //
    // Finally we call get_matches() to start the parsing process. We use the matches just as we
    // normally would
    let yml = load_yaml!("17_yaml.yml");
    let m = App::from_yaml(yml).get_matches();

    // Because the example 17_yaml.yml is rather large we'll just look a single arg so you can
    // see that it works...
    if let Some(mode) = m.value_of("mode") {
        match mode {
            "vi" => println!("You are using vi"),
            "emacs" => println!("You are using emacs..."),
            _ => unreachable!(),
        }
    } else {
        println!("--mode <MODE> wasn't used...");
    }
}

#[cfg(not(feature = "yaml"))]
fn main() {
    // As stated above, if clap is not compiled with the YAML feature, it is disabled.
    println!("YAML feature is disabled.");
    println!("Pass --features yaml to cargo when trying this example.");
}
<|endoftext|>


<commit_before>use std::collections::{HashMap,HashSet};
use std::fs;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;

use command::Attribute;
use message::Message;
use message::Flag;

use command::store::StoreName;

/// Representation of a Folder
#[derive(Clone)]
pub struct Folder {
    // How many messages are in folder/new/
    recent: usize,
    // How many messages are in the folder total
    exists: usize,
    // How many messages are not marked with the Seen flag
    unseen: usize,
    // Whether the folder has been opened as read-only or not
    readonly: bool,
    path: PathBuf,
    messages: Vec<Message>,
    // A mapping of message uids to indices in folder.messages
    uid_to_seqnum: HashMap<usize, usize>
}

// Macro to open up a directory and do stuff with it after
macro_rules! make_vec_path(
    ($path:ident, $inp:ident, $str:expr, $next:expr) => ({
        match fs::read_dir(&($path.join($str))) {
            Err(_) => { None },
            Ok(res) => {
                let $inp = res;
                $next
            }
        }
    });
);

// Macro to handle each message in the folder
macro_rules! handle_message(
    ($msg_path_entry:ident, $uid_map:ident, $messages:ident, $i:ident, $unseen:ident) => ({
        match $msg_path_entry {
            Ok(msg_path) => {
                let message = match Message::new(msg_path.path().as_path()) {
                    Ok(message) => message,
                    _ => continue
                };
                if $unseen == !0usize && message.is_unseen() {
                    $unseen = $i;
                }
                $uid_map.insert(message.get_uid(), $i);
                $i += 1;
                $messages.push(message);
            },
            _ => { continue; }
        }
    });
);

// Perform a rename operation on a message
macro_rules! rename_message(
    ($msg:ident, $curpath:expr, $new_messages:ident) => ({
        match fs::rename($msg.get_path(), &$curpath) {
            Ok(_) => {
                // if the rename operation succeeded then clone the message,
                // update its path and add the clone to our new list
                $new_messages.push($msg.rename($curpath));
            }
            _ => {
                // if the rename failed, just add the old message to our
                // new list
                $new_messages.push($msg.clone());
            }
        }
    })
);

impl Folder {
    pub fn new(path: PathBuf, examine: bool) -> Option<Folder> {
        // the EXAMINE command is always read-only
        let readonly = if examine {
            true
        } else {
            // Test SELECT for read-only status
            // We use a lock file to determine write access on a folder
            match fs::File::open(&path.join(".lock")) {
                Err(_) => {
                    match fs::File::create(&path.join(".lock")) {
                        Ok(mut file) => {
                            // Get the compiler to STFU with this match
                            match file.write(b"selected") { _ => {} }
                            drop(file);
                            false
                        }
                        _ => true,
                    }
                }
                _ => true,
            }
        };
        make_vec_path!(path, cur, "cur",
            make_vec_path!(path, new, "new", {
                let mut messages = Vec::new();
                let mut uid_to_seqnum: HashMap<usize, usize> = HashMap::new();
                let mut i = 0usize;
                let mut unseen = !0usize;

                // populate messages
                for msg_path in cur {
                    handle_message!(msg_path, uid_to_seqnum, messages, i, unseen);
                }

                let old = i;

                for msg_path in new {
                    handle_message!(msg_path, uid_to_seqnum, messages, i, unseen);
                }

                // Move the messages from folder/new to folder/cur
                messages = move_new(&messages, path.as_path(), unseen-1);
                Some(Folder {
                    path: path,
                    recent: i-old,
                    unseen: unseen,
                    exists: i,
                    messages: messages,
                    readonly: readonly,
                    uid_to_seqnum: uid_to_seqnum,
                })
            })
        )
    }

    /// Generate the SELECT/EXAMINE response based on data in the folder
    pub fn select_response(&self, tag: &str) -> String {
        let unseen_res = if self.unseen <= self.exists {
            let unseen_str = self.unseen.to_string();
            let mut res = "* OK [UNSEEN ".to_string();
            res.push_str(&unseen_str[..]);
            res.push_str("] Message ");
            res.push_str(&unseen_str[..]);
            res.push_str("th is the first unseen\r\n");
            res
        } else {
            "".to_string()
        };

        let read_status = if self.readonly {
            "[READ-ONLY]"
        } else {
            "[READ-WRITE]"
        };

        // * <n> EXISTS
        // * <n> RECENT
        // * OK UNSEEN
        // * Flags - Should match values in enum Flag in message.rs
        // * OK PERMANENTFLAG - Should match values in enum Flag in message.rs
        // * OK UIDNEXT
        // * OK UIDVALIDITY
        format!("* {} EXISTS\r\n* {} RECENT\r\n{}* FLAGS (\\Answered \\Deleted \\Draft \\Flagged \\Seen)\r\n* OK [PERMANENTFLAGS (\\Answered \\Deleted \\Draft \\Flagged \\Seen)] Permanent flags\r\n{} OK {} SELECT command was successful\r\n",
                 self.exists, self.recent, unseen_res, tag, read_status)
    }

    /// Delete on disk all the messages marked for deletion
    /// Returns the list of sequence numbers which have been deleted on disk
    /// Per RFC 3501, the later sequence numbers are calculated based on the
    /// sequence numbers at the time of the deletion not at the start of the function
    pub fn expunge(&self) -> Vec<usize> {
        let mut result = Vec::new();
        // We can't perform the deletion if the folder has been opened as
        // read-only
        if !self.readonly {
            // Vectors are 0-indexed
            let mut index = 0usize;

            // self.messages will get smaller as we go through it
            while index < self.messages.len() {
                if self.messages[index].remove_if_deleted() {
                    // Sequence numbers are 1-indexed
                    result.push(index + 1);
                } else {
                    index += 1;
                }
            }
            // Get the compiler to STFU with empty match block
            match fs::remove_file(&self.path.join(".lock")) { _ => {} }
        }
        result
    }

    pub fn message_count(&self) -> usize {
        self.messages.len()
    }

    /// Perform a fetch of the specified attributes on self.messsages[index]
    /// Return the FETCH response string to be sent back to the client
    pub fn fetch(&self, index: usize, attributes: &[Attribute]) -> String {
        let mut res = "* ".to_string();
        res.push_str(&(index+1).to_string()[..]);
        res.push_str(" FETCH (");
        res.push_str(&self.messages[index].fetch(attributes)[..]);
        res.push_str(")\r\n");
        res
    }

    /// Turn a UID into a sequence number
    pub fn get_index_from_uid(&self, uid: &usize) -> Option<&usize> {
        self.uid_to_seqnum.get(uid)
    }

    /// Perform a STORE on the specified set of sequence numbers
    /// This modifies the flags of the specified messages
    /// Returns the String response to be sent back to the client.
    pub fn store(&mut self, sequence_set: Vec<usize>, flag_name: &StoreName,
                 silent: bool, flags: HashSet<Flag>, seq_uid: bool,
                 tag: &str) -> String {
        let mut responses = String::new();
        for num in &sequence_set {
            let (uid, i) = if seq_uid {
                match self.get_index_from_uid(num) {
                    // 0 is an invalid sequence number
                    // Return it if the UID isn't found
                    None => (*num, 0usize),
                    Some(ind) => (*num, *ind+1)
                }
            } else {
                (0usize, *num)
            };

            // if i == 0 then the UID wasn't in the sequence number map
            if i == 0 {
                continue;
            }

            // Create the FETCH response for this STORE operation.
            if let Some(mut message) = self.messages.get_mut(i-1) {
                responses.push_str("* ");
                responses.push_str(&i.to_string()[..]);
                responses.push_str(" FETCH (FLAGS ");
                responses.push_str(&message.store(flag_name, flags.clone())[..]);

                // UID STORE needs to respond with the UID for each FETCH response
                if seq_uid {
                    let uid_res = format!(" UID {}", uid);
                    responses.push_str(&uid_res[..]);
                }
                responses.push_str(" )\r\n");
            }
        }

        // Return an empty string if the client wanted the STORE to be SILENT
        if silent {
            responses = String::new();
        }
        responses.push_str(tag);
        responses.push_str(" OK STORE complete\r\n");
        responses
    }

    /// Reconcile the internal state of the folder with the disk.
    pub fn check(&mut self) {
        // If it is read-only we can't write any changes to disk
        if self.readonly {
            return;
        }

        // We need to create a new list of messages because the compiler will
        // yell at us for inspecting the internal state of the message and
        // modifying that state at the same time
        let mut new_messages = Vec::new();
        for msg in &self.messages {
            // Grab the new filename composed of this message's UID and its current flags.
            let filename = msg.get_new_filename();
            let curpath = self.path.join("cur").join(filename);

            // If the new filename is the same as the current filename, add the
            // current message to our new list and move on to the next message
            if curpath == msg.get_path() {
                new_messages.push(msg.clone());
                continue;
            }
            rename_message!(msg, curpath, new_messages);
        }

        // Set the current list of messages to the new list of messages
        // The compiler *should* make this discard the old list...
        self.messages = new_messages;
    }
}

/// This moves a list of messages from folder/new/ to folder/cur/ and returns a
/// new list of messages
fn move_new(messages: &[Message], path: &Path,
            start_index: usize) -> Vec<Message> {
    let mut new_messages = Vec::new();

    // Go over the messages by index
    for (i, msg) in messages.iter().enumerate() {
        // messages before start_index are already in folder/cur/
        if i < start_index {
            new_messages.push(msg.clone());
            continue;
        }
        let curpath = path.join("cur").join(msg.get_uid().to_string());
        rename_message!(msg, curpath, new_messages);
    }

    // Return the new list of messages
    new_messages
}
<commit_msg>impr: add `Debug` derive to `Folder`<commit_after>use std::collections::{HashMap,HashSet};
use std::fs;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;

use command::Attribute;
use message::Message;
use message::Flag;

use command::store::StoreName;

/// Representation of a Folder
#[derive(Clone, Debug)]
pub struct Folder {
    // How many messages are in folder/new/
    recent: usize,
    // How many messages are in the folder total
    exists: usize,
    // How many messages are not marked with the Seen flag
    unseen: usize,
    // Whether the folder has been opened as read-only or not
    readonly: bool,
    path: PathBuf,
    messages: Vec<Message>,
    // A mapping of message uids to indices in folder.messages
    uid_to_seqnum: HashMap<usize, usize>
}

// Macro to open up a directory and do stuff with it after
macro_rules! make_vec_path(
    ($path:ident, $inp:ident, $str:expr, $next:expr) => ({
        match fs::read_dir(&($path.join($str))) {
            Err(_) => { None },
            Ok(res) => {
                let $inp = res;
                $next
            }
        }
    });
);

// Macro to handle each message in the folder
macro_rules! handle_message(
    ($msg_path_entry:ident, $uid_map:ident, $messages:ident, $i:ident, $unseen:ident) => ({
        match $msg_path_entry {
            Ok(msg_path) => {
                let message = match Message::new(msg_path.path().as_path()) {
                    Ok(message) => message,
                    _ => continue
                };
                if $unseen == !0usize && message.is_unseen() {
                    $unseen = $i;
                }
                $uid_map.insert(message.get_uid(), $i);
                $i += 1;
                $messages.push(message);
            },
            _ => { continue; }
        }
    });
);

// Perform a rename operation on a message
macro_rules! rename_message(
    ($msg:ident, $curpath:expr, $new_messages:ident) => ({
        match fs::rename($msg.get_path(), &$curpath) {
            Ok(_) => {
                // if the rename operation succeeded then clone the message,
                // update its path and add the clone to our new list
                $new_messages.push($msg.rename($curpath));
            }
            _ => {
                // if the rename failed, just add the old message to our
                // new list
                $new_messages.push($msg.clone());
            }
        }
    })
);

impl Folder {
    pub fn new(path: PathBuf, examine: bool) -> Option<Folder> {
        // the EXAMINE command is always read-only
        let readonly = if examine {
            true
        } else {
            // Test SELECT for read-only status
            // We use a lock file to determine write access on a folder
            match fs::File::open(&path.join(".lock")) {
                Err(_) => {
                    match fs::File::create(&path.join(".lock")) {
                        Ok(mut file) => {
                            // Get the compiler to STFU with this match
                            match file.write(b"selected") { _ => {} }
                            drop(file);
                            false
                        }
                        _ => true,
                    }
                }
                _ => true,
            }
        };
        make_vec_path!(path, cur, "cur",
            make_vec_path!(path, new, "new", {
                let mut messages = Vec::new();
                let mut uid_to_seqnum: HashMap<usize, usize> = HashMap::new();
                let mut i = 0usize;
                let mut unseen = !0usize;

                // populate messages
                for msg_path in cur {
                    handle_message!(msg_path, uid_to_seqnum, messages, i, unseen);
                }

                let old = i;

                for msg_path in new {
                    handle_message!(msg_path, uid_to_seqnum, messages, i, unseen);
                }

                // Move the messages from folder/new to folder/cur
                messages = move_new(&messages, path.as_path(), unseen-1);
                Some(Folder {
                    path: path,
                    recent: i-old,
                    unseen: unseen,
                    exists: i,
                    messages: messages,
                    readonly: readonly,
                    uid_to_seqnum: uid_to_seqnum,
                })
            })
        )
    }

    /// Generate the SELECT/EXAMINE response based on data in the folder
    pub fn select_response(&self, tag: &str) -> String {
        let unseen_res = if self.unseen <= self.exists {
            let unseen_str = self.unseen.to_string();
            let mut res = "* OK [UNSEEN ".to_string();
            res.push_str(&unseen_str[..]);
            res.push_str("] Message ");
            res.push_str(&unseen_str[..]);
            res.push_str("th is the first unseen\r\n");
            res
        } else {
            "".to_string()
        };

        let read_status = if self.readonly {
            "[READ-ONLY]"
        } else {
            "[READ-WRITE]"
        };

        // * <n> EXISTS
        // * <n> RECENT
        // * OK UNSEEN
        // * Flags - Should match values in enum Flag in message.rs
        // * OK PERMANENTFLAG - Should match values in enum Flag in message.rs
        // * OK UIDNEXT
        // * OK UIDVALIDITY
        format!("* {} EXISTS\r\n* {} RECENT\r\n{}* FLAGS (\\Answered \\Deleted \\Draft \\Flagged \\Seen)\r\n* OK [PERMANENTFLAGS (\\Answered \\Deleted \\Draft \\Flagged \\Seen)] Permanent flags\r\n{} OK {} SELECT command was successful\r\n",
                 self.exists, self.recent, unseen_res, tag, read_status)
    }

    /// Delete on disk all the messages marked for deletion
    /// Returns the list of sequence numbers which have been deleted on disk
    /// Per RFC 3501, the later sequence numbers are calculated based on the
    /// sequence numbers at the time of the deletion not at the start of the function
    pub fn expunge(&self) -> Vec<usize> {
        let mut result = Vec::new();
        // We can't perform the deletion if the folder has been opened as
        // read-only
        if !self.readonly {
            // Vectors are 0-indexed
            let mut index = 0usize;

            // self.messages will get smaller as we go through it
            while index < self.messages.len() {
                if self.messages[index].remove_if_deleted() {
                    // Sequence numbers are 1-indexed
                    result.push(index + 1);
                } else {
                    index += 1;
                }
            }
            // Get the compiler to STFU with empty match block
            match fs::remove_file(&self.path.join(".lock")) { _ => {} }
        }
        result
    }

    pub fn message_count(&self) -> usize {
        self.messages.len()
    }

    /// Perform a fetch of the specified attributes on self.messsages[index]
    /// Return the FETCH response string to be sent back to the client
    pub fn fetch(&self, index: usize, attributes: &[Attribute]) -> String {
        let mut res = "* ".to_string();
        res.push_str(&(index+1).to_string()[..]);
        res.push_str(" FETCH (");
        res.push_str(&self.messages[index].fetch(attributes)[..]);
        res.push_str(")\r\n");
        res
    }

    /// Turn a UID into a sequence number
    pub fn get_index_from_uid(&self, uid: &usize) -> Option<&usize> {
        self.uid_to_seqnum.get(uid)
    }

    /// Perform a STORE on the specified set of sequence numbers
    /// This modifies the flags of the specified messages
    /// Returns the String response to be sent back to the client.
    pub fn store(&mut self, sequence_set: Vec<usize>, flag_name: &StoreName,
                 silent: bool, flags: HashSet<Flag>, seq_uid: bool,
                 tag: &str) -> String {
        let mut responses = String::new();
        for num in &sequence_set {
            let (uid, i) = if seq_uid {
                match self.get_index_from_uid(num) {
                    // 0 is an invalid sequence number
                    // Return it if the UID isn't found
                    None => (*num, 0usize),
                    Some(ind) => (*num, *ind+1)
                }
            } else {
                (0usize, *num)
            };

            // if i == 0 then the UID wasn't in the sequence number map
            if i == 0 {
                continue;
            }

            // Create the FETCH response for this STORE operation.
            if let Some(mut message) = self.messages.get_mut(i-1) {
                responses.push_str("* ");
                responses.push_str(&i.to_string()[..]);
                responses.push_str(" FETCH (FLAGS ");
                responses.push_str(&message.store(flag_name, flags.clone())[..]);

                // UID STORE needs to respond with the UID for each FETCH response
                if seq_uid {
                    let uid_res = format!(" UID {}", uid);
                    responses.push_str(&uid_res[..]);
                }
                responses.push_str(" )\r\n");
            }
        }

        // Return an empty string if the client wanted the STORE to be SILENT
        if silent {
            responses = String::new();
        }
        responses.push_str(tag);
        responses.push_str(" OK STORE complete\r\n");
        responses
    }

    /// Reconcile the internal state of the folder with the disk.
    pub fn check(&mut self) {
        // If it is read-only we can't write any changes to disk
        if self.readonly {
            return;
        }

        // We need to create a new list of messages because the compiler will
        // yell at us for inspecting the internal state of the message and
        // modifying that state at the same time
        let mut new_messages = Vec::new();
        for msg in &self.messages {
            // Grab the new filename composed of this message's UID and its current flags.
            let filename = msg.get_new_filename();
            let curpath = self.path.join("cur").join(filename);

            // If the new filename is the same as the current filename, add the
            // current message to our new list and move on to the next message
            if curpath == msg.get_path() {
                new_messages.push(msg.clone());
                continue;
            }
            rename_message!(msg, curpath, new_messages);
        }

        // Set the current list of messages to the new list of messages
        // The compiler *should* make this discard the old list...
        self.messages = new_messages;
    }
}

/// This moves a list of messages from folder/new/ to folder/cur/ and returns a
/// new list of messages
fn move_new(messages: &[Message], path: &Path,
            start_index: usize) -> Vec<Message> {
    let mut new_messages = Vec::new();

    // Go over the messages by index
    for (i, msg) in messages.iter().enumerate() {
        // messages before start_index are already in folder/cur/
        if i < start_index {
            new_messages.push(msg.clone());
            continue;
        }
        let curpath = path.join("cur").join(msg.get_uid().to_string());
        rename_message!(msg, curpath, new_messages);
    }

    // Return the new list of messages
    new_messages
}
<|endoftext|>


<commit_before>extern crate readline;

fn main() {
    loop {
        let input = match readline::readline("Next: ") {
            Some(input) => input,
            None => {
                println!("");
                break;
            },
        };

        if input == "quit" {
            break;
        }

        println!("Input: '{}'", input);
    }
}
<commit_msg>Update example to show adding a line to the history<commit_after>extern crate readline;

fn main() {
    loop {
        let input = match readline::readline("Next: ") {
            Some(input) => input,
            None => {
                println!("");
                break;
            },
        };

        if input == "quit" {
            break;
        }
        // add words that start with 'a' to the history to demonstrate
        else if input[0 .. 1] == "a".to_string() {
            readline::add_history(input.as_ref());
        }

        println!("Input: '{}'", input);
    }
}
<|endoftext|>


<commit_before>use std::collections::{HashMap, HashSet};
use std::cmp;

use bip_metainfo::{InfoDictionary, File};
use bip_util::bt::InfoHash;

use disk::error::{TorrentResult, TorrentError, TorrentErrorKind};
use disk::worker::disk_worker::piece_accessor::PieceAccessor;
use disk::fs::{FileSystem};
use message::standard::PieceMessage;

/// Calculates hashes on existing files within the file system given and reports good/bad pieces.
pub struct PieceChecker<'a, F> {
    fs:            F,
    info_dict:     &'a InfoDictionary,
    checker_state: PieceCheckerState
}

impl<'a, F> PieceChecker<'a, F> where F: FileSystem + 'a {
    /// Create a new PieceChecker with an initialized state.
    pub fn new(fs: F, info_dict: &'a InfoDictionary) -> TorrentResult<PieceChecker<'a, F>> {
        let total_blocks = info_dict.pieces().count();
        let last_piece_size = last_piece_size(info_dict);

        let mut piece_checker = PieceChecker::with_state(fs, info_dict, PieceCheckerState::new(total_blocks, last_piece_size));

        try!(piece_checker.validate_files_sizes());
        try!(piece_checker.fill_checker_state());

        Ok(piece_checker)
    }

    /// Create a new PieceChecker with the given state.
    pub fn with_state(fs: F, info_dict: &'a InfoDictionary, checker_state: PieceCheckerState) -> PieceChecker<'a, F> {
        PieceChecker {
            fs:            fs,
            info_dict:     info_dict,
            checker_state: checker_state
        }
    }

    /// Calculate the diff of old to new good/bad pieces and store them in the piece checker state
    /// to be retrieved by the caller.
    pub fn calculate_diff(mut self) -> TorrentResult<PieceCheckerState> {
        let piece_length = self.info_dict.piece_length() as u64;
        // TODO: Use Block Allocator
        let mut piece_buffer = vec![0u8; piece_length as usize];

        let info_dict = self.info_dict;
        let piece_accessor = PieceAccessor::new(&self.fs, self.info_dict);

        try!(self.checker_state.run_with_whole_pieces(piece_length as usize, |message| {
            try!(piece_accessor.read_piece(&mut piece_buffer[..message.block_length()], message));

            let calculated_hash = InfoHash::from_bytes(&piece_buffer[..message.block_length()]);
            let expected_hash = InfoHash::from_hash(info_dict
                .pieces()
                .skip(message.piece_index() as usize)
                .next()
                .expect("bip_peer: Piece Checker Failed To Retrieve Expected Hash"))
                .expect("bip_peer: Wrong Length Of Expected Hash Received");

            Ok(calculated_hash == expected_hash)
        }));

        Ok(self.checker_state)
    }

    /// Fill the PieceCheckerState with all piece messages for each file in our info dictionary.
    ///
    /// This is done once when a torrent file is added to see if we have any good pieces that
    /// the caller can use to skip (if the torrent was partially downloaded before).
    fn fill_checker_state(&mut self) -> TorrentResult<()> {
        let piece_length = self.info_dict.piece_length() as u64;
        let total_bytes: u64 = self.info_dict.files().map(|file| file.length() as u64).sum();

        let full_pieces = total_bytes / piece_length;
        let last_piece_size = last_piece_size(self.info_dict);

        for piece_index in 0..full_pieces {
            self.checker_state.add_pending_block(PieceMessage::new(piece_index as u32, 0, piece_length as usize));
        }

        if last_piece_size != 0 {
            self.checker_state.add_pending_block(PieceMessage::new(full_pieces as u32, 0, last_piece_size as usize));
        }

        Ok(())
    }

    /// Validates the file sizes for the given torrent file and block allocates them if they do not exist.
    ///
    /// This function will, if the file does not exist, or exists and is zero size, fill the file with zeroes.
    /// Otherwise, if the file exists and it is of the correct size, it will be left alone. If it is of the wrong
    /// size, an error will be thrown as we do not want to overwrite and existing file that maybe just had the same
    /// name as a file in our dictionary.
    fn validate_files_sizes(&mut self) -> TorrentResult<()> {
        for file in self.info_dict.files() {
            let file_path = build_path(self.info_dict.directory(), file);
            let expected_size = file.length() as u64;

            try!(self.fs.open_file(Some(&file_path))
                .map_err(|err| err.into())
                .and_then(|mut file| {
                // File May Or May Not Have Existed Before, If The File Is Zero
                // Length, Assume It Wasn't There (User Doesn't Lose Any Data)
                let actual_size = try!(self.fs.file_size(&file));

                let size_matches = actual_size == expected_size;
                let size_is_zero = actual_size == 0;

                if !size_matches && size_is_zero {
                    self.fs.write_file(&mut file, expected_size - 1, &[0])
                        .expect("bip_peer: Failed To Create File When Validating Sizes");
                } else if !size_matches {
                    return Err(TorrentError::from_kind(TorrentErrorKind::ExistingFileSizeCheck{
                        file_path: file_path,
                        expected_size: expected_size,
                        actual_size: actual_size
                    }))
                }

                Ok(())
            }));
        }

        Ok(())
    }
}

fn last_piece_size(info_dict: &InfoDictionary) -> usize {
    let piece_length = info_dict.piece_length() as u64;
    let total_bytes: u64 = info_dict.files().map(|file| file.length() as u64).sum();

    (total_bytes % piece_length) as usize
}

fn build_path(parent_directory: Option<&str>, file: &File) -> String {
    let parent_directory = parent_directory.unwrap_or(".");

    file.paths().fold(parent_directory.to_string(), |mut acc, item| {
        acc.push_str("/");
        acc.push_str(item);

        acc
    })
}

// ----------------------------------------------------------------------------//

/// Stores state for the PieceChecker between invocations.
pub struct PieceCheckerState {
    new_states:      Vec<PieceState>,
    old_states:      HashSet<PieceState>,
    pending_blocks:  HashMap<u32, Vec<PieceMessage>>,
    total_blocks:    usize,
    last_block_size: usize
}

#[derive(PartialEq, Eq, Hash)]
pub enum PieceState {
    /// Piece was discovered as good.
    Good(u32),
    /// Piece was discovered as bad.
    Bad(u32)
}

impl PieceCheckerState {
    /// Create a new PieceCheckerState.
    pub fn new(total_blocks: usize, last_block_size: usize) -> PieceCheckerState {
        PieceCheckerState {
            new_states: Vec::new(),
            old_states: HashSet::new(),
            pending_blocks: HashMap::new(),
            total_blocks: total_blocks,
            last_block_size: last_block_size
        }
    }

    /// Add a pending piece block to the current pending blocks.
    pub fn add_pending_block(&mut self, msg: PieceMessage) {
        self.pending_blocks.entry(msg.piece_index()).or_insert(Vec::new()).push(msg);
    }

    /// Run the given closures against NewGood and NewBad messages. Each of the messages will
    /// then either be dropped (NewBad) or converted to OldGood (NewGood).
    pub fn run_with_diff<F>(&mut self, mut callback: F)
        where F: FnMut(&PieceState) {
        for piece_state in self.new_states.drain(..) {
            callback(&piece_state);

            self.old_states.insert(piece_state);
        }
    }

    /// Pass any pieces that have not been identified as OldGood into the callback which determines
    /// if the piece is good or bad so it can be marked as NewGood or NewBad.
    fn run_with_whole_pieces<F>(&mut self, piece_length: usize, mut callback: F) -> TorrentResult<()>
        where F: FnMut(&PieceMessage) -> TorrentResult<bool> {
        self.merge_pieces();

        let mut new_states = &mut self.new_states;
        let old_states = &self.old_states;

        let total_blocks = self.total_blocks;
        let last_block_size = self.last_block_size;

        for messages in self.pending_blocks.values_mut()
            .filter(|ref messages| piece_is_complete(total_blocks, last_block_size, piece_length, messages))
            .filter(|ref messages| !old_states.contains(&PieceState::Good(messages[0].piece_index()))) {
            let is_good = try!(callback(&messages[0]));

            if is_good {
                new_states.push(PieceState::Good(messages[0].piece_index()));
            } else {
                new_states.push(PieceState::Bad(messages[0].piece_index()));
            }

            messages.clear();
        }

        Ok(())
    }

    /// Merges all pending piece messages into a single messages if possible.
    fn merge_pieces(&mut self) {
        for (_, ref mut messages) in self.pending_blocks.iter_mut() {
            // Sort the messages by their block offset
            messages.sort_by(|a, b| a.block_offset().cmp(&b.block_offset()));

            let mut messages_len = messages.len();
            let mut merge_success = true;
            // See if we can merge all messages into a single message
            while merge_success && messages_len > 1 {
                let actual_last = messages.remove(messages_len - 1);
                let second_last = messages.remove(messages_len - 2);

                let opt_merged =  merge_piece_messages(&actual_last, &second_last);
                if let Some(merged) = opt_merged {
                    messages.push(merged);
                } else {
                    messages.push(second_last);
                    messages.push(actual_last);

                    merge_success = false;
                }

                messages_len = messages.len();
            }
        }
    }
}

/// True if the piece is ready to be hashed and checked (full) as good or not.
fn piece_is_complete(total_blocks: usize, last_block_size: usize, piece_length: usize, messages: &[PieceMessage]) -> bool {
    let is_single_message = messages.len() == 1;
    let is_piece_length = messages.get(0)
        .map(|message| message.block_length() == piece_length)
        .unwrap_or(false);
    let is_last_block = messages.get(0)
        .map(|message| message.piece_index() == (total_blocks - 1) as u32)
        .unwrap_or(false);
    let is_last_block_length = messages.get(0)
        .map(|message| message.block_length() == last_block_size)
        .unwrap_or(false);

    is_single_message && (is_piece_length || (is_last_block && is_last_block_length))
}

/// Merge a piece message a with a piece message b if possible.
fn merge_piece_messages(message_a: &PieceMessage, message_b: &PieceMessage) -> Option<PieceMessage> {
    let start_a = message_a.block_offset();
    let end_a = start_a + message_a.block_length() as u32;

    let start_b = message_b.block_offset();
    let end_b = start_b + message_b.block_length() as u32;

    let max_end = cmp::max(end_a, end_b);

    // Check which start to use, assuming the start falls between the other messages start to end range
    if start_b >= start_a && start_b <= end_a {
        Some(PieceMessage::new(message_a.piece_index(), start_a, max_end as usize))
    } else if start_a >= start_b && start_a <= end_b {
        Some(PieceMessage::new(message_a.piece_index(), start_b, max_end as usize))
    } else {
        None
    }
}<commit_msg>Fix Bug With PieceChecker Where Length Was Incorrectly Calculated Causing Excessive Bad Pieces<commit_after>use std::collections::{HashMap, HashSet};
use std::cmp;

use bip_metainfo::{InfoDictionary, File};
use bip_util::bt::InfoHash;

use disk::error::{TorrentResult, TorrentError, TorrentErrorKind};
use disk::worker::disk_worker::piece_accessor::PieceAccessor;
use disk::fs::{FileSystem};
use message::standard::PieceMessage;

/// Calculates hashes on existing files within the file system given and reports good/bad pieces.
pub struct PieceChecker<'a, F> {
    fs:            F,
    info_dict:     &'a InfoDictionary,
    checker_state: PieceCheckerState
}

impl<'a, F> PieceChecker<'a, F> where F: FileSystem + 'a {
    /// Create a new PieceChecker with an initialized state.
    pub fn new(fs: F, info_dict: &'a InfoDictionary) -> TorrentResult<PieceChecker<'a, F>> {
        let total_blocks = info_dict.pieces().count();
        let last_piece_size = last_piece_size(info_dict);

        let mut piece_checker = PieceChecker::with_state(fs, info_dict, PieceCheckerState::new(total_blocks, last_piece_size));

        try!(piece_checker.validate_files_sizes());
        try!(piece_checker.fill_checker_state());

        Ok(piece_checker)
    }

    /// Create a new PieceChecker with the given state.
    pub fn with_state(fs: F, info_dict: &'a InfoDictionary, checker_state: PieceCheckerState) -> PieceChecker<'a, F> {
        PieceChecker {
            fs:            fs,
            info_dict:     info_dict,
            checker_state: checker_state
        }
    }

    /// Calculate the diff of old to new good/bad pieces and store them in the piece checker state
    /// to be retrieved by the caller.
    pub fn calculate_diff(mut self) -> TorrentResult<PieceCheckerState> {
        let piece_length = self.info_dict.piece_length() as u64;
        // TODO: Use Block Allocator
        let mut piece_buffer = vec![0u8; piece_length as usize];

        let info_dict = self.info_dict;
        let piece_accessor = PieceAccessor::new(&self.fs, self.info_dict);

        try!(self.checker_state.run_with_whole_pieces(piece_length as usize, |message| {
            try!(piece_accessor.read_piece(&mut piece_buffer[..message.block_length()], message));

            let calculated_hash = InfoHash::from_bytes(&piece_buffer[..message.block_length()]);
            let expected_hash = InfoHash::from_hash(info_dict
                .pieces()
                .skip(message.piece_index() as usize)
                .next()
                .expect("bip_peer: Piece Checker Failed To Retrieve Expected Hash"))
                .expect("bip_peer: Wrong Length Of Expected Hash Received");

            Ok(calculated_hash == expected_hash)
        }));

        Ok(self.checker_state)
    }

    /// Fill the PieceCheckerState with all piece messages for each file in our info dictionary.
    ///
    /// This is done once when a torrent file is added to see if we have any good pieces that
    /// the caller can use to skip (if the torrent was partially downloaded before).
    fn fill_checker_state(&mut self) -> TorrentResult<()> {
        let piece_length = self.info_dict.piece_length() as u64;
        let total_bytes: u64 = self.info_dict.files().map(|file| file.length() as u64).sum();

        let full_pieces = total_bytes / piece_length;
        let last_piece_size = last_piece_size(self.info_dict);

        for piece_index in 0..full_pieces {
            self.checker_state.add_pending_block(PieceMessage::new(piece_index as u32, 0, piece_length as usize));
        }

        if last_piece_size != 0 {
            self.checker_state.add_pending_block(PieceMessage::new(full_pieces as u32, 0, last_piece_size as usize));
        }

        Ok(())
    }

    /// Validates the file sizes for the given torrent file and block allocates them if they do not exist.
    ///
    /// This function will, if the file does not exist, or exists and is zero size, fill the file with zeroes.
    /// Otherwise, if the file exists and it is of the correct size, it will be left alone. If it is of the wrong
    /// size, an error will be thrown as we do not want to overwrite and existing file that maybe just had the same
    /// name as a file in our dictionary.
    fn validate_files_sizes(&mut self) -> TorrentResult<()> {
        for file in self.info_dict.files() {
            let file_path = build_path(self.info_dict.directory(), file);
            let expected_size = file.length() as u64;

            try!(self.fs.open_file(Some(&file_path))
                .map_err(|err| err.into())
                .and_then(|mut file| {
                // File May Or May Not Have Existed Before, If The File Is Zero
                // Length, Assume It Wasn't There (User Doesn't Lose Any Data)
                let actual_size = try!(self.fs.file_size(&file));

                let size_matches = actual_size == expected_size;
                let size_is_zero = actual_size == 0;

                if !size_matches && size_is_zero {
                    self.fs.write_file(&mut file, expected_size - 1, &[0])
                        .expect("bip_peer: Failed To Create File When Validating Sizes");
                } else if !size_matches {
                    return Err(TorrentError::from_kind(TorrentErrorKind::ExistingFileSizeCheck{
                        file_path: file_path,
                        expected_size: expected_size,
                        actual_size: actual_size
                    }))
                }

                Ok(())
            }));
        }

        Ok(())
    }
}

fn last_piece_size(info_dict: &InfoDictionary) -> usize {
    let piece_length = info_dict.piece_length() as u64;
    let total_bytes: u64 = info_dict.files().map(|file| file.length() as u64).sum();

    (total_bytes % piece_length) as usize
}

fn build_path(parent_directory: Option<&str>, file: &File) -> String {
    let parent_directory = parent_directory.unwrap_or(".");

    file.paths().fold(parent_directory.to_string(), |mut acc, item| {
        acc.push_str("/");
        acc.push_str(item);

        acc
    })
}

// ----------------------------------------------------------------------------//

/// Stores state for the PieceChecker between invocations.
pub struct PieceCheckerState {
    new_states:      Vec<PieceState>,
    old_states:      HashSet<PieceState>,
    pending_blocks:  HashMap<u32, Vec<PieceMessage>>,
    total_blocks:    usize,
    last_block_size: usize
}

#[derive(PartialEq, Eq, Hash)]
pub enum PieceState {
    /// Piece was discovered as good.
    Good(u32),
    /// Piece was discovered as bad.
    Bad(u32)
}

impl PieceCheckerState {
    /// Create a new PieceCheckerState.
    pub fn new(total_blocks: usize, last_block_size: usize) -> PieceCheckerState {
        PieceCheckerState {
            new_states: Vec::new(),
            old_states: HashSet::new(),
            pending_blocks: HashMap::new(),
            total_blocks: total_blocks,
            last_block_size: last_block_size
        }
    }

    /// Add a pending piece block to the current pending blocks.
    pub fn add_pending_block(&mut self, msg: PieceMessage) {
        self.pending_blocks.entry(msg.piece_index()).or_insert(Vec::new()).push(msg);
    }

    /// Run the given closures against NewGood and NewBad messages. Each of the messages will
    /// then either be dropped (NewBad) or converted to OldGood (NewGood).
    pub fn run_with_diff<F>(&mut self, mut callback: F)
        where F: FnMut(&PieceState) {
        for piece_state in self.new_states.drain(..) {
            callback(&piece_state);

            self.old_states.insert(piece_state);
        }
    }

    /// Pass any pieces that have not been identified as OldGood into the callback which determines
    /// if the piece is good or bad so it can be marked as NewGood or NewBad.
    fn run_with_whole_pieces<F>(&mut self, piece_length: usize, mut callback: F) -> TorrentResult<()>
        where F: FnMut(&PieceMessage) -> TorrentResult<bool> {
        self.merge_pieces();

        let mut new_states = &mut self.new_states;
        let old_states = &self.old_states;

        let total_blocks = self.total_blocks;
        let last_block_size = self.last_block_size;

        for messages in self.pending_blocks.values_mut()
            .filter(|ref messages| piece_is_complete(total_blocks, last_block_size, piece_length, messages))
            .filter(|ref messages| !old_states.contains(&PieceState::Good(messages[0].piece_index()))) {
            let is_good = try!(callback(&messages[0]));

            if is_good {
                new_states.push(PieceState::Good(messages[0].piece_index()));
            } else {
                new_states.push(PieceState::Bad(messages[0].piece_index()));
            }

            messages.clear();
        }

        Ok(())
    }

    /// Merges all pending piece messages into a single messages if possible.
    fn merge_pieces(&mut self) {
        for (_, ref mut messages) in self.pending_blocks.iter_mut() {
            // Sort the messages by their block offset
            messages.sort_by(|a, b| a.block_offset().cmp(&b.block_offset()));

            let mut messages_len = messages.len();
            let mut merge_success = true;
            // See if we can merge all messages into a single message
            while merge_success && messages_len > 1 {
                let actual_last = messages.pop().expect("bip_peer: Failed To Merge Blocks");
                let second_last = messages.pop().expect("bip_peer: Failed To Merge Blocks");

                let opt_merged =  merge_piece_messages(&second_last, &actual_last);
                if let Some(merged) = opt_merged {
                    messages.push(merged);
                } else {
                    messages.push(second_last);
                    messages.push(actual_last);

                    merge_success = false;
                }

                messages_len = messages.len();
            }
        }
    }
}

/// True if the piece is ready to be hashed and checked (full) as good or not.
fn piece_is_complete(total_blocks: usize, last_block_size: usize, piece_length: usize, messages: &[PieceMessage]) -> bool {
    let is_single_message = messages.len() == 1;
    let is_piece_length = messages.get(0)
        .map(|message| message.block_length() == piece_length)
        .unwrap_or(false);
    let is_last_block = messages.get(0)
        .map(|message| message.piece_index() == (total_blocks - 1) as u32)
        .unwrap_or(false);
    let is_last_block_length = messages.get(0)
        .map(|message| message.block_length() == last_block_size)
        .unwrap_or(false);

    is_single_message && (is_piece_length || (is_last_block && is_last_block_length))
}

/// Merge a piece message a with a piece message b if possible.
///
/// First message's block offset should come before (or at) the block offset of the second message.
fn merge_piece_messages(message_a: &PieceMessage, message_b: &PieceMessage) -> Option<PieceMessage> {
    // Check if the pieces overlap
    let start_a = message_a.block_offset();
    let end_a = start_a + message_a.block_length() as u32;

    let start_b = message_b.block_offset();
    let end_b = start_b + message_b.block_length() as u32;

    // If start b falls between start and end a, then start a is where we start, and we end at the max of end a
    // or end b, then calculate the length from end minus start. Vice versa if a falls between start and end b.
    if start_b >= start_a && start_b <= end_a {
        let end_to_take = cmp::max(end_a, end_b);
        let length = end_to_take - start_a;

        Some(PieceMessage::new(message_a.piece_index(), start_a, length as usize))
    } else if start_a >= start_b && start_a <= end_b {
        let end_to_take = cmp::max(end_a, end_b);
        let length = end_to_take - start_b;

        Some(PieceMessage::new(message_b.piece_index(), start_b, length as usize))
    } else {
        None
    }
}<|endoftext|>


<commit_before>extern crate hwloc;

use hwloc::Topology;

/// Example on how to check for specific topology support of a feature.
fn main() {
    let topo = Topology::new();

    // Check if CPU Binding is supported
    println!("CPU Binding supported: {}", topo.support().cpu().set_process());

    // Check if Memory Binding is supported
    println!("Memory Binding supported: {}", topo.support().memory().set_process());

    // Debug Print all the Support Flags
    println!("All Flags:\n{:?}", topo.support());
}
<commit_msg>more detailed support example<commit_after>extern crate hwloc;

use hwloc::Topology;

/// Example on how to check for specific topology support of a feature.
fn main() {
    let topo = Topology::new();

    // Check if Process Binding for CPUs is supported
    println!("CPU Binding (current process) supported: {}", topo.support().cpu().set_current_process());
    println!("CPU Binding (any process) supported: {}", topo.support().cpu().set_process());

    // Check if Thread Binding for CPUs is supported
    println!("CPU Binding (current thread) supported: {}", topo.support().cpu().set_current_thread());
    println!("CPU Binding (any thread) supported: {}", topo.support().cpu().set_thread());

    // Check if Memory Binding is supported
    println!("Memory Binding supported: {}", topo.support().memory().set_current_process());

    // Debug Print all the Support Flags
    println!("All Flags:\n{:?}", topo.support());
}
<|endoftext|>


<commit_before>use cairo;
use cairo::{MatrixTrait, PatternTrait};
use markup5ever::local_name;
use std::cell::RefCell;
use std::f64;

use crate::allowed_url::Fragment;
use crate::aspect_ratio::*;
use crate::bbox::*;
use crate::coord_units::CoordUnits;
use crate::drawing_ctx::{DrawingCtx, NodeStack};
use crate::error::{AttributeResultExt, RenderingError};
use crate::float_eq_cairo::ApproxEqCairo;
use crate::length::*;
use crate::node::*;
use crate::paint_server::{PaintSource, Resolve};
use crate::parsers::ParseValue;
use crate::properties::ComputedValues;
use crate::property_bag::PropertyBag;
use crate::rect::RectangleExt;
use crate::unit_interval::UnitInterval;
use crate::viewbox::*;

coord_units!(PatternUnits, CoordUnits::ObjectBoundingBox);
coord_units!(PatternContentUnits, CoordUnits::UserSpaceOnUse);

#[derive(Clone, Default)]
pub struct PatternData {
    pub units: Option<PatternUnits>,
    pub content_units: Option<PatternContentUnits>,
    // This Option<Option<ViewBox>> is a bit strange.  We want a field
    // with value None to mean, "this field isn't resolved yet".  However,
    // the vbox can very well be *not* specified in the SVG file.
    // In that case, the fully resolved pattern will have a .vbox=Some(None) value.
    pub vbox: Option<Option<ViewBox>>,
    pub preserve_aspect_ratio: Option<AspectRatio>,
    pub affine: Option<cairo::Matrix>,
    pub fallback: Option<Fragment>,
    pub x: Option<LengthHorizontal>,
    pub y: Option<LengthVertical>,
    pub width: Option<LengthHorizontal>,
    pub height: Option<LengthVertical>,

    // Point back to our corresponding node, or to the fallback node which has children.
    // If the value is None, it means we are fully resolved and didn't find any children
    // among the fallbacks.
    pub node: Option<RsvgWeakNode>,
}

macro_rules! fallback_to (
    ($dest:expr, $default:expr) => (
        $dest = $dest.take ().or ($default)
    );
);

impl Resolve for PatternData {
    fn is_resolved(&self) -> bool {
        self.units.is_some()
            && self.content_units.is_some()
            && self.vbox.is_some()
            && self.preserve_aspect_ratio.is_some()
            && self.affine.is_some()
            && self.x.is_some()
            && self.y.is_some()
            && self.width.is_some()
            && self.height.is_some()
            && self.children_are_resolved()
    }

    fn resolve_from_fallback(&mut self, fallback: &PatternData) {
        fallback_to!(self.units, fallback.units);
        fallback_to!(self.content_units, fallback.content_units);
        fallback_to!(self.vbox, fallback.vbox);
        fallback_to!(self.preserve_aspect_ratio, fallback.preserve_aspect_ratio);
        fallback_to!(self.affine, fallback.affine);
        fallback_to!(self.x, fallback.x);
        fallback_to!(self.y, fallback.y);
        fallback_to!(self.width, fallback.width);
        fallback_to!(self.height, fallback.height);

        self.fallback = fallback.fallback.clone();

        if !self.children_are_resolved() {
            if fallback.node.is_some() {
                self.node = fallback.node.clone();
            } else {
                self.node = None;
            }
        }
    }

    fn resolve_from_defaults(&mut self) {
        fallback_to!(self.units, Some(PatternUnits::default()));
        fallback_to!(self.content_units, Some(PatternContentUnits::default()));
        fallback_to!(self.vbox, Some(None));
        fallback_to!(self.preserve_aspect_ratio, Some(AspectRatio::default()));
        fallback_to!(self.affine, Some(cairo::Matrix::identity()));
        fallback_to!(self.x, Some(Default::default()));
        fallback_to!(self.y, Some(Default::default()));
        fallback_to!(self.width, Some(Default::default()));
        fallback_to!(self.height, Some(Default::default()));
    }
}

impl PatternData {
    fn children_are_resolved(&self) -> bool {
        if let Some(ref weak) = self.node {
            let strong_node = &weak.clone().upgrade().unwrap();
            strong_node.has_children()
        } else {
            // We are an empty pattern; there is nothing further that
            // can be resolved for children.
            true
        }
    }
}

pub struct NodePattern {
    pattern: RefCell<PatternData>,
}

impl Default for NodePattern {
    fn default() -> NodePattern {
        NodePattern {
            pattern: RefCell::new(PatternData::default()),
        }
    }
}

impl NodeTrait for NodePattern {
    fn set_atts(&mut self, _: Option<&RsvgNode>, pbag: &PropertyBag<'_>) -> NodeResult {
        let mut p = self.pattern.borrow_mut();

        for (attr, value) in pbag.iter() {
            match attr {
                local_name!("patternUnits") => p.units = Some(attr.parse(value)?),

                local_name!("patternContentUnits") => p.content_units = Some(attr.parse(value)?),

                local_name!("viewBox") => p.vbox = Some(Some(attr.parse(value)?)),

                local_name!("preserveAspectRatio") => {
                    p.preserve_aspect_ratio = Some(attr.parse(value)?)
                }

                local_name!("patternTransform") => p.affine = Some(attr.parse(value)?),

                local_name!("xlink:href") => {
                    p.fallback = Some(Fragment::parse(value).attribute(attr)?);
                }

                local_name!("x") => p.x = Some(attr.parse(value)?),

                local_name!("y") => p.y = Some(attr.parse(value)?),

                local_name!("width") => {
                    p.width =
                        Some(attr.parse_and_validate(value, LengthHorizontal::check_nonnegative)?)
                }

                local_name!("height") => {
                    p.height =
                        Some(attr.parse_and_validate(value, LengthVertical::check_nonnegative)?)
                }

                _ => (),
            }
        }

        Ok(())
    }

    fn overflow_hidden(&self) -> bool {
        true
    }
}

impl PaintSource for NodePattern {
    type Source = PatternData;

    fn resolve(
        &self,
        node: &RsvgNode,
        draw_ctx: &mut DrawingCtx,
        _bbox: &BoundingBox,
    ) -> Result<Option<Self::Source>, RenderingError> {
        {
            // Initialize pattern.node and release the mutable borrow
            let mut p = self.pattern.borrow_mut();
            p.node = Some(node.downgrade());
        }

        let mut result = node
            .borrow()
            .get_impl::<NodePattern>()
            .pattern
            .borrow()
            .clone();
        let mut stack = NodeStack::new();

        while !result.is_resolved() {
            if let Some(acquired) = draw_ctx
                .acquired_nodes()
                .get_node_of_type(result.fallback.as_ref(), NodeType::Pattern)
            {
                let a_node = acquired.get();

                if stack.contains(a_node) {
                    rsvg_log!("circular reference in pattern {}", node);
                    return Err(RenderingError::CircularReference);
                }

                let node_data = a_node.borrow();
                let fallback = node_data.get_impl::<NodePattern>().pattern.borrow();
                result.resolve_from_fallback(&fallback);

                stack.push(a_node);
            } else {
                result.resolve_from_defaults();
            }
        }

        Ok(Some(result))
    }

    fn set_pattern_on_draw_context(
        &self,
        pattern: &Self::Source,
        values: &ComputedValues,
        draw_ctx: &mut DrawingCtx,
        _opacity: &UnitInterval,
        bbox: &BoundingBox,
    ) -> Result<bool, RenderingError> {
        assert!(pattern.is_resolved());

        if pattern.node.is_none() {
            // This means we didn't find any children among the fallbacks,
            // so there is nothing to render.
            return Ok(false);
        }

        let units = pattern.units.unwrap();
        let content_units = pattern.content_units.unwrap();
        let pattern_affine = pattern.affine.unwrap();
        let vbox = pattern.vbox.unwrap();
        let preserve_aspect_ratio = pattern.preserve_aspect_ratio.unwrap();

        let (pattern_x, pattern_y, pattern_width, pattern_height) = {
            let params = if units == PatternUnits(CoordUnits::ObjectBoundingBox) {
                draw_ctx.push_view_box(1.0, 1.0)
            } else {
                draw_ctx.get_view_params()
            };

            let pattern_x = pattern.x.unwrap().normalize(values, &params);
            let pattern_y = pattern.y.unwrap().normalize(values, &params);
            let pattern_width = pattern.width.unwrap().normalize(values, &params);
            let pattern_height = pattern.height.unwrap().normalize(values, &params);

            (pattern_x, pattern_y, pattern_width, pattern_height)
        };

        // Work out the size of the rectangle so it takes into account the object bounding box

        let bbwscale: f64;
        let bbhscale: f64;

        match units {
            PatternUnits(CoordUnits::ObjectBoundingBox) => {
                let bbrect = bbox.rect.unwrap();
                bbwscale = bbrect.width;
                bbhscale = bbrect.height;
            }

            PatternUnits(CoordUnits::UserSpaceOnUse) => {
                bbwscale = 1.0;
                bbhscale = 1.0;
            }
        }

        let cr = draw_ctx.get_cairo_context();
        let affine = cr.get_matrix();
        let taffine = cairo::Matrix::multiply(&pattern_affine, &affine);

        let mut scwscale = (taffine.xx * taffine.xx + taffine.xy * taffine.xy).sqrt();
        let mut schscale = (taffine.yx * taffine.yx + taffine.yy * taffine.yy).sqrt();

        let pw: i32 = (pattern_width * bbwscale * scwscale) as i32;
        let ph: i32 = (pattern_height * bbhscale * schscale) as i32;

        let scaled_width = pattern_width * bbwscale;
        let scaled_height = pattern_height * bbhscale;

        if scaled_width.abs() < f64::EPSILON
            || scaled_height.abs() < f64::EPSILON
            || pw < 1
            || ph < 1
        {
            return Ok(false);
        }

        scwscale = f64::from(pw) / scaled_width;
        schscale = f64::from(ph) / scaled_height;

        let mut affine: cairo::Matrix = cairo::Matrix::identity();

        // Create the pattern coordinate system
        match units {
            PatternUnits(CoordUnits::ObjectBoundingBox) => {
                let bbrect = bbox.rect.unwrap();
                affine.translate(
                    bbrect.x + pattern_x * bbrect.width,
                    bbrect.y + pattern_y * bbrect.height,
                );
            }

            PatternUnits(CoordUnits::UserSpaceOnUse) => {
                affine.translate(pattern_x, pattern_y);
            }
        }

        // Apply the pattern transform
        affine = cairo::Matrix::multiply(&affine, &pattern_affine);

        let mut caffine: cairo::Matrix;

        // Create the pattern contents coordinate system
        let _params = if let Some(vbox) = vbox {
            // If there is a vbox, use that
            let (mut x, mut y, w, h) = preserve_aspect_ratio.compute(
                &vbox,
                &cairo::Rectangle::new(
                    0.0,
                    0.0,
                    pattern_width * bbwscale,
                    pattern_height * bbhscale,
                ),
            );

            x -= vbox.x * w / vbox.width;
            y -= vbox.y * h / vbox.height;

            caffine = cairo::Matrix::new(w / vbox.width, 0.0, 0.0, h / vbox.height, x, y);

            draw_ctx.push_view_box(vbox.width, vbox.height)
        } else if content_units == PatternContentUnits(CoordUnits::ObjectBoundingBox) {
            // If coords are in terms of the bounding box, use them
            let bbrect = bbox.rect.unwrap();

            caffine = cairo::Matrix::identity();
            caffine.scale(bbrect.width, bbrect.height);

            draw_ctx.push_view_box(1.0, 1.0)
        } else {
            caffine = cairo::Matrix::identity();
            draw_ctx.get_view_params()
        };

        if !scwscale.approx_eq_cairo(&1.0) || !schscale.approx_eq_cairo(&1.0) {
            let mut scalematrix = cairo::Matrix::identity();
            scalematrix.scale(scwscale, schscale);
            caffine = cairo::Matrix::multiply(&caffine, &scalematrix);

            scalematrix = cairo::Matrix::identity();
            scalematrix.scale(1.0 / scwscale, 1.0 / schscale);

            affine = cairo::Matrix::multiply(&scalematrix, &affine);
        }

        // Draw to another surface

        let cr_save = draw_ctx.get_cairo_context();

        let surface = cr_save
            .get_target()
            .create_similar(cairo::Content::ColorAlpha, pw, ph);

        let cr_pattern = cairo::Context::new(&surface);

        draw_ctx.set_cairo_context(&cr_pattern);

        // Set up transformations to be determined by the contents units

        // Draw everything
        let pattern_node = pattern.node.as_ref().unwrap().upgrade().unwrap();
        let pattern_cascaded = CascadedValues::new_from_node(&pattern_node);
        let pattern_values = pattern_cascaded.get();

        cr_pattern.set_matrix(caffine);

        let res = draw_ctx.with_discrete_layer(&pattern_node, pattern_values, false, &mut |dc| {
            pattern_node.draw_children(&pattern_cascaded, dc, false)
        });

        // Return to the original coordinate system and rendering context

        draw_ctx.set_cairo_context(&cr_save);

        // Set the final surface as a Cairo pattern into the Cairo context

        let surface_pattern = cairo::SurfacePattern::create(&surface);
        surface_pattern.set_extend(cairo::Extend::Repeat);

        let mut matrix = affine;
        matrix.invert();

        surface_pattern.set_matrix(matrix);
        surface_pattern.set_filter(cairo::Filter::Best);

        cr_save.set_source(&cairo::Pattern::SurfacePattern(surface_pattern));

        res.and_then(|_| Ok(true))
    }
}

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

    #[test]
    fn pattern_resolved_from_defaults_is_really_resolved() {
        let mut pat = PatternData::default();

        pat.resolve_from_defaults();
        assert!(pat.is_resolved());
    }
}
<commit_msg>pattern: partially remove interior mutability<commit_after>use cairo;
use cairo::{MatrixTrait, PatternTrait};
use markup5ever::local_name;
use std::cell::RefCell;
use std::f64;

use crate::allowed_url::Fragment;
use crate::aspect_ratio::*;
use crate::bbox::*;
use crate::coord_units::CoordUnits;
use crate::drawing_ctx::{DrawingCtx, NodeStack};
use crate::error::{AttributeResultExt, RenderingError};
use crate::float_eq_cairo::ApproxEqCairo;
use crate::length::*;
use crate::node::*;
use crate::paint_server::{PaintSource, Resolve};
use crate::parsers::ParseValue;
use crate::properties::ComputedValues;
use crate::property_bag::PropertyBag;
use crate::rect::RectangleExt;
use crate::unit_interval::UnitInterval;
use crate::viewbox::*;

coord_units!(PatternUnits, CoordUnits::ObjectBoundingBox);
coord_units!(PatternContentUnits, CoordUnits::UserSpaceOnUse);

#[derive(Clone, Default)]
pub struct PatternData {
    pub units: Option<PatternUnits>,
    pub content_units: Option<PatternContentUnits>,
    // This Option<Option<ViewBox>> is a bit strange.  We want a field
    // with value None to mean, "this field isn't resolved yet".  However,
    // the vbox can very well be *not* specified in the SVG file.
    // In that case, the fully resolved pattern will have a .vbox=Some(None) value.
    pub vbox: Option<Option<ViewBox>>,
    pub preserve_aspect_ratio: Option<AspectRatio>,
    pub affine: Option<cairo::Matrix>,
    pub fallback: Option<Fragment>,
    pub x: Option<LengthHorizontal>,
    pub y: Option<LengthVertical>,
    pub width: Option<LengthHorizontal>,
    pub height: Option<LengthVertical>,

    // Point back to our corresponding node, or to the fallback node which has children.
    // If the value is None, it means we are fully resolved and didn't find any children
    // among the fallbacks.
    pub node: RefCell<Option<RsvgWeakNode>>,
}

macro_rules! fallback_to (
    ($dest:expr, $default:expr) => (
        $dest = $dest.take ().or ($default)
    );
);

impl Resolve for PatternData {
    fn is_resolved(&self) -> bool {
        self.units.is_some()
            && self.content_units.is_some()
            && self.vbox.is_some()
            && self.preserve_aspect_ratio.is_some()
            && self.affine.is_some()
            && self.x.is_some()
            && self.y.is_some()
            && self.width.is_some()
            && self.height.is_some()
            && self.children_are_resolved()
    }

    fn resolve_from_fallback(&mut self, fallback: &PatternData) {
        fallback_to!(self.units, fallback.units);
        fallback_to!(self.content_units, fallback.content_units);
        fallback_to!(self.vbox, fallback.vbox);
        fallback_to!(self.preserve_aspect_ratio, fallback.preserve_aspect_ratio);
        fallback_to!(self.affine, fallback.affine);
        fallback_to!(self.x, fallback.x);
        fallback_to!(self.y, fallback.y);
        fallback_to!(self.width, fallback.width);
        fallback_to!(self.height, fallback.height);

        self.fallback = fallback.fallback.clone();

        if !self.children_are_resolved() {
            if fallback.node.borrow().is_some() {
                *self.node.borrow_mut() = fallback.node.borrow().clone();
            } else {
                *self.node.borrow_mut() = None;
            }
        }
    }

    fn resolve_from_defaults(&mut self) {
        fallback_to!(self.units, Some(PatternUnits::default()));
        fallback_to!(self.content_units, Some(PatternContentUnits::default()));
        fallback_to!(self.vbox, Some(None));
        fallback_to!(self.preserve_aspect_ratio, Some(AspectRatio::default()));
        fallback_to!(self.affine, Some(cairo::Matrix::identity()));
        fallback_to!(self.x, Some(Default::default()));
        fallback_to!(self.y, Some(Default::default()));
        fallback_to!(self.width, Some(Default::default()));
        fallback_to!(self.height, Some(Default::default()));
    }
}

impl PatternData {
    fn children_are_resolved(&self) -> bool {
        if let Some(ref weak) = *self.node.borrow() {
            let strong_node = &weak.clone().upgrade().unwrap();
            strong_node.has_children()
        } else {
            // We are an empty pattern; there is nothing further that
            // can be resolved for children.
            true
        }
    }
}

pub struct NodePattern {
    pattern: PatternData,
}

impl Default for NodePattern {
    fn default() -> NodePattern {
        NodePattern {
            pattern: PatternData::default(),
        }
    }
}

impl NodeTrait for NodePattern {
    fn set_atts(&mut self, _: Option<&RsvgNode>, pbag: &PropertyBag<'_>) -> NodeResult {
        for (attr, value) in pbag.iter() {
            match attr {
                local_name!("patternUnits") => self.pattern.units = Some(attr.parse(value)?),
                local_name!("patternContentUnits") => {
                    self.pattern.content_units = Some(attr.parse(value)?)
                }
                local_name!("viewBox") => self.pattern.vbox = Some(Some(attr.parse(value)?)),
                local_name!("preserveAspectRatio") => {
                    self.pattern.preserve_aspect_ratio = Some(attr.parse(value)?)
                }
                local_name!("patternTransform") => self.pattern.affine = Some(attr.parse(value)?),
                local_name!("xlink:href") => {
                    self.pattern.fallback = Some(Fragment::parse(value).attribute(attr)?);
                }
                local_name!("x") => self.pattern.x = Some(attr.parse(value)?),
                local_name!("y") => self.pattern.y = Some(attr.parse(value)?),
                local_name!("width") => {
                    self.pattern.width =
                        Some(attr.parse_and_validate(value, LengthHorizontal::check_nonnegative)?)
                }
                local_name!("height") => {
                    self.pattern.height =
                        Some(attr.parse_and_validate(value, LengthVertical::check_nonnegative)?)
                }
                _ => (),
            }
        }

        Ok(())
    }

    fn overflow_hidden(&self) -> bool {
        true
    }
}

impl PaintSource for NodePattern {
    type Source = PatternData;

    fn resolve(
        &self,
        node: &RsvgNode,
        draw_ctx: &mut DrawingCtx,
        _bbox: &BoundingBox,
    ) -> Result<Option<Self::Source>, RenderingError> {
        *self.pattern.node.borrow_mut() = Some(node.downgrade());

        let mut result = node.borrow().get_impl::<NodePattern>().pattern.clone();
        let mut stack = NodeStack::new();

        while !result.is_resolved() {
            if let Some(acquired) = draw_ctx
                .acquired_nodes()
                .get_node_of_type(result.fallback.as_ref(), NodeType::Pattern)
            {
                let a_node = acquired.get();

                if stack.contains(a_node) {
                    rsvg_log!("circular reference in pattern {}", node);
                    return Err(RenderingError::CircularReference);
                }

                let node_data = a_node.borrow();
                result.resolve_from_fallback(&node_data.get_impl::<NodePattern>().pattern);

                stack.push(a_node);
            } else {
                result.resolve_from_defaults();
            }
        }

        Ok(Some(result))
    }

    fn set_pattern_on_draw_context(
        &self,
        pattern: &Self::Source,
        values: &ComputedValues,
        draw_ctx: &mut DrawingCtx,
        _opacity: &UnitInterval,
        bbox: &BoundingBox,
    ) -> Result<bool, RenderingError> {
        assert!(pattern.is_resolved());

        if pattern.node.borrow().is_none() {
            // This means we didn't find any children among the fallbacks,
            // so there is nothing to render.
            return Ok(false);
        }

        let units = pattern.units.unwrap();
        let content_units = pattern.content_units.unwrap();
        let pattern_affine = pattern.affine.unwrap();
        let vbox = pattern.vbox.unwrap();
        let preserve_aspect_ratio = pattern.preserve_aspect_ratio.unwrap();

        let (pattern_x, pattern_y, pattern_width, pattern_height) = {
            let params = if units == PatternUnits(CoordUnits::ObjectBoundingBox) {
                draw_ctx.push_view_box(1.0, 1.0)
            } else {
                draw_ctx.get_view_params()
            };

            let pattern_x = pattern.x.unwrap().normalize(values, &params);
            let pattern_y = pattern.y.unwrap().normalize(values, &params);
            let pattern_width = pattern.width.unwrap().normalize(values, &params);
            let pattern_height = pattern.height.unwrap().normalize(values, &params);

            (pattern_x, pattern_y, pattern_width, pattern_height)
        };

        // Work out the size of the rectangle so it takes into account the object bounding box

        let bbwscale: f64;
        let bbhscale: f64;

        match units {
            PatternUnits(CoordUnits::ObjectBoundingBox) => {
                let bbrect = bbox.rect.unwrap();
                bbwscale = bbrect.width;
                bbhscale = bbrect.height;
            }

            PatternUnits(CoordUnits::UserSpaceOnUse) => {
                bbwscale = 1.0;
                bbhscale = 1.0;
            }
        }

        let cr = draw_ctx.get_cairo_context();
        let affine = cr.get_matrix();
        let taffine = cairo::Matrix::multiply(&pattern_affine, &affine);

        let mut scwscale = (taffine.xx * taffine.xx + taffine.xy * taffine.xy).sqrt();
        let mut schscale = (taffine.yx * taffine.yx + taffine.yy * taffine.yy).sqrt();

        let pw: i32 = (pattern_width * bbwscale * scwscale) as i32;
        let ph: i32 = (pattern_height * bbhscale * schscale) as i32;

        let scaled_width = pattern_width * bbwscale;
        let scaled_height = pattern_height * bbhscale;

        if scaled_width.abs() < f64::EPSILON
            || scaled_height.abs() < f64::EPSILON
            || pw < 1
            || ph < 1
        {
            return Ok(false);
        }

        scwscale = f64::from(pw) / scaled_width;
        schscale = f64::from(ph) / scaled_height;

        let mut affine: cairo::Matrix = cairo::Matrix::identity();

        // Create the pattern coordinate system
        match units {
            PatternUnits(CoordUnits::ObjectBoundingBox) => {
                let bbrect = bbox.rect.unwrap();
                affine.translate(
                    bbrect.x + pattern_x * bbrect.width,
                    bbrect.y + pattern_y * bbrect.height,
                );
            }

            PatternUnits(CoordUnits::UserSpaceOnUse) => {
                affine.translate(pattern_x, pattern_y);
            }
        }

        // Apply the pattern transform
        affine = cairo::Matrix::multiply(&affine, &pattern_affine);

        let mut caffine: cairo::Matrix;

        // Create the pattern contents coordinate system
        let _params = if let Some(vbox) = vbox {
            // If there is a vbox, use that
            let (mut x, mut y, w, h) = preserve_aspect_ratio.compute(
                &vbox,
                &cairo::Rectangle::new(
                    0.0,
                    0.0,
                    pattern_width * bbwscale,
                    pattern_height * bbhscale,
                ),
            );

            x -= vbox.x * w / vbox.width;
            y -= vbox.y * h / vbox.height;

            caffine = cairo::Matrix::new(w / vbox.width, 0.0, 0.0, h / vbox.height, x, y);

            draw_ctx.push_view_box(vbox.width, vbox.height)
        } else if content_units == PatternContentUnits(CoordUnits::ObjectBoundingBox) {
            // If coords are in terms of the bounding box, use them
            let bbrect = bbox.rect.unwrap();

            caffine = cairo::Matrix::identity();
            caffine.scale(bbrect.width, bbrect.height);

            draw_ctx.push_view_box(1.0, 1.0)
        } else {
            caffine = cairo::Matrix::identity();
            draw_ctx.get_view_params()
        };

        if !scwscale.approx_eq_cairo(&1.0) || !schscale.approx_eq_cairo(&1.0) {
            let mut scalematrix = cairo::Matrix::identity();
            scalematrix.scale(scwscale, schscale);
            caffine = cairo::Matrix::multiply(&caffine, &scalematrix);

            scalematrix = cairo::Matrix::identity();
            scalematrix.scale(1.0 / scwscale, 1.0 / schscale);

            affine = cairo::Matrix::multiply(&scalematrix, &affine);
        }

        // Draw to another surface

        let cr_save = draw_ctx.get_cairo_context();

        let surface = cr_save
            .get_target()
            .create_similar(cairo::Content::ColorAlpha, pw, ph);

        let cr_pattern = cairo::Context::new(&surface);

        draw_ctx.set_cairo_context(&cr_pattern);

        // Set up transformations to be determined by the contents units

        // Draw everything
        let pattern_node = pattern.node.borrow().as_ref().unwrap().upgrade().unwrap();
        let pattern_cascaded = CascadedValues::new_from_node(&pattern_node);
        let pattern_values = pattern_cascaded.get();

        cr_pattern.set_matrix(caffine);

        let res = draw_ctx.with_discrete_layer(&pattern_node, pattern_values, false, &mut |dc| {
            pattern_node.draw_children(&pattern_cascaded, dc, false)
        });

        // Return to the original coordinate system and rendering context

        draw_ctx.set_cairo_context(&cr_save);

        // Set the final surface as a Cairo pattern into the Cairo context

        let surface_pattern = cairo::SurfacePattern::create(&surface);
        surface_pattern.set_extend(cairo::Extend::Repeat);

        let mut matrix = affine;
        matrix.invert();

        surface_pattern.set_matrix(matrix);
        surface_pattern.set_filter(cairo::Filter::Best);

        cr_save.set_source(&cairo::Pattern::SurfacePattern(surface_pattern));

        res.and_then(|_| Ok(true))
    }
}

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

    #[test]
    fn pattern_resolved_from_defaults_is_really_resolved() {
        let mut pat = PatternData::default();

        pat.resolve_from_defaults();
        assert!(pat.is_resolved());
    }
}
<|endoftext|>


<commit_before>// See LICENSE file for copyright and license details.

use cgmath::vector::{Vector, Vector3, Vector2};
use core::map::MapPosIter;
use core::types::{MInt, Size2, MapPos, UnitId};
use visualizer::gl_helpers::{
    set_clear_color,
    clear_screen,
    read_pixel_bytes,
};
use visualizer::camera::Camera;
use visualizer::geom::Geom;
use visualizer::mesh::Mesh;
use visualizer::types::{Color3, MFloat, MatId, Scene, NodeId, WorldPos};
use visualizer::shader::Shader;

fn i_to_f(n: MInt) -> f32 {
    n as MFloat / 255.0
}

fn get_mesh(geom: &Geom, map_size: Size2<MInt>, shader: &Shader) -> Mesh {
    use std::slice::Vector;
    let mut c_data = Vec::new();
    let mut v_data = Vec::new();
    for tile_pos in MapPosIter::new(map_size) {
        let pos3d = geom.map_pos_to_world_pos(tile_pos);
        for num in range(0 as MInt, 6) {
            let vertex = geom.index_to_hex_vertex(num);
            let next_vertex = geom.index_to_hex_vertex(num + 1);
            let col_x = i_to_f(tile_pos.x);
            let col_y = i_to_f(tile_pos.y);
            let color = Color3{r: col_x, g: col_y, b: i_to_f(1)};
            v_data.push(pos3d + vertex);
            c_data.push(color);
            v_data.push(pos3d + next_vertex);
            c_data.push(color);
            v_data.push(pos3d + Vector3::zero());
            c_data.push(color);
        }
    }
    let mut mesh = Mesh::new(v_data.as_slice());
    mesh.set_color(c_data.as_slice());
    mesh.prepare(shader);
    mesh
}

pub enum PickResult {
    PickedMapPos(MapPos),
    PickedUnitId(UnitId),
    PickedNothing
}

pub struct TilePicker {
    shader: Shader,
    map_mesh: Mesh,
    units_mesh: Option<Mesh>,
    mvp_mat_id: MatId,
    win_size: Size2<MInt>,
}

impl TilePicker {
    pub fn new(
        win_size: Size2<MInt>,
        geom: &Geom,
        map_size: Size2<MInt>
    ) -> ~TilePicker {
        let shader = Shader::new(
            &Path::new("pick.vs.glsl"),
            &Path::new("pick.fs.glsl"),
        );
        let mvp_mat_id = MatId(shader.get_uniform("mvp_mat"));
        let map_mesh = get_mesh(geom, map_size, &shader);
        let tile_picker = ~TilePicker {
            map_mesh: map_mesh,
            units_mesh: None,
            shader: shader,
            mvp_mat_id: mvp_mat_id,
            win_size: win_size,
        };
        tile_picker
    }

    pub fn set_win_size(&mut self, win_size: Size2<MInt>) {
        self.win_size = win_size;
    }

    pub fn update_units( &mut self, geom: &Geom, scene: &Scene) {
        use std::slice::Vector;
        fn get_hex_vertex(geom: &Geom, n: MInt) -> WorldPos {
            let scale_factor = 0.5;
            geom.index_to_hex_vertex(n).mul_s(scale_factor)
        }
        let last_unit_node_id = 1000; // TODO
        let mut c_data = Vec::new();
        let mut v_data = Vec::new();
        for (node_id, node) in scene.iter() {
            let NodeId(id) = *node_id;
            if id >= last_unit_node_id {
                continue;
            }
            let color = Color3 {r: i_to_f(id), g: 0.0, b: i_to_f(2)};
            for num in range(0 as MInt, 6) {
                v_data.push(node.pos + get_hex_vertex(geom, num));
                c_data.push(color);
                v_data.push(node.pos + get_hex_vertex(geom, num + 1));
                c_data.push(color);
                v_data.push(node.pos + Vector3::zero());
                c_data.push(color);
            }
        }
        // draw unit markers slightly above the floor
        let unit_marker_height = 0.01;
        for v in v_data.mut_iter() {
            v.z = unit_marker_height;
        }
        let mut mesh = Mesh::new(v_data.as_slice());
        mesh.set_color(c_data.as_slice());
        mesh.prepare(&self.shader);
        self.units_mesh = Some(mesh);
    }

    pub fn pick_tile(
        &mut self,
        camera: &Camera,
        mouse_pos: Vector2<MInt>
    ) -> PickResult {
        self.shader.activate();
        self.shader.uniform_mat4f(self.mvp_mat_id, &camera.mat());
        set_clear_color(0.0, 0.0, 0.0);
        clear_screen();
        self.map_mesh.draw(&self.shader);
        match self.units_mesh {
            Some(ref units) => units.draw(&self.shader),
            None => {},
        };
        let (r, g, b, _) = read_pixel_bytes(self.win_size, mouse_pos);
        match b {
            0 => PickedNothing,
            1 => PickedMapPos(Vector2{x: r, y: g}),
            2 => PickedUnitId(UnitId(r)),
            _ => fail!(),
        }
    }
}

// vim: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab:
<commit_msg>Picker: More informative fail! msg<commit_after>// See LICENSE file for copyright and license details.

use cgmath::vector::{Vector, Vector3, Vector2};
use core::map::MapPosIter;
use core::types::{MInt, Size2, MapPos, UnitId};
use visualizer::gl_helpers::{
    set_clear_color,
    clear_screen,
    read_pixel_bytes,
};
use visualizer::camera::Camera;
use visualizer::geom::Geom;
use visualizer::mesh::Mesh;
use visualizer::types::{Color3, MFloat, MatId, Scene, NodeId, WorldPos};
use visualizer::shader::Shader;

fn i_to_f(n: MInt) -> f32 {
    n as MFloat / 255.0
}

fn get_mesh(geom: &Geom, map_size: Size2<MInt>, shader: &Shader) -> Mesh {
    use std::slice::Vector;
    let mut c_data = Vec::new();
    let mut v_data = Vec::new();
    for tile_pos in MapPosIter::new(map_size) {
        let pos3d = geom.map_pos_to_world_pos(tile_pos);
        for num in range(0 as MInt, 6) {
            let vertex = geom.index_to_hex_vertex(num);
            let next_vertex = geom.index_to_hex_vertex(num + 1);
            let col_x = i_to_f(tile_pos.x);
            let col_y = i_to_f(tile_pos.y);
            let color = Color3{r: col_x, g: col_y, b: i_to_f(1)};
            v_data.push(pos3d + vertex);
            c_data.push(color);
            v_data.push(pos3d + next_vertex);
            c_data.push(color);
            v_data.push(pos3d + Vector3::zero());
            c_data.push(color);
        }
    }
    let mut mesh = Mesh::new(v_data.as_slice());
    mesh.set_color(c_data.as_slice());
    mesh.prepare(shader);
    mesh
}

pub enum PickResult {
    PickedMapPos(MapPos),
    PickedUnitId(UnitId),
    PickedNothing
}

pub struct TilePicker {
    shader: Shader,
    map_mesh: Mesh,
    units_mesh: Option<Mesh>,
    mvp_mat_id: MatId,
    win_size: Size2<MInt>,
}

impl TilePicker {
    pub fn new(
        win_size: Size2<MInt>,
        geom: &Geom,
        map_size: Size2<MInt>
    ) -> ~TilePicker {
        let shader = Shader::new(
            &Path::new("pick.vs.glsl"),
            &Path::new("pick.fs.glsl"),
        );
        let mvp_mat_id = MatId(shader.get_uniform("mvp_mat"));
        let map_mesh = get_mesh(geom, map_size, &shader);
        let tile_picker = ~TilePicker {
            map_mesh: map_mesh,
            units_mesh: None,
            shader: shader,
            mvp_mat_id: mvp_mat_id,
            win_size: win_size,
        };
        tile_picker
    }

    pub fn set_win_size(&mut self, win_size: Size2<MInt>) {
        self.win_size = win_size;
    }

    pub fn update_units( &mut self, geom: &Geom, scene: &Scene) {
        use std::slice::Vector;
        fn get_hex_vertex(geom: &Geom, n: MInt) -> WorldPos {
            let scale_factor = 0.5;
            geom.index_to_hex_vertex(n).mul_s(scale_factor)
        }
        let last_unit_node_id = 1000; // TODO
        let mut c_data = Vec::new();
        let mut v_data = Vec::new();
        for (node_id, node) in scene.iter() {
            let NodeId(id) = *node_id;
            if id >= last_unit_node_id {
                continue;
            }
            let color = Color3 {r: i_to_f(id), g: 0.0, b: i_to_f(2)};
            for num in range(0 as MInt, 6) {
                v_data.push(node.pos + get_hex_vertex(geom, num));
                c_data.push(color);
                v_data.push(node.pos + get_hex_vertex(geom, num + 1));
                c_data.push(color);
                v_data.push(node.pos + Vector3::zero());
                c_data.push(color);
            }
        }
        // draw unit markers slightly above the floor
        let unit_marker_height = 0.01;
        for v in v_data.mut_iter() {
            v.z = unit_marker_height;
        }
        let mut mesh = Mesh::new(v_data.as_slice());
        mesh.set_color(c_data.as_slice());
        mesh.prepare(&self.shader);
        self.units_mesh = Some(mesh);
    }

    pub fn pick_tile(
        &mut self,
        camera: &Camera,
        mouse_pos: Vector2<MInt>
    ) -> PickResult {
        self.shader.activate();
        self.shader.uniform_mat4f(self.mvp_mat_id, &camera.mat());
        set_clear_color(0.0, 0.0, 0.0);
        clear_screen();
        self.map_mesh.draw(&self.shader);
        match self.units_mesh {
            Some(ref units) => units.draw(&self.shader),
            None => {},
        };
        let (r, g, b, _) = read_pixel_bytes(self.win_size, mouse_pos);
        match b {
            0 => PickedNothing,
            1 => PickedMapPos(Vector2{x: r, y: g}),
            2 => PickedUnitId(UnitId(r)),
            n => fail!("Picker: bad color tag: {}", n),
        }
    }
}

// vim: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab:
<|endoftext|>