<commit_before>extern crate glib;
extern crate gstreamer as gst;
extern crate gtk;

#[cfg(any(feature = "profiling-listener", feature = "profiling-tracker"))]
use chrono::Utc;

use std::rc::Rc;
use std::cell::RefCell;

use std::path::PathBuf;

use std::sync::Arc;
use std::sync::mpsc::{channel, Receiver};

use gtk::prelude::*;

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

use super::{AudioController, ExportController, InfoController, VideoController};

#[derive(Clone, Debug, PartialEq)]
pub enum ControllerState {
    EOS,
    Ready,
    Paused,
    PendingExportToc,
    PendingSelectMedia,
    Playing,
    PlayingRange(u64),
    Stopped,
    Seeking(bool, bool), // (must_switch_to_play, must_keep_paused)
}

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

pub struct MainController {
    window: gtk::ApplicationWindow,
    header_bar: gtk::HeaderBar,
    play_pause_btn: gtk::ToolButton,
    export_toc_btn: gtk::Button,

    video_ctrl: VideoController,
    info_ctrl: Rc<RefCell<InfoController>>,
    audio_ctrl: Rc<RefCell<AudioController>>,
    export_ctrl: Rc<RefCell<ExportController>>,

    context: Option<PlaybackContext>,
    state: ControllerState,

    requires_async_dialog: bool, // when pipeline contains video, dialogs must wait for
    // asyncdone before opening a dialog otherwise the listener
    // may borrow the MainController while the dialog is already
    // using it leading to a borrowing conflict
    this_opt: Option<Rc<RefCell<MainController>>>,
    keep_going: bool,
    listener_src: Option<glib::SourceId>,
    tracker_src: Option<glib::SourceId>,
}

impl MainController {
    pub fn new(builder: &gtk::Builder) -> Rc<RefCell<Self>> {
        let export_toc_btn: gtk::Button = builder.get_object("export_toc-btn").unwrap();
        export_toc_btn.set_sensitive(false);

        let this = Rc::new(RefCell::new(MainController {
            window: builder.get_object("application-window").unwrap(),
            header_bar: builder.get_object("header-bar").unwrap(),
            play_pause_btn: builder.get_object("play_pause-toolbutton").unwrap(),
            export_toc_btn: export_toc_btn,

            video_ctrl: VideoController::new(builder),
            info_ctrl: InfoController::new(builder),
            audio_ctrl: AudioController::new(builder),
            export_ctrl: ExportController::new(builder),

            context: None,
            state: ControllerState::Stopped,

            requires_async_dialog: false,

            this_opt: None,
            keep_going: true,
            listener_src: None,
            tracker_src: None,
        }));

        {
            let mut this_mut = this.borrow_mut();

            let this_rc = Rc::clone(&this);
            this_mut.this_opt = Some(this_rc);

            this_mut.window.connect_delete_event(|_, _| {
                gtk::main_quit();
                Inhibit(false)
            });
            this_mut.window.set_titlebar(&this_mut.header_bar);

            let this_rc = Rc::clone(&this);
            this_mut.play_pause_btn.connect_clicked(move |_| {
                this_rc.borrow_mut().play_pause();
            });

            // TODO: add key bindings to seek by steps
            // play/pause, etc.

            let this_rc = Rc::clone(&this);
            this_mut.export_toc_btn.connect_clicked(move |_| {
                let mut this = this_rc.borrow_mut();

                if this.requires_async_dialog && this.state == ControllerState::Playing {
                    this.hold();
                    this.state = ControllerState::PendingExportToc;
                } else {
                    this.hold();
                    this.export_toc();
                }
            });

            this_mut.video_ctrl.register_callbacks(&this);
            InfoController::register_callbacks(&this_mut.info_ctrl, &this);
            AudioController::register_callbacks(&this_mut.audio_ctrl, &this);
            ExportController::register_callbacks(&this_mut.export_ctrl, &this);
        }

        let open_btn: gtk::Button = builder.get_object("open-btn").unwrap();
        let this_rc = Rc::clone(&this);
        open_btn.connect_clicked(move |_| {
            let mut this = this_rc.borrow_mut();

            if this.requires_async_dialog && this.state == ControllerState::Playing {
                this.hold();
                this.state = ControllerState::PendingSelectMedia;
            } else {
                this.hold();
                this.select_media();
            }
        });

        this
    }

    pub fn show_all(&self) {
        self.window.show_all();
    }

    pub fn get_state(&self) -> &ControllerState {
        &self.state
    }

    fn play_pause(&mut self) {
        let mut context = match self.context.take() {
            Some(context) => context,
            None => {
                self.select_media();
                return;
            }
        };

        if self.state != ControllerState::EOS {
            match context.get_state() {
                gst::State::Paused => {
                    self.register_tracker();
                    self.play_pause_btn.set_icon_name("media-playback-pause");
                    self.state = ControllerState::Playing;
                    context.play().unwrap();
                    self.context = Some(context);
                }
                gst::State::Playing => {
                    context.pause().unwrap();
                    self.play_pause_btn.set_icon_name("media-playback-start");
                    self.remove_tracker();
                    self.state = ControllerState::Paused;
                    self.context = Some(context);
                }
                _ => {
                    self.context = Some(context);
                    self.select_media();
                }
            };
        } else {
            // Restart the stream from the begining
            self.context = Some(context);
            self.seek(0, true); // accurate (slow)
        }
    }

    pub fn seek(&mut self, position: u64, accurate: bool) {
        match self.state {
            ControllerState::Stopped | ControllerState::PlayingRange(_) => (),
            _ => {
                if self.state == ControllerState::Playing || self.state == ControllerState::Paused {
                    self.info_ctrl.borrow_mut().seek(position, &self.state);
                    self.audio_ctrl.borrow_mut().seek(position, &self.state);
                }

                let (must_switch_to_play, must_keep_paused) = match self.state {
                    ControllerState::EOS |
                        ControllerState::Ready |
                        ControllerState::Seeking(true, false) => (true, false),
                    ControllerState::Seeking(true, true) => (true, true),
                    ControllerState::Paused => (false, true),
                    _ => (false, false),
                };
                self.state = ControllerState::Seeking(must_switch_to_play, must_keep_paused);

                self.context
                    .as_ref()
                    .expect("MainController::seek no context")
                    .seek(position, accurate);
            }
        }
    }

    pub fn play_range(&mut self, start: u64, end: u64, pos_to_restore: u64) {
        if self.state == ControllerState::Paused {
            self.info_ctrl.borrow_mut().start_play_range();
            self.audio_ctrl.borrow_mut().start_play_range();

            self.state = ControllerState::PlayingRange(pos_to_restore);

            self.context
                .as_ref()
                .expect("MainController::play_range no context")
                .seek_range(start, end);

            self.register_tracker();
        }
    }

    pub fn get_position(&mut self) -> u64 {
        self.context
            .as_mut()
            .expect("MainController::get_position no context")
            .get_position()
    }

    fn hold(&mut self) {
        self.remove_tracker();
        if let Some(context) = self.context.as_mut() {
            context.pause().unwrap();
        };

        self.play_pause_btn.set_icon_name("media-playback-start");
        self.window.set_sensitive(false);
    }

    fn export_toc(&mut self) {
        if let Some(mut context) = self.context.take() {
            self.info_ctrl.borrow().export_chapters(&mut context);
            self.export_ctrl.borrow_mut().open(context);
            self.state = ControllerState::Paused;
        }
    }

    pub fn restore_context(&mut self, context: PlaybackContext) {
        self.context = Some(context);
        self.state = ControllerState::Paused;
        self.window.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>) {
        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;

            for message in ui_rx.try_iter() {
                match message {
                    AsyncDone => {
                        let mut this = this_rc.borrow_mut();
                        match this.state {
                            ControllerState::PendingSelectMedia => this.select_media(),
                            ControllerState::PendingExportToc => this.export_toc(),
                            ControllerState::Seeking(must_switch_to_play, must_keep_paused) => {
                                if must_switch_to_play {
                                    this.context
                                        .as_mut()
                                        .expect("MainController::listener(AsyncDone) no context")
                                        .play()
                                        .unwrap();
                                    this.register_tracker();
                                    this.play_pause_btn.set_icon_name("media-playback-pause");
                                    this.state = ControllerState::Playing;
                                } else if must_keep_paused {
                                    this.state = ControllerState::Paused;
                                } else {
                                    this.state = ControllerState::Playing;
                                }
                            }
                            _ => (),
                        }
                    }
                    InitDone => {
                        let mut this = this_rc.borrow_mut();

                        let context = this.context.take().expect(
                            "MainController: InitDone but no context available",
                        );

                        this.requires_async_dialog = context
                            .info
                            .lock()
                            .expect("MainController::listener(InitDone) failed to lock media info")
                            .video_best
                            .is_some();

                        this.header_bar.set_subtitle(
                            Some(context.file_name.as_str()),
                        );

                        this.video_ctrl.new_media(&context);
                        this.info_ctrl.borrow_mut().new_media(&context);
                        this.audio_ctrl.borrow_mut().new_media(&context);

                        this.context = Some(context);

                        this.state = ControllerState::Ready;
                    }
                    Eos => {
                        let mut this = this_rc.borrow_mut();
                        match this.state {
                            ControllerState::PlayingRange(pos_to_restore) => {
                                // end of range => pause and seek back to pos_to_restore
                                this.context
                                    .as_ref()
                                    .expect("MainController::listener(eos) no context")
                                    .pause()
                                    .unwrap();
                                this.state = ControllerState::Paused;
                                this.remove_tracker();
                                this.seek(pos_to_restore, true); // accurate
                            }
                            _ => {
                                let position = this.get_position();
                                this.info_ctrl.borrow_mut().tick(position, true);
                                this.audio_ctrl.borrow_mut().tick();

                                #[cfg(feature = "trace-main-controller")]
                                println!("MainController::listener(eos)");

                                this.play_pause_btn.set_icon_name("media-playback-start");
                                this.state = ControllerState::EOS;

                                // The tracker will be register again in case of a seek
                                this.remove_tracker();
                            }
                        }
                    }
                    FailedToOpenMedia => {
                        eprintln!("ERROR: failed to open media");

                        let mut this = this_rc.borrow_mut();

                        this.context = None;
                        this.keep_going = false;
                        keep_going = false;
                    }
                    _ => (),
                };

                if !keep_going {
                    break;
                }
            }

            if !keep_going {
                let mut this = this_rc.borrow_mut();
                this.listener_src = None;
                this.tracker_src = None;
            }

            glib::Continue(keep_going)
        }));
    }

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

    fn register_tracker(&mut self) {
        if self.tracker_src.is_some() {
            return;
        }

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

        self.tracker_src = Some(gtk::timeout_add(TRACKER_PERIOD, move || {
            #[cfg(feature = "profiling-tracker")]
            let start = Utc::now();

            let mut this = this_rc.borrow_mut();

            #[cfg(feature = "profiling-tracker")]
            let before_tick = Utc::now();

            {
                let position = this.context
                    .as_mut()
                    .expect("MainController::tracker no context while getting position")
                    .get_position();
                this.info_ctrl.borrow_mut().tick(position, false);
                this.audio_ctrl.borrow_mut().tick();
            }

            #[cfg(feature = "profiling-tracker")]
            let end = Utc::now();

            #[cfg(feature = "profiling-tracker")]
            println!(
                "tracker,{},{},{}",
                start.time().format("%H:%M:%S%.6f"),
                before_tick.time().format("%H:%M:%S%.6f"),
                end.time().format("%H:%M:%S%.6f"),
            );

            glib::Continue(this.keep_going)
        }));
    }

    fn select_media(&mut self) {
        let file_dlg = gtk::FileChooserDialog::new(
            Some("Open a media file"),
            Some(&self.window),
            gtk::FileChooserAction::Open,
        );
        // Note: couldn't find equivalents for STOCK_OK
        file_dlg.add_button("Open", gtk::ResponseType::Ok.into());

        if file_dlg.run() == gtk::ResponseType::Ok.into() {
            if let Some(ref context) = self.context {
                context.stop();
            }
            self.open_media(file_dlg.get_filename().unwrap());
        } else {
            self.state = ControllerState::Paused;
            self.window.set_sensitive(true);
        }

        file_dlg.close();
    }

    fn open_media(&mut self, filepath: PathBuf) {
        self.remove_listener();

        self.info_ctrl.borrow_mut().cleanup();
        self.audio_ctrl.borrow_mut().cleanup();
        self.header_bar.set_subtitle("");
        self.export_toc_btn.set_sensitive(false);

        let (ctx_tx, ui_rx) = channel();

        self.keep_going = true;
        self.register_listener(LISTENER_PERIOD, ui_rx);

        let dbl_buffer_mtx = Arc::clone(&self.audio_ctrl.borrow().get_dbl_buffer_mtx());
        match PlaybackContext::new(filepath, dbl_buffer_mtx, ctx_tx) {
            Ok(context) => {
                self.context = Some(context);
                self.export_toc_btn.set_sensitive(true);
            }
            Err(error) => {
                eprintln!("Error opening media: {}", error);
            }
        };

        self.window.set_sensitive(true);
    }
}
<commit_msg>Fix inconsistent state when seeking rapidely in paused mode (#84)<commit_after>extern crate glib;
extern crate gstreamer as gst;
extern crate gtk;

#[cfg(any(feature = "profiling-listener", feature = "profiling-tracker"))]
use chrono::Utc;

use std::rc::Rc;
use std::cell::RefCell;

use std::path::PathBuf;

use std::sync::Arc;
use std::sync::mpsc::{channel, Receiver};

use gtk::prelude::*;

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

use super::{AudioController, ExportController, InfoController, VideoController};

#[derive(Clone, Debug, PartialEq)]
pub enum ControllerState {
    EOS,
    Ready,
    Paused,
    PendingExportToc,
    PendingSelectMedia,
    Playing,
    PlayingRange(u64),
    Stopped,
    Seeking(bool, bool), // (must_switch_to_play, must_keep_paused)
}

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

pub struct MainController {
    window: gtk::ApplicationWindow,
    header_bar: gtk::HeaderBar,
    play_pause_btn: gtk::ToolButton,
    export_toc_btn: gtk::Button,

    video_ctrl: VideoController,
    info_ctrl: Rc<RefCell<InfoController>>,
    audio_ctrl: Rc<RefCell<AudioController>>,
    export_ctrl: Rc<RefCell<ExportController>>,

    context: Option<PlaybackContext>,
    state: ControllerState,

    requires_async_dialog: bool, // when pipeline contains video, dialogs must wait for
    // asyncdone before opening a dialog otherwise the listener
    // may borrow the MainController while the dialog is already
    // using it leading to a borrowing conflict
    this_opt: Option<Rc<RefCell<MainController>>>,
    keep_going: bool,
    listener_src: Option<glib::SourceId>,
    tracker_src: Option<glib::SourceId>,
}

impl MainController {
    pub fn new(builder: &gtk::Builder) -> Rc<RefCell<Self>> {
        let export_toc_btn: gtk::Button = builder.get_object("export_toc-btn").unwrap();
        export_toc_btn.set_sensitive(false);

        let this = Rc::new(RefCell::new(MainController {
            window: builder.get_object("application-window").unwrap(),
            header_bar: builder.get_object("header-bar").unwrap(),
            play_pause_btn: builder.get_object("play_pause-toolbutton").unwrap(),
            export_toc_btn: export_toc_btn,

            video_ctrl: VideoController::new(builder),
            info_ctrl: InfoController::new(builder),
            audio_ctrl: AudioController::new(builder),
            export_ctrl: ExportController::new(builder),

            context: None,
            state: ControllerState::Stopped,

            requires_async_dialog: false,

            this_opt: None,
            keep_going: true,
            listener_src: None,
            tracker_src: None,
        }));

        {
            let mut this_mut = this.borrow_mut();

            let this_rc = Rc::clone(&this);
            this_mut.this_opt = Some(this_rc);

            this_mut.window.connect_delete_event(|_, _| {
                gtk::main_quit();
                Inhibit(false)
            });
            this_mut.window.set_titlebar(&this_mut.header_bar);

            let this_rc = Rc::clone(&this);
            this_mut.play_pause_btn.connect_clicked(move |_| {
                this_rc.borrow_mut().play_pause();
            });

            // TODO: add key bindings to seek by steps
            // play/pause, etc.

            let this_rc = Rc::clone(&this);
            this_mut.export_toc_btn.connect_clicked(move |_| {
                let mut this = this_rc.borrow_mut();

                if this.requires_async_dialog && this.state == ControllerState::Playing {
                    this.hold();
                    this.state = ControllerState::PendingExportToc;
                } else {
                    this.hold();
                    this.export_toc();
                }
            });

            this_mut.video_ctrl.register_callbacks(&this);
            InfoController::register_callbacks(&this_mut.info_ctrl, &this);
            AudioController::register_callbacks(&this_mut.audio_ctrl, &this);
            ExportController::register_callbacks(&this_mut.export_ctrl, &this);
        }

        let open_btn: gtk::Button = builder.get_object("open-btn").unwrap();
        let this_rc = Rc::clone(&this);
        open_btn.connect_clicked(move |_| {
            let mut this = this_rc.borrow_mut();

            if this.requires_async_dialog && this.state == ControllerState::Playing {
                this.hold();
                this.state = ControllerState::PendingSelectMedia;
            } else {
                this.hold();
                this.select_media();
            }
        });

        this
    }

    pub fn show_all(&self) {
        self.window.show_all();
    }

    pub fn get_state(&self) -> &ControllerState {
        &self.state
    }

    fn play_pause(&mut self) {
        let mut context = match self.context.take() {
            Some(context) => context,
            None => {
                self.select_media();
                return;
            }
        };

        if self.state != ControllerState::EOS {
            match context.get_state() {
                gst::State::Paused => {
                    self.register_tracker();
                    self.play_pause_btn.set_icon_name("media-playback-pause");
                    self.state = ControllerState::Playing;
                    context.play().unwrap();
                    self.context = Some(context);
                }
                gst::State::Playing => {
                    context.pause().unwrap();
                    self.play_pause_btn.set_icon_name("media-playback-start");
                    self.remove_tracker();
                    self.state = ControllerState::Paused;
                    self.context = Some(context);
                }
                _ => {
                    self.context = Some(context);
                    self.select_media();
                }
            };
        } else {
            // Restart the stream from the begining
            self.context = Some(context);
            self.seek(0, true); // accurate (slow)
        }
    }

    pub fn seek(&mut self, position: u64, accurate: bool) {
        match self.state {
            ControllerState::Stopped | ControllerState::PlayingRange(_) => (),
            _ => {
                if self.state == ControllerState::Playing || self.state == ControllerState::Paused {
                    self.info_ctrl.borrow_mut().seek(position, &self.state);
                    self.audio_ctrl.borrow_mut().seek(position, &self.state);
                }

                let (must_switch_to_play, must_keep_paused) = match self.state {
                    ControllerState::EOS |
                        ControllerState::Ready |
                        ControllerState::Seeking(true, false) => (true, false),
                    ControllerState::Seeking(true, true) => (true, true),
                    ControllerState::Paused |
                        ControllerState::Seeking(false, true) => (false, true),
                    _ => (false, false),
                };
                self.state = ControllerState::Seeking(must_switch_to_play, must_keep_paused);

                self.context
                    .as_ref()
                    .expect("MainController::seek no context")
                    .seek(position, accurate);
            }
        }
    }

    pub fn play_range(&mut self, start: u64, end: u64, pos_to_restore: u64) {
        if self.state == ControllerState::Paused {
            self.info_ctrl.borrow_mut().start_play_range();
            self.audio_ctrl.borrow_mut().start_play_range();

            self.state = ControllerState::PlayingRange(pos_to_restore);

            self.context
                .as_ref()
                .expect("MainController::play_range no context")
                .seek_range(start, end);

            self.register_tracker();
        }
    }

    pub fn get_position(&mut self) -> u64 {
        self.context
            .as_mut()
            .expect("MainController::get_position no context")
            .get_position()
    }

    fn hold(&mut self) {
        self.remove_tracker();
        if let Some(context) = self.context.as_mut() {
            context.pause().unwrap();
        };

        self.play_pause_btn.set_icon_name("media-playback-start");
        self.window.set_sensitive(false);
    }

    fn export_toc(&mut self) {
        if let Some(mut context) = self.context.take() {
            self.info_ctrl.borrow().export_chapters(&mut context);
            self.export_ctrl.borrow_mut().open(context);
            self.state = ControllerState::Paused;
        }
    }

    pub fn restore_context(&mut self, context: PlaybackContext) {
        self.context = Some(context);
        self.state = ControllerState::Paused;
        self.window.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>) {
        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;

            for message in ui_rx.try_iter() {
                match message {
                    AsyncDone => {
                        let mut this = this_rc.borrow_mut();
                        match this.state {
                            ControllerState::PendingSelectMedia => this.select_media(),
                            ControllerState::PendingExportToc => this.export_toc(),
                            ControllerState::Seeking(must_switch_to_play, must_keep_paused) => {
                                if must_switch_to_play {
                                    this.context
                                        .as_mut()
                                        .expect("MainController::listener(AsyncDone) no context")
                                        .play()
                                        .unwrap();
                                    this.register_tracker();
                                    this.play_pause_btn.set_icon_name("media-playback-pause");
                                    this.state = ControllerState::Playing;
                                } else if must_keep_paused {
                                    this.state = ControllerState::Paused;
                                } else {
                                    this.state = ControllerState::Playing;
                                }
                            }
                            _ => (),
                        }
                    }
                    InitDone => {
                        let mut this = this_rc.borrow_mut();
                        let context = this.context.take().expect(
                            "MainController: InitDone but no context available",
                        );

                        this.requires_async_dialog = context
                            .info
                            .lock()
                            .expect("MainController::listener(InitDone) failed to lock media info")
                            .video_best
                            .is_some();

                        this.header_bar.set_subtitle(
                            Some(context.file_name.as_str()),
                        );

                        this.video_ctrl.new_media(&context);
                        this.info_ctrl.borrow_mut().new_media(&context);
                        this.audio_ctrl.borrow_mut().new_media(&context);

                        this.context = Some(context);

                        this.state = ControllerState::Ready;
                    }
                    Eos => {
                        let mut this = this_rc.borrow_mut();
                        match this.state {
                            ControllerState::PlayingRange(pos_to_restore) => {
                                // end of range => pause and seek back to pos_to_restore
                                this.context
                                    .as_ref()
                                    .expect("MainController::listener(eos) no context")
                                    .pause()
                                    .unwrap();
                                this.state = ControllerState::Paused;
                                this.remove_tracker();
                                this.seek(pos_to_restore, true); // accurate
                            }
                            _ => {
                                let position = this.get_position();
                                this.info_ctrl.borrow_mut().tick(position, true);
                                this.audio_ctrl.borrow_mut().tick();

                                #[cfg(feature = "trace-main-controller")]
                                println!("MainController::listener(eos)");

                                this.play_pause_btn.set_icon_name("media-playback-start");
                                this.state = ControllerState::EOS;

                                // The tracker will be register again in case of a seek
                                this.remove_tracker();
                            }
                        }
                    }
                    FailedToOpenMedia => {
                        eprintln!("ERROR: failed to open media");

                        let mut this = this_rc.borrow_mut();
                        this.context = None;
                        this.state = ControllerState::Stopped;
                        this.keep_going = false;
                        keep_going = false;
                    }
                    _ => (),
                };

                if !keep_going {
                    break;
                }
            }

            if !keep_going {
                let mut this = this_rc.borrow_mut();
                this.listener_src = None;
                this.tracker_src = None;
            }

            glib::Continue(keep_going)
        }));
    }

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

    fn register_tracker(&mut self) {
        if self.tracker_src.is_some() {
            return;
        }

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

        self.tracker_src = Some(gtk::timeout_add(TRACKER_PERIOD, move || {
            #[cfg(feature = "profiling-tracker")]
            let start = Utc::now();

            let mut this = this_rc.borrow_mut();

            #[cfg(feature = "profiling-tracker")]
            let before_tick = Utc::now();

            {
                let position = this.context
                    .as_mut()
                    .expect("MainController::tracker no context while getting position")
                    .get_position();
                this.info_ctrl.borrow_mut().tick(position, false);
                this.audio_ctrl.borrow_mut().tick();
            }

            #[cfg(feature = "profiling-tracker")]
            let end = Utc::now();

            #[cfg(feature = "profiling-tracker")]
            println!(
                "tracker,{},{},{}",
                start.time().format("%H:%M:%S%.6f"),
                before_tick.time().format("%H:%M:%S%.6f"),
                end.time().format("%H:%M:%S%.6f"),
            );

            glib::Continue(this.keep_going)
        }));
    }

    fn select_media(&mut self) {
        let file_dlg = gtk::FileChooserDialog::new(
            Some("Open a media file"),
            Some(&self.window),
            gtk::FileChooserAction::Open,
        );
        // Note: couldn't find equivalents for STOCK_OK
        file_dlg.add_button("Open", gtk::ResponseType::Ok.into());

        if file_dlg.run() == gtk::ResponseType::Ok.into() {
            if let Some(ref context) = self.context {
                context.stop();
            }
            self.open_media(file_dlg.get_filename().unwrap());
        } else {
            self.state = ControllerState::Paused;
            self.window.set_sensitive(true);
        }

        file_dlg.close();
    }

    fn open_media(&mut self, filepath: PathBuf) {
        self.remove_listener();

        self.info_ctrl.borrow_mut().cleanup();
        self.audio_ctrl.borrow_mut().cleanup();
        self.header_bar.set_subtitle("");
        self.export_toc_btn.set_sensitive(false);

        let (ctx_tx, ui_rx) = channel();

        self.keep_going = true;
        self.register_listener(LISTENER_PERIOD, ui_rx);

        let dbl_buffer_mtx = Arc::clone(&self.audio_ctrl.borrow().get_dbl_buffer_mtx());
        match PlaybackContext::new(filepath, dbl_buffer_mtx, ctx_tx) {
            Ok(context) => {
                self.context = Some(context);
                self.export_toc_btn.set_sensitive(true);
            }
            Err(error) => {
                eprintln!("Error opening media: {}", error);
            }
        };

        self.window.set_sensitive(true);
    }
}
<|endoftext|>


<commit_before>extern crate cairo;

#[cfg(feature = "profiling-waveform-buffer")]
use chrono::Utc;

use std::any::Any;

use ::media::{AudioBuffer, SAMPLES_NORM};

use ::media::{DoubleSampleExtractor, SamplesExtractor};
use ::media::samples_extractor::SamplesExtractionState;

pub const BACKGROUND_COLOR: (f64, f64, f64) = (0.2f64, 0.2235f64, 0.2314f64);

pub struct DoubleWaveformBuffer {}
impl DoubleWaveformBuffer {
    pub fn new() -> DoubleSampleExtractor {
        DoubleSampleExtractor::new(
            Box::new(WaveformBuffer::new()),
            Box::new(WaveformBuffer::new()),
        )
    }
}

pub struct WaveformBuffer {
    state: SamplesExtractionState,
    buffer_sample_window: usize,

    width: i32,
    height: i32,
    pub image_surface: Option<cairo::ImageSurface>,
    working_image: Option<cairo::ImageSurface>,

    pub x_offset: usize,
    pub current_x: usize,
}

impl WaveformBuffer {
    pub fn new() -> Self {
        WaveformBuffer {
            state: SamplesExtractionState::new(),
            buffer_sample_window: 0,

            width: 0,
            height: 0,
            image_surface: None,
            working_image: None,

            x_offset: 0,
            current_x: 0,
        }
    }

    pub fn update_conditions(&mut self,
        pts: u64,
        duration: u64,
        width: i32,
        height: i32,
    )
    {
        let state = &mut self.state;

        self.width = width;
        self.height = height;

        state.current_sample = (
            pts as f64 / state.sample_duration
        ).round() as usize;

        let width = width as u64;
        // resolution
        state.requested_step_duration =
            if duration > width {
                duration / width
            } else {
                1
            };

        state.requested_sample_window = (
            duration as f64 / state.sample_duration
        ).round() as usize;
        state.half_requested_sample_window = state.requested_sample_window / 2;

        if self.image_surface.is_some() {
            let first_visible_sample =
                if state.eos
                && state.current_sample + state.half_requested_sample_window > state.last_sample {
                    if self.buffer_sample_window > state.requested_sample_window {
                        state.last_sample - state.requested_sample_window
                    } else {
                        state.samples_offset
                    }
                } else {
                    if state.current_sample > state.half_requested_sample_window
                        + state.samples_offset
                    {
                        state.current_sample - state.half_requested_sample_window
                    } else {
                        state.samples_offset
                    }
                };

            self.x_offset = (first_visible_sample - state.samples_offset) / state.sample_step;
            self.current_x = (state.current_sample - first_visible_sample) / state.sample_step;
        }
    }
}

impl SamplesExtractor for WaveformBuffer {
    fn as_mut_any(&mut self) -> &mut Any {
        self
    }

    fn get_extraction_state(&self) -> &SamplesExtractionState {
        &self.state
    }

    fn get_extraction_state_mut(&mut self) -> &mut SamplesExtractionState {
        &mut self.state
    }

    fn can_extract(&self) -> bool {
        self.state.requested_sample_window > 0
    }

    fn update_extraction(&mut self,
        audio_buffer: &AudioBuffer,
        first_sample: usize,
        last_sample: usize,
        sample_step: usize,
    ) {
        #[cfg(feature = "profiling-waveform-buffer")]
        let start = Utc::now();

        let buffer_sample_window = last_sample - first_sample;
        let extracted_samples_window =
            (buffer_sample_window / sample_step) as i32;

        let mut must_redraw = self.state.sample_step != sample_step;

        let working_image = {
            let mut can_reuse = false;
            let target_width = extracted_samples_window.max(self.width);

            if let Some(ref working_image) = self.working_image {
                if self.height != working_image.get_height() {
                    // height has changed => scale samples amplitude accordingly
                    must_redraw = true;
                }

                if target_width <= working_image.get_width()
                && self.height <= working_image.get_height() {
                    // expected dimensions fit in current working image => reuse it
                    can_reuse = true;
                }
            }

            if can_reuse {
                self.working_image.take().unwrap()
            } else {
                cairo::ImageSurface::create(
                    cairo::Format::Rgb24,
                    target_width,
                    self.height
                ).expect("WaveformBuffer: couldn't create image surface in update_extraction")
            }
        };

        let cr = cairo::Context::new(&working_image);
        let (mut sample_iter, mut x) =
            if must_redraw {
                // Initialization or resolution has changed
                // redraw the whole range
                self.state.sample_step = sample_step;

                // clear the image
                cr.set_source_rgb(
                    BACKGROUND_COLOR.0,
                    BACKGROUND_COLOR.1,
                    BACKGROUND_COLOR.2
                );
                cr.paint();

                (
                    audio_buffer.iter(first_sample, last_sample, sample_step),
                    0f64,
                )
            } else {
                // shift previous context
                let previous_image = self.image_surface.take()
                    .expect("WaveformBuffer: no image_surface while updating");

                let sample_step_offset =
                    (first_sample - self.state.samples_offset) / sample_step;
                cr.set_source_surface(
                    &previous_image,
                    -(sample_step_offset as f64),
                    0f64
                );
                cr.paint();

                self.image_surface = Some(previous_image);

                // prepare to add remaining samples
                (
                    audio_buffer.iter(self.state.last_sample, last_sample, sample_step),
                    (
                        (self.state.last_sample - self.state.samples_offset) / sample_step
                        - sample_step_offset
                    ) as f64,
                )
            };

        cr.scale(1f64, self.height as f64 / SAMPLES_NORM);

        if !must_redraw {
            // fill the rest of the image with background color
            cr.set_source_rgb(
                BACKGROUND_COLOR.0,
                BACKGROUND_COLOR.1,
                BACKGROUND_COLOR.2
            );
            cr.rectangle(x, 0f64, working_image.get_width() as f64 - x, SAMPLES_NORM);
            cr.stroke_preserve();
            cr.fill();
        } // else brackgroung already set while clearing the image

        if sample_iter.size_hint().0 > 0 {
            // Stroke selected samples
            cr.set_line_width(0.5f64);
            cr.set_source_rgb(0.8f64, 0.8f64, 0.8f64);

            let mut sample_value = *sample_iter.next().unwrap();
            for sample in sample_iter {
                cr.move_to(x, sample_value);
                x += 1f64;
                sample_value = *sample;
                cr.line_to(x, sample_value);
                cr.stroke();
            }
        }

        if let Some(previous_image) = self.image_surface.take() {
            self.working_image = Some(previous_image);
        }
        self.image_surface = Some(working_image);

        self.state.samples_offset = first_sample;
        self.buffer_sample_window = buffer_sample_window;
        self.state.last_sample = last_sample;

        #[cfg(feature = "profiling-waveform-buffer")]
        let end = Utc::now();

        #[cfg(feature = "profiling-waveform-buffer")]
        println!("waveform-buffer,{},{}",
            start.time().format("%H:%M:%S%.6f"),
            end.time().format("%H:%M:%S%.6f"),
        );
    }
}
<commit_msg>No need to stroke rectangle before filling it (#25)<commit_after>extern crate cairo;

#[cfg(feature = "profiling-waveform-buffer")]
use chrono::Utc;

use std::any::Any;

use ::media::{AudioBuffer, SAMPLES_NORM};

use ::media::{DoubleSampleExtractor, SamplesExtractor};
use ::media::samples_extractor::SamplesExtractionState;

pub const BACKGROUND_COLOR: (f64, f64, f64) = (0.2f64, 0.2235f64, 0.2314f64);

pub struct DoubleWaveformBuffer {}
impl DoubleWaveformBuffer {
    pub fn new() -> DoubleSampleExtractor {
        DoubleSampleExtractor::new(
            Box::new(WaveformBuffer::new()),
            Box::new(WaveformBuffer::new()),
        )
    }
}

pub struct WaveformBuffer {
    state: SamplesExtractionState,
    buffer_sample_window: usize,

    width: i32,
    height: i32,
    pub image_surface: Option<cairo::ImageSurface>,
    working_image: Option<cairo::ImageSurface>,

    pub x_offset: usize,
    pub current_x: usize,
}

impl WaveformBuffer {
    pub fn new() -> Self {
        WaveformBuffer {
            state: SamplesExtractionState::new(),
            buffer_sample_window: 0,

            width: 0,
            height: 0,
            image_surface: None,
            working_image: None,

            x_offset: 0,
            current_x: 0,
        }
    }

    pub fn update_conditions(&mut self,
        pts: u64,
        duration: u64,
        width: i32,
        height: i32,
    )
    {
        let state = &mut self.state;

        self.width = width;
        self.height = height;

        state.current_sample = (
            pts as f64 / state.sample_duration
        ).round() as usize;

        let width = width as u64;
        // resolution
        state.requested_step_duration =
            if duration > width {
                duration / width
            } else {
                1
            };

        state.requested_sample_window = (
            duration as f64 / state.sample_duration
        ).round() as usize;
        state.half_requested_sample_window = state.requested_sample_window / 2;

        if self.image_surface.is_some() {
            let first_visible_sample =
                if state.eos
                && state.current_sample + state.half_requested_sample_window > state.last_sample {
                    if self.buffer_sample_window > state.requested_sample_window {
                        state.last_sample - state.requested_sample_window
                    } else {
                        state.samples_offset
                    }
                } else {
                    if state.current_sample > state.half_requested_sample_window
                        + state.samples_offset
                    {
                        state.current_sample - state.half_requested_sample_window
                    } else {
                        state.samples_offset
                    }
                };

            self.x_offset = (first_visible_sample - state.samples_offset) / state.sample_step;
            self.current_x = (state.current_sample - first_visible_sample) / state.sample_step;
        }
    }
}

impl SamplesExtractor for WaveformBuffer {
    fn as_mut_any(&mut self) -> &mut Any {
        self
    }

    fn get_extraction_state(&self) -> &SamplesExtractionState {
        &self.state
    }

    fn get_extraction_state_mut(&mut self) -> &mut SamplesExtractionState {
        &mut self.state
    }

    fn can_extract(&self) -> bool {
        self.state.requested_sample_window > 0
    }

    fn update_extraction(&mut self,
        audio_buffer: &AudioBuffer,
        first_sample: usize,
        last_sample: usize,
        sample_step: usize,
    ) {
        #[cfg(feature = "profiling-waveform-buffer")]
        let start = Utc::now();

        let buffer_sample_window = last_sample - first_sample;
        let extracted_samples_window =
            (buffer_sample_window / sample_step) as i32;

        let mut must_redraw = self.state.sample_step != sample_step;

        let working_image = {
            let mut can_reuse = false;
            let target_width = extracted_samples_window.max(self.width);

            if let Some(ref working_image) = self.working_image {
                if self.height != working_image.get_height() {
                    // height has changed => scale samples amplitude accordingly
                    must_redraw = true;
                }

                if target_width <= working_image.get_width()
                && self.height <= working_image.get_height() {
                    // expected dimensions fit in current working image => reuse it
                    can_reuse = true;
                }
            }

            if can_reuse {
                self.working_image.take().unwrap()
            } else {
                cairo::ImageSurface::create(
                    cairo::Format::Rgb24,
                    target_width,
                    self.height
                ).expect("WaveformBuffer: couldn't create image surface in update_extraction")
            }
        };

        let cr = cairo::Context::new(&working_image);
        let (mut sample_iter, mut x) =
            if must_redraw {
                // Initialization or resolution has changed
                // redraw the whole range
                self.state.sample_step = sample_step;

                // clear the image
                cr.set_source_rgb(
                    BACKGROUND_COLOR.0,
                    BACKGROUND_COLOR.1,
                    BACKGROUND_COLOR.2
                );
                cr.paint();

                (
                    audio_buffer.iter(first_sample, last_sample, sample_step),
                    0f64,
                )
            } else {
                // shift previous context
                let previous_image = self.image_surface.take()
                    .expect("WaveformBuffer: no image_surface while updating");

                let sample_step_offset =
                    (first_sample - self.state.samples_offset) / sample_step;
                cr.set_source_surface(
                    &previous_image,
                    -(sample_step_offset as f64),
                    0f64
                );
                cr.paint();

                self.image_surface = Some(previous_image);

                // prepare to add remaining samples
                (
                    audio_buffer.iter(self.state.last_sample, last_sample, sample_step),
                    (
                        (self.state.last_sample - self.state.samples_offset) / sample_step
                        - sample_step_offset
                    ) as f64,
                )
            };

        cr.scale(1f64, self.height as f64 / SAMPLES_NORM);

        if !must_redraw {
            // fill the rest of the image with background color
            cr.set_source_rgb(
                BACKGROUND_COLOR.0,
                BACKGROUND_COLOR.1,
                BACKGROUND_COLOR.2
            );
            cr.rectangle(x, 0f64, working_image.get_width() as f64 - x, SAMPLES_NORM);
            cr.fill();
        } // else brackgroung already set while clearing the image

        if sample_iter.size_hint().0 > 0 {
            // Stroke selected samples
            cr.set_line_width(0.5f64);
            cr.set_source_rgb(0.8f64, 0.8f64, 0.8f64);

            let mut sample_value = *sample_iter.next().unwrap();
            for sample in sample_iter {
                cr.move_to(x, sample_value);
                x += 1f64;
                sample_value = *sample;
                cr.line_to(x, sample_value);
                cr.stroke();
            }
        }

        if let Some(previous_image) = self.image_surface.take() {
            self.working_image = Some(previous_image);
        }
        self.image_surface = Some(working_image);

        self.state.samples_offset = first_sample;
        self.buffer_sample_window = buffer_sample_window;
        self.state.last_sample = last_sample;

        #[cfg(feature = "profiling-waveform-buffer")]
        let end = Utc::now();

        #[cfg(feature = "profiling-waveform-buffer")]
        println!("waveform-buffer,{},{}",
            start.time().format("%H:%M:%S%.6f"),
            end.time().format("%H:%M:%S%.6f"),
        );
    }
}
<|endoftext|>


<commit_before>//! Route match guards.
//!
//! Guards are one of the way how actix-web router chooses
//! handler service. In essence it just function that accepts
//! reference to a `RequestHead` instance and returns boolean.
//! It is possible to add guards to *scopes*, *resources*
//! and *routes*. Actix provide several guards by default, like various
//! http methods, header, etc. To become a guard, type must implement `Guard`
//! trait. Simple functions coulds guards as well.
//!
//! Guard can not modify request object. But it is possible to
//! to store extra attributes on a request by using `Extensions` container.
//! Extensions container available via `RequestHead::extensions()` method.
//!
//! ```rust
//! use actix_web::{web, http, dev, guard, App, HttpResponse};
//!
//! fn main() {
//!     App::new().service(web::resource("/index.html").route(
//!         web::route()
//!              .guard(guard::Post())
//!              .guard(|head: &dev::RequestHead| head.method == http::Method::GET)
//!              .to(|| HttpResponse::MethodNotAllowed()))
//!     );
//! }
//! ```

#![allow(non_snake_case)]
use actix_http::http::{self, header, HttpTryFrom};
use actix_http::RequestHead;

/// Trait defines resource guards. Guards are used for routes selection.
///
/// Guard can not modify request object. But it is possible to
/// to store extra attributes on request by using `Extensions` container,
/// Extensions container available via `RequestHead::extensions()` method.
pub trait Guard {
    /// Check if request matches predicate
    fn check(&self, request: &RequestHead) -> bool;
}

#[doc(hidden)]
pub struct FnGuard<F: Fn(&RequestHead) -> bool + 'static>(F);

impl<F> Guard for F
where
    F: Fn(&RequestHead) -> bool + 'static,
{
    fn check(&self, head: &RequestHead) -> bool {
        (*self)(head)
    }
}

/// Return guard that matches if any of supplied guards.
///
/// ```rust
/// use actix_web::{web, guard, App, HttpResponse};
///
/// fn main() {
///     App::new().service(web::resource("/index.html").route(
///         web::route()
///              .guard(guard::Any(guard::Get()).or(guard::Post()))
///              .to(|| HttpResponse::MethodNotAllowed()))
///     );
/// }
/// ```
pub fn Any<F: Guard + 'static>(guard: F) -> AnyGuard {
    AnyGuard(vec![Box::new(guard)])
}

/// Matches if any of supplied guards matche.
pub struct AnyGuard(Vec<Box<Guard>>);

impl AnyGuard {
    /// Add guard to a list of guards to check
    pub fn or<F: Guard + 'static>(mut self, guard: F) -> Self {
        self.0.push(Box::new(guard));
        self
    }
}

impl Guard for AnyGuard {
    fn check(&self, req: &RequestHead) -> bool {
        for p in &self.0 {
            if p.check(req) {
                return true;
            }
        }
        false
    }
}

/// Return guard that matches if all of the supplied guards.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{guard, web, App, HttpResponse};
///
/// fn main() {
///     App::new().service(web::resource("/index.html").route(
///         web::route()
///             .guard(
///                 guard::All(guard::Get()).and(guard::Header("content-type", "text/plain")))
///             .to(|| HttpResponse::MethodNotAllowed()))
///     );
/// }
/// ```
pub fn All<F: Guard + 'static>(guard: F) -> AllGuard {
    AllGuard(vec![Box::new(guard)])
}

/// Matches if all of supplied guards.
pub struct AllGuard(Vec<Box<Guard>>);

impl AllGuard {
    /// Add new guard to the list of guards to check
    pub fn and<F: Guard + 'static>(mut self, guard: F) -> Self {
        self.0.push(Box::new(guard));
        self
    }
}

impl Guard for AllGuard {
    fn check(&self, request: &RequestHead) -> bool {
        for p in &self.0 {
            if !p.check(request) {
                return false;
            }
        }
        true
    }
}

/// Return guard that matches if supplied guard does not match.
pub fn Not<F: Guard + 'static>(guard: F) -> NotGuard {
    NotGuard(Box::new(guard))
}

#[doc(hidden)]
pub struct NotGuard(Box<Guard>);

impl Guard for NotGuard {
    fn check(&self, request: &RequestHead) -> bool {
        !self.0.check(request)
    }
}

/// Http method guard
#[doc(hidden)]
pub struct MethodGuard(http::Method);

impl Guard for MethodGuard {
    fn check(&self, request: &RequestHead) -> bool {
        request.method == self.0
    }
}

/// Guard to match *GET* http method
pub fn Get() -> MethodGuard {
    MethodGuard(http::Method::GET)
}

/// Predicate to match *POST* http method
pub fn Post() -> MethodGuard {
    MethodGuard(http::Method::POST)
}

/// Predicate to match *PUT* http method
pub fn Put() -> MethodGuard {
    MethodGuard(http::Method::PUT)
}

/// Predicate to match *DELETE* http method
pub fn Delete() -> MethodGuard {
    MethodGuard(http::Method::DELETE)
}

/// Predicate to match *HEAD* http method
pub fn Head() -> MethodGuard {
    MethodGuard(http::Method::HEAD)
}

/// Predicate to match *OPTIONS* http method
pub fn Options() -> MethodGuard {
    MethodGuard(http::Method::OPTIONS)
}

/// Predicate to match *CONNECT* http method
pub fn Connect() -> MethodGuard {
    MethodGuard(http::Method::CONNECT)
}

/// Predicate to match *PATCH* http method
pub fn Patch() -> MethodGuard {
    MethodGuard(http::Method::PATCH)
}

/// Predicate to match *TRACE* http method
pub fn Trace() -> MethodGuard {
    MethodGuard(http::Method::TRACE)
}

/// Predicate to match specified http method
pub fn Method(method: http::Method) -> MethodGuard {
    MethodGuard(method)
}

/// Return predicate that matches if request contains specified header and
/// value.
pub fn Header(name: &'static str, value: &'static str) -> HeaderGuard {
    HeaderGuard(
        header::HeaderName::try_from(name).unwrap(),
        header::HeaderValue::from_static(value),
    )
}

#[doc(hidden)]
pub struct HeaderGuard(header::HeaderName, header::HeaderValue);

impl Guard for HeaderGuard {
    fn check(&self, req: &RequestHead) -> bool {
        if let Some(val) = req.headers.get(&self.0) {
            return val == self.1;
        }
        false
    }
}

// /// Return predicate that matches if request contains specified Host name.
// ///
// /// ```rust,ignore
// /// # extern crate actix_web;
// /// use actix_web::{pred, App, HttpResponse};
// ///
// /// fn main() {
// ///     App::new().resource("/index.html", |r| {
// ///         r.route()
// ///             .guard(pred::Host("www.rust-lang.org"))
// ///             .f(|_| HttpResponse::MethodNotAllowed())
// ///     });
// /// }
// /// ```
// pub fn Host<H: AsRef<str>>(host: H) -> HostGuard {
//     HostGuard(host.as_ref().to_string(), None)
// }

// #[doc(hidden)]
// pub struct HostGuard(String, Option<String>);

// impl HostGuard {
//     /// Set reuest scheme to match
//     pub fn scheme<H: AsRef<str>>(&mut self, scheme: H) {
//         self.1 = Some(scheme.as_ref().to_string())
//     }
// }

// impl Guard for HostGuard {
//     fn check(&self, _req: &RequestHead) -> bool {
//         // let info = req.connection_info();
//         // if let Some(ref scheme) = self.1 {
//         //     self.0 == info.host() && scheme == info.scheme()
//         // } else {
//         //     self.0 == info.host()
//         // }
//         false
//     }
// }

#[cfg(test)]
mod tests {
    use actix_http::http::{header, Method};

    use super::*;
    use crate::test::TestRequest;

    #[test]
    fn test_header() {
        let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked")
            .to_http_request();

        let pred = Header("transfer-encoding", "chunked");
        assert!(pred.check(&req));

        let pred = Header("transfer-encoding", "other");
        assert!(!pred.check(&req));

        let pred = Header("content-type", "other");
        assert!(!pred.check(&req));
    }

    // #[test]
    // fn test_host() {
    //     let req = TestServiceRequest::default()
    //         .header(
    //             header::HOST,
    //             header::HeaderValue::from_static("www.rust-lang.org"),
    //         )
    //         .request();

    //     let pred = Host("www.rust-lang.org");
    //     assert!(pred.check(&req));

    //     let pred = Host("localhost");
    //     assert!(!pred.check(&req));
    // }

    #[test]
    fn test_methods() {
        let req = TestRequest::default().to_http_request();
        let req2 = TestRequest::default()
            .method(Method::POST)
            .to_http_request();

        assert!(Get().check(&req));
        assert!(!Get().check(&req2));
        assert!(Post().check(&req2));
        assert!(!Post().check(&req));

        let r = TestRequest::default().method(Method::PUT).to_http_request();
        assert!(Put().check(&r));
        assert!(!Put().check(&req));

        let r = TestRequest::default()
            .method(Method::DELETE)
            .to_http_request();
        assert!(Delete().check(&r));
        assert!(!Delete().check(&req));

        let r = TestRequest::default()
            .method(Method::HEAD)
            .to_http_request();
        assert!(Head().check(&r));
        assert!(!Head().check(&req));

        let r = TestRequest::default()
            .method(Method::OPTIONS)
            .to_http_request();
        assert!(Options().check(&r));
        assert!(!Options().check(&req));

        let r = TestRequest::default()
            .method(Method::CONNECT)
            .to_http_request();
        assert!(Connect().check(&r));
        assert!(!Connect().check(&req));

        let r = TestRequest::default()
            .method(Method::PATCH)
            .to_http_request();
        assert!(Patch().check(&r));
        assert!(!Patch().check(&req));

        let r = TestRequest::default()
            .method(Method::TRACE)
            .to_http_request();
        assert!(Trace().check(&r));
        assert!(!Trace().check(&req));
    }

    #[test]
    fn test_preds() {
        let r = TestRequest::default()
            .method(Method::TRACE)
            .to_http_request();

        assert!(Not(Get()).check(&r));
        assert!(!Not(Trace()).check(&r));

        assert!(All(Trace()).and(Trace()).check(&r));
        assert!(!All(Get()).and(Trace()).check(&r));

        assert!(Any(Get()).or(Trace()).check(&r));
        assert!(!Any(Get()).or(Get()).check(&r));
    }
}
<commit_msg>add fn_guard<commit_after>//! Route match guards.
//!
//! Guards are one of the way how actix-web router chooses
//! handler service. In essence it just function that accepts
//! reference to a `RequestHead` instance and returns boolean.
//! It is possible to add guards to *scopes*, *resources*
//! and *routes*. Actix provide several guards by default, like various
//! http methods, header, etc. To become a guard, type must implement `Guard`
//! trait. Simple functions coulds guards as well.
//!
//! Guard can not modify request object. But it is possible to
//! to store extra attributes on a request by using `Extensions` container.
//! Extensions container available via `RequestHead::extensions()` method.
//!
//! ```rust
//! use actix_web::{web, http, dev, guard, App, HttpResponse};
//!
//! fn main() {
//!     App::new().service(web::resource("/index.html").route(
//!         web::route()
//!              .guard(guard::Post())
//!              .guard(|head: &dev::RequestHead| head.method == http::Method::GET)
//!              .to(|| HttpResponse::MethodNotAllowed()))
//!     );
//! }
//! ```

#![allow(non_snake_case)]
use actix_http::http::{self, header, HttpTryFrom};
use actix_http::RequestHead;

/// Trait defines resource guards. Guards are used for routes selection.
///
/// Guard can not modify request object. But it is possible to
/// to store extra attributes on request by using `Extensions` container,
/// Extensions container available via `RequestHead::extensions()` method.
pub trait Guard {
    /// Check if request matches predicate
    fn check(&self, request: &RequestHead) -> bool;
}

/// Return guard that matches if all of the supplied guards.
///
/// ```rust
/// use actix_web::{guard, web, App, HttpResponse};
///
/// fn main() {
///     App::new().service(web::resource("/index.html").route(
///         web::route()
///             .guard(
///                 guard::fn_guard(|req| req.headers().contains_key("content-type")))
///             .to(|| HttpResponse::MethodNotAllowed()))
///     );
/// }
/// ```
pub fn fn_guard<F>(f: F) -> impl Guard
where
    F: Fn(&RequestHead) -> bool,
{
    FnGuard(f)
}

struct FnGuard<F: Fn(&RequestHead) -> bool>(F);

impl<F> Guard for FnGuard<F>
where
    F: Fn(&RequestHead) -> bool,
{
    fn check(&self, head: &RequestHead) -> bool {
        (self.0)(head)
    }
}

/// Return guard that matches if any of supplied guards.
///
/// ```rust
/// use actix_web::{web, guard, App, HttpResponse};
///
/// fn main() {
///     App::new().service(web::resource("/index.html").route(
///         web::route()
///              .guard(guard::Any(guard::Get()).or(guard::Post()))
///              .to(|| HttpResponse::MethodNotAllowed()))
///     );
/// }
/// ```
pub fn Any<F: Guard + 'static>(guard: F) -> AnyGuard {
    AnyGuard(vec![Box::new(guard)])
}

/// Matches if any of supplied guards matche.
pub struct AnyGuard(Vec<Box<Guard>>);

impl AnyGuard {
    /// Add guard to a list of guards to check
    pub fn or<F: Guard + 'static>(mut self, guard: F) -> Self {
        self.0.push(Box::new(guard));
        self
    }
}

impl Guard for AnyGuard {
    fn check(&self, req: &RequestHead) -> bool {
        for p in &self.0 {
            if p.check(req) {
                return true;
            }
        }
        false
    }
}

/// Return guard that matches if all of the supplied guards.
///
/// ```rust
/// use actix_web::{guard, web, App, HttpResponse};
///
/// fn main() {
///     App::new().service(web::resource("/index.html").route(
///         web::route()
///             .guard(
///                 guard::All(guard::Get()).and(guard::Header("content-type", "text/plain")))
///             .to(|| HttpResponse::MethodNotAllowed()))
///     );
/// }
/// ```
pub fn All<F: Guard + 'static>(guard: F) -> AllGuard {
    AllGuard(vec![Box::new(guard)])
}

/// Matches if all of supplied guards.
pub struct AllGuard(Vec<Box<Guard>>);

impl AllGuard {
    /// Add new guard to the list of guards to check
    pub fn and<F: Guard + 'static>(mut self, guard: F) -> Self {
        self.0.push(Box::new(guard));
        self
    }
}

impl Guard for AllGuard {
    fn check(&self, request: &RequestHead) -> bool {
        for p in &self.0 {
            if !p.check(request) {
                return false;
            }
        }
        true
    }
}

/// Return guard that matches if supplied guard does not match.
pub fn Not<F: Guard + 'static>(guard: F) -> NotGuard {
    NotGuard(Box::new(guard))
}

#[doc(hidden)]
pub struct NotGuard(Box<Guard>);

impl Guard for NotGuard {
    fn check(&self, request: &RequestHead) -> bool {
        !self.0.check(request)
    }
}

/// Http method guard
#[doc(hidden)]
pub struct MethodGuard(http::Method);

impl Guard for MethodGuard {
    fn check(&self, request: &RequestHead) -> bool {
        request.method == self.0
    }
}

/// Guard to match *GET* http method
pub fn Get() -> MethodGuard {
    MethodGuard(http::Method::GET)
}

/// Predicate to match *POST* http method
pub fn Post() -> MethodGuard {
    MethodGuard(http::Method::POST)
}

/// Predicate to match *PUT* http method
pub fn Put() -> MethodGuard {
    MethodGuard(http::Method::PUT)
}

/// Predicate to match *DELETE* http method
pub fn Delete() -> MethodGuard {
    MethodGuard(http::Method::DELETE)
}

/// Predicate to match *HEAD* http method
pub fn Head() -> MethodGuard {
    MethodGuard(http::Method::HEAD)
}

/// Predicate to match *OPTIONS* http method
pub fn Options() -> MethodGuard {
    MethodGuard(http::Method::OPTIONS)
}

/// Predicate to match *CONNECT* http method
pub fn Connect() -> MethodGuard {
    MethodGuard(http::Method::CONNECT)
}

/// Predicate to match *PATCH* http method
pub fn Patch() -> MethodGuard {
    MethodGuard(http::Method::PATCH)
}

/// Predicate to match *TRACE* http method
pub fn Trace() -> MethodGuard {
    MethodGuard(http::Method::TRACE)
}

/// Predicate to match specified http method
pub fn Method(method: http::Method) -> MethodGuard {
    MethodGuard(method)
}

/// Return predicate that matches if request contains specified header and
/// value.
pub fn Header(name: &'static str, value: &'static str) -> HeaderGuard {
    HeaderGuard(
        header::HeaderName::try_from(name).unwrap(),
        header::HeaderValue::from_static(value),
    )
}

#[doc(hidden)]
pub struct HeaderGuard(header::HeaderName, header::HeaderValue);

impl Guard for HeaderGuard {
    fn check(&self, req: &RequestHead) -> bool {
        if let Some(val) = req.headers.get(&self.0) {
            return val == self.1;
        }
        false
    }
}

// /// Return predicate that matches if request contains specified Host name.
// ///
// /// ```rust,ignore
// /// # extern crate actix_web;
// /// use actix_web::{pred, App, HttpResponse};
// ///
// /// fn main() {
// ///     App::new().resource("/index.html", |r| {
// ///         r.route()
// ///             .guard(pred::Host("www.rust-lang.org"))
// ///             .f(|_| HttpResponse::MethodNotAllowed())
// ///     });
// /// }
// /// ```
// pub fn Host<H: AsRef<str>>(host: H) -> HostGuard {
//     HostGuard(host.as_ref().to_string(), None)
// }

// #[doc(hidden)]
// pub struct HostGuard(String, Option<String>);

// impl HostGuard {
//     /// Set reuest scheme to match
//     pub fn scheme<H: AsRef<str>>(&mut self, scheme: H) {
//         self.1 = Some(scheme.as_ref().to_string())
//     }
// }

// impl Guard for HostGuard {
//     fn check(&self, _req: &RequestHead) -> bool {
//         // let info = req.connection_info();
//         // if let Some(ref scheme) = self.1 {
//         //     self.0 == info.host() && scheme == info.scheme()
//         // } else {
//         //     self.0 == info.host()
//         // }
//         false
//     }
// }

#[cfg(test)]
mod tests {
    use actix_http::http::{header, Method};

    use super::*;
    use crate::test::TestRequest;

    #[test]
    fn test_header() {
        let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked")
            .to_http_request();

        let pred = Header("transfer-encoding", "chunked");
        assert!(pred.check(&req));

        let pred = Header("transfer-encoding", "other");
        assert!(!pred.check(&req));

        let pred = Header("content-type", "other");
        assert!(!pred.check(&req));
    }

    // #[test]
    // fn test_host() {
    //     let req = TestServiceRequest::default()
    //         .header(
    //             header::HOST,
    //             header::HeaderValue::from_static("www.rust-lang.org"),
    //         )
    //         .request();

    //     let pred = Host("www.rust-lang.org");
    //     assert!(pred.check(&req));

    //     let pred = Host("localhost");
    //     assert!(!pred.check(&req));
    // }

    #[test]
    fn test_methods() {
        let req = TestRequest::default().to_http_request();
        let req2 = TestRequest::default()
            .method(Method::POST)
            .to_http_request();

        assert!(Get().check(&req));
        assert!(!Get().check(&req2));
        assert!(Post().check(&req2));
        assert!(!Post().check(&req));

        let r = TestRequest::default().method(Method::PUT).to_http_request();
        assert!(Put().check(&r));
        assert!(!Put().check(&req));

        let r = TestRequest::default()
            .method(Method::DELETE)
            .to_http_request();
        assert!(Delete().check(&r));
        assert!(!Delete().check(&req));

        let r = TestRequest::default()
            .method(Method::HEAD)
            .to_http_request();
        assert!(Head().check(&r));
        assert!(!Head().check(&req));

        let r = TestRequest::default()
            .method(Method::OPTIONS)
            .to_http_request();
        assert!(Options().check(&r));
        assert!(!Options().check(&req));

        let r = TestRequest::default()
            .method(Method::CONNECT)
            .to_http_request();
        assert!(Connect().check(&r));
        assert!(!Connect().check(&req));

        let r = TestRequest::default()
            .method(Method::PATCH)
            .to_http_request();
        assert!(Patch().check(&r));
        assert!(!Patch().check(&req));

        let r = TestRequest::default()
            .method(Method::TRACE)
            .to_http_request();
        assert!(Trace().check(&r));
        assert!(!Trace().check(&req));
    }

    #[test]
    fn test_preds() {
        let r = TestRequest::default()
            .method(Method::TRACE)
            .to_http_request();

        assert!(Not(Get()).check(&r));
        assert!(!Not(Trace()).check(&r));

        assert!(All(Trace()).and(Trace()).check(&r));
        assert!(!All(Get()).and(Trace()).check(&r));

        assert!(Any(Get()).or(Trace()).check(&r));
        assert!(!Any(Get()).or(Get()).check(&r));
    }
}
<|endoftext|>


<commit_before>#![allow(non_upper_case_globals)]

use crate::abi::call::{ArgAbi, FnAbi};

fn classify_ret_ty<Ty>(ret: &mut ArgAbi<'_, Ty>) {
    if ret.layout.is_aggregate() {
        ret.make_indirect();
    } else {
        ret.extend_integer_width_to(8); // Is 8 correct?
    }
}

fn classify_arg_ty<Ty>(arg: &mut ArgAbi<'_, Ty>) {
    if arg.layout.is_aggregate() {
        arg.make_indirect();
    } else {
        arg.extend_integer_width_to(8);
    }
}

pub fn compute_abi_info<Ty>(fty: &mut FnAbi<'_, Ty>) {
    if !fty.ret.is_ignore() {
        classify_ret_ty(&mut fty.ret);
    }

    for arg in &mut fty.args {
        if arg.is_ignore() {
            continue;
        }

        classify_arg_ty(arg);
    }
}
<commit_msg>[AVR] Update ABI type classification logic to match the the AVR-Clang ABI<commit_after>//! LLVM-frontend specific AVR calling convention implementation.
//!
//! # Current calling convention ABI
//!
//! Inherited from Clang's `clang::DefaultABIInfo` implementation - self described
//! as
//!
//! > the default implementation for ABI specific details. This implementation
//! > provides information which results in
//! > self-consistent and sensible LLVM IR generation, but does not
//! > conform to any particular ABI.
//! >
//! > - Doxygen Doxumentation of `clang::DefaultABIInfo`
//!
//! This calling convention may not match AVR-GCC in all cases.
//!
//! In the future, an AVR-GCC compatible argument classification ABI should be
//! adopted in both Rust and Clang.
//!
//! *NOTE*: Currently, this module implements the same calling convention
//! that clang with AVR currently does - the default, simple, unspecialized
//! ABI implementation available to all targets. This ABI is not
//! binary-compatible with AVR-GCC. Once LLVM [PR46140](https://bugs.llvm.org/show_bug.cgi?id=46140)
//! is completed, this module should be updated to match so that both Clang
//! and Rust emit code to the same AVR-GCC compatible ABI.
//!
//! In particular, both Clang and Rust may not have the same semantics
//! when promoting arguments to indirect references as AVR-GCC. It is important
//! to note that the core AVR ABI implementation within LLVM itself is ABI
//! compatible with AVR-GCC - Rust and AVR-GCC only differ in the small amount
//! of compiler frontend specific calling convention logic implemented here.

use crate::abi::call::{ArgAbi, FnAbi};

fn classify_ret_ty<Ty>(ret: &mut ArgAbi<'_, Ty>) {
    if ret.layout.is_aggregate() {
        ret.make_indirect();
    }
}

fn classify_arg_ty<Ty>(arg: &mut ArgAbi<'_, Ty>) {
    if arg.layout.is_aggregate() {
        arg.make_indirect();
    }
}

pub fn compute_abi_info<Ty>(fty: &mut FnAbi<'_, Ty>) {
    if !fty.ret.is_ignore() {
        classify_ret_ty(&mut fty.ret);
    }

    for arg in &mut fty.args {
        if arg.is_ignore() {
            continue;
        }

        classify_arg_ty(arg);
    }
}
<|endoftext|>