<commit_before>use super::{Color, Event, Point, Rect, Renderer, Widget};

use std::sync::Arc;

extern crate orbital;

pub struct WindowRenderer<'a> {
    inner: &'a mut Box<orbital::Window>
}

impl<'a> WindowRenderer<'a> {
    pub fn new(inner: &'a mut Box<orbital::Window>) -> WindowRenderer {
        WindowRenderer {
            inner: inner
        }
    }
}

impl<'a> Renderer for WindowRenderer<'a> {
    fn clear(&mut self, color: Color) {
        self.inner.set(orbital::Color {
            data: color.data
        });
    }

    fn char(&mut self, pos: Point, c: char, color: Color) {
        self.inner.char(pos.x, pos.y, c, orbital::Color {
            data: color.data
        });
    }

    fn rect(&mut self, rect: Rect, color: Color) {
        self.inner.rect(rect.x, rect.y, rect.width, rect.height, orbital::Color {
            data: color.data
        });
    }
}

impl<'a> Drop for WindowRenderer<'a> {
    fn drop(&mut self) {
        self.inner.sync();
    }
}

pub struct Window {
    inner: Box<orbital::Window>,
    pub widgets: Vec<Arc<Widget>>,
    pub bg: Color,
}

impl Window {
    pub fn new(rect: Rect, title: &str) -> Box<Self> {
        Box::new(Window {
            inner: orbital::Window::new(rect.x, rect.y, rect.width, rect.height, title).unwrap(),
            widgets: Vec::new(),
            bg: Color::rgb(237, 233, 227),
        })
    }

    pub fn draw(&mut self) {
        let mut renderer = WindowRenderer::new(&mut self.inner);
        renderer.clear(self.bg);
        for widget in self.widgets.iter() {
            widget.draw(&mut renderer);
        }
    }

    pub fn exec(&mut self) {
        self.draw();
        while let Some(orbital_event) = self.inner.poll() {
            let mut events = Vec::new();

            let event = match orbital_event.to_option() {
                orbital::EventOption::Mouse(mouse_event) => events.push(Event::Mouse {
                    point: Point::new(mouse_event.x, mouse_event.y),
                    left_button: mouse_event.left_button,
                    middle_button: mouse_event.middle_button,
                    right_button: mouse_event.right_button,
                }),
                orbital::EventOption::Key(key_event) => if key_event.pressed {
                    match key_event.scancode {
                        orbital::K_BKSP => events.push(Event::Backspace),
                        orbital::K_DEL => events.push(Event::Delete),
                        orbital::K_UP => events.push(Event::UpArrow),
                        orbital::K_DOWN => events.push(Event::DownArrow),
                        orbital::K_LEFT => events.push(Event::LeftArrow),
                        orbital::K_RIGHT => events.push(Event::RightArrow),
                        _ => if key_event.character != '\0' {
                            events.push(Event::Text {
                                c: key_event.character
                            })
                        }
                    }
                },
                _ => ()
            };

            for event in events.iter() {
                for widget in self.widgets.iter() {
                    widget.event(*event);
                }
            }

            self.draw();
        }
    }
}
<commit_msg>Remove warnings<commit_after>use super::{Color, Event, Point, Rect, Renderer, Widget};

use std::sync::Arc;

extern crate orbital;

pub struct WindowRenderer<'a> {
    inner: &'a mut Box<orbital::Window>
}

impl<'a> WindowRenderer<'a> {
    pub fn new(inner: &'a mut Box<orbital::Window>) -> WindowRenderer {
        WindowRenderer {
            inner: inner
        }
    }
}

impl<'a> Renderer for WindowRenderer<'a> {
    fn clear(&mut self, color: Color) {
        self.inner.set(orbital::Color {
            data: color.data
        });
    }

    fn char(&mut self, pos: Point, c: char, color: Color) {
        self.inner.char(pos.x, pos.y, c, orbital::Color {
            data: color.data
        });
    }

    fn rect(&mut self, rect: Rect, color: Color) {
        self.inner.rect(rect.x, rect.y, rect.width, rect.height, orbital::Color {
            data: color.data
        });
    }
}

impl<'a> Drop for WindowRenderer<'a> {
    fn drop(&mut self) {
        self.inner.sync();
    }
}

pub struct Window {
    inner: Box<orbital::Window>,
    pub widgets: Vec<Arc<Widget>>,
    pub bg: Color,
}

impl Window {
    pub fn new(rect: Rect, title: &str) -> Box<Self> {
        Box::new(Window {
            inner: orbital::Window::new(rect.x, rect.y, rect.width, rect.height, title).unwrap(),
            widgets: Vec::new(),
            bg: Color::rgb(237, 233, 227),
        })
    }

    pub fn draw(&mut self) {
        let mut renderer = WindowRenderer::new(&mut self.inner);
        renderer.clear(self.bg);
        for widget in self.widgets.iter() {
            widget.draw(&mut renderer);
        }
    }

    pub fn exec(&mut self) {
        self.draw();
        while let Some(orbital_event) = self.inner.poll() {
            let mut events = Vec::new();

            match orbital_event.to_option() {
                orbital::EventOption::Mouse(mouse_event) => events.push(Event::Mouse {
                    point: Point::new(mouse_event.x, mouse_event.y),
                    left_button: mouse_event.left_button,
                    middle_button: mouse_event.middle_button,
                    right_button: mouse_event.right_button,
                }),
                orbital::EventOption::Key(key_event) => if key_event.pressed {
                    match key_event.scancode {
                        orbital::K_BKSP => events.push(Event::Backspace),
                        orbital::K_DEL => events.push(Event::Delete),
                        orbital::K_UP => events.push(Event::UpArrow),
                        orbital::K_DOWN => events.push(Event::DownArrow),
                        orbital::K_LEFT => events.push(Event::LeftArrow),
                        orbital::K_RIGHT => events.push(Event::RightArrow),
                        _ => if key_event.character != '\0' {
                            events.push(Event::Text {
                                c: key_event.character
                            })
                        }
                    }
                },
                _ => ()
            };

            for event in events.iter() {
                for widget in self.widgets.iter() {
                    widget.event(*event);
                }
            }

            self.draw();
        }
    }
}
<|endoftext|>


<commit_before>extern crate byteorder;
extern crate futures;
extern crate linear_map;
extern crate protobuf;

extern crate librespot_core;
extern crate librespot_protocol as protocol;

pub mod cover;

use futures::Future;
use linear_map::LinearMap;

use librespot_core::mercury::MercuryError;
use librespot_core::session::Session;
use librespot_core::spotify_id::{FileId, SpotifyId};

pub use protocol::metadata::AudioFile_Format as FileFormat;

fn countrylist_contains(list: &str, country: &str) -> bool {
    list.chunks(2).any(|cc| cc == country)
}

fn parse_restrictions<'s, I>(restrictions: I, country: &str, catalogue: &str) -> bool
where
    I: IntoIterator<Item = &'s protocol::metadata::Restriction>,
{
    let mut forbidden = "".to_string();
    let mut has_forbidden = false;

    let mut allowed = "".to_string();
    let mut has_allowed = false;

    let rs = restrictions
        .into_iter()
        .filter(|r| r.get_catalogue_str().contains(&catalogue.to_owned()));

    for r in rs {
        if r.has_countries_forbidden() {
            forbidden.push_str(r.get_countries_forbidden());
            has_forbidden = true;
        }

        if r.has_countries_allowed() {
            allowed.push_str(r.get_countries_allowed());
            has_allowed = true;
        }
    }

    (has_forbidden || has_allowed)
        && (!has_forbidden || !countrylist_contains(forbidden.as_str(), country))
        && (!has_allowed || countrylist_contains(allowed.as_str(), country))
}

pub trait Metadata: Send + Sized + 'static {
    type Message: protobuf::Message;

    fn request_url(id: SpotifyId) -> String;
    fn parse(msg: &Self::Message, session: &Session) -> Self;

    fn get(session: &Session, id: SpotifyId) -> Box<Future<Item = Self, Error = MercuryError>> {
        let uri = Self::request_url(id);
        let request = session.mercury().get(uri);

        let session = session.clone();
        Box::new(request.and_then(move |response| {
            let data = response.payload.first().expect("Empty payload");
            let msg: Self::Message = protobuf::parse_from_bytes(data).unwrap();

            Ok(Self::parse(&msg, &session))
        }))
    }
}

#[derive(Debug, Clone)]
pub struct Track {
    pub id: SpotifyId,
    pub name: String,
    pub duration: i32,
    pub album: SpotifyId,
    pub artists: Vec<SpotifyId>,
    pub files: LinearMap<FileFormat, FileId>,
    pub alternatives: Vec<SpotifyId>,
    pub available: bool,
}

#[derive(Debug, Clone)]
pub struct Album {
    pub id: SpotifyId,
    pub name: String,
    pub artists: Vec<SpotifyId>,
    pub tracks: Vec<SpotifyId>,
    pub covers: Vec<FileId>,
}

#[derive(Debug, Clone)]
pub struct Playlist {
    pub user: String,
    pub length: i32,
    pub name: String,
    pub tracks: Vec<SpotifyId>,
}

#[derive(Debug, Clone)]
pub struct Artist {
    pub id: SpotifyId,
    pub name: String,
    pub top_tracks: Vec<SpotifyId>,
}

impl Metadata for Track {
    type Message = protocol::metadata::Track;

    fn request_url(id: SpotifyId) -> String {
        format!("hm://metadata/3/track/{}", id.to_base16())
    }

    fn parse(msg: &Self::Message, session: &Session) -> Self {
        let country = session.country();

        let artists = msg
            .get_artist()
            .iter()
            .filter(|artist| artist.has_gid())
            .map(|artist| SpotifyId::from_raw(artist.get_gid()).unwrap())
            .collect::<Vec<_>>();

        let files = msg
            .get_file()
            .iter()
            .filter(|file| file.has_file_id())
            .map(|file| {
                let mut dst = [0u8; 20];
                dst.clone_from_slice(file.get_file_id());
                (file.get_format(), FileId(dst))
            })
            .collect();

        Track {
            id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
            name: msg.get_name().to_owned(),
            duration: msg.get_duration(),
            album: SpotifyId::from_raw(msg.get_album().get_gid()).unwrap(),
            artists: artists,
            files: files,
            alternatives: msg
                .get_alternative()
                .iter()
                .map(|alt| SpotifyId::from_raw(alt.get_gid()).unwrap())
                .collect(),
            available: parse_restrictions(msg.get_restriction(), &country, "premium"),
        }
    }
}

impl Metadata for Album {
    type Message = protocol::metadata::Album;

    fn request_url(id: SpotifyId) -> String {
        format!("hm://metadata/3/album/{}", id.to_base16())
    }

    fn parse(msg: &Self::Message, _: &Session) -> Self {
        let artists = msg
            .get_artist()
            .iter()
            .filter(|artist| artist.has_gid())
            .map(|artist| SpotifyId::from_raw(artist.get_gid()).unwrap())
            .collect::<Vec<_>>();

        let tracks = msg
            .get_disc()
            .iter()
            .flat_map(|disc| disc.get_track())
            .filter(|track| track.has_gid())
            .map(|track| SpotifyId::from_raw(track.get_gid()).unwrap())
            .collect::<Vec<_>>();

        let covers = msg
            .get_cover_group()
            .get_image()
            .iter()
            .filter(|image| image.has_file_id())
            .map(|image| {
                let mut dst = [0u8; 20];
                dst.clone_from_slice(image.get_file_id());
                FileId(dst)
            })
            .collect::<Vec<_>>();

        Album {
            id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
            name: msg.get_name().to_owned(),
            artists: artists,
            tracks: tracks,
            covers: covers,
        }
    }
}

impl Metadata for Playlist {
    type Message = protocol::playlist4changes::SelectedListContent;

    fn request_url(id: SpotifyId) -> String {
        format!("hm://playlist/v2/playlist/{}", id.to_base62())
    }

    fn parse(msg: &Self::Message, _: &Session) -> Self {

        let tracks = msg
            .get_contents()
            .get_items()
            .iter()
            .map(|item| {
                let uri_split = item.get_uri().split(":");
                let uri_parts: Vec<&str> = uri_split.collect();
                SpotifyId::from_base62(uri_parts[2]).unwrap()
            })
            .collect::<Vec<_>>();

        println!("parse Message: {:?}", msg);
        Playlist {
            //id: SpotifyId::from_raw(msg.get_attributes().get_id()).unwrap(),
            name: msg.get_attributes().get_name().to_owned(),
            length: msg.get_length(),
            tracks: tracks,
            user: msg.get_owner_username().to_string(),
        }
    }
}

impl Metadata for Artist {
    type Message = protocol::metadata::Artist;

    fn request_url(id: SpotifyId) -> String {
        format!("hm://metadata/3/artist/{}", id.to_base16())
    }

    fn parse(msg: &Self::Message, session: &Session) -> Self {
        let country = session.country();

        let top_tracks: Vec<SpotifyId> = match msg
            .get_top_track()
            .iter()
            .find(|tt| !tt.has_country() || countrylist_contains(tt.get_country(), &country))
        {
            Some(tracks) => tracks
                .get_track()
                .iter()
                .filter(|track| track.has_gid())
                .map(|track| SpotifyId::from_raw(track.get_gid()).unwrap())
                .collect::<Vec<_>>(),
            None => Vec::new(),
        };

        Artist {
            id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
            name: msg.get_name().to_owned(),
            top_tracks: top_tracks,
        }
    }
}

struct StrChunks<'s>(&'s str, usize);

trait StrChunksExt {
    fn chunks(&self, size: usize) -> StrChunks;
}

impl StrChunksExt for str {
    fn chunks(&self, size: usize) -> StrChunks {
        StrChunks(self, size)
    }
}

impl<'s> Iterator for StrChunks<'s> {
    type Item = &'s str;
    fn next(&mut self) -> Option<&'s str> {
        let &mut StrChunks(data, size) = self;
        if data.is_empty() {
            None
        } else {
            let ret = Some(&data[..size]);
            self.0 = &data[size..];
            ret
        }
    }
}
<commit_msg>Remove forgotten println<commit_after>extern crate byteorder;
extern crate futures;
extern crate linear_map;
extern crate protobuf;

extern crate librespot_core;
extern crate librespot_protocol as protocol;

pub mod cover;

use futures::Future;
use linear_map::LinearMap;

use librespot_core::mercury::MercuryError;
use librespot_core::session::Session;
use librespot_core::spotify_id::{FileId, SpotifyId};

pub use protocol::metadata::AudioFile_Format as FileFormat;

fn countrylist_contains(list: &str, country: &str) -> bool {
    list.chunks(2).any(|cc| cc == country)
}

fn parse_restrictions<'s, I>(restrictions: I, country: &str, catalogue: &str) -> bool
where
    I: IntoIterator<Item = &'s protocol::metadata::Restriction>,
{
    let mut forbidden = "".to_string();
    let mut has_forbidden = false;

    let mut allowed = "".to_string();
    let mut has_allowed = false;

    let rs = restrictions
        .into_iter()
        .filter(|r| r.get_catalogue_str().contains(&catalogue.to_owned()));

    for r in rs {
        if r.has_countries_forbidden() {
            forbidden.push_str(r.get_countries_forbidden());
            has_forbidden = true;
        }

        if r.has_countries_allowed() {
            allowed.push_str(r.get_countries_allowed());
            has_allowed = true;
        }
    }

    (has_forbidden || has_allowed)
        && (!has_forbidden || !countrylist_contains(forbidden.as_str(), country))
        && (!has_allowed || countrylist_contains(allowed.as_str(), country))
}

pub trait Metadata: Send + Sized + 'static {
    type Message: protobuf::Message;

    fn request_url(id: SpotifyId) -> String;
    fn parse(msg: &Self::Message, session: &Session) -> Self;

    fn get(session: &Session, id: SpotifyId) -> Box<Future<Item = Self, Error = MercuryError>> {
        let uri = Self::request_url(id);
        let request = session.mercury().get(uri);

        let session = session.clone();
        Box::new(request.and_then(move |response| {
            let data = response.payload.first().expect("Empty payload");
            let msg: Self::Message = protobuf::parse_from_bytes(data).unwrap();

            Ok(Self::parse(&msg, &session))
        }))
    }
}

#[derive(Debug, Clone)]
pub struct Track {
    pub id: SpotifyId,
    pub name: String,
    pub duration: i32,
    pub album: SpotifyId,
    pub artists: Vec<SpotifyId>,
    pub files: LinearMap<FileFormat, FileId>,
    pub alternatives: Vec<SpotifyId>,
    pub available: bool,
}

#[derive(Debug, Clone)]
pub struct Album {
    pub id: SpotifyId,
    pub name: String,
    pub artists: Vec<SpotifyId>,
    pub tracks: Vec<SpotifyId>,
    pub covers: Vec<FileId>,
}

#[derive(Debug, Clone)]
pub struct Playlist {
    pub user: String,
    pub length: i32,
    pub name: String,
    pub tracks: Vec<SpotifyId>,
}

#[derive(Debug, Clone)]
pub struct Artist {
    pub id: SpotifyId,
    pub name: String,
    pub top_tracks: Vec<SpotifyId>,
}

impl Metadata for Track {
    type Message = protocol::metadata::Track;

    fn request_url(id: SpotifyId) -> String {
        format!("hm://metadata/3/track/{}", id.to_base16())
    }

    fn parse(msg: &Self::Message, session: &Session) -> Self {
        let country = session.country();

        let artists = msg
            .get_artist()
            .iter()
            .filter(|artist| artist.has_gid())
            .map(|artist| SpotifyId::from_raw(artist.get_gid()).unwrap())
            .collect::<Vec<_>>();

        let files = msg
            .get_file()
            .iter()
            .filter(|file| file.has_file_id())
            .map(|file| {
                let mut dst = [0u8; 20];
                dst.clone_from_slice(file.get_file_id());
                (file.get_format(), FileId(dst))
            })
            .collect();

        Track {
            id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
            name: msg.get_name().to_owned(),
            duration: msg.get_duration(),
            album: SpotifyId::from_raw(msg.get_album().get_gid()).unwrap(),
            artists: artists,
            files: files,
            alternatives: msg
                .get_alternative()
                .iter()
                .map(|alt| SpotifyId::from_raw(alt.get_gid()).unwrap())
                .collect(),
            available: parse_restrictions(msg.get_restriction(), &country, "premium"),
        }
    }
}

impl Metadata for Album {
    type Message = protocol::metadata::Album;

    fn request_url(id: SpotifyId) -> String {
        format!("hm://metadata/3/album/{}", id.to_base16())
    }

    fn parse(msg: &Self::Message, _: &Session) -> Self {
        let artists = msg
            .get_artist()
            .iter()
            .filter(|artist| artist.has_gid())
            .map(|artist| SpotifyId::from_raw(artist.get_gid()).unwrap())
            .collect::<Vec<_>>();

        let tracks = msg
            .get_disc()
            .iter()
            .flat_map(|disc| disc.get_track())
            .filter(|track| track.has_gid())
            .map(|track| SpotifyId::from_raw(track.get_gid()).unwrap())
            .collect::<Vec<_>>();

        let covers = msg
            .get_cover_group()
            .get_image()
            .iter()
            .filter(|image| image.has_file_id())
            .map(|image| {
                let mut dst = [0u8; 20];
                dst.clone_from_slice(image.get_file_id());
                FileId(dst)
            })
            .collect::<Vec<_>>();

        Album {
            id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
            name: msg.get_name().to_owned(),
            artists: artists,
            tracks: tracks,
            covers: covers,
        }
    }
}

impl Metadata for Playlist {
    type Message = protocol::playlist4changes::SelectedListContent;

    fn request_url(id: SpotifyId) -> String {
        format!("hm://playlist/v2/playlist/{}", id.to_base62())
    }

    fn parse(msg: &Self::Message, _: &Session) -> Self {

        let tracks = msg
            .get_contents()
            .get_items()
            .iter()
            .map(|item| {
                let uri_split = item.get_uri().split(":");
                let uri_parts: Vec<&str> = uri_split.collect();
                SpotifyId::from_base62(uri_parts[2]).unwrap()
            })
            .collect::<Vec<_>>();

        Playlist {
            name: msg.get_attributes().get_name().to_owned(),
            length: msg.get_length(),
            tracks: tracks,
            user: msg.get_owner_username().to_string(),
        }
    }
}

impl Metadata for Artist {
    type Message = protocol::metadata::Artist;

    fn request_url(id: SpotifyId) -> String {
        format!("hm://metadata/3/artist/{}", id.to_base16())
    }

    fn parse(msg: &Self::Message, session: &Session) -> Self {
        let country = session.country();

        let top_tracks: Vec<SpotifyId> = match msg
            .get_top_track()
            .iter()
            .find(|tt| !tt.has_country() || countrylist_contains(tt.get_country(), &country))
        {
            Some(tracks) => tracks
                .get_track()
                .iter()
                .filter(|track| track.has_gid())
                .map(|track| SpotifyId::from_raw(track.get_gid()).unwrap())
                .collect::<Vec<_>>(),
            None => Vec::new(),
        };

        Artist {
            id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
            name: msg.get_name().to_owned(),
            top_tracks: top_tracks,
        }
    }
}

struct StrChunks<'s>(&'s str, usize);

trait StrChunksExt {
    fn chunks(&self, size: usize) -> StrChunks;
}

impl StrChunksExt for str {
    fn chunks(&self, size: usize) -> StrChunks {
        StrChunks(self, size)
    }
}

impl<'s> Iterator for StrChunks<'s> {
    type Item = &'s str;
    fn next(&mut self) -> Option<&'s str> {
        let &mut StrChunks(data, size) = self;
        if data.is_empty() {
            None
        } else {
            let ret = Some(&data[..size]);
            self.0 = &data[size..];
            ret
        }
    }
}
<|endoftext|>


<commit_before>extern crate byteorder;
extern crate futures;
extern crate linear_map;
extern crate protobuf;

extern crate librespot_core;
extern crate librespot_protocol as protocol;

pub mod cover;

use futures::Future;
use linear_map::LinearMap;

use librespot_core::mercury::MercuryError;
use librespot_core::session::Session;
use librespot_core::spotify_id::{FileId, SpotifyId};

pub use protocol::metadata::AudioFile_Format as FileFormat;

fn countrylist_contains(list: &str, country: &str) -> bool {
    list.chunks(2).any(|cc| cc == country)
}

fn parse_restrictions<'s, I>(restrictions: I, country: &str, catalogue: &str) -> bool
where
    I: IntoIterator<Item = &'s protocol::metadata::Restriction>,
{
    let mut forbidden = "".to_string();
    let mut has_forbidden = false;

    let mut allowed = "".to_string();
    let mut has_allowed = false;

    let rs = restrictions
        .into_iter()
        .filter(|r| r.get_catalogue_str().contains(&catalogue.to_owned()));

    for r in rs {
        if r.has_countries_forbidden() {
            forbidden.push_str(r.get_countries_forbidden());
            has_forbidden = true;
        }

        if r.has_countries_allowed() {
            allowed.push_str(r.get_countries_allowed());
            has_allowed = true;
        }
    }

    (has_forbidden || has_allowed)
        && (!has_forbidden || !countrylist_contains(forbidden.as_str(), country))
        && (!has_allowed || countrylist_contains(allowed.as_str(), country))
}

pub trait Metadata: Send + Sized + 'static {
    type Message: protobuf::Message;

    fn request_url(id: SpotifyId) -> String;
    fn parse(msg: &Self::Message, session: &Session) -> Self;

    fn get(session: &Session, id: SpotifyId) -> Box<Future<Item = Self, Error = MercuryError>> {
        let uri = Self::request_url(id);
        let request = session.mercury().get(uri);

        let session = session.clone();
        Box::new(request.and_then(move |response| {
            let data = response.payload.first().expect("Empty payload");
            let msg: Self::Message = protobuf::parse_from_bytes(data).unwrap();

            Ok(Self::parse(&msg, &session))
        }))
    }
}

#[derive(Debug, Clone)]
pub struct Track {
    pub id: SpotifyId,
    pub name: String,
    pub duration: i32,
    pub album: SpotifyId,
    pub artists: Vec<SpotifyId>,
    pub files: LinearMap<FileFormat, FileId>,
    pub alternatives: Vec<SpotifyId>,
    pub available: bool,
}

#[derive(Debug, Clone)]
pub struct Album {
    pub id: SpotifyId,
    pub name: String,
    pub artists: Vec<SpotifyId>,
    pub tracks: Vec<SpotifyId>,
    pub covers: Vec<FileId>,
}

#[derive(Debug, Clone)]
pub struct Playlist {
    pub user: String,
    pub length: i32,
    pub name: String,
    pub tracks: Vec<SpotifyId>,
}

#[derive(Debug, Clone)]
pub struct Artist {
    pub id: SpotifyId,
    pub name: String,
    pub top_tracks: Vec<SpotifyId>,
}

impl Metadata for Track {
    type Message = protocol::metadata::Track;

    fn request_url(id: SpotifyId) -> String {
        format!("hm://metadata/3/track/{}", id.to_base16())
    }

    fn parse(msg: &Self::Message, session: &Session) -> Self {
        let country = session.country();

        let artists = msg
            .get_artist()
            .iter()
            .filter(|artist| artist.has_gid())
            .map(|artist| SpotifyId::from_raw(artist.get_gid()).unwrap())
            .collect::<Vec<_>>();

        let files = msg
            .get_file()
            .iter()
            .filter(|file| file.has_file_id())
            .map(|file| {
                let mut dst = [0u8; 20];
                dst.clone_from_slice(file.get_file_id());
                (file.get_format(), FileId(dst))
            })
            .collect();

        Track {
            id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
            name: msg.get_name().to_owned(),
            duration: msg.get_duration(),
            album: SpotifyId::from_raw(msg.get_album().get_gid()).unwrap(),
            artists: artists,
            files: files,
            alternatives: msg
                .get_alternative()
                .iter()
                .map(|alt| SpotifyId::from_raw(alt.get_gid()).unwrap())
                .collect(),
            available: parse_restrictions(msg.get_restriction(), &country, "premium"),
        }
    }
}

impl Metadata for Album {
    type Message = protocol::metadata::Album;

    fn request_url(id: SpotifyId) -> String {
        format!("hm://metadata/3/album/{}", id.to_base16())
    }

    fn parse(msg: &Self::Message, _: &Session) -> Self {
        let artists = msg
            .get_artist()
            .iter()
            .filter(|artist| artist.has_gid())
            .map(|artist| SpotifyId::from_raw(artist.get_gid()).unwrap())
            .collect::<Vec<_>>();

        let tracks = msg
            .get_disc()
            .iter()
            .flat_map(|disc| disc.get_track())
            .filter(|track| track.has_gid())
            .map(|track| SpotifyId::from_raw(track.get_gid()).unwrap())
            .collect::<Vec<_>>();

        let covers = msg
            .get_cover_group()
            .get_image()
            .iter()
            .filter(|image| image.has_file_id())
            .map(|image| {
                let mut dst = [0u8; 20];
                dst.clone_from_slice(image.get_file_id());
                FileId(dst)
            })
            .collect::<Vec<_>>();

        Album {
            id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
            name: msg.get_name().to_owned(),
            artists: artists,
            tracks: tracks,
            covers: covers,
        }
    }
}

impl Metadata for Playlist {
    type Message = protocol::playlist4changes::SelectedListContent;

    fn base_url() -> &'static str {
        "hm://playlist/v2/playlist"
    }

    fn get(session: &Session, id: SpotifyId) -> Box<Future<Item = Self, Error = MercuryError>> {
        let uri = format!("{}/{}", Self::base_url(), id.to_base62());
        let request = session.mercury().get(uri);

        let session = session.clone();
        Box::new(request.and_then(move |response| {
            let data = response.payload.first().expect("Empty payload");
            let msg: Self::Message = protobuf::parse_from_bytes(data).unwrap();

            Ok(Self::parse(&msg, &session))
        }))
    }

    fn parse(msg: &Self::Message, _: &Session) -> Self {

        let tracks = msg
            .get_contents()
            .get_items()
            .iter()
            .map(|item| {
                let uri_split = item.get_uri().split(":");
                let uri_parts: Vec<&str> = uri_split.collect();
                SpotifyId::from_base62(uri_parts[2]).unwrap()
            })
            .collect::<Vec<_>>();

        println!("parse Message: {:?}", msg);
        Playlist {
            //id: SpotifyId::from_raw(msg.get_attributes().get_id()).unwrap(),
            name: msg.get_attributes().get_name().to_owned(),
            length: msg.get_length(),
            tracks: tracks,
            user: msg.get_owner_username().to_string(),
        }
    }
}

impl Metadata for Artist {
    type Message = protocol::metadata::Artist;

    fn request_url(id: SpotifyId) -> String {
        format!("hm://metadata/3/artist/{}", id.to_base16())
    }

    fn parse(msg: &Self::Message, session: &Session) -> Self {
        let country = session.country();

        let top_tracks: Vec<SpotifyId> = match msg
            .get_top_track()
            .iter()
            .find(|tt| !tt.has_country() || countrylist_contains(tt.get_country(), &country))
        {
            Some(tracks) => tracks
                .get_track()
                .iter()
                .filter(|track| track.has_gid())
                .map(|track| SpotifyId::from_raw(track.get_gid()).unwrap())
                .collect::<Vec<_>>(),
            None => Vec::new(),
        };

        Artist {
            id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
            name: msg.get_name().to_owned(),
            top_tracks: top_tracks,
        }
    }
}

struct StrChunks<'s>(&'s str, usize);

trait StrChunksExt {
    fn chunks(&self, size: usize) -> StrChunks;
}

impl StrChunksExt for str {
    fn chunks(&self, size: usize) -> StrChunks {
        StrChunks(self, size)
    }
}

impl<'s> Iterator for StrChunks<'s> {
    type Item = &'s str;
    fn next(&mut self) -> Option<&'s str> {
        let &mut StrChunks(data, size) = self;
        if data.is_empty() {
            None
        } else {
            let ret = Some(&data[..size]);
            self.0 = &data[size..];
            ret
        }
    }
}
<commit_msg>Make standard metadata get handle also playlists<commit_after>extern crate byteorder;
extern crate futures;
extern crate linear_map;
extern crate protobuf;

extern crate librespot_core;
extern crate librespot_protocol as protocol;

pub mod cover;

use futures::Future;
use linear_map::LinearMap;

use librespot_core::mercury::MercuryError;
use librespot_core::session::Session;
use librespot_core::spotify_id::{FileId, SpotifyId};

pub use protocol::metadata::AudioFile_Format as FileFormat;

fn countrylist_contains(list: &str, country: &str) -> bool {
    list.chunks(2).any(|cc| cc == country)
}

fn parse_restrictions<'s, I>(restrictions: I, country: &str, catalogue: &str) -> bool
where
    I: IntoIterator<Item = &'s protocol::metadata::Restriction>,
{
    let mut forbidden = "".to_string();
    let mut has_forbidden = false;

    let mut allowed = "".to_string();
    let mut has_allowed = false;

    let rs = restrictions
        .into_iter()
        .filter(|r| r.get_catalogue_str().contains(&catalogue.to_owned()));

    for r in rs {
        if r.has_countries_forbidden() {
            forbidden.push_str(r.get_countries_forbidden());
            has_forbidden = true;
        }

        if r.has_countries_allowed() {
            allowed.push_str(r.get_countries_allowed());
            has_allowed = true;
        }
    }

    (has_forbidden || has_allowed)
        && (!has_forbidden || !countrylist_contains(forbidden.as_str(), country))
        && (!has_allowed || countrylist_contains(allowed.as_str(), country))
}

pub trait Metadata: Send + Sized + 'static {
    type Message: protobuf::Message;

    fn request_url(id: SpotifyId) -> String;
    fn parse(msg: &Self::Message, session: &Session) -> Self;

    fn get(session: &Session, id: SpotifyId) -> Box<Future<Item = Self, Error = MercuryError>> {
        let uri = Self::request_url(id);
        let request = session.mercury().get(uri);

        let session = session.clone();
        Box::new(request.and_then(move |response| {
            let data = response.payload.first().expect("Empty payload");
            let msg: Self::Message = protobuf::parse_from_bytes(data).unwrap();

            Ok(Self::parse(&msg, &session))
        }))
    }
}

#[derive(Debug, Clone)]
pub struct Track {
    pub id: SpotifyId,
    pub name: String,
    pub duration: i32,
    pub album: SpotifyId,
    pub artists: Vec<SpotifyId>,
    pub files: LinearMap<FileFormat, FileId>,
    pub alternatives: Vec<SpotifyId>,
    pub available: bool,
}

#[derive(Debug, Clone)]
pub struct Album {
    pub id: SpotifyId,
    pub name: String,
    pub artists: Vec<SpotifyId>,
    pub tracks: Vec<SpotifyId>,
    pub covers: Vec<FileId>,
}

#[derive(Debug, Clone)]
pub struct Playlist {
    pub user: String,
    pub length: i32,
    pub name: String,
    pub tracks: Vec<SpotifyId>,
}

#[derive(Debug, Clone)]
pub struct Artist {
    pub id: SpotifyId,
    pub name: String,
    pub top_tracks: Vec<SpotifyId>,
}

impl Metadata for Track {
    type Message = protocol::metadata::Track;

    fn request_url(id: SpotifyId) -> String {
        format!("hm://metadata/3/track/{}", id.to_base16())
    }

    fn parse(msg: &Self::Message, session: &Session) -> Self {
        let country = session.country();

        let artists = msg
            .get_artist()
            .iter()
            .filter(|artist| artist.has_gid())
            .map(|artist| SpotifyId::from_raw(artist.get_gid()).unwrap())
            .collect::<Vec<_>>();

        let files = msg
            .get_file()
            .iter()
            .filter(|file| file.has_file_id())
            .map(|file| {
                let mut dst = [0u8; 20];
                dst.clone_from_slice(file.get_file_id());
                (file.get_format(), FileId(dst))
            })
            .collect();

        Track {
            id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
            name: msg.get_name().to_owned(),
            duration: msg.get_duration(),
            album: SpotifyId::from_raw(msg.get_album().get_gid()).unwrap(),
            artists: artists,
            files: files,
            alternatives: msg
                .get_alternative()
                .iter()
                .map(|alt| SpotifyId::from_raw(alt.get_gid()).unwrap())
                .collect(),
            available: parse_restrictions(msg.get_restriction(), &country, "premium"),
        }
    }
}

impl Metadata for Album {
    type Message = protocol::metadata::Album;

    fn request_url(id: SpotifyId) -> String {
        format!("hm://metadata/3/album/{}", id.to_base16())
    }

    fn parse(msg: &Self::Message, _: &Session) -> Self {
        let artists = msg
            .get_artist()
            .iter()
            .filter(|artist| artist.has_gid())
            .map(|artist| SpotifyId::from_raw(artist.get_gid()).unwrap())
            .collect::<Vec<_>>();

        let tracks = msg
            .get_disc()
            .iter()
            .flat_map(|disc| disc.get_track())
            .filter(|track| track.has_gid())
            .map(|track| SpotifyId::from_raw(track.get_gid()).unwrap())
            .collect::<Vec<_>>();

        let covers = msg
            .get_cover_group()
            .get_image()
            .iter()
            .filter(|image| image.has_file_id())
            .map(|image| {
                let mut dst = [0u8; 20];
                dst.clone_from_slice(image.get_file_id());
                FileId(dst)
            })
            .collect::<Vec<_>>();

        Album {
            id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
            name: msg.get_name().to_owned(),
            artists: artists,
            tracks: tracks,
            covers: covers,
        }
    }
}

impl Metadata for Playlist {
    type Message = protocol::playlist4changes::SelectedListContent;

    fn request_url(id: SpotifyId) -> String {
        format!("hm://playlist/v2/playlist/{}", id.to_base62())
    }

    fn parse(msg: &Self::Message, _: &Session) -> Self {

        let tracks = msg
            .get_contents()
            .get_items()
            .iter()
            .map(|item| {
                let uri_split = item.get_uri().split(":");
                let uri_parts: Vec<&str> = uri_split.collect();
                SpotifyId::from_base62(uri_parts[2]).unwrap()
            })
            .collect::<Vec<_>>();

        println!("parse Message: {:?}", msg);
        Playlist {
            //id: SpotifyId::from_raw(msg.get_attributes().get_id()).unwrap(),
            name: msg.get_attributes().get_name().to_owned(),
            length: msg.get_length(),
            tracks: tracks,
            user: msg.get_owner_username().to_string(),
        }
    }
}

impl Metadata for Artist {
    type Message = protocol::metadata::Artist;

    fn request_url(id: SpotifyId) -> String {
        format!("hm://metadata/3/artist/{}", id.to_base16())
    }

    fn parse(msg: &Self::Message, session: &Session) -> Self {
        let country = session.country();

        let top_tracks: Vec<SpotifyId> = match msg
            .get_top_track()
            .iter()
            .find(|tt| !tt.has_country() || countrylist_contains(tt.get_country(), &country))
        {
            Some(tracks) => tracks
                .get_track()
                .iter()
                .filter(|track| track.has_gid())
                .map(|track| SpotifyId::from_raw(track.get_gid()).unwrap())
                .collect::<Vec<_>>(),
            None => Vec::new(),
        };

        Artist {
            id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
            name: msg.get_name().to_owned(),
            top_tracks: top_tracks,
        }
    }
}

struct StrChunks<'s>(&'s str, usize);

trait StrChunksExt {
    fn chunks(&self, size: usize) -> StrChunks;
}

impl StrChunksExt for str {
    fn chunks(&self, size: usize) -> StrChunks {
        StrChunks(self, size)
    }
}

impl<'s> Iterator for StrChunks<'s> {
    type Item = &'s str;
    fn next(&mut self) -> Option<&'s str> {
        let &mut StrChunks(data, size) = self;
        if data.is_empty() {
            None
        } else {
            let ret = Some(&data[..size]);
            self.0 = &data[size..];
            ret
        }
    }
}
<|endoftext|>


<commit_before>use std::rc::Rc;

use gltf;

use render::math::*;
use render::mesh::Mesh;
use render::scene::Scene;
use shader::Shader;

pub struct Node {
    // TODO!!: camera?
    pub children: Vec<Node>,
    pub matrix: Matrix4,
    pub mesh: Option<Rc<Mesh>>,
    pub rotation: Quaternion,
    pub scale: Vector3,
    pub translation: Vector3,
    // TODO: weights
    // weights_id: usize,
    pub name: Option<String>,

    final_transform: Matrix4, // including parent transforms
    model_loc: Option<i32>,
}

impl Node {
    pub fn from_gltf(g_node: gltf::scene::Node, scene: &mut Scene) -> Node {
        let m = &g_node.matrix();
        let matrix = Matrix4::new(
            m[0], m[1], m[2], m[3],
            m[4], m[5], m[6], m[7],
            m[8], m[9], m[10], m[11],
            m[12], m[13], m[14], m[15],
        );
        let r = &g_node.rotation();
        let rotation = Quaternion::new(r[3], r[0], r[1], r[2]); // NOTE: different element order!
        let mut mesh = None;
        if let Some(g_mesh) = g_node.mesh() {
            if let Some(g_mesh) = scene.meshes.iter().find(|mesh| (***mesh).index == g_mesh.index()) {
                mesh = Some(g_mesh.clone());
            }

            if mesh.is_none() { // not using else due to borrow-checking madness
                mesh = Some(Rc::new(Mesh::from_gltf(g_mesh)));
                scene.meshes.push(mesh.clone().unwrap());
            }
        }
        Node {
            children: g_node.children()
                .map(|g_node| Node::from_gltf(g_node, scene))
                .collect(),
            // TODO: why doesn't this work?
            // matrix: Matrix4::from(&g_node.matrix()),
            matrix: matrix,
            mesh: mesh,
            rotation: rotation,
            scale: Vector3::from(g_node.scale()),
            translation: Vector3::from(g_node.translation()),
            name: g_node.name().map(|s| s.into()),

            final_transform: Matrix4::identity(),
            model_loc: None,
        }
    }

    pub fn update_transform(&mut self, parent_transform: &Matrix4) {
        self.final_transform = *parent_transform;

        if !self.matrix.is_identity() {
            self.final_transform = self.final_transform * self.matrix;
        }
        else {
            // TODO?: detect if all default and set None? does NOT happen for any sample model
            self.final_transform = self.final_transform *
                Matrix4::from_translation(self.translation) *
                Matrix4::from_nonuniform_scale(self.scale.x, self.scale.y, self.scale.z) *
                Matrix4::from(self.rotation);
        }

        for node in &mut self.children {
            node.update_transform(&self.final_transform);
        }
    }

    pub fn draw(&mut self, shader: &mut Shader) {
        if let Some(ref mesh) = self.mesh {
            unsafe {
                if self.model_loc.is_none() {
                    self.model_loc = Some(shader.uniform_location("model"));
                }
                shader.set_mat4(self.model_loc.unwrap(), &self.final_transform);
            }

            (*mesh).draw(shader);
        }
        for node in &mut self.children {
            node.draw(shader);
        }
    }
}
<commit_msg>refactor Node::from_gltf<commit_after>use std::rc::Rc;

use gltf;

use render::math::*;
use render::mesh::Mesh;
use render::scene::Scene;
use shader::Shader;

pub struct Node {
    // TODO!!: camera?
    pub children: Vec<Node>,
    pub matrix: Matrix4,
    pub mesh: Option<Rc<Mesh>>,
    pub rotation: Quaternion,
    pub scale: Vector3,
    pub translation: Vector3,
    // TODO: weights
    // weights_id: usize,
    pub name: Option<String>,

    final_transform: Matrix4, // including parent transforms
    model_loc: Option<i32>,
}

impl Node {
    pub fn from_gltf(g_node: gltf::scene::Node, scene: &mut Scene) -> Node {
        // convert matrix in 3 steps due to type system weirdness
        let matrix = &g_node.matrix();
        let matrix: &Matrix4 = matrix.into();
        let matrix = *matrix;

        let r = &g_node.rotation();
        let rotation = Quaternion::new(r[3], r[0], r[1], r[2]); // NOTE: different element order!

        let mut mesh = None;
        if let Some(g_mesh) = g_node.mesh() {
            if let Some(existing_mesh) = scene.meshes.iter().find(|mesh| (***mesh).index == g_mesh.index()) {
                mesh = Some(existing_mesh.clone());
            }

            if mesh.is_none() { // not using else due to borrow-checking madness
                mesh = Some(Rc::new(Mesh::from_gltf(g_mesh)));
                scene.meshes.push(mesh.clone().unwrap());
            }
        }
        Node {
            children: g_node.children()
                .map(|g_node| Node::from_gltf(g_node, scene))
                .collect(),
            matrix: matrix,
            mesh: mesh,
            rotation: rotation,
            scale: Vector3::from(g_node.scale()),
            translation: Vector3::from(g_node.translation()),
            name: g_node.name().map(|s| s.into()),

            final_transform: Matrix4::identity(),
            model_loc: None,
        }
    }

    pub fn update_transform(&mut self, parent_transform: &Matrix4) {
        self.final_transform = *parent_transform;

        if !self.matrix.is_identity() {
            self.final_transform = self.final_transform * self.matrix;
        }
        else {
            // TODO?: detect if all default and set None? does NOT happen for any sample model
            self.final_transform = self.final_transform *
                Matrix4::from_translation(self.translation) *
                Matrix4::from_nonuniform_scale(self.scale.x, self.scale.y, self.scale.z) *
                Matrix4::from(self.rotation);
        }

        for node in &mut self.children {
            node.update_transform(&self.final_transform);
        }
    }

    pub fn draw(&mut self, shader: &mut Shader) {
        if let Some(ref mesh) = self.mesh {
            unsafe {
                if self.model_loc.is_none() {
                    self.model_loc = Some(shader.uniform_location("model"));
                }
                shader.set_mat4(self.model_loc.unwrap(), &self.final_transform);
            }

            (*mesh).draw(shader);
        }
        for node in &mut self.children {
            node.draw(shader);
        }
    }
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2015 Benjamin Fry <benjaminfry@me.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
use std::net::Ipv4Addr;
use std::sync::Arc as Rc;
use std::cmp::Ordering;

use ::serialize::binary::*;
use ::error::*;
use super::record_data::RData;
use super::record_type::RecordType;
use super::dns_class::DNSClass;
use super::domain;

/*
 * RFC 1035        Domain Implementation and Specification    November 1987
 *
 * 4.1.3. Resource record format
 *
 * The answer, authority, and additional sections all share the same
 * format: a variable number of resource records, where the number of
 * records is specified in the corresponding count field in the header.
 * Each resource record has the following format:
 *                                     1  1  1  1  1  1
 *       0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
 *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 *     |                                               |
 *     /                                               /
 *     /                      NAME                     /
 *     |                                               |
 *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 *     |                      TYPE                     |
 *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 *     |                     CLASS                     |
 *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 *     |                      TTL                      |
 *     |                                               |
 *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 *     |                   RDLENGTH                    |
 *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
 *     /                     RDATA                     /
 *     /                                               /
 *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 *
 * where:
 *
 * NAME            a domain name to which this resource record pertains.
 *
 * TYPE            two octets containing one of the RR type codes.  This
 *                 field specifies the meaning of the data in the RDATA
 *                 field.
 *
 * CLASS           two octets which specify the class of the data in the
 *                 RDATA field.
 *
 * TTL             a 32 bit unsigned integer that specifies the time
 *                 interval (in seconds) that the resource record may be
 *                 cached before it should be discarded.  Zero values are
 *                 interpreted to mean that the RR can only be used for the
 *                 transaction in progress, and should not be cached.
 *
 * RDLENGTH        an unsigned 16 bit integer that specifies the length in
 *                 octets of the RDATA field.
 *
 * RDATA           a variable length string of octets that describes the
 *                 resource.  The format of this information varies
 *                 according to the TYPE and CLASS of the resource record.
 *                 For example, the if the TYPE is A and the CLASS is IN,
 *                 the RDATA field is a 4 octet ARPA Internet address.
 */
#[derive(Eq, Hash, Ord, Debug, Clone)]
pub struct Record {
  name_labels: domain::Name,
  rr_type: RecordType,
  dns_class: DNSClass,
  ttl: u32,
  rdata: RData,
}

impl Record {
  /**
   * Creates a not very useful empty record, use the setters to build a more useful object
   *  There are no optional elements in this object, defaults are an empty name, type A, class IN,
   *  ttl of 0 and the 0.0.0.0 ip address.
   */
  pub fn new() -> Record {
    Record {
      // TODO: these really should all be Optionals, I was lazy.
      name_labels: domain::Name::new(),
      rr_type: RecordType::A,
      dns_class: DNSClass::IN,
      ttl: 0,
      rdata: RData::A { address: Ipv4Addr::new(0,0,0,0) }
    }
  }

  pub fn name(&mut self, name: domain::Name) -> &mut Self { self.name_labels = name; self }
  pub fn add_name(&mut self, label: String) -> &mut Self { self.name_labels.add_label(Rc::new(label)); self }
  pub fn rr_type(&mut self, rr_type: RecordType) -> &mut Self { self.rr_type = rr_type; self }
  pub fn dns_class(&mut self, dns_class: DNSClass) -> &mut Self { self.dns_class = dns_class; self }
  pub fn ttl(&mut self, ttl: u32) -> &mut Self { self.ttl = ttl; self }
  pub fn rdata(&mut self, rdata: RData) -> &mut Self { self.rdata = rdata; self }

  pub fn get_name(&self) -> &domain::Name { &self.name_labels }
  pub fn get_rr_type(&self) -> RecordType { self.rr_type }
  pub fn get_dns_class(&self) -> DNSClass { self.dns_class }
  pub fn get_ttl(&self) -> u32 { self.ttl }
  pub fn get_rdata(&self) -> &RData { &self.rdata }
}

impl BinSerializable<Record> for Record {
  /// parse a resource record line example:
  ///  WARNING: the record_bytes is 100% consumed and destroyed in this parsing process
  fn read(decoder: &mut BinDecoder) -> DecodeResult<Record> {
    // NAME            an owner name, i.e., the name of the node to which this
    //                 resource record pertains.
    let name_labels: domain::Name = try!(domain::Name::read(decoder));

    // TYPE            two octets containing one of the RR TYPE codes.
    let record_type: RecordType = try!(RecordType::read(decoder));
    decoder.set_record_type(record_type);

    // CLASS           two octets containing one of the RR CLASS codes.
    let class: DNSClass = try!(DNSClass::read(decoder));

    // TTL             a 32 bit signed integer that specifies the time interval
    //                that the resource record may be cached before the source
    //                of the information should again be consulted.  Zero
    //                values are interpreted to mean that the RR can only be
    //                used for the transaction in progress, and should not be
    //                cached.  For example, SOA records are always distributed
    //                with a zero TTL to prohibit caching.  Zero values can
    //                also be used for extremely volatile data.
    // note: u32 seems more accurate given that it can only be positive
    let ttl: u32 = try!(decoder.read_u32());

    // RDLENGTH        an unsigned 16 bit integer that specifies the length in
    //                octets of the RDATA field.
    let rd_length: u16 = try!(decoder.read_u16());

    // this is to handle updates, RFC 2036, which uses 0 to indicate certain aspects of
    //  pre-requisites
    let rdata: RData = if rd_length == 0 {
      RData::NULL{ anything: vec![] }
    } else {
      decoder.set_rdata_length(rd_length);

      // RDATA           a variable length string of octets that describes the
      //                resource.  The format of this information varies
      //                according to the TYPE and CLASS of the resource record.
      try!(RData::read(decoder))
    };

    Ok(Record{ name_labels: name_labels, rr_type: record_type, dns_class: class, ttl: ttl, rdata: rdata })
  }

  fn emit(&self, encoder: &mut BinEncoder) -> EncodeResult {
    try!(self.name_labels.emit(encoder));
    try!(self.rr_type.emit(encoder));
    try!(self.dns_class.emit(encoder));
    try!(encoder.emit_u32(self.ttl));

    // gah... need to write rdata before we know the size of rdata...
    // TODO: should we skip the fixed size header and write the rdata first? then write the header?
    let mut tmp_buf: Vec<u8> = Vec::with_capacity(512);
    {
      let mut tmp_encoder: BinEncoder = BinEncoder::with_offset(&mut tmp_buf, encoder.offset() + 2 /*for u16 len*/);
      try!(self.rdata.emit(&mut tmp_encoder));
    }

    assert!(tmp_buf.len() <= u16::max_value() as usize);

    try!(encoder.emit_u16(tmp_buf.len() as u16));
    encoder.reserve(tmp_buf.len());

    tmp_buf.reverse();
    while let Some(byte) = tmp_buf.pop() {
      try!(encoder.emit(byte));
    }

    Ok(())
  }
}

/// RFC 2136                       DNS Update                     April 1997
///
///   1.1.1. Two RRs are considered equal if their NAME, CLASS, TYPE,
///   RDLENGTH and RDATA fields are equal.  Note that the time-to-live
///   (TTL) field is explicitly excluded from the comparison.
///
///   1.1.2. The rules for comparison of character strings in names are
///   specified in [RFC1035 2.3.3]. i.e. case insensitive
impl PartialEq for Record {
    fn eq(&self, other: &Self) -> bool {
      // self == other && // the same pointer
      self.name_labels == other.name_labels &&
      self.rr_type == other.rr_type &&
      self.dns_class == other.dns_class &&
      self.rdata == other.rdata
    }

    fn ne(&self, other: &Self) -> bool {
      !self.eq(other)
    }
}

/// returns the value of the compare if the items are greater or lesser, but coninues on equal
macro_rules! compare_or_equal {
  ( $x:ident, $y:ident, $z:ident ) => (
    match $x.$z.partial_cmp(&$y.$z) {
      o @ Some(Ordering::Less) | o @ Some(Ordering::Greater) => return o,
      None => return None,
      Some(Ordering::Equal) => (),
    }
  );
}

impl PartialOrd<Record> for Record {
  // RFC 4034                DNSSEC Resource Records               March 2005
  //
  // 6.2.  Canonical RR Form
  //
  //    For the purposes of DNS security, the canonical form of an RR is the
  //    wire format of the RR where:
  //
  //    1.  every domain name in the RR is fully expanded (no DNS name
  //        compression) and fully qualified;
  //
  //    2.  all uppercase US-ASCII letters in the owner name of the RR are
  //        replaced by the corresponding lowercase US-ASCII letters;
  //
  //    3.  if the type of the RR is NS, MD, MF, CNAME, SOA, MB, MG, MR, PTR,
  //        HINFO, MINFO, MX, HINFO, RP, AFSDB, RT, SIG, PX, NXT, NAPTR, KX,
  //        SRV, DNAME, A6, RRSIG, or NSEC, all uppercase US-ASCII letters in
  //        the DNS names contained within the RDATA are replaced by the
  //        corresponding lowercase US-ASCII letters;
  //
  //    4.  if the owner name of the RR is a wildcard name, the owner name is
  //        in its original unexpanded form, including the "*" label (no
  //        wildcard substitution); and
  //
  //    5.  the RR's TTL is set to its original value as it appears in the
  //        originating authoritative zone or the Original TTL field of the
  //        covering RRSIG RR.
  fn partial_cmp(&self, other: &Record) -> Option<Ordering> {
    // TODO: given that the ordering of Resource Records is dependent on it's binary form and this
    //  method will be used during insertion sort or similar, we should probably do this
    //  conversion once somehow and store it separately. Or should the internal storage of all
    //  resource records be maintained in binary?

    compare_or_equal!(self, other, name_labels);
    compare_or_equal!(self, other, rr_type);
    compare_or_equal!(self, other, dns_class);
    compare_or_equal!(self, other, ttl);
    compare_or_equal!(self, other, rdata);

    // got here, means they are equal
    Some(Ordering::Equal)
  }
}

#[cfg(test)]
mod tests {
  use std::net::Ipv4Addr;

  use super::*;

  use ::serialize::binary::*;
  use ::rr::record_data::RData;
  use ::rr::record_type::RecordType;
  use ::rr::dns_class::DNSClass;

  #[test]
  fn test_emit_and_read() {
    let mut record = Record::new();
    record.add_name("www".to_string()).add_name("example".to_string()).add_name("com".to_string())
    .rr_type(RecordType::A).dns_class(DNSClass::IN).ttl(5)
    .rdata(RData::A { address: Ipv4Addr::new(192, 168, 0, 1)});

    let mut vec_bytes: Vec<u8> = Vec::with_capacity(512);
    {
      let mut encoder = BinEncoder::new(&mut vec_bytes);
      record.emit(&mut encoder).unwrap();
    }

    let mut decoder = BinDecoder::new(&vec_bytes);

    let got = Record::read(&mut decoder).unwrap();

    assert_eq!(got, record);
  }
}
<commit_msg>canonical ordering tests for resource records<commit_after>/*
 * Copyright (C) 2015 Benjamin Fry <benjaminfry@me.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
use std::net::Ipv4Addr;
use std::sync::Arc as Rc;
use std::cmp::Ordering;

use ::serialize::binary::*;
use ::error::*;
use super::record_data::RData;
use super::record_type::RecordType;
use super::dns_class::DNSClass;
use super::domain;

/*
 * RFC 1035        Domain Implementation and Specification    November 1987
 *
 * 4.1.3. Resource record format
 *
 * The answer, authority, and additional sections all share the same
 * format: a variable number of resource records, where the number of
 * records is specified in the corresponding count field in the header.
 * Each resource record has the following format:
 *                                     1  1  1  1  1  1
 *       0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
 *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 *     |                                               |
 *     /                                               /
 *     /                      NAME                     /
 *     |                                               |
 *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 *     |                      TYPE                     |
 *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 *     |                     CLASS                     |
 *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 *     |                      TTL                      |
 *     |                                               |
 *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 *     |                   RDLENGTH                    |
 *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
 *     /                     RDATA                     /
 *     /                                               /
 *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 *
 * where:
 *
 * NAME            a domain name to which this resource record pertains.
 *
 * TYPE            two octets containing one of the RR type codes.  This
 *                 field specifies the meaning of the data in the RDATA
 *                 field.
 *
 * CLASS           two octets which specify the class of the data in the
 *                 RDATA field.
 *
 * TTL             a 32 bit unsigned integer that specifies the time
 *                 interval (in seconds) that the resource record may be
 *                 cached before it should be discarded.  Zero values are
 *                 interpreted to mean that the RR can only be used for the
 *                 transaction in progress, and should not be cached.
 *
 * RDLENGTH        an unsigned 16 bit integer that specifies the length in
 *                 octets of the RDATA field.
 *
 * RDATA           a variable length string of octets that describes the
 *                 resource.  The format of this information varies
 *                 according to the TYPE and CLASS of the resource record.
 *                 For example, the if the TYPE is A and the CLASS is IN,
 *                 the RDATA field is a 4 octet ARPA Internet address.
 */
#[derive(Eq, Hash, Ord, Debug, Clone)]
pub struct Record {
  name_labels: domain::Name,
  rr_type: RecordType,
  dns_class: DNSClass,
  ttl: u32,
  rdata: RData,
}

impl Record {
  /**
   * Creates a not very useful empty record, use the setters to build a more useful object
   *  There are no optional elements in this object, defaults are an empty name, type A, class IN,
   *  ttl of 0 and the 0.0.0.0 ip address.
   */
  pub fn new() -> Record {
    Record {
      // TODO: these really should all be Optionals, I was lazy.
      name_labels: domain::Name::new(),
      rr_type: RecordType::A,
      dns_class: DNSClass::IN,
      ttl: 0,
      rdata: RData::A { address: Ipv4Addr::new(0,0,0,0) }
    }
  }

  pub fn name(&mut self, name: domain::Name) -> &mut Self { self.name_labels = name; self }
  pub fn add_name(&mut self, label: String) -> &mut Self { self.name_labels.add_label(Rc::new(label)); self }
  pub fn rr_type(&mut self, rr_type: RecordType) -> &mut Self { self.rr_type = rr_type; self }
  pub fn dns_class(&mut self, dns_class: DNSClass) -> &mut Self { self.dns_class = dns_class; self }
  pub fn ttl(&mut self, ttl: u32) -> &mut Self { self.ttl = ttl; self }
  pub fn rdata(&mut self, rdata: RData) -> &mut Self { self.rdata = rdata; self }

  pub fn get_name(&self) -> &domain::Name { &self.name_labels }
  pub fn get_rr_type(&self) -> RecordType { self.rr_type }
  pub fn get_dns_class(&self) -> DNSClass { self.dns_class }
  pub fn get_ttl(&self) -> u32 { self.ttl }
  pub fn get_rdata(&self) -> &RData { &self.rdata }
}

impl BinSerializable<Record> for Record {
  /// parse a resource record line example:
  ///  WARNING: the record_bytes is 100% consumed and destroyed in this parsing process
  fn read(decoder: &mut BinDecoder) -> DecodeResult<Record> {
    // NAME            an owner name, i.e., the name of the node to which this
    //                 resource record pertains.
    let name_labels: domain::Name = try!(domain::Name::read(decoder));

    // TYPE            two octets containing one of the RR TYPE codes.
    let record_type: RecordType = try!(RecordType::read(decoder));
    decoder.set_record_type(record_type);

    // CLASS           two octets containing one of the RR CLASS codes.
    let class: DNSClass = try!(DNSClass::read(decoder));

    // TTL             a 32 bit signed integer that specifies the time interval
    //                that the resource record may be cached before the source
    //                of the information should again be consulted.  Zero
    //                values are interpreted to mean that the RR can only be
    //                used for the transaction in progress, and should not be
    //                cached.  For example, SOA records are always distributed
    //                with a zero TTL to prohibit caching.  Zero values can
    //                also be used for extremely volatile data.
    // note: u32 seems more accurate given that it can only be positive
    let ttl: u32 = try!(decoder.read_u32());

    // RDLENGTH        an unsigned 16 bit integer that specifies the length in
    //                octets of the RDATA field.
    let rd_length: u16 = try!(decoder.read_u16());

    // this is to handle updates, RFC 2036, which uses 0 to indicate certain aspects of
    //  pre-requisites
    let rdata: RData = if rd_length == 0 {
      RData::NULL{ anything: vec![] }
    } else {
      decoder.set_rdata_length(rd_length);

      // RDATA           a variable length string of octets that describes the
      //                resource.  The format of this information varies
      //                according to the TYPE and CLASS of the resource record.
      try!(RData::read(decoder))
    };

    Ok(Record{ name_labels: name_labels, rr_type: record_type, dns_class: class, ttl: ttl, rdata: rdata })
  }

  fn emit(&self, encoder: &mut BinEncoder) -> EncodeResult {
    try!(self.name_labels.emit(encoder));
    try!(self.rr_type.emit(encoder));
    try!(self.dns_class.emit(encoder));
    try!(encoder.emit_u32(self.ttl));

    // gah... need to write rdata before we know the size of rdata...
    // TODO: should we skip the fixed size header and write the rdata first? then write the header?
    let mut tmp_buf: Vec<u8> = Vec::with_capacity(512);
    {
      let mut tmp_encoder: BinEncoder = BinEncoder::with_offset(&mut tmp_buf, encoder.offset() + 2 /*for u16 len*/);
      try!(self.rdata.emit(&mut tmp_encoder));
    }

    assert!(tmp_buf.len() <= u16::max_value() as usize);

    try!(encoder.emit_u16(tmp_buf.len() as u16));
    encoder.reserve(tmp_buf.len());

    tmp_buf.reverse();
    while let Some(byte) = tmp_buf.pop() {
      try!(encoder.emit(byte));
    }

    Ok(())
  }
}

/// RFC 2136                       DNS Update                     April 1997
///
///   1.1.1. Two RRs are considered equal if their NAME, CLASS, TYPE,
///   RDLENGTH and RDATA fields are equal.  Note that the time-to-live
///   (TTL) field is explicitly excluded from the comparison.
///
///   1.1.2. The rules for comparison of character strings in names are
///   specified in [RFC1035 2.3.3]. i.e. case insensitive
impl PartialEq for Record {
    fn eq(&self, other: &Self) -> bool {
      // self == other && // the same pointer
      self.name_labels == other.name_labels &&
      self.rr_type == other.rr_type &&
      self.dns_class == other.dns_class &&
      self.rdata == other.rdata
    }

    fn ne(&self, other: &Self) -> bool {
      !self.eq(other)
    }
}

/// returns the value of the compare if the items are greater or lesser, but coninues on equal
macro_rules! compare_or_equal {
  ( $x:ident, $y:ident, $z:ident ) => (
    match $x.$z.partial_cmp(&$y.$z) {
      o @ Some(Ordering::Less) | o @ Some(Ordering::Greater) => return o,
      None => return None,
      Some(Ordering::Equal) => (),
    }
  );
}

impl PartialOrd<Record> for Record {
  // RFC 4034                DNSSEC Resource Records               March 2005
  //
  // 6.2.  Canonical RR Form
  //
  //    For the purposes of DNS security, the canonical form of an RR is the
  //    wire format of the RR where:
  //
  //    1.  every domain name in the RR is fully expanded (no DNS name
  //        compression) and fully qualified;
  //
  //    2.  all uppercase US-ASCII letters in the owner name of the RR are
  //        replaced by the corresponding lowercase US-ASCII letters;
  //
  //    3.  if the type of the RR is NS, MD, MF, CNAME, SOA, MB, MG, MR, PTR,
  //        HINFO, MINFO, MX, HINFO, RP, AFSDB, RT, SIG, PX, NXT, NAPTR, KX,
  //        SRV, DNAME, A6, RRSIG, or NSEC, all uppercase US-ASCII letters in
  //        the DNS names contained within the RDATA are replaced by the
  //        corresponding lowercase US-ASCII letters;
  //
  //    4.  if the owner name of the RR is a wildcard name, the owner name is
  //        in its original unexpanded form, including the "*" label (no
  //        wildcard substitution); and
  //
  //    5.  the RR's TTL is set to its original value as it appears in the
  //        originating authoritative zone or the Original TTL field of the
  //        covering RRSIG RR.
  fn partial_cmp(&self, other: &Record) -> Option<Ordering> {
    // TODO: given that the ordering of Resource Records is dependent on it's binary form and this
    //  method will be used during insertion sort or similar, we should probably do this
    //  conversion once somehow and store it separately. Or should the internal storage of all
    //  resource records be maintained in binary?

    compare_or_equal!(self, other, name_labels);
    compare_or_equal!(self, other, rr_type);
    compare_or_equal!(self, other, dns_class);
    compare_or_equal!(self, other, ttl);
    compare_or_equal!(self, other, rdata);

    // got here, means they are equal
    Some(Ordering::Equal)
  }
}

#[cfg(test)]
mod tests {
  use std::net::Ipv4Addr;
  use std::cmp::Ordering;

  use super::*;

  use ::serialize::binary::*;
  use ::rr::record_data::RData;
  use ::rr::record_type::RecordType;
  use ::rr::dns_class::DNSClass;
  use ::rr::Name;

  #[test]
  fn test_emit_and_read() {
    let mut record = Record::new();
    record.add_name("www".to_string()).add_name("example".to_string()).add_name("com".to_string())
    .rr_type(RecordType::A).dns_class(DNSClass::IN).ttl(5)
    .rdata(RData::A { address: Ipv4Addr::new(192, 168, 0, 1)});

    let mut vec_bytes: Vec<u8> = Vec::with_capacity(512);
    {
      let mut encoder = BinEncoder::new(&mut vec_bytes);
      record.emit(&mut encoder).unwrap();
    }

    let mut decoder = BinDecoder::new(&vec_bytes);

    let got = Record::read(&mut decoder).unwrap();

    assert_eq!(got, record);
  }

  #[test]
  fn test_order() {
    let mut record = Record::new();
    record.add_name("www".to_string()).add_name("example".to_string()).add_name("com".to_string())
    .rr_type(RecordType::A).dns_class(DNSClass::IN).ttl(5)
    .rdata(RData::A { address: Ipv4Addr::new(192, 168, 0, 1)});

    let mut greater_name = record.clone();
    greater_name.name(Name::new().label("zzz").label("example").label("com"));

    let mut greater_type = record.clone();
    greater_type.rr_type(RecordType::AAAA);

    let mut greater_class = record.clone();
    greater_class.dns_class(DNSClass::NONE);

    let mut greater_rdata = record.clone();
    greater_rdata.rdata(RData::A { address: Ipv4Addr::new(192, 168, 0, 255) });

    let compares = vec![(&record, &greater_name),
                        (&record, &greater_type),
                        (&record, &greater_class),
                        (&record, &greater_rdata),];

    assert_eq!(record.clone(), record.clone());
    for (r, g) in compares {
      println!("r, g: {:?}, {:?}", r, g);
      assert_eq!(r.cmp(g), Ordering::Less);
    }
  }
}
<|endoftext|>


<commit_before>use htmlescape::encode_minimal;
use schema::FieldValue;
use schema::Value;
use std::collections::BTreeMap;
use tokenizer::BoxedTokenizer;
use tokenizer::{Token, TokenStream, Tokenizer};
use Document;
use Index;
use Term;

#[derive(Debug)]
pub struct HighlightSection {
    start: usize,
    stop: usize,
}

impl HighlightSection {
    fn new(start: usize, stop: usize) -> HighlightSection {
        HighlightSection { start, stop }
    }
}

#[derive(Debug)]
pub struct FragmentCandidate {
    score: f32,
    start_offset: usize,
    stop_offset: usize,
    num_chars: usize,
    highlighted: Vec<HighlightSection>,
}

impl FragmentCandidate {
    /// Create a basic `FragmentCandidate`
    ///
    /// `score`, `num_chars` are set to 0
    /// and `highlighted` is set to empty vec
    /// stop_offset is set to start_offset, which is taken as a param.
    fn new(start_offset: usize) -> FragmentCandidate {
        FragmentCandidate {
            score: 0.0,
            start_offset: start_offset,
            stop_offset: start_offset,
            num_chars: 0,
            highlighted: vec![],
        }
    }

    /// Updates `score` and `highlighted` fields of the objects.
    ///
    /// taking the token and terms, the token is added to the fragment.
    /// if the token is one of the terms, the score
    /// and highlighted fields are updated in the fragment.
    fn try_add_token(&mut self, token: &Token, terms: &BTreeMap<String, f32>) {
        self.stop_offset = token.offset_to;

        if let Some(score) = terms.get(&token.text.to_lowercase()) {
            self.score += score;
            self.highlighted
                .push(HighlightSection::new(token.offset_from, token.offset_to));
        }
    }
}

#[derive(Debug)]
pub struct Snippet {
    fragments: String,
    highlighted: Vec<HighlightSection>,
}

const HIGHLIGHTEN_PREFIX: &str = "";
const HIGHLIGHTEN_POSTFIX: &str = "";

impl Snippet {
    /// Returns a hignlightned html from the `Snippet`.
    pub fn to_html(&self) -> String {
        let mut html = String::new();
        let mut start_from: usize = 0;

        for item in self.highlighted.iter() {
            html.push_str(&encode_minimal(&self.fragments[start_from..item.start]));
            html.push_str(HIGHLIGHTEN_PREFIX);
            html.push_str(&encode_minimal(&self.fragments[item.start..item.stop]));
            html.push_str(HIGHLIGHTEN_POSTFIX);
            start_from = item.stop;
        }
        html.push_str(&encode_minimal(
            &self.fragments[start_from..self.fragments.len()],
        ));
        html
    }
}

/// Returns a non-empty list of "good" fragments.
///
/// If no target term is within the text, then the function
/// should return an empty Vec.
///
/// If a target term is within the text, then the returned
/// list is required to be non-empty.
///
/// The returned list is non-empty and contain less
/// than 12 possibly overlapping fragments.
///
/// All fragments should contain at least one target term
/// and have at most `max_num_chars` characters (not bytes).
///
/// It is ok to emit non-overlapping fragments, for instance,
/// one short and one long containing the same keyword, in order
/// to leave optimization opportunity to the fragment selector
/// upstream.
///
/// Fragments must be valid in the sense that `&text[fragment.start..fragment.stop]`\
/// has to be a valid string.
fn search_fragments<'a>(
    tokenizer: Box<BoxedTokenizer>,
    text: &'a str,
    terms: BTreeMap<String, f32>,
    max_num_chars: usize,
) -> Vec<FragmentCandidate> {
    let mut token_stream = tokenizer.token_stream(text);
    let mut fragment = FragmentCandidate::new(0);
    let mut fragments: Vec<FragmentCandidate> = vec![];

    while let Some(next) = token_stream.next() {
        if (next.offset_to - fragment.start_offset) > max_num_chars {
            if fragment.score > 0.0 {
                fragments.push(fragment)
            };
            fragment = FragmentCandidate::new(next.offset_from);
        }
        fragment.try_add_token(next, &terms);
    }
    if fragment.score > 0.0 {
        fragments.push(fragment)
    }

    fragments
}

/// Returns a Snippet
///
/// Takes a vector of `FragmentCandidate`s and the text.
/// Figures out the best fragment from it and creates a snippet.
fn select_best_fragment_combination<'a>(
    fragments: Vec<FragmentCandidate>,
    text: &'a str,
) -> Snippet {
    if let Some(init) = fragments.iter().nth(0) {
        let fragment =
            fragments.iter().skip(1).fold(
                init,
                |acc, item| {
                    if item.score > acc.score {
                        item
                    } else {
                        acc
                    }
                },
            );
        let fragment_text = &text[fragment.start_offset..fragment.stop_offset];
        let highlighted = fragment
            .highlighted
            .iter()
            .map(|item| {
                HighlightSection::new(
                    item.start - fragment.start_offset,
                    item.stop - fragment.start_offset,
                )
            }).collect();
        Snippet {
            fragments: fragment_text.to_owned(),
            highlighted: highlighted,
        }
    } else {
        // when there no fragments to chose from,
        // for now create a empty snippet
        Snippet {
            fragments: String::new(),
            highlighted: vec![],
        }
    }
}

pub fn generate_snippet<'a>(
    doc: &'a [FieldValue],
    index: &Index,
    terms: Vec<Term>,
    max_num_chars: usize,
) -> Snippet {
    unimplemented!();
}

#[cfg(test)]
mod tests {
    use super::{search_fragments, select_best_fragment_combination};
    use std::collections::BTreeMap;
    use std::iter::Iterator;
    use tokenizer::{box_tokenizer, SimpleTokenizer};

    const TOKENIZER: SimpleTokenizer = SimpleTokenizer;

    #[test]
    fn test_snippet() {
        let boxed_tokenizer = box_tokenizer(TOKENIZER);

        let text = "Rust is a systems programming language sponsored by Mozilla which describes it as a \"safe, concurrent, practical language\", supporting functional and imperative-procedural paradigms. Rust is syntactically similar to C++[according to whom?], but its designers intend it to provide better memory safety while still maintaining performance.

Rust is free and open-source software, released under an MIT License, or Apache License 2.0. Its designers have refined the language through the experiences of writing the Servo web browser layout engine[14] and the Rust compiler. A large proportion of current commits to the project are from community members.[15]

Rust won first place for \"most loved programming language\" in the Stack Overflow Developer Survey in 2016, 2017, and 2018.
";

        let mut terms = BTreeMap::new();
        terms.insert(String::from("rust"), 1.0);
        terms.insert(String::from("language"), 0.9);

        let fragments = search_fragments(boxed_tokenizer, &text, terms, 100);
        assert_eq!(fragments.len(), 7);
        {
            let first = fragments.iter().nth(0).unwrap();
            assert_eq!(first.score, 1.9);
            assert_eq!(first.stop_offset, 89);
        }
        let snippet = select_best_fragment_combination(fragments, &text);
        assert_eq!(snippet.fragments, "Rust is a systems programming language sponsored by Mozilla which describes it as a \"safe".to_owned());
        assert_eq!(snippet.to_html(), "Rust is a systems programming language sponsored by Mozilla which describes it as a "safe".to_owned())
    }

    #[test]
    fn test_snippet_in_second_fragment() {
        let boxed_tokenizer = box_tokenizer(TOKENIZER);

        let text = "a b c d e f g";

        let mut terms = BTreeMap::new();
        terms.insert(String::from("c"), 1.0);

        let fragments = search_fragments(boxed_tokenizer, &text, terms, 3);

        assert_eq!(fragments.len(), 1);
        {
            let first = fragments.iter().nth(0).unwrap();
            assert_eq!(first.score, 1.0);
            assert_eq!(first.start_offset, 4);
            assert_eq!(first.stop_offset, 7);
        }

        let snippet = select_best_fragment_combination(fragments, &text);
        assert_eq!(snippet.fragments, "c d");
        assert_eq!(snippet.to_html(), "c d");
    }

    #[test]
    fn test_snippet_with_term_at_the_end_of_fragment() {
        let boxed_tokenizer = box_tokenizer(TOKENIZER);

        let text = "a b c d e f f g";

        let mut terms = BTreeMap::new();
        terms.insert(String::from("f"), 1.0);

        let fragments = search_fragments(boxed_tokenizer, &text, terms, 3);

        assert_eq!(fragments.len(), 2);
        {
            let first = fragments.iter().nth(0).unwrap();
            assert_eq!(first.score, 1.0);
            assert_eq!(first.stop_offset, 11);
            assert_eq!(first.start_offset, 8);
        }

        let snippet = select_best_fragment_combination(fragments, &text);
        assert_eq!(snippet.fragments, "e f");
        assert_eq!(snippet.to_html(), "e f");
    }

    #[test]
    fn test_snippet_with_term_not_in_text() {
        let boxed_tokenizer = box_tokenizer(TOKENIZER);

        let text = "a b c d";

        let mut terms = BTreeMap::new();
        terms.insert(String::from("z"), 1.0);

        let fragments = search_fragments(boxed_tokenizer, &text, terms, 3);

        assert_eq!(fragments.len(), 0);

        let snippet = select_best_fragment_combination(fragments, &text);
        assert_eq!(snippet.fragments, "");
        assert_eq!(snippet.to_html(), "");
    }

    fn test_snippet_with_no_terms() {
        let boxed_tokenizer = box_tokenizer(TOKENIZER);

        let text = "a b c d";

        let mut terms = BTreeMap::new();
        let fragments = search_fragments(boxed_tokenizer, &text, terms, 3);
        assert_eq!(fragments.len(), 0);

        let snippet = select_best_fragment_combination(fragments, &text);
        assert_eq!(snippet.fragments, "");
        assert_eq!(snippet.to_html(), "");
    }
}
<commit_msg>add a test for second fragment having higher score<commit_after>use htmlescape::encode_minimal;
use schema::FieldValue;
use schema::Value;
use std::collections::BTreeMap;
use tokenizer::BoxedTokenizer;
use tokenizer::{Token, TokenStream, Tokenizer};
use Document;
use Index;
use Term;

#[derive(Debug)]
pub struct HighlightSection {
    start: usize,
    stop: usize,
}

impl HighlightSection {
    fn new(start: usize, stop: usize) -> HighlightSection {
        HighlightSection { start, stop }
    }
}

#[derive(Debug)]
pub struct FragmentCandidate {
    score: f32,
    start_offset: usize,
    stop_offset: usize,
    num_chars: usize,
    highlighted: Vec<HighlightSection>,
}

impl FragmentCandidate {
    /// Create a basic `FragmentCandidate`
    ///
    /// `score`, `num_chars` are set to 0
    /// and `highlighted` is set to empty vec
    /// stop_offset is set to start_offset, which is taken as a param.
    fn new(start_offset: usize) -> FragmentCandidate {
        FragmentCandidate {
            score: 0.0,
            start_offset: start_offset,
            stop_offset: start_offset,
            num_chars: 0,
            highlighted: vec![],
        }
    }

    /// Updates `score` and `highlighted` fields of the objects.
    ///
    /// taking the token and terms, the token is added to the fragment.
    /// if the token is one of the terms, the score
    /// and highlighted fields are updated in the fragment.
    fn try_add_token(&mut self, token: &Token, terms: &BTreeMap<String, f32>) {
        self.stop_offset = token.offset_to;

        if let Some(score) = terms.get(&token.text.to_lowercase()) {
            self.score += score;
            self.highlighted
                .push(HighlightSection::new(token.offset_from, token.offset_to));
        }
    }
}

#[derive(Debug)]
pub struct Snippet {
    fragments: String,
    highlighted: Vec<HighlightSection>,
}

const HIGHLIGHTEN_PREFIX: &str = "";
const HIGHLIGHTEN_POSTFIX: &str = "";

impl Snippet {
    /// Returns a hignlightned html from the `Snippet`.
    pub fn to_html(&self) -> String {
        let mut html = String::new();
        let mut start_from: usize = 0;

        for item in self.highlighted.iter() {
            html.push_str(&encode_minimal(&self.fragments[start_from..item.start]));
            html.push_str(HIGHLIGHTEN_PREFIX);
            html.push_str(&encode_minimal(&self.fragments[item.start..item.stop]));
            html.push_str(HIGHLIGHTEN_POSTFIX);
            start_from = item.stop;
        }
        html.push_str(&encode_minimal(
            &self.fragments[start_from..self.fragments.len()],
        ));
        html
    }
}

/// Returns a non-empty list of "good" fragments.
///
/// If no target term is within the text, then the function
/// should return an empty Vec.
///
/// If a target term is within the text, then the returned
/// list is required to be non-empty.
///
/// The returned list is non-empty and contain less
/// than 12 possibly overlapping fragments.
///
/// All fragments should contain at least one target term
/// and have at most `max_num_chars` characters (not bytes).
///
/// It is ok to emit non-overlapping fragments, for instance,
/// one short and one long containing the same keyword, in order
/// to leave optimization opportunity to the fragment selector
/// upstream.
///
/// Fragments must be valid in the sense that `&text[fragment.start..fragment.stop]`\
/// has to be a valid string.
fn search_fragments<'a>(
    tokenizer: Box<BoxedTokenizer>,
    text: &'a str,
    terms: BTreeMap<String, f32>,
    max_num_chars: usize,
) -> Vec<FragmentCandidate> {
    let mut token_stream = tokenizer.token_stream(text);
    let mut fragment = FragmentCandidate::new(0);
    let mut fragments: Vec<FragmentCandidate> = vec![];

    while let Some(next) = token_stream.next() {
        if (next.offset_to - fragment.start_offset) > max_num_chars {
            if fragment.score > 0.0 {
                fragments.push(fragment)
            };
            fragment = FragmentCandidate::new(next.offset_from);
        }
        fragment.try_add_token(next, &terms);
    }
    if fragment.score > 0.0 {
        fragments.push(fragment)
    }

    fragments
}

/// Returns a Snippet
///
/// Takes a vector of `FragmentCandidate`s and the text.
/// Figures out the best fragment from it and creates a snippet.
fn select_best_fragment_combination<'a>(
    fragments: Vec<FragmentCandidate>,
    text: &'a str,
) -> Snippet {
    if let Some(init) = fragments.iter().nth(0) {
        let fragment =
            fragments.iter().skip(1).fold(
                init,
                |acc, item| {
                    if item.score > acc.score {
                        item
                    } else {
                        acc
                    }
                },
            );
        let fragment_text = &text[fragment.start_offset..fragment.stop_offset];
        let highlighted = fragment
            .highlighted
            .iter()
            .map(|item| {
                HighlightSection::new(
                    item.start - fragment.start_offset,
                    item.stop - fragment.start_offset,
                )
            }).collect();
        Snippet {
            fragments: fragment_text.to_owned(),
            highlighted: highlighted,
        }
    } else {
        // when there no fragments to chose from,
        // for now create a empty snippet
        Snippet {
            fragments: String::new(),
            highlighted: vec![],
        }
    }
}

pub fn generate_snippet<'a>(
    doc: &'a [FieldValue],
    index: &Index,
    terms: Vec<Term>,
    max_num_chars: usize,
) -> Snippet {
    unimplemented!();
}

#[cfg(test)]
mod tests {
    use super::{search_fragments, select_best_fragment_combination};
    use std::collections::BTreeMap;
    use std::iter::Iterator;
    use tokenizer::{box_tokenizer, SimpleTokenizer};

    const TOKENIZER: SimpleTokenizer = SimpleTokenizer;

    #[test]
    fn test_snippet() {
        let boxed_tokenizer = box_tokenizer(TOKENIZER);

        let text = "Rust is a systems programming language sponsored by Mozilla which describes it as a \"safe, concurrent, practical language\", supporting functional and imperative-procedural paradigms. Rust is syntactically similar to C++[according to whom?], but its designers intend it to provide better memory safety while still maintaining performance.

Rust is free and open-source software, released under an MIT License, or Apache License 2.0. Its designers have refined the language through the experiences of writing the Servo web browser layout engine[14] and the Rust compiler. A large proportion of current commits to the project are from community members.[15]

Rust won first place for \"most loved programming language\" in the Stack Overflow Developer Survey in 2016, 2017, and 2018.
";

        let mut terms = BTreeMap::new();
        terms.insert(String::from("rust"), 1.0);
        terms.insert(String::from("language"), 0.9);

        let fragments = search_fragments(boxed_tokenizer, &text, terms, 100);
        assert_eq!(fragments.len(), 7);
        {
            let first = fragments.iter().nth(0).unwrap();
            assert_eq!(first.score, 1.9);
            assert_eq!(first.stop_offset, 89);
        }
        let snippet = select_best_fragment_combination(fragments, &text);
        assert_eq!(snippet.fragments, "Rust is a systems programming language sponsored by Mozilla which describes it as a \"safe".to_owned());
        assert_eq!(snippet.to_html(), "Rust is a systems programming language sponsored by Mozilla which describes it as a "safe".to_owned())
    }

    #[test]
    fn test_snippet_in_second_fragment() {
        let boxed_tokenizer = box_tokenizer(TOKENIZER);

        let text = "a b c d e f g";

        let mut terms = BTreeMap::new();
        terms.insert(String::from("c"), 1.0);

        let fragments = search_fragments(boxed_tokenizer, &text, terms, 3);

        assert_eq!(fragments.len(), 1);
        {
            let first = fragments.iter().nth(0).unwrap();
            assert_eq!(first.score, 1.0);
            assert_eq!(first.start_offset, 4);
            assert_eq!(first.stop_offset, 7);
        }

        let snippet = select_best_fragment_combination(fragments, &text);
        assert_eq!(snippet.fragments, "c d");
        assert_eq!(snippet.to_html(), "c d");
    }

    #[test]
    fn test_snippet_with_term_at_the_end_of_fragment() {
        let boxed_tokenizer = box_tokenizer(TOKENIZER);

        let text = "a b c d e f f g";

        let mut terms = BTreeMap::new();
        terms.insert(String::from("f"), 1.0);

        let fragments = search_fragments(boxed_tokenizer, &text, terms, 3);

        assert_eq!(fragments.len(), 2);
        {
            let first = fragments.iter().nth(0).unwrap();
            assert_eq!(first.score, 1.0);
            assert_eq!(first.stop_offset, 11);
            assert_eq!(first.start_offset, 8);
        }

        let snippet = select_best_fragment_combination(fragments, &text);
        assert_eq!(snippet.fragments, "e f");
        assert_eq!(snippet.to_html(), "e f");
    }

    #[test]
    fn test_snippet_with_second_fragment_has_the_highest_score() {
        let boxed_tokenizer = box_tokenizer(TOKENIZER);

        let text = "a b c d e f g";

        let mut terms = BTreeMap::new();
        terms.insert(String::from("f"), 1.0);
        terms.insert(String::from("a"), 0.9);

        let fragments = search_fragments(boxed_tokenizer, &text, terms, 7);

        assert_eq!(fragments.len(), 2);
        {
            let first = fragments.iter().nth(0).unwrap();
            assert_eq!(first.score, 0.9);
            assert_eq!(first.stop_offset, 7);
            assert_eq!(first.start_offset, 0);
        }

        let snippet = select_best_fragment_combination(fragments, &text);
        assert_eq!(snippet.fragments, "e f g");
        assert_eq!(snippet.to_html(), "e f g");
    }

    #[test]
    fn test_snippet_with_term_not_in_text() {
        let boxed_tokenizer = box_tokenizer(TOKENIZER);

        let text = "a b c d";

        let mut terms = BTreeMap::new();
        terms.insert(String::from("z"), 1.0);

        let fragments = search_fragments(boxed_tokenizer, &text, terms, 3);

        assert_eq!(fragments.len(), 0);

        let snippet = select_best_fragment_combination(fragments, &text);
        assert_eq!(snippet.fragments, "");
        assert_eq!(snippet.to_html(), "");
    }

    #[test]
    fn test_snippet_with_no_terms() {
        let boxed_tokenizer = box_tokenizer(TOKENIZER);

        let text = "a b c d";

        let mut terms = BTreeMap::new();
        let fragments = search_fragments(boxed_tokenizer, &text, terms, 3);
        assert_eq!(fragments.len(), 0);

        let snippet = select_best_fragment_combination(fragments, &text);
        assert_eq!(snippet.fragments, "");
        assert_eq!(snippet.to_html(), "");
    }
}
<|endoftext|>


<commit_before>use num_traits::{One, Zero};
use std::cmp::Ordering;
use std::fmt;
use std::ops::{Add, Sub, Mul, Div};
use std::str::FromStr;

use traits::Weight;

#[derive(Clone, Debug)]
pub struct HwFloat(f64);

impl PartialEq for HwFloat {
    fn eq(&self, other: &HwFloat) -> bool {
        &self.0 == &other.0
    }
}

impl Eq for HwFloat {}

impl PartialOrd for HwFloat {
    fn partial_cmp(&self, other: &HwFloat) -> Option<Ordering> {
        self.0.partial_cmp(&other.0)
    }
}

impl Ord for HwFloat {
    fn cmp(&self, other: &HwFloat) -> Ordering {
        self.partial_cmp(other).unwrap_or(Ordering::Equal)
    }
}

macro_rules! derive_ops {
    ($tr: ident, $meth: ident) => {
        impl $tr<HwFloat> for HwFloat {
            type Output = HwFloat;

            #[inline]
            fn $meth(self, other: HwFloat) -> HwFloat {
                HwFloat((self.0).$meth(other.0))
            }
        }

        impl<'a> $tr<&'a HwFloat> for HwFloat {
            type Output = HwFloat;

            #[inline]
            fn $meth(self, other: &HwFloat) -> HwFloat {
                HwFloat((self.0).$meth(&other.0))
            }
        }

        impl<'a> $tr<HwFloat> for &'a HwFloat {
            type Output = HwFloat;

            #[inline]
            fn $meth(self, other: HwFloat) -> HwFloat {
                HwFloat((self.0).$meth(other.0))
            }
        }

        impl<'a, 'b> $tr<&'b HwFloat> for &'a HwFloat {
            type Output = HwFloat;

            #[inline]
            fn $meth(self, other: &HwFloat) -> HwFloat {
                HwFloat((self.0).$meth(&other.0))
            }
        }
    }
}

derive_ops!(Add, add);
derive_ops!(Sub, sub);
derive_ops!(Mul, mul);
derive_ops!(Div, div);

impl Zero for HwFloat {
    #[inline]
    fn zero() -> HwFloat {
        HwFloat(0.0)
    }

    #[inline]
    fn is_zero(&self) -> bool {
        self.0 == 0.0
    }
}

impl One for HwFloat {
    #[inline]
    fn one() -> HwFloat {
        HwFloat(1.0)
    }
}

impl FromStr for HwFloat {
    type Err = <f64 as FromStr>::Err;

    #[inline]
    fn from_str(s: &str) -> Result<HwFloat, <f64 as FromStr>::Err> {
        FromStr::from_str(s).map(HwFloat)
    }
}

impl fmt::Display for HwFloat {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.0.fmt(f)
    }
}

impl Weight for HwFloat {
    #[inline]
    fn from_i64(n: i64) -> HwFloat {
        HwFloat(n as f64)
    }

    #[inline]
    fn fuzzy_eq(&self, other: &HwFloat) -> bool {
        (self.0 - other.0).abs() < 1.0e-8
    }
}
<commit_msg>HwFloat: Raise an error on cmp with NaN<commit_after>use num_traits::{One, Zero};
use std::cmp::Ordering;
use std::fmt;
use std::ops::{Add, Sub, Mul, Div};
use std::str::FromStr;

use traits::Weight;

#[derive(Clone, Debug)]
pub struct HwFloat(f64);

impl PartialEq for HwFloat {
    fn eq(&self, other: &HwFloat) -> bool {
        &self.0 == &other.0
    }
}

impl Eq for HwFloat {}

impl PartialOrd for HwFloat {
    fn partial_cmp(&self, other: &HwFloat) -> Option<Ordering> {
        self.0.partial_cmp(&other.0)
    }
}

impl Ord for HwFloat {
    fn cmp(&self, other: &HwFloat) -> Ordering {
        self.partial_cmp(other).unwrap()
    }
}

macro_rules! derive_ops {
    ($tr: ident, $meth: ident) => {
        impl $tr<HwFloat> for HwFloat {
            type Output = HwFloat;

            #[inline]
            fn $meth(self, other: HwFloat) -> HwFloat {
                HwFloat((self.0).$meth(other.0))
            }
        }

        impl<'a> $tr<&'a HwFloat> for HwFloat {
            type Output = HwFloat;

            #[inline]
            fn $meth(self, other: &HwFloat) -> HwFloat {
                HwFloat((self.0).$meth(&other.0))
            }
        }

        impl<'a> $tr<HwFloat> for &'a HwFloat {
            type Output = HwFloat;

            #[inline]
            fn $meth(self, other: HwFloat) -> HwFloat {
                HwFloat((self.0).$meth(other.0))
            }
        }

        impl<'a, 'b> $tr<&'b HwFloat> for &'a HwFloat {
            type Output = HwFloat;

            #[inline]
            fn $meth(self, other: &HwFloat) -> HwFloat {
                HwFloat((self.0).$meth(&other.0))
            }
        }
    }
}

derive_ops!(Add, add);
derive_ops!(Sub, sub);
derive_ops!(Mul, mul);
derive_ops!(Div, div);

impl Zero for HwFloat {
    #[inline]
    fn zero() -> HwFloat {
        HwFloat(0.0)
    }

    #[inline]
    fn is_zero(&self) -> bool {
        self.0 == 0.0
    }
}

impl One for HwFloat {
    #[inline]
    fn one() -> HwFloat {
        HwFloat(1.0)
    }
}

impl FromStr for HwFloat {
    type Err = <f64 as FromStr>::Err;

    #[inline]
    fn from_str(s: &str) -> Result<HwFloat, <f64 as FromStr>::Err> {
        FromStr::from_str(s).map(HwFloat)
    }
}

impl fmt::Display for HwFloat {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.0.fmt(f)
    }
}

impl Weight for HwFloat {
    #[inline]
    fn from_i64(n: i64) -> HwFloat {
        HwFloat(n as f64)
    }

    #[inline]
    fn fuzzy_eq(&self, other: &HwFloat) -> bool {
        (self.0 - other.0).abs() < 1.0e-8
    }
}
<|endoftext|>