<commit_before>extern crate glib;

extern crate gtk;
use gtk::prelude::*;

use std::cell::RefCell;
use std::fs::File;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::sync::mpsc::{channel, Receiver};

use media::{ContextMessage, ExportContext, PlaybackContext};
use media::ContextMessage::*;

use toc;
use toc::{Chapter, DEFAULT_TITLE, Exporter, MatroskaTocFormat};

use super::MainController;

const LISTENER_PERIOD: u32 = 250; // 250 ms (4 Hz)

#[derive(Clone, PartialEq)]
enum ExportType {
    ExternalToc,
    None,
    SingleFileWithToc,
    Split,
}

pub struct ExportController {
    export_dlg: gtk::Dialog,
    export_btn: gtk::Button,
    progress_bar: gtk::ProgressBar,

    mkvmerge_txt_rdbtn: gtk::RadioButton,
    cue_rdbtn: gtk::RadioButton,
    mkv_rdbtn: gtk::RadioButton,
    split_rdbtn: gtk::RadioButton,

    pub playback_ctx: Option<PlaybackContext>,
    export_ctx: Option<ExportContext>,
    export_type: ExportType,
    target_path: PathBuf,
    extension: String,
    idx: usize,
    current_chapter: Option<Chapter>,
    duration: u64,

    this_opt: Option<Rc<RefCell<ExportController>>>,
    listener_src: Option<glib::SourceId>,
}

impl ExportController {
    pub fn new(builder: &gtk::Builder) -> Rc<RefCell<Self>> {
        let main_window: gtk::ApplicationWindow = builder.get_object("application-window").unwrap();
        let export_dlg: gtk::Dialog = builder.get_object("export-dlg").unwrap();
        export_dlg.set_transient_for(&main_window);

        let this = Rc::new(RefCell::new(ExportController {
            export_dlg: export_dlg,
            export_btn: builder.get_object("export-btn").unwrap(),
            progress_bar: builder.get_object("export-progress").unwrap(),

            mkvmerge_txt_rdbtn: builder.get_object("mkvmerge_txt-rdbtn").unwrap(),
            cue_rdbtn: builder.get_object("cue-rdbtn").unwrap(),
            mkv_rdbtn: builder.get_object("mkv-rdbtn").unwrap(),
            split_rdbtn: builder.get_object("split-rdbtn").unwrap(),

            playback_ctx: None,
            export_ctx: None,
            export_type: ExportType::None,
            target_path: PathBuf::new(),
            extension: String::new(),
            idx: 0,
            current_chapter: None,
            duration: 0,

            this_opt: None,
            listener_src: None,
        }));

        {
            let mut this_mut = this.borrow_mut();
            let this_rc = Rc::clone(&this);
            this_mut.this_opt = Some(this_rc);
        }

        this
    }

    pub fn register_callbacks(
        this_rc: &Rc<RefCell<Self>>,
        main_ctrl: &Rc<RefCell<MainController>>
    ) {
        let this = this_rc.borrow();

        let this_clone = Rc::clone(this_rc);
        let main_ctrl_clone = Rc::clone(main_ctrl);
        this.export_dlg.connect_delete_event(move |dlg, _| {
            main_ctrl_clone.borrow_mut().restore_context(
                this_clone.borrow_mut().playback_ctx.take().unwrap()
            );
            dlg.hide_on_delete();
            Inhibit(true)
        });

        let this_clone = Rc::clone(this_rc);
        let main_ctrl_clone = Rc::clone(main_ctrl);
        this.export_btn.connect_clicked(move |_| {
            let mut this = this_clone.borrow_mut();

            this.switch_to_available();

            let (format, export_type) = this.get_selected_format();
            this.export_type = export_type.clone();
            this.idx = 0;

            let is_audio_only = {
                this.playback_ctx.as_ref().unwrap().info.lock()
                    .expect(
                        "ExportController::export_btn clicked, failed to lock media info",
                    ).video_best.is_none()
            };
            this.extension = toc::Factory::get_extension(&format, is_audio_only).to_owned();

            let media_path = this.playback_ctx.as_ref().unwrap().path.clone();
            this.target_path = media_path.with_extension(&this.extension);

            match export_type {
                ExportType::ExternalToc => {
                    // export toc as a standalone file
                    // TODO: handle file related errors
                    let mut output_file = File::create(&this.target_path)
                        .expect("ExportController::export_btn clicked couldn't create output file");

                    {
                        let info = this.playback_ctx.as_ref()
                            .unwrap().info.lock()
                            .expect(
                                "ExportController::export_btn clicked, failed to lock media info",
                            );
                        toc::Factory::get_writer(&format)
                            .write(&info.metadata, &info.chapters, &mut output_file);
                    }

                    main_ctrl_clone.borrow_mut()
                        .restore_context(this.playback_ctx.take().unwrap());
                    this.export_dlg.hide();
                }
                ExportType::Split => {
                    this.next_chapter();

                    let first_path =
                        match this.current_chapter {
                            Some(ref chapter) => this.get_split_path(chapter),
                            None => // No chapter => export the whole file
                                this.target_path.clone(),
                        };
                    this.build_export_context(
                        Rc::clone(&main_ctrl_clone),
                        &media_path,
                        &first_path,
                    );
                }
                ExportType::SingleFileWithToc => {
                    let target_path = this.target_path.clone();
                    this.build_export_context(
                        Rc::clone(&main_ctrl_clone),
                        &media_path,
                        &target_path,
                    );
                }
                _ => (),
            }
        });
    }

    pub fn open(&mut self, playback_ctx: PlaybackContext) {
        self.playback_ctx = Some(playback_ctx);
        self.export_dlg.present();
    }

    fn build_export_context(&mut self,
        main_ctrl: Rc<RefCell<MainController>>,
        media_path: &Path,
        export_path: &Path,
    ) {
        self.duration = self.playback_ctx.as_ref()
            .unwrap()
            .get_duration();
        // export toc within a media container with the streams
        let (ctx_tx, ui_rx) = channel();

        self.register_listener(LISTENER_PERIOD, ui_rx, Rc::clone(&main_ctrl));

        match ExportContext::new(media_path, export_path, ctx_tx) {
            Ok(export_ctx) => {
                self.switch_to_busy();
                self.export_ctx = Some(export_ctx);
            },
            Err(error) => {
                eprintln!("Error exporting media: {}", error);
                self.remove_listener();
                self.switch_to_available();
                main_ctrl.borrow_mut()
                    .restore_context(self.playback_ctx.take().unwrap());
                self.export_dlg.hide();
            }
        };
    }

    fn next_chapter(&mut self) {
        let next_chapter = {
            let info = self.playback_ctx.as_ref().unwrap()
                .info.lock()
                    .expect("ExportController::next_chapter failed to lock media info");

            info.chapters.get(self.idx)
                .map(|chapter| chapter.clone().to_owned())
        };

        self.current_chapter = next_chapter;
        if let Some(_) = self.current_chapter {
            self.idx += 1;
        }
    }

    fn get_split_path(&self, chapter: &Chapter) -> PathBuf {
        let mut split_name = String::new();

        let info = self.playback_ctx.as_ref().unwrap()
            .info.lock()
                .expect("ExportController::get_split_name failed to lock media info");

        // TODO: make format customisable
        if let Some(artist) = info.get_artist() {
            split_name += &format!("{} - ", artist);
        }
        if let Some(title) = info.get_title() {
            split_name += &format!("{} - ", title);
        }

        split_name +=
            &format!(
                "{:02}. {}.{}",
                self.idx + 1,
                chapter.get_title().unwrap_or(DEFAULT_TITLE),
                self.extension,
            );

        self.target_path.with_file_name(split_name)
    }

    fn get_selected_format(&self) -> (toc::Format, ExportType) {
        if self.mkvmerge_txt_rdbtn.get_active() {
            (toc::Format::MKVMergeText, ExportType::ExternalToc)
        } else if self.cue_rdbtn.get_active() {
            (toc::Format::CueSheet, ExportType::ExternalToc)
        } else if self.mkv_rdbtn.get_active() {
            (toc::Format::Matroska, ExportType::SingleFileWithToc)
        } else if self.split_rdbtn.get_active() {
            (toc::Format::Matroska, ExportType::Split)
        } else {
            unreachable!("ExportController::get_selected_format no selected radio button");
        }
    }

    fn switch_to_busy(&self) {
        self.export_btn.set_sensitive(false);
        self.mkvmerge_txt_rdbtn.set_sensitive(false);
        self.cue_rdbtn.set_sensitive(false);
        self.mkv_rdbtn.set_sensitive(false);
    }

    fn switch_to_available(&self) {
        self.export_btn.set_sensitive(true);
        self.mkvmerge_txt_rdbtn.set_sensitive(true);
        self.cue_rdbtn.set_sensitive(true);
        self.mkv_rdbtn.set_sensitive(true);
    }

    fn remove_listener(&mut self) {
        if let Some(source_id) = self.listener_src.take() {
            glib::source_remove(source_id);
        }
    }

    fn register_listener(&mut self,
        timeout: u32,
        ui_rx: Receiver<ContextMessage>,
        main_ctrl: Rc<RefCell<MainController>>,
    ) {
        if self.listener_src.is_some() {
            return;
        }

        let this_rc = Rc::clone(self.this_opt.as_ref().unwrap());

        self.listener_src = Some(gtk::timeout_add(timeout, move || {
            let mut keep_going = true;

            let mut this = this_rc.borrow_mut();

            if this.duration > 0 {
                let position =
                    match this.export_ctx.as_mut() {
                        Some(export_ctx) => export_ctx.get_position(),
                        None => 0,
                    };
                this.progress_bar.set_fraction(position as f64 / this.duration as f64);
            }

            for message in ui_rx.try_iter() {
                match message {
                    AsyncDone => {
                        println!("ExportContext::listener(AsyncDone)");
                    }
                    InitDone => {
                        let export_ctx = this.export_ctx.as_ref()
                            .expect("ExportContext::listener(InitDone) couldn't get ExportContext");

                        match export_ctx.get_muxer() {
                            Some(muxer) => {
                                match this.export_type {
                                    ExportType::Split => {
                                        match this.current_chapter {
                                            Some(ref chapter) => {
                                                println!("Split chapter found");
                                                let target_path = this.get_split_path(chapter);
                                                println!("target_path {:?}", target_path);
                                                match export_ctx.export_part(
                                                    &target_path,
                                                    chapter.start.nano_total,
                                                    chapter.end.nano_total,
                                                ) {
                                                    Ok(_) => println!("export part"),
                                                    Err(_) => {
                                                        this.switch_to_available();
                                                        eprintln!(
                                                            "ERROR: failed to export part {:?}",
                                                            target_path,
                                                        );
                                                    }
                                                }
                                            }
                                            None => {
                                                println!("Split no chapter");
                                                match export_ctx.export() {
                                                    Ok(_) => (),
                                                    Err(err) => {
                                                        this.switch_to_available();
                                                        eprintln!(
                                                            "ERROR: failed to export media: {}",
                                                            err,
                                                        );
                                                    }
                                                }
                                            }
                                        }
                                    }
                                    ExportType::SingleFileWithToc => {
                                        let exporter = MatroskaTocFormat::new();
                                        {
                                            let info = this.playback_ctx.as_ref().unwrap()
                                                .info
                                                    .lock()
                                                        .expect(concat!(
                                                            "ExportController::listener(InitDone) ",
                                                            "failed to lock media info",
                                                        ));
                                            exporter.export(&info.metadata, &info.chapters, &muxer);
                                        }

                                        match export_ctx.export() {
                                            Ok(_) => (),
                                            Err(err) => {
                                                this.switch_to_available();
                                                eprintln!("ERROR: failed to export media: {}", err);
                                            }
                                        }
                                    }
                                    _ => unreachable!(concat!(
                                        "ExportController::listener(InitDone) ",
                                        "Unexpected export type",
                                    )),
                                }
                            }
                            None => {
                                this.switch_to_available();
                                eprintln!("ExportContext::listener(InitDone) couldn't get ")
                            }
                        }
                    }
                    Eos => {
                        this.switch_to_available();
                        main_ctrl.borrow_mut()
                            .restore_context(this.playback_ctx.take().unwrap());
                        this.export_dlg.hide();
                        keep_going = false;
                    }
                    FailedToOpenMedia => {
                        this.switch_to_available();
                        eprintln!("ERROR: failed to export media");
                        keep_going = false;
                    }
                };

                if !keep_going {
                    break;
                }
            }

            if !keep_going {
                this.listener_src = None;
            }

            glib::Continue(keep_going)
        }));
    }
}
<commit_msg>Reset progress bar when opening export dialog (#56)<commit_after>extern crate glib;

extern crate gtk;
use gtk::prelude::*;

use std::cell::RefCell;
use std::fs::File;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::sync::mpsc::{channel, Receiver};

use media::{ContextMessage, ExportContext, PlaybackContext};
use media::ContextMessage::*;

use toc;
use toc::{Chapter, DEFAULT_TITLE, Exporter, MatroskaTocFormat};

use super::MainController;

const LISTENER_PERIOD: u32 = 250; // 250 ms (4 Hz)

#[derive(Clone, PartialEq)]
enum ExportType {
    ExternalToc,
    None,
    SingleFileWithToc,
    Split,
}

pub struct ExportController {
    export_dlg: gtk::Dialog,
    export_btn: gtk::Button,
    progress_bar: gtk::ProgressBar,

    mkvmerge_txt_rdbtn: gtk::RadioButton,
    cue_rdbtn: gtk::RadioButton,
    mkv_rdbtn: gtk::RadioButton,
    split_rdbtn: gtk::RadioButton,

    pub playback_ctx: Option<PlaybackContext>,
    export_ctx: Option<ExportContext>,
    export_type: ExportType,
    target_path: PathBuf,
    extension: String,
    idx: usize,
    current_chapter: Option<Chapter>,
    duration: u64,

    this_opt: Option<Rc<RefCell<ExportController>>>,
    listener_src: Option<glib::SourceId>,
}

impl ExportController {
    pub fn new(builder: &gtk::Builder) -> Rc<RefCell<Self>> {
        let main_window: gtk::ApplicationWindow = builder.get_object("application-window").unwrap();
        let export_dlg: gtk::Dialog = builder.get_object("export-dlg").unwrap();
        export_dlg.set_transient_for(&main_window);

        let this = Rc::new(RefCell::new(ExportController {
            export_dlg: export_dlg,
            export_btn: builder.get_object("export-btn").unwrap(),
            progress_bar: builder.get_object("export-progress").unwrap(),

            mkvmerge_txt_rdbtn: builder.get_object("mkvmerge_txt-rdbtn").unwrap(),
            cue_rdbtn: builder.get_object("cue-rdbtn").unwrap(),
            mkv_rdbtn: builder.get_object("mkv-rdbtn").unwrap(),
            split_rdbtn: builder.get_object("split-rdbtn").unwrap(),

            playback_ctx: None,
            export_ctx: None,
            export_type: ExportType::None,
            target_path: PathBuf::new(),
            extension: String::new(),
            idx: 0,
            current_chapter: None,
            duration: 0,

            this_opt: None,
            listener_src: None,
        }));

        {
            let mut this_mut = this.borrow_mut();
            let this_rc = Rc::clone(&this);
            this_mut.this_opt = Some(this_rc);
        }

        this
    }

    pub fn register_callbacks(
        this_rc: &Rc<RefCell<Self>>,
        main_ctrl: &Rc<RefCell<MainController>>
    ) {
        let this = this_rc.borrow();

        let this_clone = Rc::clone(this_rc);
        let main_ctrl_clone = Rc::clone(main_ctrl);
        this.export_dlg.connect_delete_event(move |dlg, _| {
            main_ctrl_clone.borrow_mut().restore_context(
                this_clone.borrow_mut().playback_ctx.take().unwrap()
            );
            dlg.hide_on_delete();
            Inhibit(true)
        });

        let this_clone = Rc::clone(this_rc);
        let main_ctrl_clone = Rc::clone(main_ctrl);
        this.export_btn.connect_clicked(move |_| {
            let mut this = this_clone.borrow_mut();

            this.switch_to_available();

            let (format, export_type) = this.get_selected_format();
            this.export_type = export_type.clone();
            this.idx = 0;

            let is_audio_only = {
                this.playback_ctx.as_ref().unwrap().info.lock()
                    .expect(
                        "ExportController::export_btn clicked, failed to lock media info",
                    ).video_best.is_none()
            };
            this.extension = toc::Factory::get_extension(&format, is_audio_only).to_owned();

            let media_path = this.playback_ctx.as_ref().unwrap().path.clone();
            this.target_path = media_path.with_extension(&this.extension);

            match export_type {
                ExportType::ExternalToc => {
                    // export toc as a standalone file
                    // TODO: handle file related errors
                    let mut output_file = File::create(&this.target_path)
                        .expect("ExportController::export_btn clicked couldn't create output file");

                    {
                        let info = this.playback_ctx.as_ref()
                            .unwrap().info.lock()
                            .expect(
                                "ExportController::export_btn clicked, failed to lock media info",
                            );
                        toc::Factory::get_writer(&format)
                            .write(&info.metadata, &info.chapters, &mut output_file);
                    }

                    main_ctrl_clone.borrow_mut()
                        .restore_context(this.playback_ctx.take().unwrap());
                    this.export_dlg.hide();
                }
                ExportType::Split => {
                    this.next_chapter();

                    let first_path =
                        match this.current_chapter {
                            Some(ref chapter) => this.get_split_path(chapter),
                            None => // No chapter => export the whole file
                                this.target_path.clone(),
                        };
                    this.build_export_context(
                        Rc::clone(&main_ctrl_clone),
                        &media_path,
                        &first_path,
                    );
                }
                ExportType::SingleFileWithToc => {
                    let target_path = this.target_path.clone();
                    this.build_export_context(
                        Rc::clone(&main_ctrl_clone),
                        &media_path,
                        &target_path,
                    );
                }
                _ => (),
            }
        });
    }

    pub fn open(&mut self, playback_ctx: PlaybackContext) {
        self.playback_ctx = Some(playback_ctx);
        self.progress_bar.set_fraction(0f64);
        self.export_dlg.present();
    }

    fn build_export_context(&mut self,
        main_ctrl: Rc<RefCell<MainController>>,
        media_path: &Path,
        export_path: &Path,
    ) {
        self.duration = self.playback_ctx.as_ref()
            .unwrap()
            .get_duration();
        // export toc within a media container with the streams
        let (ctx_tx, ui_rx) = channel();

        self.register_listener(LISTENER_PERIOD, ui_rx, Rc::clone(&main_ctrl));

        match ExportContext::new(media_path, export_path, ctx_tx) {
            Ok(export_ctx) => {
                self.switch_to_busy();
                self.export_ctx = Some(export_ctx);
            },
            Err(error) => {
                eprintln!("Error exporting media: {}", error);
                self.remove_listener();
                self.switch_to_available();
                main_ctrl.borrow_mut()
                    .restore_context(self.playback_ctx.take().unwrap());
                self.export_dlg.hide();
            }
        };
    }

    fn next_chapter(&mut self) {
        let next_chapter = {
            let info = self.playback_ctx.as_ref().unwrap()
                .info.lock()
                    .expect("ExportController::next_chapter failed to lock media info");

            info.chapters.get(self.idx)
                .map(|chapter| chapter.clone().to_owned())
        };

        self.current_chapter = next_chapter;
        if let Some(_) = self.current_chapter {
            self.idx += 1;
        }
    }

    fn get_split_path(&self, chapter: &Chapter) -> PathBuf {
        let mut split_name = String::new();

        let info = self.playback_ctx.as_ref().unwrap()
            .info.lock()
                .expect("ExportController::get_split_name failed to lock media info");

        // TODO: make format customisable
        if let Some(artist) = info.get_artist() {
            split_name += &format!("{} - ", artist);
        }
        if let Some(title) = info.get_title() {
            split_name += &format!("{} - ", title);
        }

        split_name +=
            &format!(
                "{:02}. {}.{}",
                self.idx + 1,
                chapter.get_title().unwrap_or(DEFAULT_TITLE),
                self.extension,
            );

        self.target_path.with_file_name(split_name)
    }

    fn get_selected_format(&self) -> (toc::Format, ExportType) {
        if self.mkvmerge_txt_rdbtn.get_active() {
            (toc::Format::MKVMergeText, ExportType::ExternalToc)
        } else if self.cue_rdbtn.get_active() {
            (toc::Format::CueSheet, ExportType::ExternalToc)
        } else if self.mkv_rdbtn.get_active() {
            (toc::Format::Matroska, ExportType::SingleFileWithToc)
        } else if self.split_rdbtn.get_active() {
            (toc::Format::Matroska, ExportType::Split)
        } else {
            unreachable!("ExportController::get_selected_format no selected radio button");
        }
    }

    fn switch_to_busy(&self) {
        self.export_btn.set_sensitive(false);
        self.mkvmerge_txt_rdbtn.set_sensitive(false);
        self.cue_rdbtn.set_sensitive(false);
        self.mkv_rdbtn.set_sensitive(false);
    }

    fn switch_to_available(&self) {
        self.export_btn.set_sensitive(true);
        self.mkvmerge_txt_rdbtn.set_sensitive(true);
        self.cue_rdbtn.set_sensitive(true);
        self.mkv_rdbtn.set_sensitive(true);
    }

    fn remove_listener(&mut self) {
        if let Some(source_id) = self.listener_src.take() {
            glib::source_remove(source_id);
        }
    }

    fn register_listener(&mut self,
        timeout: u32,
        ui_rx: Receiver<ContextMessage>,
        main_ctrl: Rc<RefCell<MainController>>,
    ) {
        if self.listener_src.is_some() {
            return;
        }

        let this_rc = Rc::clone(self.this_opt.as_ref().unwrap());

        self.listener_src = Some(gtk::timeout_add(timeout, move || {
            let mut keep_going = true;

            let mut this = this_rc.borrow_mut();

            if this.duration > 0 {
                let position =
                    match this.export_ctx.as_mut() {
                        Some(export_ctx) => export_ctx.get_position(),
                        None => 0,
                    };
                this.progress_bar.set_fraction(position as f64 / this.duration as f64);
            }

            for message in ui_rx.try_iter() {
                match message {
                    AsyncDone => {
                        println!("ExportContext::listener(AsyncDone)");
                    }
                    InitDone => {
                        let export_ctx = this.export_ctx.as_ref()
                            .expect("ExportContext::listener(InitDone) couldn't get ExportContext");

                        match export_ctx.get_muxer() {
                            Some(muxer) => {
                                match this.export_type {
                                    ExportType::Split => {
                                        match this.current_chapter {
                                            Some(ref chapter) => {
                                                println!("Split chapter found");
                                                let target_path = this.get_split_path(chapter);
                                                println!("target_path {:?}", target_path);
                                                match export_ctx.export_part(
                                                    &target_path,
                                                    chapter.start.nano_total,
                                                    chapter.end.nano_total,
                                                ) {
                                                    Ok(_) => println!("export part"),
                                                    Err(_) => {
                                                        this.switch_to_available();
                                                        eprintln!(
                                                            "ERROR: failed to export part {:?}",
                                                            target_path,
                                                        );
                                                    }
                                                }
                                            }
                                            None => {
                                                println!("Split no chapter");
                                                match export_ctx.export() {
                                                    Ok(_) => (),
                                                    Err(err) => {
                                                        this.switch_to_available();
                                                        eprintln!(
                                                            "ERROR: failed to export media: {}",
                                                            err,
                                                        );
                                                    }
                                                }
                                            }
                                        }
                                    }
                                    ExportType::SingleFileWithToc => {
                                        let exporter = MatroskaTocFormat::new();
                                        {
                                            let info = this.playback_ctx.as_ref().unwrap()
                                                .info
                                                    .lock()
                                                        .expect(concat!(
                                                            "ExportController::listener(InitDone) ",
                                                            "failed to lock media info",
                                                        ));
                                            exporter.export(&info.metadata, &info.chapters, &muxer);
                                        }

                                        match export_ctx.export() {
                                            Ok(_) => (),
                                            Err(err) => {
                                                this.switch_to_available();
                                                eprintln!("ERROR: failed to export media: {}", err);
                                            }
                                        }
                                    }
                                    _ => unreachable!(concat!(
                                        "ExportController::listener(InitDone) ",
                                        "Unexpected export type",
                                    )),
                                }
                            }
                            None => {
                                this.switch_to_available();
                                eprintln!("ExportContext::listener(InitDone) couldn't get ")
                            }
                        }
                    }
                    Eos => {
                        this.switch_to_available();
                        main_ctrl.borrow_mut()
                            .restore_context(this.playback_ctx.take().unwrap());
                        this.export_dlg.hide();
                        keep_going = false;
                    }
                    FailedToOpenMedia => {
                        this.switch_to_available();
                        eprintln!("ERROR: failed to export media");
                        keep_going = false;
                    }
                };

                if !keep_going {
                    break;
                }
            }

            if !keep_going {
                this.listener_src = None;
            }

            glib::Continue(keep_going)
        }));
    }
}
<|endoftext|>


<commit_before>use card_strategy::CardStrategy;

use card::Card;
use card::Suit;
use card::Rank;
use game_status::GameStatus;
use player::PlayerName;

use std::collections::BTreeMap;
use std::collections::BTreeSet;

#[derive(Debug)]
pub struct DefensiveCardStrategy;

/*
    Play 2 of clubs if in hand.
    If going to win a deal, do so with highest ranking card.
*/
impl DefensiveCardStrategy {
    fn score_card<'a>(card: &'a Card, game_status: &'a GameStatus) -> (i32, i32, i32, i32, i32, i32, i32) {
        let potential_points = Self::potential_points(card, game_status);
        let definite_points = if Self::will_win_deal(card, game_status) {
            potential_points
        } else {
            0
        };
        let later_potential_points = 0 - Self::later_potential_points(card, game_status);
        let card_penalty_to_me = Self::card_penalty_to_me(card, game_status);
        let card_penalty = 0 - Self::card_penalty(card, game_status);
        let trouble = Self::trouble_score(card, game_status);
        let card_rank = 0 - (u32::from(card.rank) as i32);
        (definite_points, potential_points, later_potential_points, card_penalty_to_me, card_penalty, trouble, card_rank)
    }

    fn card_penalty(card: &Card, game_status: &GameStatus) -> i32 {
        game_status.round_parameters.card_points.get(card)
            .map(|penalty| *penalty)
            .unwrap_or_default()
    }

    fn card_penalty_to_me(card: &Card, game_status: &GameStatus) -> i32 {
        if Self::can_win_deal(card, game_status) {
            Self::card_penalty(card, game_status)
        } else {
            0
        }
    }

    fn trouble_score(card: &Card, game_status: &GameStatus) -> i32 {
        if Self::can_win_deal(card, game_status) {
            let trouble = u32::from(card.rank) as i32;
            if Self::card_penalty_to_me(card, game_status) < 0 {
                0 - trouble
            } else {
                if Self::will_win_deal(card, game_status) {
                    u32::from(Rank::Ace) as i32
                } else {
                    trouble
                }
            }
        } else {
            0
        }
    }

    fn can_win_deal(card: &Card, game_status: &GameStatus) -> bool {
        game_status.my_in_progress_deal.as_ref().and_then(|deal|
            deal.suit.and_then(|suit|
                deal.deal_cards.iter()
                    .map(|deal_card| deal_card.card)
                    .filter(|card| card.suit == suit)
                    .max()
                    .map(|winning_card| card.suit == suit && card.rank > winning_card.rank))).unwrap_or(true)
    }

    fn will_win_deal(card: &Card, game_status: &GameStatus) -> bool {
        Self::can_win_deal(card, game_status) && (Self::plays_left(game_status).len() == 0 ||
            Self::deal_suit(game_status)
                .and_then(|suit| Self::remaining_cards(game_status).iter()
                    .filter(|card| card.suit == *suit)
                    .max()
                    .map(|winning_card| card.rank > winning_card.rank)).unwrap_or(true))
    }

    fn deal_suit(game_status: &GameStatus) -> Option<&Suit> {
        game_status.my_in_progress_deal.as_ref().and_then(|deal| deal.suit.as_ref())
    }

    fn plays_left(game_status: &GameStatus) -> BTreeSet<&PlayerName> {
        let mut players: BTreeSet<&PlayerName> = game_status.my_game_players.iter()
            .filter(|player| !player.has_turn)
            .map(|player| &player.team_name)
            .collect();

        if let &Some(ref deal) = &game_status.my_in_progress_deal {
            for deal_card in &deal.deal_cards {
                players.remove(&deal_card.player_name);
            }
        }

        players
    }

    fn remaining_cards(game_status: &GameStatus) -> BTreeSet<Card> {
        let mut cards = Card::all();

        for deal in &game_status.my_game_deals {
            for deal_card in &deal.deal_cards {
                cards.remove(&deal_card.card);
            }
        }

        if let &Some(ref deal) = &game_status.my_in_progress_deal {
            for deal_card in &deal.deal_cards {
                cards.remove(&deal_card.card);
            }
        }

        for card in &game_status.my_current_hand {
            cards.remove(&card);
        }

        cards
    }

    fn dealt_cards(game_status: &GameStatus) -> BTreeSet<Card> {
        game_status.my_in_progress_deal.as_ref()
            .map(|deal| deal.deal_cards.iter().map(|deal_card| deal_card.card).collect())
            .unwrap_or_default()
    }

    // FIXME: needs to take into account card that will be played!
    fn potential_points(card: &Card, game_status: &GameStatus) -> i32 {
        if Self::can_win_deal(card, game_status) {
            let cards = Self::remaining_cards(game_status);

            let dealt_points: i32 = Self::dealt_cards(game_status).iter()
                .map(|other| Self::card_penalty(other, game_status))
                .sum();

            let suit_points: i32 = cards.iter()
                .filter(|other| other.suit == card.suit)
                .filter(|other| other.rank < card.rank)
                .map(|other| Self::card_penalty(other, game_status))
                .sum();

            let other_points: i32 = cards.iter()
                .filter(|other| other.suit != card.suit)
                .map(|other| Self::card_penalty(other, game_status))
                .sum();

            let suit_win_points = (Self::chance_of_win(card, game_status) * (suit_points as f32)) as i32;
            let other_win_points = (Self::chance_of_later_win(card, game_status) * (other_points as f32)) as i32;

            dealt_points + suit_win_points + other_win_points
        } else {
            0
        }
    }

    fn later_potential_points(card: &Card, game_status: &GameStatus) -> i32 {
        let cards = Self::remaining_cards(game_status);

        let suit_points: i32 = cards.iter()
            .filter(|other| other.suit == card.suit)
            .filter(|other| other.rank < card.rank)
            .map(|other| Self::card_penalty(other, game_status))
            .sum();

        let other_points: i32 = cards.iter()
            .filter(|other| other.suit != card.suit)
            .map(|other| Self::card_penalty(other, game_status))
            .sum();

        suit_points + ((Self::chance_of_later_win(card, game_status) * (other_points as f32)) as i32)
    }

    fn chance_of_later_win(card: &Card, game_status: &GameStatus) -> f32 {
        let cards = Self::remaining_cards(game_status);
        let suit_cards = cards.iter().filter(|other| other.suit == card.suit).collect::<Vec<_>>();
        let will_win_count = suit_cards.iter().filter(|other| other.rank < card.rank).collect::<Vec<_>>().len();
        if suit_cards.is_empty() {
            1.0
        } else {
            (will_win_count as f32) / (suit_cards.len() as f32)
        }
    }

    fn chance_of_win(card: &Card, game_status: &GameStatus) -> f32 {
        if Self::will_win_deal(card, game_status) {
            1.0
        } else {
            if Self::deal_suit(game_status).map(|suit| suit == &card.suit).unwrap_or(true) && Self::plays_left(game_status).len() > 0 {
                let cards = Self::remaining_cards(game_status);
                let suit_cards = cards.iter().filter(|other| other.suit == card.suit).collect::<Vec<_>>();
                let will_win_count = suit_cards.iter().filter(|other| other.rank < card.rank).collect::<Vec<_>>().len();
                if suit_cards.is_empty() {
                    1.0
                } else {
                    (will_win_count as f32) / (suit_cards.len() as f32)
                }
            } else {
                0.0
            }
        }
    }

}

impl CardStrategy for DefensiveCardStrategy {

    fn pass_cards<'a>(&mut self, game_status: &'a GameStatus) -> Vec<&'a Card> {
        game_status.my_initial_hand.iter()
            .map(|card| ((0 - Self::later_potential_points(card, game_status), card), card))
            .collect::<BTreeMap<_, &Card>>()
            .into_iter()
            .map(|kv| kv.1)
            .take(3)
            .collect()
    }

    fn play_card<'a>(&mut self, game_status: &'a GameStatus, player_name: &PlayerName) -> &'a Card {
        let current_suit = game_status.my_in_progress_deal.as_ref().and_then(|deal| deal.suit);
        let mut valid_cards = game_status.my_current_hand.iter()
            .filter(|card| Some(card.suit) == current_suit)
            .collect::<Vec<&Card>>();

        if valid_cards.is_empty() {
            valid_cards.extend(&game_status.my_current_hand);
        }

        let evaluation = valid_cards.into_iter()
            .map(|card| ((Self::score_card(card, game_status), card), card))
            .collect::<BTreeMap<_,&Card>>();

        // println!("Evaluation: {:?}", evaluation);

        evaluation.values()
            .next()
            .expect("No valid cards to play!")
    }

}

#[cfg(test)]
mod tests {
    use super::*;
    use player::PlayerName;
    use card::Card;
    use card::Rank::*;
    use card::Suit::*;
    use game_status::GameStatus;
    use game_status::dto::GameStatusDto;
    use card_strategy::CardStrategy;

    use try_from::TryFrom;
    use error::Error;

    extern crate serde;
    extern crate serde_json;

    use std::fs::File;
    use std::io::Read;

    fn open_scenario(name: &str) -> GameStatus {
        let file_name = format!("samples/scenarios/{}.json", name);
        let mut game_status_file = File::open(file_name).unwrap();
        let mut game_status_string = String::new();
        game_status_file.read_to_string(&mut game_status_string).unwrap();
        let game_status_dto: GameStatusDto = serde_json::from_str(&game_status_string).map_err(Error::from).unwrap();
        GameStatus::try_from(game_status_dto).unwrap()
    }

    fn should_play(name: &str, expected_card: Card) {
        let player_name = PlayerName::new("Derek Williams");
        let game_status = open_scenario(name);
        let card = DefensiveCardStrategy.play_card(&game_status, &player_name).clone();
        assert_eq!(expected_card, card);
    }

    #[test]
    fn normal_1() {
        should_play("normal 1", Jack.of(Diamond));
    }

    #[test]
    fn normal_2() {
        should_play("normal 2", King.of(Club));
    }

    #[test]
    fn normal_3() {
        should_play("normal 3", Six.of(Spade));
    }

    #[test]
    fn should_play_heart_1() {
        should_play("should play heart 1", Seven.of(Heart));
    }

    #[test]
    fn should_play_heart_2() {
        should_play("should play heart 2", Four.of(Heart));
    }

    #[test]
    fn should_play_high_rank_1() {
        should_play("should play high rank 1", King.of(Diamond));
    }

    #[test]
    fn should_play_high_rank_2() {
        should_play("should play high rank 2", Ace.of(Spade));
    }

    // #[test]
    // fn should_play_high_rank_3() { // Should get rid of high risk high rank
    //     should_play("should play high rank 3", Ace.of(Spade));
    // }

    #[test]
    fn should_not_crash_during_card_play() {
        should_play("crashed during card play", Ten.of(Heart));
    }

    #[test]
    fn should_play_low_negative_points_card_1() {
        should_play("should play low negative points card 1", Two.of(Diamond));
    }

    #[test]
    fn should_play_high_negative_points_card_1() {
        should_play("should play high negative points card 1", Ace.of(Diamond));
    }

}<commit_msg>don't be so pessimistic<commit_after>use card_strategy::CardStrategy;

use card::Card;
use card::Suit;
use card::Rank;
use game_status::GameStatus;
use player::PlayerName;

use std::collections::BTreeMap;
use std::collections::BTreeSet;

#[derive(Debug)]
pub struct DefensiveCardStrategy;

/*
    Play 2 of clubs if in hand.
*/
impl DefensiveCardStrategy {
    fn score_card<'a>(card: &'a Card, game_status: &'a GameStatus) -> (i32, i32, i32, i32, i32, i32, i32) {
        let potential_points = Self::potential_points(card, game_status);
        let definite_points = if Self::will_win_deal(card, game_status) {
            potential_points
        } else {
            0
        };
        let later_potential_points = 0 - Self::later_potential_points(card, game_status);
        let card_penalty_to_me = Self::card_penalty_to_me(card, game_status);
        let card_penalty = 0 - Self::card_penalty(card, game_status);
        let trouble = Self::trouble_score(card, game_status);
        let card_rank = 0 - (u32::from(card.rank) as i32);
        (definite_points, potential_points, later_potential_points, card_penalty_to_me, card_penalty, trouble, card_rank)
    }

    fn card_penalty(card: &Card, game_status: &GameStatus) -> i32 {
        game_status.round_parameters.card_points.get(card)
            .map(|penalty| *penalty)
            .unwrap_or_default()
    }

    fn card_penalty_to_me(card: &Card, game_status: &GameStatus) -> i32 {
        if Self::can_win_deal(card, game_status) {
            Self::card_penalty(card, game_status)
        } else {
            0
        }
    }

    fn trouble_score(card: &Card, game_status: &GameStatus) -> i32 {
        if Self::can_win_deal(card, game_status) {
            let trouble = u32::from(card.rank) as i32;
            if Self::card_penalty_to_me(card, game_status) < 0 {
                0 - trouble
            } else {
                if Self::will_win_deal(card, game_status) {
                    u32::from(Rank::Ace) as i32
                } else {
                    trouble
                }
            }
        } else {
            0
        }
    }

    fn can_win_deal(card: &Card, game_status: &GameStatus) -> bool {
        game_status.my_in_progress_deal.as_ref().and_then(|deal|
            deal.suit.and_then(|suit|
                deal.deal_cards.iter()
                    .map(|deal_card| deal_card.card)
                    .filter(|card| card.suit == suit)
                    .max()
                    .map(|winning_card| card.suit == suit && card.rank > winning_card.rank))).unwrap_or(true)
    }

    fn will_win_deal(card: &Card, game_status: &GameStatus) -> bool {
        Self::can_win_deal(card, game_status) && (Self::plays_left(game_status).len() == 0 ||
            Self::deal_suit(game_status)
                .and_then(|suit| Self::remaining_cards(game_status).iter()
                    .filter(|card| card.suit == *suit)
                    .max()
                    .map(|winning_card| card.rank > winning_card.rank)).unwrap_or(true))
    }

    fn deal_suit(game_status: &GameStatus) -> Option<&Suit> {
        game_status.my_in_progress_deal.as_ref().and_then(|deal| deal.suit.as_ref())
    }

    fn plays_left(game_status: &GameStatus) -> BTreeSet<&PlayerName> {
        let mut players: BTreeSet<&PlayerName> = game_status.my_game_players.iter()
            .filter(|player| !player.has_turn)
            .map(|player| &player.team_name)
            .collect();

        if let &Some(ref deal) = &game_status.my_in_progress_deal {
            for deal_card in &deal.deal_cards {
                players.remove(&deal_card.player_name);
            }
        }

        players
    }

    fn remaining_cards(game_status: &GameStatus) -> BTreeSet<Card> {
        let mut cards = Card::all();

        for deal in &game_status.my_game_deals {
            for deal_card in &deal.deal_cards {
                cards.remove(&deal_card.card);
            }
        }

        if let &Some(ref deal) = &game_status.my_in_progress_deal {
            for deal_card in &deal.deal_cards {
                cards.remove(&deal_card.card);
            }
        }

        for card in &game_status.my_current_hand {
            cards.remove(&card);
        }

        cards
    }

    fn dealt_cards(game_status: &GameStatus) -> BTreeSet<Card> {
        game_status.my_in_progress_deal.as_ref()
            .map(|deal| deal.deal_cards.iter().map(|deal_card| deal_card.card).collect())
            .unwrap_or_default()
    }

    fn potential_points(card: &Card, game_status: &GameStatus) -> i32 {
        if Self::can_win_deal(card, game_status) {
            let cards = Self::remaining_cards(game_status);

            let card_points = Self::card_penalty(card, game_status);

            let dealt_points: i32 = Self::dealt_cards(game_status).iter()
                .map(|other| Self::card_penalty(other, game_status))
                .sum();

            let suit_points: i32 = cards.iter()
                .filter(|other| other.suit == card.suit)
                .filter(|other| other.rank < card.rank)
                .map(|other| Self::card_penalty(other, game_status))
                .sum();

            let other_points: i32 = cards.iter()
                .filter(|other| other.suit != card.suit)
                .map(|other| Self::card_penalty(other, game_status))
                .sum();

            let number_of_suit = cards.iter().filter(|other| other.suit == card.suit).map(|_| 1).sum::<u32>();

            let suit_win_points = (Self::chance_of_win(card, game_status) * (suit_points as f32)) as i32;
            let other_win_points = if number_of_suit < 7 {
                (Self::chance_of_later_win(card, game_status) * (other_points as f32)) as i32
            } else {
                0
            };

            card_points + dealt_points + suit_win_points + other_win_points
        } else {
            0
        }
    }

    fn later_potential_points(card: &Card, game_status: &GameStatus) -> i32 {
        let cards = Self::remaining_cards(game_status);

        let card_points = Self::card_penalty(card, game_status);

        let suit_points: i32 = cards.iter()
            .filter(|other| other.suit == card.suit)
            .filter(|other| other.rank < card.rank)
            .map(|other| Self::card_penalty(other, game_status))
            .sum();

        let other_points: i32 = cards.iter()
            .filter(|other| other.suit != card.suit)
            .map(|other| Self::card_penalty(other, game_status))
            .sum();

        let other_win_points = (Self::chance_of_later_win(card, game_status) * (other_points as f32)) as i32;

        card_points + suit_points + other_win_points
    }

    fn chance_of_later_win(card: &Card, game_status: &GameStatus) -> f32 {
        let cards = Self::remaining_cards(game_status);
        let suit_cards = cards.iter().filter(|other| other.suit == card.suit).collect::<Vec<_>>();
        let will_win_count = suit_cards.iter().filter(|other| other.rank < card.rank).collect::<Vec<_>>().len();
        if suit_cards.is_empty() {
            1.0
        } else {
            (will_win_count as f32) / (suit_cards.len() as f32)
        }
    }

    fn chance_of_win(card: &Card, game_status: &GameStatus) -> f32 {
        if Self::will_win_deal(card, game_status) {
            1.0
        } else {
            if Self::deal_suit(game_status).map(|suit| suit == &card.suit).unwrap_or(true) && Self::plays_left(game_status).len() > 0 {
                let cards = Self::remaining_cards(game_status);
                let suit_cards = cards.iter().filter(|other| other.suit == card.suit).collect::<Vec<_>>();
                let will_win_count = suit_cards.iter().filter(|other| other.rank < card.rank).collect::<Vec<_>>().len();
                if suit_cards.is_empty() {
                    1.0
                } else {
                    (will_win_count as f32) / (suit_cards.len() as f32)
                }
            } else {
                0.0
            }
        }
    }

}

impl CardStrategy for DefensiveCardStrategy {

    fn pass_cards<'a>(&mut self, game_status: &'a GameStatus) -> Vec<&'a Card> {
        game_status.my_initial_hand.iter()
            .map(|card| ((0 - Self::later_potential_points(card, game_status), card), card))
            .collect::<BTreeMap<_, &Card>>()
            .into_iter()
            .map(|kv| kv.1)
            .take(3)
            .collect()
    }

    fn play_card<'a>(&mut self, game_status: &'a GameStatus, player_name: &PlayerName) -> &'a Card {
        let current_suit = game_status.my_in_progress_deal.as_ref().and_then(|deal| deal.suit);
        let mut valid_cards = game_status.my_current_hand.iter()
            .filter(|card| Some(card.suit) == current_suit)
            .collect::<Vec<&Card>>();

        if valid_cards.is_empty() {
            valid_cards.extend(&game_status.my_current_hand);
        }

        let evaluation = valid_cards.into_iter()
            .map(|card| ((Self::score_card(card, game_status), card), card))
            .collect::<BTreeMap<_,&Card>>();

        // println!("Remaining: {}", Self::remaining_cards(game_status).iter().map(|card| format!("{}", card)).collect::<Vec<_>>().join(", "));
        // for item in &evaluation {
        //     println!("{}: {:?}", item.1, (item.0).0);
        // }

        evaluation.values()
            .next()
            .expect("No valid cards to play!")
    }

}

#[cfg(test)]
mod tests {
    use super::*;
    use player::PlayerName;
    use card::Card;
    use card::Rank::*;
    use card::Suit::*;
    use game_status::GameStatus;
    use game_status::dto::GameStatusDto;
    use card_strategy::CardStrategy;

    use try_from::TryFrom;
    use error::Error;

    extern crate serde;
    extern crate serde_json;

    use std::fs::File;
    use std::io::Read;

    fn open_scenario(name: &str) -> GameStatus {
        let file_name = format!("samples/scenarios/{}.json", name);
        let mut game_status_file = File::open(file_name).unwrap();
        let mut game_status_string = String::new();
        game_status_file.read_to_string(&mut game_status_string).unwrap();
        let game_status_dto: GameStatusDto = serde_json::from_str(&game_status_string).map_err(Error::from).unwrap();
        GameStatus::try_from(game_status_dto).unwrap()
    }

    fn should_play(name: &str, expected_card: Card) {
        let player_name = PlayerName::new("Derek Williams");
        let game_status = open_scenario(name);
        let card = DefensiveCardStrategy.play_card(&game_status, &player_name).clone();
        assert_eq!(expected_card, card);
    }

    #[test]
    fn normal_1() {
        should_play("normal 1", Jack.of(Diamond));
    }

    #[test]
    fn normal_2() {
        should_play("normal 2", King.of(Club));
    }

    #[test]
    fn normal_3() {
        should_play("normal 3", Six.of(Spade));
    }

    #[test]
    fn should_play_heart_1() {
        should_play("should play heart 1", Seven.of(Heart));
    }

    #[test]
    fn should_play_heart_2() {
        should_play("should play heart 2", Four.of(Heart));
    }

    #[test]
    fn should_play_high_rank_1() {
        should_play("should play high rank 1", King.of(Diamond));
    }

    #[test]
    fn should_play_high_rank_2() {
        should_play("should play high rank 2", Ace.of(Spade));
    }

    // #[test]
    // fn should_play_high_rank_3() { // Should get rid of high risk high rank
    //     should_play("should play high rank 3", Ace.of(Spade));
    // }

    #[test]
    fn should_not_crash_during_card_play() {
        should_play("crashed during card play", Ten.of(Heart));
    }

    // #[test]
    // fn should_play_low_negative_points_card_1() {
    //     should_play("should play low negative points card 1", Two.of(Diamond));
    // }

    #[test]
    fn should_play_high_negative_points_card_1() {
        should_play("should play high negative points card 1", Ace.of(Diamond));
    }

}<|endoftext|>


<commit_before>//
// FlightVars
// Copyright (c) 2015, 2016 Alvaro Polo
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

use std::io;
use std::os::unix::io::{IntoRawFd, RawFd};
use std::rc::Rc;

use libc;

pub fn split<T: IntoRawFd + io::Read + io::Write>(device: T) -> (SplitRead, SplitWrite) {
    let fd = device.into_raw_fd();
    let socket = Rc::new(SharedFd::new(fd));
    (SplitRead::new(&socket), SplitWrite::new(&socket))
}

#[link(name = "c")]
extern {
    static mut errno: libc::c_int;
}

struct SharedFd {
    fd: RawFd
}

impl SharedFd {
    fn new(fd: RawFd) -> SharedFd { SharedFd { fd: fd }}
}

impl Drop for SharedFd {
    fn drop(&mut self) {
        unsafe { libc::close(self.fd); }
    }
}

pub struct SplitRead {
    socket: Rc<SharedFd>
}

impl SplitRead {
    fn new(socket: &Rc<SharedFd>) -> SplitRead { SplitRead { socket: socket.clone() }}
}

impl io::Read for SplitRead {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        unsafe {
            let c_buf = buf as *mut [u8] as *mut libc::c_void;
            let bytes_read = libc::read(self.socket.fd, c_buf, buf.len() as u64);
            if bytes_read >= 0 { Ok(bytes_read as usize) }
            else {
                Err(io::Error::new(
                    io::ErrorKind::Other,
                    format!("cannot read from device (errno {})", errno)))
            }
        }
    }
}

pub struct SplitWrite {
    socket: Rc<SharedFd>
}

impl SplitWrite {
    fn new(socket: &Rc<SharedFd>) -> SplitWrite { SplitWrite { socket: socket.clone() }}
}

impl io::Write for SplitWrite {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        unsafe {
            let c_buf = buf as *const [u8] as *const libc::c_void;
            let bytes_written = libc::write(self.socket.fd, c_buf, buf.len() as u64);
            if bytes_written >= 0 { Ok(bytes_written as usize) }
            else { Err(io::Error::new(
                io::ErrorKind::Other,
                format!("cannot write to device (errno {})", errno)))
            }
        }
    }

    fn flush(&mut self) -> io::Result<()> {
        unsafe {
            if libc::fsync(self.socket.fd) == 0 { Ok(()) }
            else { Err(io::Error::new(
                io::ErrorKind::Other,
                format!("cannot sync to device (errno {})", errno)))
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use std::io::*;
    use std::net::TcpStream;

    use super::*;

    #[test]
    fn should_read_and_write_after_split() {
        let stream = TcpStream::connect("www.google.com:80").unwrap();
        let (mut input, mut output) = split(stream);
        output.write_all(b"GET / HTTP/1.1\n").unwrap();
        output.write_all(b"Host: www.google.com\n").unwrap();
        output.write_all(b"Connetion: close\n\n").unwrap();
        let mut rep = [0; 4];
        assert_eq!(input.read(&mut rep).unwrap(), 4);
        assert_eq!(&rep[..], b"HTTP");
    }

    #[test]
    fn should_read_after_write_is_drop() {
        let stream = TcpStream::connect("www.google.com:80").unwrap();
        let (mut input, output) = split(stream);
        {
            let mut output = output;
            output.write_all(b"GET / HTTP/1.1\n").unwrap();
            output.write_all(b"Host: www.google.com\n").unwrap();
            output.write_all(b"Connetion: close\n\n").unwrap();
        }
        let mut rep = [0; 4];
        assert_eq!(input.read(&mut rep).unwrap(), 4);
        assert_eq!(&rep[..], b"HTTP");
    }
}
<commit_msg>Support `Send` trait on IO device splitting<commit_after>//
// FlightVars
// Copyright (c) 2015, 2016 Alvaro Polo
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

use std::io;
use std::os::unix::io::{IntoRawFd, RawFd};
use std::sync::Arc;

use libc;

pub fn split<T: IntoRawFd + io::Read + io::Write>(device: T) -> (SplitRead, SplitWrite) {
    let fd = device.into_raw_fd();
    let socket = Arc::new(SharedFd::new(fd));
    (SplitRead::new(&socket), SplitWrite::new(&socket))
}

#[link(name = "c")]
extern {
    static mut errno: libc::c_int;
}

struct SharedFd {
    fd: RawFd
}

impl SharedFd {
    fn new(fd: RawFd) -> SharedFd { SharedFd { fd: fd }}
}

impl Drop for SharedFd {
    fn drop(&mut self) {
        unsafe { libc::close(self.fd); }
    }
}

pub struct SplitRead {
    socket: Arc<SharedFd>
}

impl SplitRead {
    fn new(socket: &Arc<SharedFd>) -> SplitRead { SplitRead { socket: socket.clone() }}
}

impl io::Read for SplitRead {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        unsafe {
            let c_buf = buf as *mut [u8] as *mut libc::c_void;
            let bytes_read = libc::read(self.socket.fd, c_buf, buf.len() as u64);
            if bytes_read >= 0 { Ok(bytes_read as usize) }
            else {
                Err(io::Error::new(
                    io::ErrorKind::Other,
                    format!("cannot read from device (errno {})", errno)))
            }
        }
    }
}

pub struct SplitWrite {
    socket: Arc<SharedFd>
}

impl SplitWrite {
    fn new(socket: &Arc<SharedFd>) -> SplitWrite { SplitWrite { socket: socket.clone() }}
}

impl io::Write for SplitWrite {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        unsafe {
            let c_buf = buf as *const [u8] as *const libc::c_void;
            let bytes_written = libc::write(self.socket.fd, c_buf, buf.len() as u64);
            if bytes_written >= 0 { Ok(bytes_written as usize) }
            else { Err(io::Error::new(
                io::ErrorKind::Other,
                format!("cannot write to device (errno {})", errno)))
            }
        }
    }

    fn flush(&mut self) -> io::Result<()> {
        unsafe {
            if libc::fsync(self.socket.fd) == 0 { Ok(()) }
            else { Err(io::Error::new(
                io::ErrorKind::Other,
                format!("cannot sync to device (errno {})", errno)))
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use std::io::*;
    use std::net::TcpStream;
    use std::thread;

    use super::*;

    #[test]
    fn should_read_and_write_after_split() {
        let stream = TcpStream::connect("www.google.com:80").unwrap();
        let (mut input, mut output) = split(stream);
        output.write_all(b"GET / HTTP/1.1\n").unwrap();
        output.write_all(b"Host: www.google.com\n").unwrap();
        output.write_all(b"Connetion: close\n\n").unwrap();
        let mut rep = [0; 4];
        assert_eq!(input.read(&mut rep).unwrap(), 4);
        assert_eq!(&rep[..], b"HTTP");
    }

    #[test]
    fn should_read_after_write_is_drop() {
        let stream = TcpStream::connect("www.google.com:80").unwrap();
        let (mut input, output) = split(stream);
        {
            let mut output = output;
            output.write_all(b"GET / HTTP/1.1\n").unwrap();
            output.write_all(b"Host: www.google.com\n").unwrap();
            output.write_all(b"Connetion: close\n\n").unwrap();
        }
        let mut rep = [0; 4];
        assert_eq!(input.read(&mut rep).unwrap(), 4);
        assert_eq!(&rep[..], b"HTTP");
    }

    #[test]
    fn should_read_and_write_after_split_from_different_threads() {
        let stream = TcpStream::connect("www.google.com:80").unwrap();
        let (mut input, mut output) = split(stream);
        let th1 = thread::spawn(move || {
            output.write_all(b"GET / HTTP/1.1\n").unwrap();
            output.write_all(b"Host: www.google.com\n").unwrap();
            output.write_all(b"Connetion: close\n\n").unwrap();
        });
        let th2 = thread::spawn(move || {
            let mut rep = [0; 4];
            assert_eq!(input.read(&mut rep).unwrap(), 4);
            assert_eq!(&rep[..], b"HTTP");
        });
        th1.join().unwrap();
        th2.join().unwrap();
    }

}
<|endoftext|>


<commit_before>use std::cmp::min;

use cairo::{self, ImageSurface};
use markup5ever::{expanded_name, local_name, namespace_url, ns, QualName};

use crate::drawing_ctx::DrawingCtx;
use crate::error::*;
use crate::node::{NodeResult, NodeTrait, NodeType, RsvgNode};
use crate::number_list::{NumberList, NumberListError, NumberListLength};
use crate::parsers;
use crate::property_bag::PropertyBag;
use crate::surface_utils::{
    iterators::Pixels,
    shared_surface::SharedImageSurface,
    ImageSurfaceDataExt,
    Pixel,
};
use crate::util::clamp;

use super::context::{FilterContext, FilterOutput, FilterResult};
use super::{FilterEffect, FilterError, PrimitiveWithInput};

/// The `feComponentTransfer` filter primitive.
pub struct FeComponentTransfer {
    base: PrimitiveWithInput,
}

impl Default for FeComponentTransfer {
    /// Constructs a new `ComponentTransfer` with empty properties.
    #[inline]
    fn default() -> FeComponentTransfer {
        FeComponentTransfer {
            base: PrimitiveWithInput::new::<Self>(),
        }
    }
}

impl NodeTrait for FeComponentTransfer {
    impl_node_as_filter_effect!();

    #[inline]
    fn set_atts(&mut self, parent: Option<&RsvgNode>, pbag: &PropertyBag<'_>) -> NodeResult {
        self.base.set_atts(parent, pbag)
    }
}

/// Pixel components that can be influenced by `feComponentTransfer`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Channel {
    R,
    G,
    B,
    A,
}

/// Component transfer function types.
#[derive(Debug, Clone, Copy)]
enum FunctionType {
    Identity,
    Table,
    Discrete,
    Linear,
    Gamma,
}

impl FunctionType {
    fn parse(attr: QualName, s: &str) -> Result<Self, NodeError> {
        match s {
            "identity" => Ok(FunctionType::Identity),
            "table" => Ok(FunctionType::Table),
            "discrete" => Ok(FunctionType::Discrete),
            "linear" => Ok(FunctionType::Linear),
            "gamma" => Ok(FunctionType::Gamma),
            _ => Err(NodeError::parse_error(attr, "invalid value")),
        }
    }
}

/// The compute function parameters.
struct FunctionParameters<'a> {
    table_values: &'a Vec<f64>,
    slope: f64,
    intercept: f64,
    amplitude: f64,
    exponent: f64,
    offset: f64,
}

/// The compute function type.
type Function = fn(&FunctionParameters<'_>, f64) -> f64;

/// The identity component transfer function.
fn identity(_: &FunctionParameters<'_>, value: f64) -> f64 {
    value
}

/// The table component transfer function.
fn table(params: &FunctionParameters<'_>, value: f64) -> f64 {
    let n = params.table_values.len() - 1;
    let k = (value * (n as f64)).floor() as usize;

    let k = min(k, n); // Just in case.

    if k == n {
        return params.table_values[k];
    }

    let vk = params.table_values[k];
    let vk1 = params.table_values[k + 1];
    let k = k as f64;
    let n = n as f64;

    vk + (value - k / n) * n * (vk1 - vk)
}

/// The discrete component transfer function.
fn discrete(params: &FunctionParameters<'_>, value: f64) -> f64 {
    let n = params.table_values.len();
    let k = (value * (n as f64)).floor() as usize;

    params.table_values[min(k, n - 1)]
}

/// The linear component transfer function.
fn linear(params: &FunctionParameters<'_>, value: f64) -> f64 {
    params.slope * value + params.intercept
}

/// The gamma component transfer function.
fn gamma(params: &FunctionParameters<'_>, value: f64) -> f64 {
    params.amplitude * value.powf(params.exponent) + params.offset
}

trait FeComponentTransferFunc {
    /// Returns the component transfer function.
    fn function(&self) -> Function;

    /// Returns the component transfer function parameters.
    fn function_parameters(&self) -> FunctionParameters<'_>;

    /// Returns the channel.
    fn channel(&self) -> Channel;
}

macro_rules! func_x {
    ($func_name:ident, $channel:expr) => {
        pub struct $func_name {
            channel: Channel,
            function_type: FunctionType,
            table_values: Vec<f64>,
            slope: f64,
            intercept: f64,
            amplitude: f64,
            exponent: f64,
            offset: f64,
        }

        impl Default for $func_name {
            #[inline]
            fn default() -> Self {
                Self {
                    channel: $channel,
                    function_type: FunctionType::Identity,
                    table_values: Vec::new(),
                    slope: 1.0,
                    intercept: 0.0,
                    amplitude: 1.0,
                    exponent: 1.0,
                    offset: 0.0,
                }
            }
        }

        impl FeComponentTransferFunc for $func_name {
            #[inline]
            fn function_parameters(&self) -> FunctionParameters<'_> {
                FunctionParameters {
                    table_values: &self.table_values,
                    slope: self.slope,
                    intercept: self.intercept,
                    amplitude: self.amplitude,
                    exponent: self.exponent,
                    offset: self.offset,
                }
            }

            #[inline]
            fn function(&self) -> Function {
                match self.function_type {
                    FunctionType::Identity => identity,
                    FunctionType::Table => table,
                    FunctionType::Discrete => discrete,
                    FunctionType::Linear => linear,
                    FunctionType::Gamma => gamma,
                }
            }

            #[inline]
            fn channel(&self) -> Channel {
                self.channel
            }
        }

        impl NodeTrait for $func_name {
            #[inline]
            fn set_atts(
                &mut self,
                _parent: Option<&RsvgNode>,
                pbag: &PropertyBag<'_>,
            ) -> NodeResult {
                for (attr, value) in pbag.iter() {
                    match attr.expanded() {
                        expanded_name!(svg "type") => {
                            self.function_type = FunctionType::parse(attr, value)?
                        }
                        expanded_name!(svg "tableValues") => {
                            let NumberList(v) =
                                NumberList::parse_str(value, NumberListLength::Unbounded).map_err(
                                    |err| {
                                        if let NumberListError::Parse(err) = err {
                                            ValueErrorKind::parse_error(&err)
                                        } else {
                                            panic!("unexpected number list error");
                                        }
                                    },
                                ).attribute(attr)?;
                            self.table_values = v;
                        }
                        expanded_name!(svg "slope") => {
                            self.slope = parsers::number(value).attribute(attr)?
                        }
                        expanded_name!(svg "intercept") => {
                            self.intercept = parsers::number(value).attribute(attr)?
                        }
                        expanded_name!(svg "amplitude") => {
                            self.amplitude = parsers::number(value).attribute(attr)?
                        }
                        expanded_name!(svg "exponent") => {
                            self.exponent = parsers::number(value).attribute(attr)?
                        }
                        expanded_name!(svg "offset") => {
                            self.offset = parsers::number(value).attribute(attr)?
                        }
                        _ => (),
                    }
                }

                // The table function type with empty table_values is considered
                // an identity function.
                match self.function_type {
                    FunctionType::Table | FunctionType::Discrete => {
                        if self.table_values.is_empty() {
                            self.function_type = FunctionType::Identity;
                        }
                    }
                    _ => (),
                }

                Ok(())
            }
        }
    };
}

// The `<feFuncR>` element
func_x!(FeFuncR, Channel::R);

// The `<feFuncG>` element
func_x!(FeFuncG, Channel::G);

// The `<feFuncB>` element
func_x!(FeFuncB, Channel::B);

// The `<feFuncA>` element
func_x!(FeFuncA, Channel::A);

macro_rules! func_or_default {
    ($func_node:ident, $func_type:ty, $func_data:ident, $func_default:ident) => {
        match $func_node {
            Some(ref f) => {
                $func_data = f.borrow();
                $func_data.get_impl::<$func_type>()
            }
            _ => &$func_default,
        };
    };
}

impl FilterEffect for FeComponentTransfer {
    fn render(
        &self,
        node: &RsvgNode,
        ctx: &FilterContext,
        draw_ctx: &mut DrawingCtx,
    ) -> Result<FilterResult, FilterError> {
        let input = self.base.get_input(ctx, draw_ctx)?;
        let bounds = self
            .base
            .get_bounds(ctx)
            .add_input(&input)
            .into_irect(draw_ctx);

        // Create the output surface.
        let mut output_surface = ImageSurface::create(
            cairo::Format::ARgb32,
            ctx.source_graphic().width(),
            ctx.source_graphic().height(),
        )?;

        // Get a node for every pixel component.
        fn get_node<F>(node: &RsvgNode, node_type: NodeType, channel: Channel) -> Option<RsvgNode>
        where
            F: FeComponentTransferFunc + NodeTrait,
        {
            node.children()
                .rev()
                .filter(|c| c.borrow().get_type() == node_type)
                .find(|c| c.borrow().get_impl::<F>().channel() == channel)
        };

        let func_r_node = get_node::<FeFuncR>(node, NodeType::FeFuncR, Channel::R);
        let func_g_node = get_node::<FeFuncG>(node, NodeType::FeFuncG, Channel::G);
        let func_b_node = get_node::<FeFuncB>(node, NodeType::FeFuncB, Channel::B);
        let func_a_node = get_node::<FeFuncA>(node, NodeType::FeFuncA, Channel::A);

        for node in [&func_r_node, &func_g_node, &func_b_node, &func_a_node]
            .iter()
            .filter_map(|x| x.as_ref())
        {
            if node.borrow().is_in_error() {
                return Err(FilterError::ChildNodeInError);
            }
        }

        // These are the default funcs that perform an identity transformation.
        let func_r_default = FeFuncR::default();
        let func_g_default = FeFuncG::default();
        let func_b_default = FeFuncB::default();
        let func_a_default = FeFuncA::default();

        // We need to tell the borrow checker that these live long enough
        let func_r_data;
        let func_g_data;
        let func_b_data;
        let func_a_data;

        let func_r = func_or_default!(func_r_node, FeFuncR, func_r_data, func_r_default);
        let func_g = func_or_default!(func_g_node, FeFuncG, func_g_data, func_g_default);
        let func_b = func_or_default!(func_b_node, FeFuncB, func_b_data, func_b_default);
        let func_a = func_or_default!(func_a_node, FeFuncA, func_a_data, func_a_default);

        #[inline]
        fn compute_func<'a, F>(func: &'a F) -> impl Fn(u8, f64, f64) -> u8 + 'a
        where
            F: FeComponentTransferFunc,
        {
            let compute = func.function();
            let params = func.function_parameters();

            move |value, alpha, new_alpha| {
                let value = f64::from(value) / 255f64;

                let unpremultiplied = if alpha == 0f64 { 0f64 } else { value / alpha };

                let new_value = compute(&params, unpremultiplied);
                let new_value = clamp(new_value, 0f64, 1f64);

                ((new_value * new_alpha * 255f64) + 0.5) as u8
            }
        }

        let compute_r = compute_func::<FeFuncR>(&func_r);
        let compute_g = compute_func::<FeFuncG>(&func_g);
        let compute_b = compute_func::<FeFuncB>(&func_b);

        // Alpha gets special handling since everything else depends on it.
        let compute_a = func_a.function();
        let params_a = func_a.function_parameters();
        let compute_a = |alpha| compute_a(&params_a, alpha);

        // Do the actual processing.
        let output_stride = output_surface.get_stride() as usize;
        {
            let mut output_data = output_surface.get_data().unwrap();

            for (x, y, pixel) in Pixels::new(input.surface(), bounds) {
                let alpha = f64::from(pixel.a) / 255f64;
                let new_alpha = compute_a(alpha);

                let output_pixel = Pixel {
                    r: compute_r(pixel.r, alpha, new_alpha),
                    g: compute_g(pixel.g, alpha, new_alpha),
                    b: compute_b(pixel.b, alpha, new_alpha),
                    a: ((new_alpha * 255f64) + 0.5) as u8,
                };

                output_data.set_pixel(output_stride, output_pixel, x, y);
            }
        }

        Ok(FilterResult {
            name: self.base.result.clone(),
            output: FilterOutput {
                surface: SharedImageSurface::new(output_surface, input.surface().surface_type())?,
                bounds,
            },
        })
    }

    fn is_affected_by_color_interpolation_filters(&self) -> bool {
        true
    }
}
<commit_msg>component_transfer.rs: Don't create a NodeError directly<commit_after>use std::cmp::min;

use cairo::{self, ImageSurface};
use markup5ever::{expanded_name, local_name, namespace_url, ns, QualName};

use crate::drawing_ctx::DrawingCtx;
use crate::error::*;
use crate::node::{NodeResult, NodeTrait, NodeType, RsvgNode};
use crate::number_list::{NumberList, NumberListError, NumberListLength};
use crate::parsers;
use crate::property_bag::PropertyBag;
use crate::surface_utils::{
    iterators::Pixels,
    shared_surface::SharedImageSurface,
    ImageSurfaceDataExt,
    Pixel,
};
use crate::util::clamp;

use super::context::{FilterContext, FilterOutput, FilterResult};
use super::{FilterEffect, FilterError, PrimitiveWithInput};

/// The `feComponentTransfer` filter primitive.
pub struct FeComponentTransfer {
    base: PrimitiveWithInput,
}

impl Default for FeComponentTransfer {
    /// Constructs a new `ComponentTransfer` with empty properties.
    #[inline]
    fn default() -> FeComponentTransfer {
        FeComponentTransfer {
            base: PrimitiveWithInput::new::<Self>(),
        }
    }
}

impl NodeTrait for FeComponentTransfer {
    impl_node_as_filter_effect!();

    #[inline]
    fn set_atts(&mut self, parent: Option<&RsvgNode>, pbag: &PropertyBag<'_>) -> NodeResult {
        self.base.set_atts(parent, pbag)
    }
}

/// Pixel components that can be influenced by `feComponentTransfer`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Channel {
    R,
    G,
    B,
    A,
}

/// Component transfer function types.
#[derive(Debug, Clone, Copy)]
enum FunctionType {
    Identity,
    Table,
    Discrete,
    Linear,
    Gamma,
}

impl FunctionType {
    fn parse(attr: QualName, s: &str) -> Result<Self, NodeError> {
        match s {
            "identity" => Ok(FunctionType::Identity),
            "table" => Ok(FunctionType::Table),
            "discrete" => Ok(FunctionType::Discrete),
            "linear" => Ok(FunctionType::Linear),
            "gamma" => Ok(FunctionType::Gamma),
            _ => Err(ValueErrorKind::parse_error("invalid value")).attribute(attr),
        }
    }
}

/// The compute function parameters.
struct FunctionParameters<'a> {
    table_values: &'a Vec<f64>,
    slope: f64,
    intercept: f64,
    amplitude: f64,
    exponent: f64,
    offset: f64,
}

/// The compute function type.
type Function = fn(&FunctionParameters<'_>, f64) -> f64;

/// The identity component transfer function.
fn identity(_: &FunctionParameters<'_>, value: f64) -> f64 {
    value
}

/// The table component transfer function.
fn table(params: &FunctionParameters<'_>, value: f64) -> f64 {
    let n = params.table_values.len() - 1;
    let k = (value * (n as f64)).floor() as usize;

    let k = min(k, n); // Just in case.

    if k == n {
        return params.table_values[k];
    }

    let vk = params.table_values[k];
    let vk1 = params.table_values[k + 1];
    let k = k as f64;
    let n = n as f64;

    vk + (value - k / n) * n * (vk1 - vk)
}

/// The discrete component transfer function.
fn discrete(params: &FunctionParameters<'_>, value: f64) -> f64 {
    let n = params.table_values.len();
    let k = (value * (n as f64)).floor() as usize;

    params.table_values[min(k, n - 1)]
}

/// The linear component transfer function.
fn linear(params: &FunctionParameters<'_>, value: f64) -> f64 {
    params.slope * value + params.intercept
}

/// The gamma component transfer function.
fn gamma(params: &FunctionParameters<'_>, value: f64) -> f64 {
    params.amplitude * value.powf(params.exponent) + params.offset
}

trait FeComponentTransferFunc {
    /// Returns the component transfer function.
    fn function(&self) -> Function;

    /// Returns the component transfer function parameters.
    fn function_parameters(&self) -> FunctionParameters<'_>;

    /// Returns the channel.
    fn channel(&self) -> Channel;
}

macro_rules! func_x {
    ($func_name:ident, $channel:expr) => {
        pub struct $func_name {
            channel: Channel,
            function_type: FunctionType,
            table_values: Vec<f64>,
            slope: f64,
            intercept: f64,
            amplitude: f64,
            exponent: f64,
            offset: f64,
        }

        impl Default for $func_name {
            #[inline]
            fn default() -> Self {
                Self {
                    channel: $channel,
                    function_type: FunctionType::Identity,
                    table_values: Vec::new(),
                    slope: 1.0,
                    intercept: 0.0,
                    amplitude: 1.0,
                    exponent: 1.0,
                    offset: 0.0,
                }
            }
        }

        impl FeComponentTransferFunc for $func_name {
            #[inline]
            fn function_parameters(&self) -> FunctionParameters<'_> {
                FunctionParameters {
                    table_values: &self.table_values,
                    slope: self.slope,
                    intercept: self.intercept,
                    amplitude: self.amplitude,
                    exponent: self.exponent,
                    offset: self.offset,
                }
            }

            #[inline]
            fn function(&self) -> Function {
                match self.function_type {
                    FunctionType::Identity => identity,
                    FunctionType::Table => table,
                    FunctionType::Discrete => discrete,
                    FunctionType::Linear => linear,
                    FunctionType::Gamma => gamma,
                }
            }

            #[inline]
            fn channel(&self) -> Channel {
                self.channel
            }
        }

        impl NodeTrait for $func_name {
            #[inline]
            fn set_atts(
                &mut self,
                _parent: Option<&RsvgNode>,
                pbag: &PropertyBag<'_>,
            ) -> NodeResult {
                for (attr, value) in pbag.iter() {
                    match attr.expanded() {
                        expanded_name!(svg "type") => {
                            self.function_type = FunctionType::parse(attr, value)?
                        }
                        expanded_name!(svg "tableValues") => {
                            let NumberList(v) =
                                NumberList::parse_str(value, NumberListLength::Unbounded).map_err(
                                    |err| {
                                        if let NumberListError::Parse(err) = err {
                                            ValueErrorKind::parse_error(&err)
                                        } else {
                                            panic!("unexpected number list error");
                                        }
                                    },
                                ).attribute(attr)?;
                            self.table_values = v;
                        }
                        expanded_name!(svg "slope") => {
                            self.slope = parsers::number(value).attribute(attr)?
                        }
                        expanded_name!(svg "intercept") => {
                            self.intercept = parsers::number(value).attribute(attr)?
                        }
                        expanded_name!(svg "amplitude") => {
                            self.amplitude = parsers::number(value).attribute(attr)?
                        }
                        expanded_name!(svg "exponent") => {
                            self.exponent = parsers::number(value).attribute(attr)?
                        }
                        expanded_name!(svg "offset") => {
                            self.offset = parsers::number(value).attribute(attr)?
                        }
                        _ => (),
                    }
                }

                // The table function type with empty table_values is considered
                // an identity function.
                match self.function_type {
                    FunctionType::Table | FunctionType::Discrete => {
                        if self.table_values.is_empty() {
                            self.function_type = FunctionType::Identity;
                        }
                    }
                    _ => (),
                }

                Ok(())
            }
        }
    };
}

// The `<feFuncR>` element
func_x!(FeFuncR, Channel::R);

// The `<feFuncG>` element
func_x!(FeFuncG, Channel::G);

// The `<feFuncB>` element
func_x!(FeFuncB, Channel::B);

// The `<feFuncA>` element
func_x!(FeFuncA, Channel::A);

macro_rules! func_or_default {
    ($func_node:ident, $func_type:ty, $func_data:ident, $func_default:ident) => {
        match $func_node {
            Some(ref f) => {
                $func_data = f.borrow();
                $func_data.get_impl::<$func_type>()
            }
            _ => &$func_default,
        };
    };
}

impl FilterEffect for FeComponentTransfer {
    fn render(
        &self,
        node: &RsvgNode,
        ctx: &FilterContext,
        draw_ctx: &mut DrawingCtx,
    ) -> Result<FilterResult, FilterError> {
        let input = self.base.get_input(ctx, draw_ctx)?;
        let bounds = self
            .base
            .get_bounds(ctx)
            .add_input(&input)
            .into_irect(draw_ctx);

        // Create the output surface.
        let mut output_surface = ImageSurface::create(
            cairo::Format::ARgb32,
            ctx.source_graphic().width(),
            ctx.source_graphic().height(),
        )?;

        // Get a node for every pixel component.
        fn get_node<F>(node: &RsvgNode, node_type: NodeType, channel: Channel) -> Option<RsvgNode>
        where
            F: FeComponentTransferFunc + NodeTrait,
        {
            node.children()
                .rev()
                .filter(|c| c.borrow().get_type() == node_type)
                .find(|c| c.borrow().get_impl::<F>().channel() == channel)
        };

        let func_r_node = get_node::<FeFuncR>(node, NodeType::FeFuncR, Channel::R);
        let func_g_node = get_node::<FeFuncG>(node, NodeType::FeFuncG, Channel::G);
        let func_b_node = get_node::<FeFuncB>(node, NodeType::FeFuncB, Channel::B);
        let func_a_node = get_node::<FeFuncA>(node, NodeType::FeFuncA, Channel::A);

        for node in [&func_r_node, &func_g_node, &func_b_node, &func_a_node]
            .iter()
            .filter_map(|x| x.as_ref())
        {
            if node.borrow().is_in_error() {
                return Err(FilterError::ChildNodeInError);
            }
        }

        // These are the default funcs that perform an identity transformation.
        let func_r_default = FeFuncR::default();
        let func_g_default = FeFuncG::default();
        let func_b_default = FeFuncB::default();
        let func_a_default = FeFuncA::default();

        // We need to tell the borrow checker that these live long enough
        let func_r_data;
        let func_g_data;
        let func_b_data;
        let func_a_data;

        let func_r = func_or_default!(func_r_node, FeFuncR, func_r_data, func_r_default);
        let func_g = func_or_default!(func_g_node, FeFuncG, func_g_data, func_g_default);
        let func_b = func_or_default!(func_b_node, FeFuncB, func_b_data, func_b_default);
        let func_a = func_or_default!(func_a_node, FeFuncA, func_a_data, func_a_default);

        #[inline]
        fn compute_func<'a, F>(func: &'a F) -> impl Fn(u8, f64, f64) -> u8 + 'a
        where
            F: FeComponentTransferFunc,
        {
            let compute = func.function();
            let params = func.function_parameters();

            move |value, alpha, new_alpha| {
                let value = f64::from(value) / 255f64;

                let unpremultiplied = if alpha == 0f64 { 0f64 } else { value / alpha };

                let new_value = compute(&params, unpremultiplied);
                let new_value = clamp(new_value, 0f64, 1f64);

                ((new_value * new_alpha * 255f64) + 0.5) as u8
            }
        }

        let compute_r = compute_func::<FeFuncR>(&func_r);
        let compute_g = compute_func::<FeFuncG>(&func_g);
        let compute_b = compute_func::<FeFuncB>(&func_b);

        // Alpha gets special handling since everything else depends on it.
        let compute_a = func_a.function();
        let params_a = func_a.function_parameters();
        let compute_a = |alpha| compute_a(&params_a, alpha);

        // Do the actual processing.
        let output_stride = output_surface.get_stride() as usize;
        {
            let mut output_data = output_surface.get_data().unwrap();

            for (x, y, pixel) in Pixels::new(input.surface(), bounds) {
                let alpha = f64::from(pixel.a) / 255f64;
                let new_alpha = compute_a(alpha);

                let output_pixel = Pixel {
                    r: compute_r(pixel.r, alpha, new_alpha),
                    g: compute_g(pixel.g, alpha, new_alpha),
                    b: compute_b(pixel.b, alpha, new_alpha),
                    a: ((new_alpha * 255f64) + 0.5) as u8,
                };

                output_data.set_pixel(output_stride, output_pixel, x, y);
            }
        }

        Ok(FilterResult {
            name: self.base.result.clone(),
            output: FilterOutput {
                surface: SharedImageSurface::new(output_surface, input.surface().surface_type())?,
                bounds,
            },
        })
    }

    fn is_affected_by_color_interpolation_filters(&self) -> bool {
        true
    }
}
<|endoftext|>