<commit_before>extern crate regex;
use mysql::conn::pool::{MyPool,MyPooledConn};
use mysql::conn::Stmt;

use std::process::{Command, Stdio, Child};
use std::error::Error;
use std::io::prelude::*;
use std::io::BufReader;
use std::path::Path;
use lib::config::ConfigGen;
use std::convert::Into;

use lib::DownloadError;

use CONFIG;

macro_rules! regex(
    ($s:expr) => (regex::Regex::new($s).unwrap());
);

#[derive(Clone)]
pub struct DownloadDB {
    pub url: String,
    pub quality: i16,
    pub playlist: bool,
    pub compress: bool,
    pub qid: i64,
    pub folder: String, // download folder, changes for playlists
    pub pool: MyPool,
}

impl DownloadDB {
    pub fn update_video(&mut self,url: String, qid: i64){
        self.qid = qid;
        self.url = url;
    }
    pub fn update_folder(&mut self, folder: String){
        self.folder = folder;
    }
}

pub struct Downloader<'a> {
    pub ddb: &'a DownloadDB,
    defaults: &'a ConfigGen,
    // pool: MyPool,
}

impl<'a> Downloader<'a>{
    pub fn new(ddb: &'a DownloadDB, defaults: &'a ConfigGen) -> Downloader<'a>{
        Downloader {ddb: ddb, defaults: defaults}
    }

    ///Regex matching: [download]  13.4% of 275.27MiB at 525.36KiB/s ETA 07:52
    ///
    ///Downloads a video, updates the DB
    ///TODO: get the sql statements out of the class
    ///TODO: wrap errors
    ///Doesn't care about DMCAs, will emit errors on them
    ///download_audio: ignore quality & download audio_raw codec for split containers
    pub fn download_file(&self, file_path: &str, download_audio: bool) -> Result<bool,DownloadError> {
        println!("{:?}", self.ddb.url);

        let curr_quality = if download_audio {
            &CONFIG.codecs.audio_raw
        }else{
            &self.ddb.quality
        };

        println!("quality: {}",curr_quality);
        let mut child = try!(self.run_download_process(file_path,curr_quality));
        let stdout = BufReader::new(child.stdout.take().unwrap());

        let mut conn = self.ddb.pool.get_conn().unwrap();
        let mut statement = self.prepare_progress_updater(&mut conn);
        let re = regex!(r"\d+\.\d%");

        for line in stdout.lines(){
            match line{
                Err(why) => panic!("couldn't read cmd stdout: {}", Error::description(&why)),
                Ok(text) => {
                        println!("Out: {}",text);
                        match re.find(&text) {
                            Some(s) => { //println!("Match at {}", s.0);
                                        println!("{}", &text[s.0..s.1]); // ONLY with ASCII chars makeable!
                                        try!(self.update_progress(&mut statement, &text[s.0..s.1].to_string()));
                                    },
                            None => {},
                        }
                    },
            }
        }

        try!(child.wait()); // waits for finish & then exists zombi process fixes #10

        Ok(true)
    }

    ///Trys to get the original name of a file, while checking for availability
    ///
    pub fn get_file_name(&self) -> Result<String,DownloadError> {
        let mut child = try!(self.run_filename_process());
        let mut stdout_buffer = BufReader::new(child.stdout.take().unwrap());
        let mut stderr_buffer = BufReader::new(child.stderr.take().unwrap());

        let mut stdout: String = String::new();
        try!(stdout_buffer.read_to_string(&mut stdout));
        let mut stderr: String = String::new();
        try!(stderr_buffer.read_to_string(&mut stderr));

        try!(child.wait());
        //println!("stderr: {:?}", stderr);
        //println!("stdout: {:?}", stdout);
        if stderr.is_empty() == true {
            println!("get_file_name: {:?}", stdout);
            Ok(stdout.trim().to_string())
        }else{
            if stderr.contains("not available in your country") || stderr.contains("contains content from") {
                return Err(DownloadError::DMCAError);
            }else{
                return Err(DownloadError::DownloadError(stderr));
            }
        }
    }

    ///Get playlist file ids
    pub fn get_playlist(&self) -> Result<String,DownloadError> {
        let mut child = try!(self.run_playlist_extract());
        let mut stdout_buffer = BufReader::new(child.stdout.take().unwrap());
        let mut stderr_buffer = BufReader::new(child.stderr.take().unwrap());

        let mut stdout: String = String::new();
        try!(stdout_buffer.read_to_string(&mut stdout));
        let mut stderr: String = String::new();
        try!(stderr_buffer.read_to_string(&mut stderr));

        try!(child.wait());
        //println!("stderr: {:?}", stderr);
        //println!("stdout: {:?}", stdout);
        if stderr.is_empty() == true {
            println!("get_playlist: {:?}", stdout);
            Ok(stdout.trim().to_string())
        }else{
            if stderr.contains("not available in your country") || stderr.contains("contains content from") {
                return Err(DownloadError::DMCAError);
            }else{
                return Err(DownloadError::DownloadError(stderr));
            }
        }
    }

    ///Retrive playlist name, will kill the process due to yt-dl starting detail retrieval
    pub fn get_playlist_name(&self) -> Result<String,DownloadError> {
        let mut child = try!(self.run_playlist_get_name());
        let stdout = BufReader::new(child.stdout.take().unwrap());

        let re = regex!(r"[download] Downloading playlist: (.*)");

        let mut name: Option<String> = None;
        for line in stdout.lines(){
            match line{
                Err(why) => panic!("couldn't read cmd stdout: {}", Error::description(&why)),
                Ok(text) => {
                        println!("Out: {}",text);
                        if name.is_none() {
                            match re.captures(&text) {
                                Some(cap) => {
                                            println!("{}", cap.at(1).unwrap()); // ONLY with ASCII chars makeable!
                                            name = Some(cap.at(1).unwrap().to_string());
                                            try!(child.kill());
                                        },
                                None => {},
                            }
                        }
                    },
            }
        }

        try!(child.wait()); // waits for finish & then exists zombi process fixes #10

        name.ok_or(DownloadError::DownloadError("no playlist name".to_string()))
    }

    fn run_download_process(&self, file_path: &str, quality: &i16) -> Result<Child,DownloadError> {
        match Command::new("youtube-dl")
                                    .arg("--newline")
                                    .arg("-r")
                                    .arg(format!("{}M",self.defaults.download_mbps))
                                    .arg("-f")
                                    .arg(quality.to_string())
                                    .arg("-o")
                                    .arg(file_path)
                                    .arg(&self.ddb.url)
                                    .stdin(Stdio::null())
                                    .stdout(Stdio::piped())
                                    .spawn() {
            Err(why) => Err(DownloadError::InternalError(Error::description(&why).into())),
            Ok(process) => Ok(process),
        }
    }

    fn run_filename_process(&self) -> Result<Child,DownloadError> {
        match Command::new("youtube-dl")
                                    .arg("--get-filename")
                                    .arg("-o")
                                    .arg("%(title)s")
                                    .arg(&self.ddb.url)
                                    .stdin(Stdio::null())
                                    .stdout(Stdio::piped())
                                    .stderr(Stdio::piped())
                                    .spawn() {
            Err(why) => Err(DownloadError::InternalError(Error::description(&why).into())),
            Ok(process) => Ok(process),
        }
    }

    fn run_playlist_extract(&self) -> Result<Child,DownloadError> {
        match Command::new("youtube-dl")
                                    .arg("-s")
                                    .arg("--print-json")
                                    .arg("--flat-playlist")
                                    .arg(&self.ddb.url)
                                    .stdin(Stdio::null())
                                    .stdout(Stdio::piped())
                                    .spawn() {
            Err(why) => Err(DownloadError::InternalError(Error::description(&why).into())),
            Ok(process) => Ok(process),
        }
    }

    fn run_playlist_get_name(&self) -> Result<Child,DownloadError> {
        match Command::new("youtube-dl")
                                    .arg("-s")
                                    .arg("--no-warnings")
                                    .arg(&self.ddb.url)
                                    .stdin(Stdio::null())
                                    .stdout(Stdio::piped())
                                    .spawn() {
            Err(why) => Err(DownloadError::InternalError(Error::description(&why).into())),
            Ok(process) => Ok(process),
        }
    }

    // MyPooledConn does only live when MyOpts is alive -> lifetime needs to be declared
    fn prepare_progress_updater(&'a self,conn: &'a mut MyPooledConn) -> Stmt<'a> { // no livetime needed: struct livetime used
        conn.prepare("UPDATE querydetails SET progress = ? WHERE qid = ?").unwrap()
    }

    ///updater called from the stdout progress
    fn update_progress(&self,stmt: &mut Stmt, progress: &String) -> Result<(),DownloadError>{
        try!(stmt.execute(&[progress,&self.ddb.qid]).map(|_| Ok(())))
        //-> only return errors, ignore the return value of stmt.execute
    }

    ///This function does a 3rd party binding in case it's needed
    ///due to the country restrictions
    ///Because hyper doesn't support timeout settings atm, we're calling an external
    ///lib
    ///The returned value contains the original video name, the lib downloads & saves
    ///the file at the given folder under the given name
    pub fn lib_request_video(&self) -> Result<String,DownloadError> {
        let mut child = try!(self.lib_request_video_cmd());
        println!("Requesting video via lib..");
        let stdout = BufReader::new(child.stdout.take().unwrap());
        let mut stderr_buffer = BufReader::new(child.stderr.take().unwrap());

        let mut conn = self.ddb.pool.get_conn().unwrap();
        let mut statement = self.prepare_progress_updater(&mut conn);
        let re = regex!(r"step (\d)");

        let mut last_line = String::new();
        for line in stdout.lines(){
            match line{
                Err(why) => panic!("couldn't read cmd stdout: {}", Error::description(&why)),
                Ok(text) => {
                        println!("Out: {}",text);
                        match re.captures(&text) {
                            Some(cap) => { //println!("Match at {}", s.0);
                                        println!("{}", cap.at(1).unwrap()); // ONLY with ASCII chars makeable!
                                        try!(self.update_progress(&mut statement, &cap.at(1).unwrap().to_string()));
                                    },
                            None => {last_line = text.clone()},
                        }
                    },
            }
        }

        let mut stderr: String = String::new();
        try!(stderr_buffer.read_to_string(&mut stderr));

        try!(child.wait());

        if !stderr.is_empty() {
            println!("stderr: {:?}", stderr);
            return Err(DownloadError::InternalError(stderr));
        }
        //this ONLY works because `filename ` is ascii..
        let out = last_line[last_line.find("filename ").unwrap()+9..].trim().to_string();
        //stdout.trim();

        Ok(out)
    }

    ///Generate the lib-cmd `request [..]?v=asdf -folder /downloads -a -name testfile`
    fn lib_request_video_cmd(&self) -> Result<Child,DownloadError> {
        let java_path = Path::new(&self.defaults.jar_cmd);
        println!("{:?}", format!("{}/java -jar {}/offliberty.jar",self.defaults.jar_cmd,self.defaults.jar_folder));
        match Command::new("./java")
                                        .current_dir(java_path)
                                        .arg("-jar")
                                        .arg(format!("{}/offliberty.jar",&self.defaults.jar_folder))
                                        .arg("request")
                                        .arg(&self.ddb.url)
                                        .arg("-folder")
                                        .arg(&self.ddb.folder)
                                        .arg(self.gen_request_str())
                                        .arg("-name")
                                        .arg(self.ddb.qid.to_string()) //eq. format! https://botbot.me/mozilla/rust/msg/37524131/
                                        .stdin(Stdio::null())
                                        .stdout(Stdio::piped())
                                        .stderr(Stdio::piped())
                                        .spawn() {
                Err(why) => {println!("{:?}",why); Err(DownloadError::InternalError(Error::description(&why).into()))},
                Ok(process) => Ok(process),
            }
    }

    ///Generate -a or -v, based on if an audio or video quality is requested
    fn gen_request_str(&self) -> &'a str{
        if self.is_audio() && !self.is_aac() {
            "-a"
        } else {
            "-v"
        }
    }

    ///Check if the quality is 141, standing for audio or not
    pub fn is_audio(&self) -> bool {
        if self.ddb.quality == CONFIG.codecs.audio_raw {
            true
        } else if self.ddb.quality == CONFIG.codecs.audio_source_hq {
            true
        } else if self.ddb.quality == CONFIG.codecs.audio_mp3 {
            true
        } else {
            false
        }
    }

    ///Check if is AAC file
    fn is_aac(&self) -> bool {
        CONFIG.extensions.aac.contains(&self.ddb.quality)
    }
}<commit_msg>finished get_playlist_ids<commit_after>extern crate regex;
use mysql::conn::pool::{MyPool,MyPooledConn};
use mysql::conn::Stmt;

use std::process::{Command, Stdio, Child};
use std::error::Error;
use std::io::prelude::*;
use std::io::BufReader;
use std::path::Path;
use lib::config::ConfigGen;
use std::convert::Into;

use lib::DownloadError;

use CONFIG;

macro_rules! regex(
    ($s:expr) => (regex::Regex::new($s).unwrap());
);

#[derive(Clone)]
pub struct DownloadDB {
    pub url: String,
    pub quality: i16,
    pub playlist: bool,
    pub compress: bool,
    pub qid: i64,
    pub folder: String, // download folder, changes for playlists
    pub pool: MyPool,
}

impl DownloadDB {
    pub fn update_video(&mut self,url: String, qid: i64){
        self.qid = qid;
        self.url = url;
    }
    pub fn update_folder(&mut self, folder: String){
        self.folder = folder;
    }
}

pub struct Downloader<'a> {
    pub ddb: &'a DownloadDB,
    defaults: &'a ConfigGen,
    // pool: MyPool,
}

impl<'a> Downloader<'a>{
    pub fn new(ddb: &'a DownloadDB, defaults: &'a ConfigGen) -> Downloader<'a>{
        Downloader {ddb: ddb, defaults: defaults}
    }

    ///Regex matching: [download]  13.4% of 275.27MiB at 525.36KiB/s ETA 07:52
    ///
    ///Downloads a video, updates the DB
    ///TODO: get the sql statements out of the class
    ///TODO: wrap errors
    ///Doesn't care about DMCAs, will emit errors on them
    ///download_audio: ignore quality & download audio_raw codec for split containers
    pub fn download_file(&self, file_path: &str, download_audio: bool) -> Result<bool,DownloadError> {
        println!("{:?}", self.ddb.url);

        let curr_quality = if download_audio {
            &CONFIG.codecs.audio_raw
        }else{
            &self.ddb.quality
        };

        println!("quality: {}",curr_quality);
        let mut child = try!(self.run_download_process(file_path,curr_quality));
        let stdout = BufReader::new(child.stdout.take().unwrap());

        let mut conn = self.ddb.pool.get_conn().unwrap();
        let mut statement = self.prepare_progress_updater(&mut conn);
        let re = regex!(r"\d+\.\d%");

        for line in stdout.lines(){
            match line{
                Err(why) => panic!("couldn't read cmd stdout: {}", Error::description(&why)),
                Ok(text) => {
                        println!("Out: {}",text);
                        match re.find(&text) {
                            Some(s) => { //println!("Match at {}", s.0);
                                        println!("{}", &text[s.0..s.1]); // ONLY with ASCII chars makeable!
                                        try!(self.update_progress(&mut statement, &text[s.0..s.1].to_string()));
                                    },
                            None => {},
                        }
                    },
            }
        }

        try!(child.wait()); // waits for finish & then exists zombi process fixes #10

        Ok(true)
    }

    ///Trys to get the original name of a file, while checking for availability
    ///
    pub fn get_file_name(&self) -> Result<String,DownloadError> {
        let mut child = try!(self.run_filename_process());
        let mut stdout_buffer = BufReader::new(child.stdout.take().unwrap());
        let mut stderr_buffer = BufReader::new(child.stderr.take().unwrap());

        let mut stdout: String = String::new();
        try!(stdout_buffer.read_to_string(&mut stdout));
        let mut stderr: String = String::new();
        try!(stderr_buffer.read_to_string(&mut stderr));

        try!(child.wait());
        //println!("stderr: {:?}", stderr);
        //println!("stdout: {:?}", stdout);
        if stderr.is_empty() == true {
            println!("get_file_name: {:?}", stdout);
            Ok(stdout.trim().to_string())
        }else{
            if stderr.contains("not available in your country") || stderr.contains("contains content from") {
                return Err(DownloadError::DMCAError);
            }else{
                return Err(DownloadError::DownloadError(stderr));
            }
        }
    }

    ///Get playlist file ids
    pub fn get_playlist_ids(&self) -> Result<Vec<String>,DownloadError> {
        let mut child = try!(self.run_playlist_extract());
        let stdout = BufReader::new(child.stdout.take().unwrap());
        let mut stderr_buffer = BufReader::new(child.stderr.take().unwrap());

        let re = regex!(r#""url": "([a-zA-Z0-9_-]+)""#);

        let mut id_list: Vec<String> = Vec::new();
        for line in stdout.lines(){
            match line{
                Err(why) => panic!("couldn't read cmd stdout: {}", Error::description(&why)),
                Ok(text) => {
                        println!("Out: {}",text);
                        match re.captures(&text) {
                            Some(cap) => { //println!("Match at {}", s.0);
                                        println!("{}", cap.at(1).unwrap()); // ONLY with ASCII chars makeable!
                                        id_list.push(cap.at(1).unwrap().to_string());
                                    },
                            None => {},
                        }
                    },
            }
        }

        let mut stderr: String = String::new();
        try!(stderr_buffer.read_to_string(&mut stderr));

        try!(child.wait());

        if !stderr.is_empty() {
            println!("stderr: {:?}", stderr);
            return Err(DownloadError::InternalError(stderr));
        }

        Ok(id_list)
    }

    ///Retrive playlist name, will kill the process due to yt-dl starting detail retrieval
    pub fn get_playlist_name(&self) -> Result<String,DownloadError> {
        let mut child = try!(self.run_playlist_get_name());
        let stdout = BufReader::new(child.stdout.take().unwrap());

        let re = regex!(r"[download] Downloading playlist: (.*)");

        let mut name: Option<String> = None;
        for line in stdout.lines(){
            match line{
                Err(why) => panic!("couldn't read cmd stdout: {}", Error::description(&why)),
                Ok(text) => {
                        println!("Out: {}",text);
                        if name.is_none() {
                            match re.captures(&text) {
                                Some(cap) => {
                                            println!("{}", cap.at(1).unwrap()); // ONLY with ASCII chars makeable!
                                            name = Some(cap.at(1).unwrap().to_string());
                                            try!(child.kill());
                                        },
                                None => {},
                            }
                        }
                    },
            }
        }

        try!(child.wait()); // waits for finish & then exists zombi process fixes #10

        name.ok_or(DownloadError::DownloadError("no playlist name".to_string()))
    }

    fn run_download_process(&self, file_path: &str, quality: &i16) -> Result<Child,DownloadError> {
        match Command::new("youtube-dl")
                                    .arg("--newline")
                                    .arg("-r")
                                    .arg(format!("{}M",self.defaults.download_mbps))
                                    .arg("-f")
                                    .arg(quality.to_string())
                                    .arg("-o")
                                    .arg(file_path)
                                    .arg(&self.ddb.url)
                                    .stdin(Stdio::null())
                                    .stdout(Stdio::piped())
                                    .spawn() {
            Err(why) => Err(DownloadError::InternalError(Error::description(&why).into())),
            Ok(process) => Ok(process),
        }
    }

    fn run_filename_process(&self) -> Result<Child,DownloadError> {
        match Command::new("youtube-dl")
                                    .arg("--get-filename")
                                    .arg("-o")
                                    .arg("%(title)s")
                                    .arg(&self.ddb.url)
                                    .stdin(Stdio::null())
                                    .stdout(Stdio::piped())
                                    .stderr(Stdio::piped())
                                    .spawn() {
            Err(why) => Err(DownloadError::InternalError(Error::description(&why).into())),
            Ok(process) => Ok(process),
        }
    }

    fn run_playlist_extract(&self) -> Result<Child,DownloadError> {
        match Command::new("youtube-dl")
                                    .arg("-s")
                                    .arg("--print-json")
                                    .arg("--flat-playlist")
                                    .arg(&self.ddb.url)
                                    .stdin(Stdio::null())
                                    .stdout(Stdio::piped())
                                    .spawn() {
            Err(why) => Err(DownloadError::InternalError(Error::description(&why).into())),
            Ok(process) => Ok(process),
        }
    }

    fn run_playlist_get_name(&self) -> Result<Child,DownloadError> {
        match Command::new("youtube-dl")
                                    .arg("-s")
                                    .arg("--no-warnings")
                                    .arg(&self.ddb.url)
                                    .stdin(Stdio::null())
                                    .stdout(Stdio::piped())
                                    .spawn() {
            Err(why) => Err(DownloadError::InternalError(Error::description(&why).into())),
            Ok(process) => Ok(process),
        }
    }

    // MyPooledConn does only live when MyOpts is alive -> lifetime needs to be declared
    fn prepare_progress_updater(&'a self,conn: &'a mut MyPooledConn) -> Stmt<'a> { // no livetime needed: struct livetime used
        conn.prepare("UPDATE querydetails SET progress = ? WHERE qid = ?").unwrap()
    }

    ///updater called from the stdout progress
    fn update_progress(&self,stmt: &mut Stmt, progress: &String) -> Result<(),DownloadError>{
        try!(stmt.execute(&[progress,&self.ddb.qid]).map(|_| Ok(())))
        //-> only return errors, ignore the return value of stmt.execute
    }

    ///This function does a 3rd party binding in case it's needed
    ///due to the country restrictions
    ///Because hyper doesn't support timeout settings atm, we're calling an external
    ///lib
    ///The returned value contains the original video name, the lib downloads & saves
    ///the file at the given folder under the given name
    pub fn lib_request_video(&self) -> Result<String,DownloadError> {
        let mut child = try!(self.lib_request_video_cmd());
        println!("Requesting video via lib..");
        let stdout = BufReader::new(child.stdout.take().unwrap());
        let mut stderr_buffer = BufReader::new(child.stderr.take().unwrap());

        let mut conn = self.ddb.pool.get_conn().unwrap();
        let mut statement = self.prepare_progress_updater(&mut conn);
        let re = regex!(r"step (\d)");

        let mut last_line = String::new();
        for line in stdout.lines(){
            match line{
                Err(why) => panic!("couldn't read cmd stdout: {}", Error::description(&why)),
                Ok(text) => {
                        println!("Out: {}",text);
                        match re.captures(&text) {
                            Some(cap) => { //println!("Match at {}", s.0);
                                        println!("{}", cap.at(1).unwrap()); // ONLY with ASCII chars makeable!
                                        try!(self.update_progress(&mut statement, &cap.at(1).unwrap().to_string()));
                                    },
                            None => {last_line = text.clone()},
                        }
                    },
            }
        }

        let mut stderr: String = String::new();
        try!(stderr_buffer.read_to_string(&mut stderr));

        try!(child.wait());

        if !stderr.is_empty() {
            println!("stderr: {:?}", stderr);
            return Err(DownloadError::InternalError(stderr));
        }
        //this ONLY works because `filename ` is ascii..
        let out = last_line[last_line.find("filename ").unwrap()+9..].trim().to_string();
        //stdout.trim();

        Ok(out)
    }

    ///Generate the lib-cmd `request [..]?v=asdf -folder /downloads -a -name testfile`
    fn lib_request_video_cmd(&self) -> Result<Child,DownloadError> {
        let java_path = Path::new(&self.defaults.jar_cmd);
        println!("{:?}", format!("{}/java -jar {}/offliberty.jar",self.defaults.jar_cmd,self.defaults.jar_folder));
        match Command::new("./java")
                                        .current_dir(java_path)
                                        .arg("-jar")
                                        .arg(format!("{}/offliberty.jar",&self.defaults.jar_folder))
                                        .arg("request")
                                        .arg(&self.ddb.url)
                                        .arg("-folder")
                                        .arg(&self.ddb.folder)
                                        .arg(self.gen_request_str())
                                        .arg("-name")
                                        .arg(self.ddb.qid.to_string()) //eq. format! https://botbot.me/mozilla/rust/msg/37524131/
                                        .stdin(Stdio::null())
                                        .stdout(Stdio::piped())
                                        .stderr(Stdio::piped())
                                        .spawn() {
                Err(why) => {println!("{:?}",why); Err(DownloadError::InternalError(Error::description(&why).into()))},
                Ok(process) => Ok(process),
            }
    }

    ///Generate -a or -v, based on if an audio or video quality is requested
    fn gen_request_str(&self) -> &'a str{
        if self.is_audio() && !self.is_aac() {
            "-a"
        } else {
            "-v"
        }
    }

    ///Check if the quality is 141, standing for audio or not
    pub fn is_audio(&self) -> bool {
        if self.ddb.quality == CONFIG.codecs.audio_raw {
            true
        } else if self.ddb.quality == CONFIG.codecs.audio_source_hq {
            true
        } else if self.ddb.quality == CONFIG.codecs.audio_mp3 {
            true
        } else {
            false
        }
    }

    ///Check if is AAC file
    fn is_aac(&self) -> bool {
        CONFIG.extensions.aac.contains(&self.ddb.quality)
    }
}<|endoftext|>


<commit_before>use librp::errors::{PrefetchError};
use librp::prefetch::{PrefetchHeader};
use librp::timestamp::{WinTimestamp};
use librp::utils;
use byteorder::{ReadBytesExt, LittleEndian};
use std::io::Read;
use std::io::Cursor;
use std::io::BufReader;
use std::fmt;
use std::mem;
#[derive(Debug)]
pub enum FileInformation{
    V17(FileInformationV17),
    V23(FileInformationV23),
    V26(FileInformationV26),
    V30(FileInformationV30)
}
#[derive(Debug)]
pub struct FileInformationV17{
    pub metrics_array_offset: u32, //The offset is relative to the start of the file
    pub metrics_entry_count: u32,
    pub trace_array_offset: u32,  //The offset is relative to the start of the file
    pub trace_entry_count: u32,
    pub filename_offset: u32,
    pub filename_length: u32,
    pub volume_info_offset: u32,
    pub volume_info_count: u32,
    pub volume_info_size: u32,
    pub last_run_time: u64,
    // pub unknown1: [u8; 16],
    pub run_count: u32,
    pub unknown2: u32
}
#[derive(Debug)]
pub struct FileInformationV23{
    pub metrics_array_offset: u32, //The offset is relative to the start of the file
    pub metrics_entry_count: u32,
    pub trace_array_offset: u32,  //The offset is relative to the start of the file
    pub trace_entry_count: u32,
    pub filename_offset: u32,
    pub filename_length: u32,
    pub volume_info_offset: u32,
    pub volume_info_count: u32,
    pub volume_info_size: u32,
    pub unknown3: u64,
    pub last_run_time: u64,
    // pub unknown1: [u8; 16],
    pub run_count: u32,
    pub unknown2: u32,
    // pub unknown4: [u8; 80]
}

pub struct FileInformationV26{
    pub metrics_array_offset: u32, //The offset is relative to the start of the file
    pub metrics_entry_count: u32,
    pub trace_array_offset: u32,  //The offset is relative to the start of the file
    pub trace_entry_count: u32,
    pub filename_offset: u32,
    pub filename_length: u32,
    pub volume_info_offset: u32,
    pub volume_info_count: u32,
    pub volume_info_size: u32,
    pub unknown3: u64,
    pub last_run_time: [WinTimestamp;8],
    pub unknown1: [u8; 16],
    pub run_count: u32,
    pub unknown2: u32,
    pub unknown5: u32,
    pub unknown4: [u8; 88]
}
impl fmt::Debug for FileInformationV26 {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "FileInformationV26 {{ \
            metrics_array_offset: {}, \
            metrics_entry_count: {}, \
            trace_array_offset: {}, \
            trace_entry_count: {}, \
            filename_offset: {}, \
            filename_length: {}, \
            volume_info_offset: {}, \
            volume_info_count: {}, \
            volume_info_size: {}, \
            unknown3: {}, \
            last_run_time: {:?}, \
            unknown1: {:?}, \
            run_count: {}, \
            unknown2: {}, \
            unknown5: {}, \
            unknown4: {:?}\
            }}",
            self.metrics_array_offset,
            self.metrics_entry_count,
            self.trace_array_offset,
            self.trace_entry_count,
            self.filename_offset,
            self.filename_length,
            self.volume_info_offset,
            self.volume_info_count,
            self.volume_info_size,
            self.unknown3,
            self.last_run_time,
            utils::to_hex_string(self.unknown1.to_vec()),
            self.run_count,
            self.unknown2,
            self.unknown5,
            utils::to_hex_string(self.unknown4.to_vec())
        )
    }
}
pub struct FileInformationV30{
    pub metrics_array_offset: u32, //The offset is relative to the start of the file
    pub metrics_entry_count: u32,
    pub trace_array_offset: u32,  //The offset is relative to the start of the file
    pub trace_entry_count: u32,
    pub filename_offset: u32,
    pub filename_length: u32,
    pub volume_info_offset: u32,
    pub volume_info_count: u32,
    pub volume_info_size: u32,
    pub unknown3: u64,
    pub last_run_time: [WinTimestamp;8],
    pub unknown1: [u8; 16],
    pub run_count: u32,
    pub unknown2: u32,
    pub unknown5: u32,
    pub unknown4: [u8; 88]
}
impl fmt::Debug for FileInformationV30 {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "FileInformationV30 {{ \
            metrics_array_offset: {}, \
            metrics_entry_count: {}, \
            trace_array_offset: {}, \
            trace_entry_count: {}, \
            filename_offset: {}, \
            filename_length: {}, \
            volume_info_offset: {}, \
            volume_info_count: {}, \
            volume_info_size: {}, \
            unknown3: {}, \
            last_run_time: {:?}, \
            unknown1: {:?}, \
            run_count: {}, \
            unknown2: {}, \
            unknown5: {}, \
            unknown4: {:?}\
            }}",
            self.metrics_array_offset,
            self.metrics_entry_count,
            self.trace_array_offset,
            self.trace_entry_count,
            self.filename_offset,
            self.filename_length,
            self.volume_info_offset,
            self.volume_info_count,
            self.volume_info_size,
            self.unknown3,
            self.last_run_time,
            utils::to_hex_string(self.unknown1.to_vec()),
            self.run_count,
            self.unknown2,
            self.unknown5,
            utils::to_hex_string(self.unknown4.to_vec())
        )
    }
}

impl FileInformation{
    pub fn new<R: Read>(header: &PrefetchHeader,reader: R) -> Result<FileInformation,PrefetchError> {
        if header.version == 17{
            let file_info: FileInformationV17 = unsafe {
                mem::zeroed()
            };
            Ok(
                FileInformation::V17(
                    file_info
                )
            )
        } else if header.version == 23{
            let file_info: FileInformationV23 = unsafe {
                mem::zeroed()
            };
            Ok(
                FileInformation::V23(
                    file_info
                )
            )
        } else if header.version == 26{
            Ok(
                FileInformation::V26(get_fileinfo_v26(reader)?)
            )
        } else if header.version == 30{
            Ok(
                FileInformation::V30(get_fileinfo_v30(reader)?)
            )
        } else {
            Err(PrefetchError::parse_error(
                format!("Error parsing file info. Invalid version: {}",header.version)
            ))
        }
    }
}
pub fn get_fileinfo_v26<R: Read>(mut reader: R) -> Result<FileInformationV26,PrefetchError> {
    let mut bytes: [u8; 224] = [0;224];
    reader.read_exact(&mut bytes)?;

    println!("fileinfo buffer hex: {:?}",utils::to_hex_string(bytes.to_vec()));

    let mut buffer = BufReader::new(
        Cursor::new(bytes.to_vec())
    );

    let mut fileinfo_v26: FileInformationV26 = unsafe {
        mem::zeroed()
    };

    fileinfo_v26.metrics_array_offset = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v26.metrics_entry_count = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v26.trace_array_offset = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v26.trace_entry_count = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v26.filename_offset = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v26.filename_length = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v26.volume_info_offset = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v26.volume_info_count = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v26.volume_info_size = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v26.unknown3 = buffer.read_u64::<LittleEndian>()?;
    for n in 0..8 {
        fileinfo_v26.last_run_time[n] = WinTimestamp(buffer.read_u64::<LittleEndian>()?);
    }
    buffer.read_exact(&mut fileinfo_v26.unknown1)?;
    fileinfo_v26.run_count = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v26.unknown2 = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v26.unknown5 = buffer.read_u32::<LittleEndian>()?;
    buffer.read_exact(&mut fileinfo_v26.unknown4)?;

    Ok(fileinfo_v26)
}
pub fn get_fileinfo_v30<R: Read>(mut reader: R) -> Result<FileInformationV30,PrefetchError> {
    let mut bytes: [u8; 224] = [0;224];
    reader.read_exact(&mut bytes)?;

    println!("fileinfo buffer hex: {:?}",utils::to_hex_string(bytes.to_vec()));

    let mut buffer = BufReader::new(
        Cursor::new(bytes.to_vec())
    );

    let mut fileinfo_v30: FileInformationV30 = unsafe {
        mem::zeroed()
    };

    fileinfo_v30.metrics_array_offset = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v30.metrics_entry_count = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v30.trace_array_offset = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v30.trace_entry_count = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v30.filename_offset = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v30.filename_length = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v30.volume_info_offset = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v30.volume_info_count = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v30.volume_info_size = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v30.unknown3 = buffer.read_u64::<LittleEndian>()?;
    for n in 0..8 {
        fileinfo_v30.last_run_time[n] = WinTimestamp(buffer.read_u64::<LittleEndian>()?);
    }
    buffer.read_exact(&mut fileinfo_v30.unknown1)?;
    fileinfo_v30.run_count = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v30.unknown2 = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v30.unknown5 = buffer.read_u32::<LittleEndian>()?;
    buffer.read_exact(&mut fileinfo_v30.unknown4)?;

    Ok(fileinfo_v30)
}
<commit_msg>Implemented parsing FileInformation v17 and v23<commit_after>use librp::errors::{PrefetchError};
use librp::prefetch::{PrefetchHeader};
use librp::timestamp::{WinTimestamp};
use librp::utils;
use byteorder::{ReadBytesExt, LittleEndian};
use std::io::Read;
use std::io::Cursor;
use std::io::BufReader;
use std::fmt;
use std::mem;

#[derive(Debug)]
pub enum FileInformation{
    V17(FileInformationV17),
    V23(FileInformationV23),
    V26(FileInformationV26),
    V30(FileInformationV30)
}
pub struct FileInformationV17{
    pub metrics_array_offset: u32, //The offset is relative to the start of the file
    pub metrics_entry_count: u32,
    pub trace_array_offset: u32,  //The offset is relative to the start of the file
    pub trace_entry_count: u32,
    pub filename_offset: u32,
    pub filename_length: u32,
    pub volume_info_offset: u32,
    pub volume_info_count: u32,
    pub volume_info_size: u32,
    pub last_run_time: WinTimestamp,
    pub unknown1: [u8; 16],
    pub run_count: u32,
    pub unknown2: u32
}
impl fmt::Debug for FileInformationV17 {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "FileInformationV17 {{ \
            metrics_array_offset: {}, \
            metrics_entry_count: {}, \
            trace_array_offset: {}, \
            trace_entry_count: {}, \
            filename_offset: {}, \
            filename_length: {}, \
            volume_info_offset: {}, \
            volume_info_count: {}, \
            volume_info_size: {}, \
            last_run_time: {:?}, \
            unknown1: {:?}, \
            run_count: {}, \
            unknown2: {}, \
            }}",
            self.metrics_array_offset,
            self.metrics_entry_count,
            self.trace_array_offset,
            self.trace_entry_count,
            self.filename_offset,
            self.filename_length,
            self.volume_info_offset,
            self.volume_info_count,
            self.volume_info_size,
            self.last_run_time,
            utils::to_hex_string(self.unknown1.to_vec()),
            self.run_count,
            self.unknown2
        )
    }
}
pub struct FileInformationV23{
    pub metrics_array_offset: u32, //The offset is relative to the start of the file
    pub metrics_entry_count: u32,
    pub trace_array_offset: u32,  //The offset is relative to the start of the file
    pub trace_entry_count: u32,
    pub filename_offset: u32,
    pub filename_length: u32,
    pub volume_info_offset: u32,
    pub volume_info_count: u32,
    pub volume_info_size: u32,
    pub unknown3: u64,
    pub last_run_time: WinTimestamp,
    pub unknown1: [u8; 16],
    pub run_count: u32,
    pub unknown2: u32,
    pub unknown4: [u8; 80]
}
impl fmt::Debug for FileInformationV23 {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "FileInformationV23 {{ \
            metrics_array_offset: {}, \
            metrics_entry_count: {}, \
            trace_array_offset: {}, \
            trace_entry_count: {}, \
            filename_offset: {}, \
            filename_length: {}, \
            volume_info_offset: {}, \
            volume_info_count: {}, \
            volume_info_size: {}, \
            unknown3: {}, \
            last_run_time: {:?}, \
            unknown1: {:?}, \
            run_count: {}, \
            unknown2: {}, \
            unknown4: {:?}\
            }}",
            self.metrics_array_offset,
            self.metrics_entry_count,
            self.trace_array_offset,
            self.trace_entry_count,
            self.filename_offset,
            self.filename_length,
            self.volume_info_offset,
            self.volume_info_count,
            self.volume_info_size,
            self.unknown3,
            self.last_run_time,
            utils::to_hex_string(self.unknown1.to_vec()),
            self.run_count,
            self.unknown2,
            utils::to_hex_string(self.unknown4.to_vec())
        )
    }
}
pub struct FileInformationV26{
    pub metrics_array_offset: u32, //The offset is relative to the start of the file
    pub metrics_entry_count: u32,
    pub trace_array_offset: u32,  //The offset is relative to the start of the file
    pub trace_entry_count: u32,
    pub filename_offset: u32,
    pub filename_length: u32,
    pub volume_info_offset: u32,
    pub volume_info_count: u32,
    pub volume_info_size: u32,
    pub unknown3: u64,
    pub last_run_time: [WinTimestamp;8],
    pub unknown1: [u8; 16],
    pub run_count: u32,
    pub unknown2: u32,
    pub unknown5: u32,
    pub unknown4: [u8; 88]
}
impl fmt::Debug for FileInformationV26 {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "FileInformationV26 {{ \
            metrics_array_offset: {}, \
            metrics_entry_count: {}, \
            trace_array_offset: {}, \
            trace_entry_count: {}, \
            filename_offset: {}, \
            filename_length: {}, \
            volume_info_offset: {}, \
            volume_info_count: {}, \
            volume_info_size: {}, \
            unknown3: {}, \
            last_run_time: {:?}, \
            unknown1: {:?}, \
            run_count: {}, \
            unknown2: {}, \
            unknown5: {}, \
            unknown4: {:?}\
            }}",
            self.metrics_array_offset,
            self.metrics_entry_count,
            self.trace_array_offset,
            self.trace_entry_count,
            self.filename_offset,
            self.filename_length,
            self.volume_info_offset,
            self.volume_info_count,
            self.volume_info_size,
            self.unknown3,
            self.last_run_time,
            utils::to_hex_string(self.unknown1.to_vec()),
            self.run_count,
            self.unknown2,
            self.unknown5,
            utils::to_hex_string(self.unknown4.to_vec())
        )
    }
}
pub struct FileInformationV30{
    pub metrics_array_offset: u32, //The offset is relative to the start of the file
    pub metrics_entry_count: u32,
    pub trace_array_offset: u32,  //The offset is relative to the start of the file
    pub trace_entry_count: u32,
    pub filename_offset: u32,
    pub filename_length: u32,
    pub volume_info_offset: u32,
    pub volume_info_count: u32,
    pub volume_info_size: u32,
    pub unknown3: u64,
    pub last_run_time: [WinTimestamp;8],
    pub unknown1: [u8; 16],
    pub run_count: u32,
    pub unknown2: u32,
    pub unknown5: u32,
    pub unknown4: [u8; 88]
}
impl fmt::Debug for FileInformationV30 {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "FileInformationV30 {{ \
            metrics_array_offset: {}, \
            metrics_entry_count: {}, \
            trace_array_offset: {}, \
            trace_entry_count: {}, \
            filename_offset: {}, \
            filename_length: {}, \
            volume_info_offset: {}, \
            volume_info_count: {}, \
            volume_info_size: {}, \
            unknown3: {}, \
            last_run_time: {:?}, \
            unknown1: {:?}, \
            run_count: {}, \
            unknown2: {}, \
            unknown5: {}, \
            unknown4: {:?}\
            }}",
            self.metrics_array_offset,
            self.metrics_entry_count,
            self.trace_array_offset,
            self.trace_entry_count,
            self.filename_offset,
            self.filename_length,
            self.volume_info_offset,
            self.volume_info_count,
            self.volume_info_size,
            self.unknown3,
            self.last_run_time,
            utils::to_hex_string(self.unknown1.to_vec()),
            self.run_count,
            self.unknown2,
            self.unknown5,
            utils::to_hex_string(self.unknown4.to_vec())
        )
    }
}

impl FileInformation{
    pub fn new<R: Read>(header: &PrefetchHeader,reader: R) -> Result<FileInformation,PrefetchError> {
        if header.version == 17{
            Ok(
                FileInformation::V17(get_fileinfo_v17(reader)?)
            )
        } else if header.version == 23{
            Ok(
                FileInformation::V23(get_fileinfo_v23(reader)?)
            )
        } else if header.version == 26{
            Ok(
                FileInformation::V26(get_fileinfo_v26(reader)?)
            )
        } else if header.version == 30{
            Ok(
                FileInformation::V30(get_fileinfo_v30(reader)?)
            )
        } else {
            Err(PrefetchError::parse_error(
                format!("Error parsing file info. Invalid version: {}",header.version)
            ))
        }
    }
}
pub fn get_fileinfo_v17<R: Read>(mut reader: R) -> Result<FileInformationV17,PrefetchError> {
    let mut bytes: [u8; 68] = [0;68];
    reader.read_exact(&mut bytes)?;

    println!("fileinfo buffer hex: {:?}",utils::to_hex_string(bytes.to_vec()));

    let mut buffer = BufReader::new(
        Cursor::new(bytes.to_vec())
    );

    let mut fileinfo_v17: FileInformationV17 = unsafe {
        mem::zeroed()
    };

    fileinfo_v17.metrics_array_offset = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v17.metrics_entry_count = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v17.trace_array_offset = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v17.trace_entry_count = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v17.filename_offset = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v17.filename_length = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v17.volume_info_offset = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v17.volume_info_count = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v17.volume_info_size = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v17.last_run_time = WinTimestamp(buffer.read_u64::<LittleEndian>()?);
    buffer.read_exact(&mut fileinfo_v17.unknown1)?;
    fileinfo_v17.run_count = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v17.unknown2 = buffer.read_u32::<LittleEndian>()?;

    Ok(fileinfo_v17)
}
pub fn get_fileinfo_v23<R: Read>(mut reader: R) -> Result<FileInformationV23,PrefetchError> {
    let mut bytes: [u8; 156] = [0;156];
    reader.read_exact(&mut bytes)?;

    println!("fileinfo buffer hex: {:?}",utils::to_hex_string(bytes.to_vec()));

    let mut buffer = BufReader::new(
        Cursor::new(bytes.to_vec())
    );

    let mut fileinfo_v23: FileInformationV23 = unsafe {
        mem::zeroed()
    };

    fileinfo_v23.metrics_array_offset = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v23.metrics_entry_count = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v23.trace_array_offset = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v23.trace_entry_count = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v23.filename_offset = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v23.filename_length = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v23.volume_info_offset = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v23.volume_info_count = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v23.volume_info_size = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v23.unknown3 = buffer.read_u64::<LittleEndian>()?;
    fileinfo_v23.last_run_time = WinTimestamp(buffer.read_u64::<LittleEndian>()?);
    buffer.read_exact(&mut fileinfo_v23.unknown1)?;
    fileinfo_v23.run_count = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v23.unknown2 = buffer.read_u32::<LittleEndian>()?;
    buffer.read_exact(&mut fileinfo_v23.unknown4)?;

    Ok(fileinfo_v23)
}
pub fn get_fileinfo_v26<R: Read>(mut reader: R) -> Result<FileInformationV26,PrefetchError> {
    let mut bytes: [u8; 224] = [0;224];
    reader.read_exact(&mut bytes)?;

    println!("fileinfo buffer hex: {:?}",utils::to_hex_string(bytes.to_vec()));

    let mut buffer = BufReader::new(
        Cursor::new(bytes.to_vec())
    );

    let mut fileinfo_v26: FileInformationV26 = unsafe {
        mem::zeroed()
    };

    fileinfo_v26.metrics_array_offset = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v26.metrics_entry_count = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v26.trace_array_offset = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v26.trace_entry_count = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v26.filename_offset = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v26.filename_length = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v26.volume_info_offset = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v26.volume_info_count = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v26.volume_info_size = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v26.unknown3 = buffer.read_u64::<LittleEndian>()?;
    for n in 0..8 {
        fileinfo_v26.last_run_time[n] = WinTimestamp(buffer.read_u64::<LittleEndian>()?);
    }
    buffer.read_exact(&mut fileinfo_v26.unknown1)?;
    fileinfo_v26.run_count = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v26.unknown2 = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v26.unknown5 = buffer.read_u32::<LittleEndian>()?;
    buffer.read_exact(&mut fileinfo_v26.unknown4)?;

    Ok(fileinfo_v26)
}
pub fn get_fileinfo_v30<R: Read>(mut reader: R) -> Result<FileInformationV30,PrefetchError> {
    let mut bytes: [u8; 224] = [0;224];
    reader.read_exact(&mut bytes)?;

    println!("fileinfo buffer hex: {:?}",utils::to_hex_string(bytes.to_vec()));

    let mut buffer = BufReader::new(
        Cursor::new(bytes.to_vec())
    );

    let mut fileinfo_v30: FileInformationV30 = unsafe {
        mem::zeroed()
    };

    fileinfo_v30.metrics_array_offset = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v30.metrics_entry_count = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v30.trace_array_offset = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v30.trace_entry_count = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v30.filename_offset = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v30.filename_length = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v30.volume_info_offset = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v30.volume_info_count = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v30.volume_info_size = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v30.unknown3 = buffer.read_u64::<LittleEndian>()?;
    for n in 0..8 {
        fileinfo_v30.last_run_time[n] = WinTimestamp(buffer.read_u64::<LittleEndian>()?);
    }
    buffer.read_exact(&mut fileinfo_v30.unknown1)?;
    fileinfo_v30.run_count = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v30.unknown2 = buffer.read_u32::<LittleEndian>()?;
    fileinfo_v30.unknown5 = buffer.read_u32::<LittleEndian>()?;
    buffer.read_exact(&mut fileinfo_v30.unknown4)?;

    Ok(fileinfo_v30)
}
<|endoftext|>


<commit_before>// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Networking primitives for TCP/UDP communication.

#![stable(feature = "rust1", since = "1.0.0")]

use fmt;
use io::{self, Error, ErrorKind};
use sys_common::net as net_imp;

#[stable(feature = "rust1", since = "1.0.0")]
pub use self::ip::{IpAddr, Ipv4Addr, Ipv6Addr, Ipv6MulticastScope};
#[stable(feature = "rust1", since = "1.0.0")]
pub use self::addr::{SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs};
#[stable(feature = "rust1", since = "1.0.0")]
pub use self::tcp::{TcpStream, TcpListener, Incoming};
#[stable(feature = "rust1", since = "1.0.0")]
pub use self::udp::UdpSocket;
#[stable(feature = "rust1", since = "1.0.0")]
pub use self::parser::AddrParseError;

mod ip;
mod addr;
mod tcp;
mod udp;
mod parser;
#[cfg(test)]
mod test;

/// Possible values which can be passed to the [`shutdown`] method of
/// [`TcpStream`].
///
/// [`shutdown`]: struct.TcpStream.html#method.shutdown
/// [`TcpStream`]: struct.TcpStream.html
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[stable(feature = "rust1", since = "1.0.0")]
pub enum Shutdown {
    /// The reading portion of the [`TcpStream`] should be shut down.
    ///
    /// All currently blocked and future [reads] will return [`Ok(0)`].
    ///
    /// [`TcpStream`]: ../../std/net/struct.TcpStream.html
    /// [reads]: ../../std/io/trait.Read.html
    /// [`Ok(0)`]: ../../std/result/enum.Result.html#variant.Ok
    #[stable(feature = "rust1", since = "1.0.0")]
    Read,
    /// The writing portion of the [`TcpStream`] should be shut down.
    ///
    /// All currently blocked and future [writes] will return an error.
    ///
    /// [`TcpStream`]: ../../std/net/struct.TcpStream.html
    /// [writes]: ../../std/io/trait.Write.html
    #[stable(feature = "rust1", since = "1.0.0")]
    Write,
    /// Both the reading and the writing portions of the [`TcpStream`] should be shut down.
    ///
    /// See [`Shutdown::Read`] and [`Shutdown::Write`] for more information.
    ///
    /// [`TcpStream`]: ../../std/net/struct.TcpStream.html
    /// [`Shutdown::Read`]: #variant.Read
    /// [`Shutdown::Write`]: #variant.Write
    #[stable(feature = "rust1", since = "1.0.0")]
    Both,
}

#[doc(hidden)]
trait NetInt {
    fn from_be(i: Self) -> Self;
    fn to_be(&self) -> Self;
}
macro_rules! doit {
    ($($t:ident)*) => ($(impl NetInt for $t {
        fn from_be(i: Self) -> Self { <$t>::from_be(i) }
        fn to_be(&self) -> Self { <$t>::to_be(*self) }
    })*)
}
doit! { i8 i16 i32 i64 isize u8 u16 u32 u64 usize }

fn hton<I: NetInt>(i: I) -> I { i.to_be() }
fn ntoh<I: NetInt>(i: I) -> I { I::from_be(i) }

fn each_addr<A: ToSocketAddrs, F, T>(addr: A, mut f: F) -> io::Result<T>
    where F: FnMut(&SocketAddr) -> io::Result<T>
{
    let mut last_err = None;
    for addr in addr.to_socket_addrs()? {
        match f(&addr) {
            Ok(l) => return Ok(l),
            Err(e) => last_err = Some(e),
        }
    }
    Err(last_err.unwrap_or_else(|| {
        Error::new(ErrorKind::InvalidInput,
                   "could not resolve to any addresses")
    }))
}

/// An iterator over `SocketAddr` values returned from a host lookup operation.
#[unstable(feature = "lookup_host", reason = "unsure about the returned \
                                              iterator and returning socket \
                                              addresses",
           issue = "27705")]
pub struct LookupHost(net_imp::LookupHost);

#[unstable(feature = "lookup_host", reason = "unsure about the returned \
                                              iterator and returning socket \
                                              addresses",
           issue = "27705")]
impl Iterator for LookupHost {
    type Item = SocketAddr;
    fn next(&mut self) -> Option<SocketAddr> { self.0.next() }
}

#[unstable(feature = "lookup_host", reason = "unsure about the returned \
                                              iterator and returning socket \
                                              addresses",
           issue = "27705")]
impl fmt::Debug for LookupHost {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.pad("LookupHost { .. }")
    }
}

/// Resolve the host specified by `host` as a number of `SocketAddr` instances.
///
/// This method may perform a DNS query to resolve `host` and may also inspect
/// system configuration to resolve the specified hostname.
///
/// The returned iterator will skip over any unknown addresses returned by the
/// operating system.
///
/// # Examples
///
/// ```no_run
/// #![feature(lookup_host)]
///
/// use std::net;
///
/// # fn foo() -> std::io::Result<()> {
/// for host in net::lookup_host("rust-lang.org")? {
///     println!("found address: {}", host);
/// }
/// # Ok(())
/// # }
/// ```
#[unstable(feature = "lookup_host", reason = "unsure about the returned \
                                              iterator and returning socket \
                                              addresses",
           issue = "27705")]
pub fn lookup_host(host: &str) -> io::Result<LookupHost> {
    net_imp::lookup_host(host).map(LookupHost)
}
<commit_msg>Expanded std::net module docs<commit_after>// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Networking primitives for TCP/UDP communication.
//!
//! This module provides networking functionality for the Transmission Control and User
//! Datagram Protocols, as well as types for IP and socket addresses.
//!
//! # Organization
//!
//! * [`TcpListener`] and [`TcpStream`] provide functionality for communication over TCP
//! * [`UdpSocket`] provides functionality for communication over UDP
//! * [`IpAddr`] represents IP addresses of either IPv4 or IPv6; [`Ipv4Addr`] and
//!   [`Ipv6Addr`] are respectively IPv4 and IPv6 addresses
//! * [`SocketAddr`] represents socket addresses of either IPv4 or IPv6; [`SocketAddrV4`]
//!   and [`SocketAddrV6`] are respectively IPv4 and IPv6 socket addresses
//! * [`ToSocketAddr`] is a trait that used for generic address resolution interacting
//!   with networking objects like [`TcpListener`], [`TcpStream`] or [`UdpSocket`]
//! * Other types are return or parameter types for various methods in this module
//!
//! [`IpAddr`]: ../../std/net/enum.IpAddr.html
//! [`Ipv4Addr`]: ../../std/net/struct.Ipv4Addr.html
//! [`Ipv6Addr`]: ../../std/net/struct.Ipv6Addr.html
//! [`SocketAddr`]: ../../std/net/enum.SocketAddr.html
//! [`SocketAddrV4`]: ../../std/net/struct.SocketAddrV4.html
//! [`SocketAddrV6`]: ../../std/net/struct.SocketAddrV6.html
//! [`TcpListener`]: ../../std/net/struct.TcpListener.html
//! [`TcpStream`]: ../../std/net/struct.TcpStream.html
//! [`ToSocketAddr`]: ../../std/net/trait.ToSocketAddr.html
//! [`UdpSocket`]: ../../std/net/struct.UdpSocket.html

#![stable(feature = "rust1", since = "1.0.0")]

use fmt;
use io::{self, Error, ErrorKind};
use sys_common::net as net_imp;

#[stable(feature = "rust1", since = "1.0.0")]
pub use self::ip::{IpAddr, Ipv4Addr, Ipv6Addr, Ipv6MulticastScope};
#[stable(feature = "rust1", since = "1.0.0")]
pub use self::addr::{SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs};
#[stable(feature = "rust1", since = "1.0.0")]
pub use self::tcp::{TcpStream, TcpListener, Incoming};
#[stable(feature = "rust1", since = "1.0.0")]
pub use self::udp::UdpSocket;
#[stable(feature = "rust1", since = "1.0.0")]
pub use self::parser::AddrParseError;

mod ip;
mod addr;
mod tcp;
mod udp;
mod parser;
#[cfg(test)]
mod test;

/// Possible values which can be passed to the [`shutdown`] method of
/// [`TcpStream`].
///
/// [`shutdown`]: struct.TcpStream.html#method.shutdown
/// [`TcpStream`]: struct.TcpStream.html
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[stable(feature = "rust1", since = "1.0.0")]
pub enum Shutdown {
    /// The reading portion of the [`TcpStream`] should be shut down.
    ///
    /// All currently blocked and future [reads] will return [`Ok(0)`].
    ///
    /// [`TcpStream`]: ../../std/net/struct.TcpStream.html
    /// [reads]: ../../std/io/trait.Read.html
    /// [`Ok(0)`]: ../../std/result/enum.Result.html#variant.Ok
    #[stable(feature = "rust1", since = "1.0.0")]
    Read,
    /// The writing portion of the [`TcpStream`] should be shut down.
    ///
    /// All currently blocked and future [writes] will return an error.
    ///
    /// [`TcpStream`]: ../../std/net/struct.TcpStream.html
    /// [writes]: ../../std/io/trait.Write.html
    #[stable(feature = "rust1", since = "1.0.0")]
    Write,
    /// Both the reading and the writing portions of the [`TcpStream`] should be shut down.
    ///
    /// See [`Shutdown::Read`] and [`Shutdown::Write`] for more information.
    ///
    /// [`TcpStream`]: ../../std/net/struct.TcpStream.html
    /// [`Shutdown::Read`]: #variant.Read
    /// [`Shutdown::Write`]: #variant.Write
    #[stable(feature = "rust1", since = "1.0.0")]
    Both,
}

#[doc(hidden)]
trait NetInt {
    fn from_be(i: Self) -> Self;
    fn to_be(&self) -> Self;
}
macro_rules! doit {
    ($($t:ident)*) => ($(impl NetInt for $t {
        fn from_be(i: Self) -> Self { <$t>::from_be(i) }
        fn to_be(&self) -> Self { <$t>::to_be(*self) }
    })*)
}
doit! { i8 i16 i32 i64 isize u8 u16 u32 u64 usize }

fn hton<I: NetInt>(i: I) -> I { i.to_be() }
fn ntoh<I: NetInt>(i: I) -> I { I::from_be(i) }

fn each_addr<A: ToSocketAddrs, F, T>(addr: A, mut f: F) -> io::Result<T>
    where F: FnMut(&SocketAddr) -> io::Result<T>
{
    let mut last_err = None;
    for addr in addr.to_socket_addrs()? {
        match f(&addr) {
            Ok(l) => return Ok(l),
            Err(e) => last_err = Some(e),
        }
    }
    Err(last_err.unwrap_or_else(|| {
        Error::new(ErrorKind::InvalidInput,
                   "could not resolve to any addresses")
    }))
}

/// An iterator over `SocketAddr` values returned from a host lookup operation.
#[unstable(feature = "lookup_host", reason = "unsure about the returned \
                                              iterator and returning socket \
                                              addresses",
           issue = "27705")]
pub struct LookupHost(net_imp::LookupHost);

#[unstable(feature = "lookup_host", reason = "unsure about the returned \
                                              iterator and returning socket \
                                              addresses",
           issue = "27705")]
impl Iterator for LookupHost {
    type Item = SocketAddr;
    fn next(&mut self) -> Option<SocketAddr> { self.0.next() }
}

#[unstable(feature = "lookup_host", reason = "unsure about the returned \
                                              iterator and returning socket \
                                              addresses",
           issue = "27705")]
impl fmt::Debug for LookupHost {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.pad("LookupHost { .. }")
    }
}

/// Resolve the host specified by `host` as a number of `SocketAddr` instances.
///
/// This method may perform a DNS query to resolve `host` and may also inspect
/// system configuration to resolve the specified hostname.
///
/// The returned iterator will skip over any unknown addresses returned by the
/// operating system.
///
/// # Examples
///
/// ```no_run
/// #![feature(lookup_host)]
///
/// use std::net;
///
/// # fn foo() -> std::io::Result<()> {
/// for host in net::lookup_host("rust-lang.org")? {
///     println!("found address: {}", host);
/// }
/// # Ok(())
/// # }
/// ```
#[unstable(feature = "lookup_host", reason = "unsure about the returned \
                                              iterator and returning socket \
                                              addresses",
           issue = "27705")]
pub fn lookup_host(host: &str) -> io::Result<LookupHost> {
    net_imp::lookup_host(host).map(LookupHost)
}
<|endoftext|>


<commit_before>//! Types/fns concerning URLs (see RFC 3986)

export url, userinfo, query, from_str, to_str, get_scheme;

type url = {
    scheme: ~str,
    user: option<userinfo>,
    host: ~str,
    path: ~str,
    query: query,
    fragment: option<~str>
};

type userinfo = {
    user: ~str,
    pass: option<~str>
};

type query = ~[(~str, ~str)];

fn url(-scheme: ~str, -user: option<userinfo>, -host: ~str,
       -path: ~str, -query: query, -fragment: option<~str>) -> url {
    { scheme: scheme, user: user, host: host,
     path: path, query: query, fragment: fragment }
}

fn userinfo(-user: ~str, -pass: option<~str>) -> userinfo {
    {user: user, pass: pass}
}

fn split_char_first(s: ~str, c: char) -> (~str, ~str) {
    let mut v = str::splitn_char(s, c, 1);
    if v.len() == 1 {
        return (s, ~"");
    } else {
        return (vec::shift(v), vec::pop(v));
    }
}

fn userinfo_from_str(uinfo: ~str) -> userinfo {
    let (user, p) = split_char_first(uinfo, ':');
    let pass = if str::len(p) == 0 {
        option::none
    } else {
        option::some(p)
    };
    return userinfo(user, pass);
}

fn userinfo_to_str(-userinfo: userinfo) -> ~str {
    if option::is_some(userinfo.pass) {
        return str::concat(~[copy userinfo.user, ~":",
                          option::unwrap(copy userinfo.pass),
                          ~"@"]);
    } else {
        return str::concat(~[copy userinfo.user, ~"@"]);
    }
}

fn query_from_str(rawquery: ~str) -> query {
    let mut query: query = ~[];
    if str::len(rawquery) != 0 {
        for str::split_char(rawquery, '&').each |p| {
            let (k, v) = split_char_first(p, '=');
            vec::push(query, (k, v));
        };
    }
    return query;
}

fn query_to_str(query: query) -> ~str {
    let mut strvec = ~[];
    for query.each |kv| {
        let (k, v) = kv;
        strvec += ~[fmt!{"%s=%s", k, v}];
    };
    return str::connect(strvec, ~"&");
}

fn get_scheme(rawurl: ~str) -> option::option<(~str, ~str)> {
    for str::each_chari(rawurl) |i,c| {
        if char::is_alphabetic(c) {
            again;
        } else if c == ':' && i != 0 {
            return option::some((rawurl.slice(0,i),
                              rawurl.slice(i+3,str::len(rawurl))));
        } else {
            return option::none;
        }
    };
    return option::none;
}

/**
 * Parse a `str` to a `url`
 *
 * # Arguments
 *
 * `rawurl` - a string representing a full url, including scheme.
 *
 * # Returns
 *
 * a `url` that contains the parsed representation of the url.
 *
 */

fn from_str(rawurl: ~str) -> result::result<url, ~str> {
    let mut schm = get_scheme(rawurl);
    if option::is_none(schm) {
        return result::err(~"invalid scheme");
    }
    let (scheme, rest) = option::unwrap(schm);
    let (u, rest) = split_char_first(rest, '@');
    let user = if str::len(rest) == 0 {
        option::none
    } else {
        option::some(userinfo_from_str(u))
    };
    let rest = if str::len(rest) == 0 {
         u
    } else {
        rest
    };
    let (rest, frag) = split_char_first(rest, '#');
    let fragment = if str::len(frag) == 0 {
        option::none
    } else {
        option::some(frag)
    };
    let (rest, query) = split_char_first(rest, '?');
    let query = query_from_str(query);
    let (host, pth) = split_char_first(rest, '/');
    let mut path = pth;
    if str::len(path) != 0 {
        str::unshift_char(path, '/');
    }

    return result::ok(url(scheme, user, host, path, query, fragment));
}

/**
 * Format a `url` as a string
 *
 * # Arguments
 *
 * `url` - a url.
 *
 * # Returns
 *
 * a `str` that contains the formatted url. Note that this will usually
 * be an inverse of `from_str` but might strip out unneeded separators.
 * for example, "http://somehost.com?", when parsed and formatted, will
 * result in just "http://somehost.com".
 *
 */
fn to_str(url: url) -> ~str {
    let user = if option::is_some(url.user) {
      userinfo_to_str(option::unwrap(copy url.user))
    } else {
       ~""
    };
    let query = if url.query.len() == 0 {
        ~""
    } else {
        str::concat(~[~"?", query_to_str(url.query)])
    };
    let fragment = if option::is_some(url.fragment) {
        str::concat(~[~"#", option::unwrap(copy url.fragment)])
    } else {
        ~""
    };

    return str::concat(~[copy url.scheme,
                      ~"://",
                      user,
                      copy url.host,
                      copy url.path,
                      query,
                      fragment]);
}

impl of to_str::to_str for url {
    fn to_str() -> ~str {
        to_str(self)
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_full_url_parse_and_format() {
        let url = ~"http://user:pass@rust-lang.org/doc?s=v#something";
        assert to_str(result::unwrap(from_str(url))) == url;
    }

    #[test]
    fn test_userless_url_parse_and_format() {
        let url = ~"http://rust-lang.org/doc?s=v#something";
        assert to_str(result::unwrap(from_str(url))) == url;
    }

    #[test]
    fn test_queryless_url_parse_and_format() {
        let url = ~"http://user:pass@rust-lang.org/doc#something";
        assert to_str(result::unwrap(from_str(url))) == url;
    }

    #[test]
    fn test_empty_query_url_parse_and_format() {
        let url = ~"http://user:pass@rust-lang.org/doc?#something";
        let should_be = ~"http://user:pass@rust-lang.org/doc#something";
        assert to_str(result::unwrap(from_str(url))) == should_be;
    }

    #[test]
    fn test_fragmentless_url_parse_and_format() {
        let url = ~"http://user:pass@rust-lang.org/doc?q=v";
        assert to_str(result::unwrap(from_str(url))) == url;
    }

    #[test]
    fn test_minimal_url_parse_and_format() {
        let url = ~"http://rust-lang.org/doc";
        assert to_str(result::unwrap(from_str(url))) == url;
    }

    #[test]
    fn test_scheme_host_only_url_parse_and_format() {
        let url = ~"http://rust-lang.org";
        assert to_str(result::unwrap(from_str(url))) == url;
    }

    #[test]
    fn test_pathless_url_parse_and_format() {
        let url = ~"http://user:pass@rust-lang.org?q=v#something";
        assert to_str(result::unwrap(from_str(url))) == url;
    }

    #[test]
    fn test_scheme_host_fragment_only_url_parse_and_format() {
        let url = ~"http://rust-lang.org#something";
        assert to_str(result::unwrap(from_str(url))) == url;
    }

}
<commit_msg>std: integrating erickt's url encoding/decoding from github.com/erickt/rust-uri into std::net::url<commit_after>//! Types/fns concerning URLs (see RFC 3986)

import map;
import map::{hashmap, str_hash};
import io::{reader, reader_util};
import dvec::{dvec, extensions};

export url, userinfo, query;
export from_str, to_str;
export get_scheme;

export encode, decode;
export encode_component, decode_component;
export encode_form_urlencoded, decode_form_urlencoded;

type url = {
    scheme: ~str,
    user: option<userinfo>,
    host: ~str,
    path: ~str,
    query: query,
    fragment: option<~str>
};

type userinfo = {
    user: ~str,
    pass: option<~str>
};

type query = ~[(~str, ~str)];

fn url(-scheme: ~str, -user: option<userinfo>, -host: ~str,
       -path: ~str, -query: query, -fragment: option<~str>) -> url {
    { scheme: scheme, user: user, host: host,
     path: path, query: query, fragment: fragment }
}

fn userinfo(-user: ~str, -pass: option<~str>) -> userinfo {
    {user: user, pass: pass}
}

fn encode_inner(s: ~str, full_url: bool) -> ~str {
    do io::with_str_reader(s) |rdr| {
        let mut out = ~"";

        while !rdr.eof() {
            let ch = rdr.read_byte() as char;
            alt ch {
              // unreserved:
              'A' to 'Z' |
              'a' to 'z' |
              '0' to '9' |
              '-' | '.' | '_' | '~' {
                str::push_char(out, ch);
              }
              _ {
                if full_url {
                    alt ch {
                      // gen-delims:
                      ':' | '/' | '?' | '#' | '[' | ']' | '@' |

                      // sub-delims:
                      '!' | '$' | '&' | '"' | '(' | ')' | '*' |
                      '+' | ',' | ';' | '=' {
                        str::push_char(out, ch);
                      }

                      _ { out += #fmt("%%%X", ch as uint); }
                    }
                } else {
                    out += #fmt("%%%X", ch as uint);
                }
              }
            }
        }

        out
    }
}

/** Encodes a URI by replacing reserved characters with percent encoded character
 * sequences.
 *
 * This function is compliant with RFC 3986.
 */
fn encode(s: ~str) -> ~str {
    encode_inner(s, true)
}

/** Encodes a URI component by replacing reserved characters with percent encoded
 * character sequences.
 *
 * This function is compliant with RFC 3986.
 */
fn encode_component(s: ~str) -> ~str {
    encode_inner(s, false)
}

fn decode_inner(s: ~str, full_url: bool) -> ~str {
    do io::with_str_reader(s) |rdr| {
        let mut out = ~"";

        while !rdr.eof() {
            alt rdr.read_char() {
              '%' {
                let bytes = rdr.read_bytes(2u);
                let ch = uint::parse_buf(bytes, 16u).get() as char;

                if full_url {
                    // Only decode some characters:
                    alt ch {
                      // gen-delims:
                      ':' | '/' | '?' | '#' | '[' | ']' | '@' |

                      // sub-delims:
                      '!' | '$' | '&' | '"' | '(' | ')' | '*' |
                      '+' | ',' | ';' | '=' {
                        str::push_char(out, '%');
                        str::push_char(out, bytes[0u] as char);
                        str::push_char(out, bytes[1u] as char);
                      }

                      ch { str::push_char(out, ch); }
                    }
                } else {
                      str::push_char(out, ch);
                }
              }
              ch { str::push_char(out, ch); }
            }
        }

        out
    }
}

/** Decode a string encoded with percent encoding.
 *
 * This will only decode escape sequences generated by encode_uri.
 */
fn decode(s: ~str) -> ~str {
    decode_inner(s, true)
}

/** Decode a string encoded with percent encoding.
 */
fn decode_component(s: ~str) -> ~str {
    decode_inner(s, false)
}

fn encode_plus(s: ~str) -> ~str {
    do io::with_str_reader(s) |rdr| {
        let mut out = ~"";

        while !rdr.eof() {
            let ch = rdr.read_byte() as char;
            alt ch {
              'A' to 'Z' | 'a' to 'z' | '0' to '9' | '_' | '.' | '-' {
                str::push_char(out, ch);
              }
              ' ' { str::push_char(out, '+'); }
              _ { out += #fmt("%%%X", ch as uint); }
            }
        }

        out
    }
}

/** Encode a hashmap to the 'application/x-www-form-urlencoded' media type.
 */
fn encode_form_urlencoded(m: hashmap<~str, @dvec<@~str>>) -> ~str {
    let mut out = ~"";
    let mut first = true;

    for m.each |key, values| {
        let key = encode_plus(key);

        for (*values).each |value| {
            if first {
                first = false;
            } else {
                str::push_char(out, '&');
                first = false;
            }

            out += #fmt("%s=%s", key, encode_plus(*value));
        }
    }

    out
}

/** Decode a string encoded with the 'application/x-www-form-urlencoded' media
 * type into a hashmap.
 */
fn decode_form_urlencoded(s: ~[u8]) -> hashmap<~str, @dvec<@~str>> {
    do io::with_bytes_reader(s) |rdr| {
        let m = str_hash();
        let mut key = ~"";
        let mut value = ~"";
        let mut parsing_key = true;

        while !rdr.eof() {
            alt rdr.read_char() {
              '&' | ';' {
                if key != ~"" && value != ~"" {
                    let values = alt m.find(key) {
                      some(values) { values }
                      none {
                        let values = @dvec();
                        m.insert(key, values);
                        values
                      }
                    };
                    (*values).push(@value)
                }

                parsing_key = true;
                key = ~"";
                value = ~"";
              }
              '=' { parsing_key = false; }
              ch {
                let ch = alt ch {
                  '%' {
                    uint::parse_buf(rdr.read_bytes(2u), 16u).get() as char
                  }
                  '+' { ' ' }
                  ch { ch }
                };

                if parsing_key {
                    str::push_char(key, ch)
                } else {
                    str::push_char(value, ch)
                }
              }
            }
        }

        if key != ~"" && value != ~"" {
            let values = alt m.find(key) {
              some(values) { values }
              none {
                let values = @dvec();
                m.insert(key, values);
                values
              }
            };
            (*values).push(@value)
        }

        m
    }
}

fn split_char_first(s: ~str, c: char) -> (~str, ~str) {
    let mut v = str::splitn_char(s, c, 1);
    if v.len() == 1 {
        return (s, ~"");
    } else {
        return (vec::shift(v), vec::pop(v));
    }
}

fn userinfo_from_str(uinfo: ~str) -> userinfo {
    let (user, p) = split_char_first(uinfo, ':');
    let pass = if str::len(p) == 0 {
        option::none
    } else {
        option::some(p)
    };
    return userinfo(user, pass);
}

fn userinfo_to_str(-userinfo: userinfo) -> ~str {
    if option::is_some(userinfo.pass) {
        return str::concat(~[copy userinfo.user, ~":",
                          option::unwrap(copy userinfo.pass),
                          ~"@"]);
    } else {
        return str::concat(~[copy userinfo.user, ~"@"]);
    }
}

fn query_from_str(rawquery: ~str) -> query {
    let mut query: query = ~[];
    if str::len(rawquery) != 0 {
        for str::split_char(rawquery, '&').each |p| {
            let (k, v) = split_char_first(p, '=');
            vec::push(query, (decode_component(k), decode_component(v)));
        };
    }
    return query;
}

fn query_to_str(query: query) -> ~str {
    let mut strvec = ~[];
    for query.each |kv| {
        let (k, v) = kv;
        strvec += ~[#fmt("%s=%s", encode_component(k), encode_component(v))];
    };
    return str::connect(strvec, ~"&");
}

fn get_scheme(rawurl: ~str) -> option::option<(~str, ~str)> {
    for str::each_chari(rawurl) |i,c| {
        if char::is_alphabetic(c) {
            again;
        } else if c == ':' && i != 0 {
            return option::some((rawurl.slice(0,i),
                              rawurl.slice(i+3,str::len(rawurl))));
        } else {
            return option::none;
        }
    };
    return option::none;
}

/**
 * Parse a `str` to a `url`
 *
 * # Arguments
 *
 * `rawurl` - a string representing a full url, including scheme.
 *
 * # Returns
 *
 * a `url` that contains the parsed representation of the url.
 *
 */

fn from_str(rawurl: ~str) -> result::result<url, ~str> {
    let mut schm = get_scheme(rawurl);
    if option::is_none(schm) {
        return result::err(~"invalid scheme");
    }
    let (scheme, rest) = option::unwrap(schm);
    let (u, rest) = split_char_first(rest, '@');
    let user = if str::len(rest) == 0 {
        option::none
    } else {
        option::some(userinfo_from_str(u))
    };
    let rest = if str::len(rest) == 0 {
         u
    } else {
        rest
    };
    let (rest, frag) = split_char_first(rest, '#');
    let fragment = if str::len(frag) == 0 {
        option::none
    } else {
        option::some(frag)
    };
    let (rest, query) = split_char_first(rest, '?');
    let query = query_from_str(query);
    let (host, pth) = split_char_first(rest, '/');
    let mut path = decode_component(pth);
    if str::len(path) != 0 {
        str::unshift_char(path, '/');
    }

    return result::ok(url(scheme, user, host, path, query, fragment));
}

/**
 * Format a `url` as a string
 *
 * # Arguments
 *
 * `url` - a url.
 *
 * # Returns
 *
 * a `str` that contains the formatted url. Note that this will usually
 * be an inverse of `from_str` but might strip out unneeded separators.
 * for example, "http://somehost.com?", when parsed and formatted, will
 * result in just "http://somehost.com".
 *
 */
fn to_str(url: url) -> ~str {
    let user = if option::is_some(url.user) {
      userinfo_to_str(option::unwrap(copy url.user))
    } else {
       ~""
    };
    let query = if url.query.len() == 0 {
        ~""
    } else {
        str::concat(~[~"?", query_to_str(url.query)])
    };
    let fragment = if option::is_some(url.fragment) {
        str::concat(~[~"#", option::unwrap(copy url.fragment)])
    } else {
        ~""
    };

    return str::concat(~[copy url.scheme,
                      ~"://",
                      user,
                      copy url.host,
                      copy url.path,
                      query,
                      fragment]);
}

impl of to_str::to_str for url {
    fn to_str() -> ~str {
        to_str(self)
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_full_url_parse_and_format() {
        let url = ~"http://user:pass@rust-lang.org/doc?s=v#something";
        assert to_str(result::unwrap(from_str(url))) == url;
    }

    #[test]
    fn test_userless_url_parse_and_format() {
        let url = ~"http://rust-lang.org/doc?s=v#something";
        assert to_str(result::unwrap(from_str(url))) == url;
    }

    #[test]
    fn test_queryless_url_parse_and_format() {
        let url = ~"http://user:pass@rust-lang.org/doc#something";
        assert to_str(result::unwrap(from_str(url))) == url;
    }

    #[test]
    fn test_empty_query_url_parse_and_format() {
        let url = ~"http://user:pass@rust-lang.org/doc?#something";
        let should_be = ~"http://user:pass@rust-lang.org/doc#something";
        assert to_str(result::unwrap(from_str(url))) == should_be;
    }

    #[test]
    fn test_fragmentless_url_parse_and_format() {
        let url = ~"http://user:pass@rust-lang.org/doc?q=v";
        assert to_str(result::unwrap(from_str(url))) == url;
    }

    #[test]
    fn test_minimal_url_parse_and_format() {
        let url = ~"http://rust-lang.org/doc";
        assert to_str(result::unwrap(from_str(url))) == url;
    }

    #[test]
    fn test_scheme_host_only_url_parse_and_format() {
        let url = ~"http://rust-lang.org";
        assert to_str(result::unwrap(from_str(url))) == url;
    }

    #[test]
    fn test_pathless_url_parse_and_format() {
        let url = ~"http://user:pass@rust-lang.org?q=v#something";
        assert to_str(result::unwrap(from_str(url))) == url;
    }

    #[test]
    fn test_scheme_host_fragment_only_url_parse_and_format() {
        let url = ~"http://rust-lang.org#something";
        assert to_str(result::unwrap(from_str(url))) == url;
    }

    #[test]
    fn test_url_component_encoding() {
        let url = ~"http://rust-lang.org/doc%20uments?ba%25d%20=%23%26%2B";
        let u = result::unwrap(from_str(url));
        assert u.path == ~"/doc uments";
        assert u.query.find(|kv| kv.first() == ~"ba%d ")
            .get().second() == ~"#&+";
    }

    #[test]
    fn test_encode() {
        assert encode(~"") == ~"";
        assert encode(~"http://example.com") == ~"http://example.com";
        assert encode(~"foo bar% baz") == ~"foo%20bar%25%20baz";
        assert encode(~" ") == ~"%20";
        assert encode(~"!") == ~"!";
        assert encode(~"\"") == ~"\"";
        assert encode(~"#") == ~"#";
        assert encode(~"$") == ~"$";
        assert encode(~"%") == ~"%25";
        assert encode(~"&") == ~"&";
        assert encode(~"'") == ~"%27";
        assert encode(~"(") == ~"(";
        assert encode(~")") == ~")";
        assert encode(~"*") == ~"*";
        assert encode(~"+") == ~"+";
        assert encode(~",") == ~",";
        assert encode(~"/") == ~"/";
        assert encode(~":") == ~":";
        assert encode(~";") == ~";";
        assert encode(~"=") == ~"=";
        assert encode(~"?") == ~"?";
        assert encode(~"@") == ~"@";
        assert encode(~"[") == ~"[";
        assert encode(~"]") == ~"]";
    }

    #[test]
    fn test_encode_component() {
        assert encode_component(~"") == ~"";
        assert encode_component(~"http://example.com") ==
            ~"http%3A%2F%2Fexample.com";
        assert encode_component(~"foo bar% baz") == ~"foo%20bar%25%20baz";
        assert encode_component(~" ") == ~"%20";
        assert encode_component(~"!") == ~"%21";
        assert encode_component(~"#") == ~"%23";
        assert encode_component(~"$") == ~"%24";
        assert encode_component(~"%") == ~"%25";
        assert encode_component(~"&") == ~"%26";
        assert encode_component(~"'") == ~"%27";
        assert encode_component(~"(") == ~"%28";
        assert encode_component(~")") == ~"%29";
        assert encode_component(~"*") == ~"%2A";
        assert encode_component(~"+") == ~"%2B";
        assert encode_component(~",") == ~"%2C";
        assert encode_component(~"/") == ~"%2F";
        assert encode_component(~":") == ~"%3A";
        assert encode_component(~";") == ~"%3B";
        assert encode_component(~"=") == ~"%3D";
        assert encode_component(~"?") == ~"%3F";
        assert encode_component(~"@") == ~"%40";
        assert encode_component(~"[") == ~"%5B";
        assert encode_component(~"]") == ~"%5D";
    }

    #[test]
    fn test_decode() {
        assert decode(~"") == ~"";
        assert decode(~"abc/def 123") == ~"abc/def 123";
        assert decode(~"abc%2Fdef%20123") == ~"abc%2Fdef 123";
        assert decode(~"%20") == ~" ";
        assert decode(~"%21") == ~"%21";
        assert decode(~"%22") == ~"%22";
        assert decode(~"%23") == ~"%23";
        assert decode(~"%24") == ~"%24";
        assert decode(~"%25") == ~"%";
        assert decode(~"%26") == ~"%26";
        assert decode(~"%27") == ~"'";
        assert decode(~"%28") == ~"%28";
        assert decode(~"%29") == ~"%29";
        assert decode(~"%2A") == ~"%2A";
        assert decode(~"%2B") == ~"%2B";
        assert decode(~"%2C") == ~"%2C";
        assert decode(~"%2F") == ~"%2F";
        assert decode(~"%3A") == ~"%3A";
        assert decode(~"%3B") == ~"%3B";
        assert decode(~"%3D") == ~"%3D";
        assert decode(~"%3F") == ~"%3F";
        assert decode(~"%40") == ~"%40";
        assert decode(~"%5B") == ~"%5B";
        assert decode(~"%5D") == ~"%5D";
    }

    #[test]
    fn test_decode_component() {
        assert decode_component(~"") == ~"";
        assert decode_component(~"abc/def 123") == ~"abc/def 123";
        assert decode_component(~"abc%2Fdef%20123") == ~"abc/def 123";
        assert decode_component(~"%20") == ~" ";
        assert decode_component(~"%21") == ~"!";
        assert decode_component(~"%22") == ~"\"";
        assert decode_component(~"%23") == ~"#";
        assert decode_component(~"%24") == ~"$";
        assert decode_component(~"%25") == ~"%";
        assert decode_component(~"%26") == ~"&";
        assert decode_component(~"%27") == ~"'";
        assert decode_component(~"%28") == ~"(";
        assert decode_component(~"%29") == ~")";
        assert decode_component(~"%2A") == ~"*";
        assert decode_component(~"%2B") == ~"+";
        assert decode_component(~"%2C") == ~",";
        assert decode_component(~"%2F") == ~"/";
        assert decode_component(~"%3A") == ~":";
        assert decode_component(~"%3B") == ~";";
        assert decode_component(~"%3D") == ~"=";
        assert decode_component(~"%3F") == ~"?";
        assert decode_component(~"%40") == ~"@";
        assert decode_component(~"%5B") == ~"[";
        assert decode_component(~"%5D") == ~"]";
    }

    #[test]
    fn test_encode_form_urlencoded() {
        let m = str_hash();
        assert encode_form_urlencoded(m) == ~"";

        m.insert(~"", @dvec());
        m.insert(~"foo", @dvec());
        assert encode_form_urlencoded(m) == ~"";

        let m = str_hash();
        m.insert(~"foo", @dvec::from_vec(~[mut @~"bar", @~"123"]));
        assert encode_form_urlencoded(m) == ~"foo=bar&foo=123";

        let m = str_hash();
        m.insert(~"foo bar", @dvec::from_vec(~[mut @~"abc", @~"12 = 34"]));
        assert encode_form_urlencoded(m) == ~"foo+bar=abc&foo+bar=12+%3D+34";
    }

    #[test]
    fn test_decode_form_urlencoded() {
        import map::hash_from_strs;

        assert decode_form_urlencoded(~[]) == str_hash();

        let s = str::bytes(~"a=1&foo+bar=abc&foo+bar=12+%3D+34");
        assert decode_form_urlencoded(s) == hash_from_strs(~[
            (~"a", @dvec::from_elem(@~"1")),
            (~"foo bar", @dvec::from_vec(~[mut @~"abc", @~"12 = 34"]))
        ]);
    }

}

<|endoftext|>