<commit_before>use std::str::{Chars};
use std::iter::Peekable;

use parser_combinators::primitives::{SourcePosition};
use unicode_segmentation::{UnicodeSegmentation, GraphemeIndices};

use super::token::{Token, TokenType};

#[derive(Clone)]
struct CodeIter<'a> {
    iter: GraphemeIndices<'a>,
    buf: Option<Peekable<Chars<'a>>>,
    grapheme: Option<&'a str>,
    offset: usize,
    line: i32,
    column: i32,
}

impl <'a> Iterator for CodeIter<'a> {
    type Item = (char, usize, i32, i32);

    fn next(&mut self) -> Option<(char, usize, i32, i32)> {
        if self.buf.is_none() {
            if self.peek().is_none() {
                return None;
            }
        }
        if let Some(ref mut buf) = self.buf {
            match buf.next() {
                Some(ch) => {
                    return Some((ch, self.offset, self.line, self.column));
                }
                None => {}
            }
        }
        self.buf = None;
        return self.next();
    }
}

impl <'a> CodeIter<'a> {
    fn peek(&mut self) -> Option<(char, usize, i32, i32)> {
        if let Some(ref mut buf) = self.buf {
            if let Some(ch) = buf.peek() {
                return Some((*ch, self.offset, self.line, self.column));
            }
        }
        if let Some(gr) = self.grapheme {
            if gr == "\n" {
                self.line += 1;
                self.column = 1;
            } else {
                self.column += 1;
            }
            self.offset += gr.as_bytes().len();
            self.buf = None;
            self.grapheme = None;
        }
        match self.iter.next() {
            Some((_, grapheme)) => {
                self.grapheme = Some(grapheme);
                self.buf = Some(grapheme.chars().peekable());
                self.peek()
            }
            None => None,
        }
    }
}

#[derive(Clone)]
pub struct Tokenizer<'a> {
    data: &'a str,
    iter: CodeIter<'a>,
    braces: Vec<char>,
    indents: Vec<usize>,
}

impl<'a> Tokenizer<'a> {

    pub fn new(val: &'a str) -> Tokenizer<'a> {
        return Tokenizer {
            data: val,
            iter: CodeIter {
                iter: UnicodeSegmentation::grapheme_indices(val, true),
                buf: None,
                grapheme: None,
                offset: 0,
                line: 1,
                column: 1,
                },
            braces: vec!(),
            indents: vec!(0),
        };
    }
}

impl<'a> Iterator for Tokenizer<'a> {
    type Item = Token<'a>;
    fn next(&mut self) -> Option<Token<'a>> {
        'outer: loop {
            match self.iter.peek() {
                Some(('\n', _, _, 1)) => {
                    self.iter.next();
                    continue 'outer;
                }
                Some((' ', _, _, 1)) => {
                    let mut niter = self.iter.clone();
                    loop {
                        niter.next();
                        match niter.peek() {
                            Some((' ', _, _, _)) => continue,
                            Some(('#', _, _, _)) => {
                                self.iter = niter;
                                loop {
                                    match self.iter.next() {
                                        Some(('\n', _, _, _)) | None
                                        => continue 'outer,
                                        _ => continue,
                                    }
                                }
                            }
                            Some(('\n', _, _, _)) | None => {
                                self.iter = niter;
                                continue 'outer;
                            }
                            Some((x, _, line, col)) => {
                                let indent = (col - 1) as usize;
                                let curindent = *self.indents.last().unwrap();
                                let typ;
                                if indent == curindent {
                                    self.iter = niter;    // always commit
                                    continue 'outer;
                                } else if indent > curindent {
                                    self.indents.push(indent);
                                    self.iter = niter;    // always commit
                                    typ = TokenType::Indent;
                                } else {
                                    self.indents.pop();
                                    let nind = *self.indents.last().unwrap();
                                    if nind < indent {
                                        // TODO(tailhook) how to report err?
                                        return None;
                                    } else if nind == indent {
                                        self.iter = niter;  // commit if last
                                    }
                                    typ = TokenType::Dedent;
                                }
                                let pos = SourcePosition {
                                    line: line,
                                    column: col,
                                    };
                                return Some((typ, "", pos));
                            }
                        }
                    }
                    continue 'outer;
                }
                Some(('#', _, line, 1)) => {
                    loop {
                        match self.iter.next() {
                            Some(('\n', _, _, _)) | None => continue 'outer,
                            _ => continue,
                        }
                    }
                }
                Some((ch, _, _, 1)) if ch != ' ' && self.indents.len() > 1 => {
                    self.indents.pop().unwrap();
                    let pos = SourcePosition {
                        line: self.iter.line,
                        column: self.iter.column,
                        };
                    return Some((TokenType::Dedent, "", pos));
                }
                _ => {}
            }
            match self.iter.next() {
                Some((ch, off, line, column)) => {
                    let pos = SourcePosition {
                        line: line,
                        column: column,
                        };
                    match ch {
                        '\n' => {
                            if column == 1 {
                                continue;  // skip empty line
                            }
                            return Some((TokenType::Newline, "\n", pos));
                        }
                        '('|'{'|'[' => {
                            let typ = match ch {
                                '(' => TokenType::OpenParen,
                                '{' => TokenType::OpenBrace,
                                '[' => TokenType::OpenBracket,
                                _ => unreachable!(),
                            };
                            self.braces.push(ch);
                            return Some((typ, &self.data[off..off+1], pos));
                        }
                        ')'|'}'|']' => {
                            let (typ, val) = match ch {
                                ')' => (TokenType::CloseParen, '('),
                                '}' => (TokenType::CloseBrace, '{'),
                                ']' => (TokenType::CloseBracket, '['),
                                _ => unreachable!(),
                            };
                            let br = self.braces.pop();
                            if Some(val) == br {
                                return Some((typ, &self.data[off..off+1], pos));
                            } else {
                                // TODO(tailhook) how to communicate error?
                                return None;
                            }
                        }
                        ':'|'.'|'='|'-'|',' => {
                            let typ = match ch {
                                ':' => TokenType::Colon,
                                '.' => TokenType::Dot,
                                '=' => TokenType::Equals,
                                '-' => {
                                    match self.iter.peek() {
                                        Some(('>', _, _, _)) => {
                                            self.iter.next();
                                            TokenType::ArrowRight
                                        }
                                        _ => TokenType::Dash,
                                    }
                                }
                                ',' => TokenType::Comma,
                                _ => unreachable!(),
                            };
                            return Some((typ,
                                &self.data[off..self.iter.offset],
                                pos));
                        }
                        '#' => {
                            loop {
                                match self.iter.next() {
                                    Some(('\n', _, _, _)) | None => break,
                                    Some(_) => {}
                                }
                            }
                            return Some((TokenType::Newline, "\n", pos));
                        }
                        '"'|'\'' => {
                            let dlm = ch;
                            loop {
                                match self.iter.next() {
                                    Some((ch, _, _, _)) if ch == dlm => {
                                        break;
                                    }
                                    Some(('\\', _, _, _)) => {
                                        // any char allowed after slash
                                        self.iter.next();
                                    }
                                    Some(_) => {}
                                    None => {
                                        // TODO(tailhook) return error
                                        break;
                                    }
                                }
                            }
                            let value = &self.data[off..self.iter.offset+1];
                            return Some((TokenType::String, value, pos));
                        }
                        ' '|'\t' => {  // Skip whitespace
                            continue;
                        }
                        'a'...'z'|'A'...'Z'|'_' => {
                            let mut offset = self.data.len();
                            loop {
                                match self.iter.peek() {
                                    Some((x, off, _, _)) => {
                                        match x {
                                            'a'...'z'|'A'...'Z'|'0'...'9'|'_'
                                            => {}
                                            _ => {
                                                offset = off;
                                                break;
                                            }
                                        }
                                    }
                                    None => break,
                                }
                                self.iter.next();
                            }
                            let value = &self.data[off..offset];
                            let tok = match value {
                                "css" => TokenType::Css,
                                "html" => TokenType::Html,
                                "import" => TokenType::Import,
                                "from" => TokenType::From,
                                "if" => TokenType::If,
                                "elif" => TokenType::Elif,
                                "for" => TokenType::For,
                                "in" => TokenType::In,
                                "of" => TokenType::Of,
                                "as" => TokenType::As,
                                "else" => TokenType::Else,
                                "events" => TokenType::Events,
                                "store" => TokenType::Store,
                                "link" => TokenType::Link,
                                "new" => TokenType::New,
                                _ => TokenType::Ident,
                            };
                            return Some((tok, value, pos));
                        }
                        '0'...'9' => {
                            let mut offset = self.data.len();
                            loop {
                                match self.iter.peek() {
                                    Some((x, off, _, _)) => {
                                        match x {
                                            'a'...'z'|'A'...'Z'
                                            |'0'...'9'|'_'|'.'
                                            => {}
                                            _ => {
                                                offset = off;
                                                break;
                                            }
                                        }
                                    }
                                    None => break,
                                }
                                self.iter.next();
                            }
                            let value = &self.data[off..offset];
                            return Some((TokenType::Number, value, pos));
                        }
                        _ => {
                            return None; // Unexpected character
                        }
                    }
                }
                None => {
                    let pos = SourcePosition {
                        line: self.iter.line,
                        column: self.iter.column,
                        };
                    match self.indents.pop() {
                        Some(level) if level > 0 => {
                            return Some((TokenType::Dedent, "", pos));
                        }
                        _ => {
                            return Some((TokenType::Eof, "", pos));
                        }
                    }
                }
            }
        }
    }
}
<commit_msg>Eliminated warnings<commit_after>use std::str::{Chars};
use std::iter::Peekable;

use parser_combinators::primitives::{SourcePosition};
use unicode_segmentation::{UnicodeSegmentation, GraphemeIndices};

use super::token::{Token, TokenType};

#[derive(Clone)]
struct CodeIter<'a> {
    iter: GraphemeIndices<'a>,
    buf: Option<Peekable<Chars<'a>>>,
    grapheme: Option<&'a str>,
    offset: usize,
    line: i32,
    column: i32,
}

impl <'a> Iterator for CodeIter<'a> {
    type Item = (char, usize, i32, i32);

    fn next(&mut self) -> Option<(char, usize, i32, i32)> {
        if self.buf.is_none() {
            if self.peek().is_none() {
                return None;
            }
        }
        if let Some(ref mut buf) = self.buf {
            match buf.next() {
                Some(ch) => {
                    return Some((ch, self.offset, self.line, self.column));
                }
                None => {}
            }
        }
        self.buf = None;
        return self.next();
    }
}

impl <'a> CodeIter<'a> {
    fn peek(&mut self) -> Option<(char, usize, i32, i32)> {
        if let Some(ref mut buf) = self.buf {
            if let Some(ch) = buf.peek() {
                return Some((*ch, self.offset, self.line, self.column));
            }
        }
        if let Some(gr) = self.grapheme {
            if gr == "\n" {
                self.line += 1;
                self.column = 1;
            } else {
                self.column += 1;
            }
            self.offset += gr.as_bytes().len();
            self.buf = None;
            self.grapheme = None;
        }
        match self.iter.next() {
            Some((_, grapheme)) => {
                self.grapheme = Some(grapheme);
                self.buf = Some(grapheme.chars().peekable());
                self.peek()
            }
            None => None,
        }
    }
}

#[derive(Clone)]
pub struct Tokenizer<'a> {
    data: &'a str,
    iter: CodeIter<'a>,
    braces: Vec<char>,
    indents: Vec<usize>,
}

impl<'a> Tokenizer<'a> {

    pub fn new(val: &'a str) -> Tokenizer<'a> {
        return Tokenizer {
            data: val,
            iter: CodeIter {
                iter: UnicodeSegmentation::grapheme_indices(val, true),
                buf: None,
                grapheme: None,
                offset: 0,
                line: 1,
                column: 1,
                },
            braces: vec!(),
            indents: vec!(0),
        };
    }
}

impl<'a> Iterator for Tokenizer<'a> {
    type Item = Token<'a>;
    fn next(&mut self) -> Option<Token<'a>> {
        'outer: loop {
            match self.iter.peek() {
                Some(('\n', _, _, 1)) => {
                    self.iter.next();
                    continue 'outer;
                }
                Some((' ', _, _, 1)) => {
                    let mut niter = self.iter.clone();
                    loop {
                        niter.next();
                        match niter.peek() {
                            Some((' ', _, _, _)) => continue,
                            Some(('#', _, _, _)) => {
                                self.iter = niter;
                                loop {
                                    match self.iter.next() {
                                        Some(('\n', _, _, _)) | None
                                        => continue 'outer,
                                        _ => continue,
                                    }
                                }
                            }
                            Some(('\n', _, _, _)) | None => {
                                self.iter = niter;
                                continue 'outer;
                            }
                            Some((_, _, line, col)) => {
                                let indent = (col - 1) as usize;
                                let curindent = *self.indents.last().unwrap();
                                let typ;
                                if indent == curindent {
                                    self.iter = niter;    // always commit
                                    continue 'outer;
                                } else if indent > curindent {
                                    self.indents.push(indent);
                                    self.iter = niter;    // always commit
                                    typ = TokenType::Indent;
                                } else {
                                    self.indents.pop();
                                    let nind = *self.indents.last().unwrap();
                                    if nind < indent {
                                        // TODO(tailhook) how to report err?
                                        return None;
                                    } else if nind == indent {
                                        self.iter = niter;  // commit if last
                                    }
                                    typ = TokenType::Dedent;
                                }
                                let pos = SourcePosition {
                                    line: line,
                                    column: col,
                                    };
                                return Some((typ, "", pos));
                            }
                        }
                    }
                }
                Some(('#', _, _, 1)) => {
                    loop {
                        match self.iter.next() {
                            Some(('\n', _, _, _)) | None => continue 'outer,
                            _ => continue,
                        }
                    }
                }
                Some((ch, _, _, 1)) if ch != ' ' && self.indents.len() > 1 => {
                    self.indents.pop().unwrap();
                    let pos = SourcePosition {
                        line: self.iter.line,
                        column: self.iter.column,
                        };
                    return Some((TokenType::Dedent, "", pos));
                }
                _ => {}
            }
            match self.iter.next() {
                Some((ch, off, line, column)) => {
                    let pos = SourcePosition {
                        line: line,
                        column: column,
                        };
                    match ch {
                        '\n' => {
                            if column == 1 {
                                continue;  // skip empty line
                            }
                            return Some((TokenType::Newline, "\n", pos));
                        }
                        '('|'{'|'[' => {
                            let typ = match ch {
                                '(' => TokenType::OpenParen,
                                '{' => TokenType::OpenBrace,
                                '[' => TokenType::OpenBracket,
                                _ => unreachable!(),
                            };
                            self.braces.push(ch);
                            return Some((typ, &self.data[off..off+1], pos));
                        }
                        ')'|'}'|']' => {
                            let (typ, val) = match ch {
                                ')' => (TokenType::CloseParen, '('),
                                '}' => (TokenType::CloseBrace, '{'),
                                ']' => (TokenType::CloseBracket, '['),
                                _ => unreachable!(),
                            };
                            let br = self.braces.pop();
                            if Some(val) == br {
                                return Some((typ, &self.data[off..off+1], pos));
                            } else {
                                // TODO(tailhook) how to communicate error?
                                return None;
                            }
                        }
                        ':'|'.'|'='|'-'|',' => {
                            let typ = match ch {
                                ':' => TokenType::Colon,
                                '.' => TokenType::Dot,
                                '=' => TokenType::Equals,
                                '-' => {
                                    match self.iter.peek() {
                                        Some(('>', _, _, _)) => {
                                            self.iter.next();
                                            TokenType::ArrowRight
                                        }
                                        _ => TokenType::Dash,
                                    }
                                }
                                ',' => TokenType::Comma,
                                _ => unreachable!(),
                            };
                            return Some((typ,
                                &self.data[off..self.iter.offset],
                                pos));
                        }
                        '#' => {
                            loop {
                                match self.iter.next() {
                                    Some(('\n', _, _, _)) | None => break,
                                    Some(_) => {}
                                }
                            }
                            return Some((TokenType::Newline, "\n", pos));
                        }
                        '"'|'\'' => {
                            let dlm = ch;
                            loop {
                                match self.iter.next() {
                                    Some((ch, _, _, _)) if ch == dlm => {
                                        break;
                                    }
                                    Some(('\\', _, _, _)) => {
                                        // any char allowed after slash
                                        self.iter.next();
                                    }
                                    Some(_) => {}
                                    None => {
                                        // TODO(tailhook) return error
                                        break;
                                    }
                                }
                            }
                            let value = &self.data[off..self.iter.offset+1];
                            return Some((TokenType::String, value, pos));
                        }
                        ' '|'\t' => {  // Skip whitespace
                            continue;
                        }
                        'a'...'z'|'A'...'Z'|'_' => {
                            let mut offset = self.data.len();
                            loop {
                                match self.iter.peek() {
                                    Some((x, off, _, _)) => {
                                        match x {
                                            'a'...'z'|'A'...'Z'|'0'...'9'|'_'
                                            => {}
                                            _ => {
                                                offset = off;
                                                break;
                                            }
                                        }
                                    }
                                    None => break,
                                }
                                self.iter.next();
                            }
                            let value = &self.data[off..offset];
                            let tok = match value {
                                "css" => TokenType::Css,
                                "html" => TokenType::Html,
                                "import" => TokenType::Import,
                                "from" => TokenType::From,
                                "if" => TokenType::If,
                                "elif" => TokenType::Elif,
                                "for" => TokenType::For,
                                "in" => TokenType::In,
                                "of" => TokenType::Of,
                                "as" => TokenType::As,
                                "else" => TokenType::Else,
                                "events" => TokenType::Events,
                                "store" => TokenType::Store,
                                "link" => TokenType::Link,
                                "new" => TokenType::New,
                                _ => TokenType::Ident,
                            };
                            return Some((tok, value, pos));
                        }
                        '0'...'9' => {
                            let mut offset = self.data.len();
                            loop {
                                match self.iter.peek() {
                                    Some((x, off, _, _)) => {
                                        match x {
                                            'a'...'z'|'A'...'Z'
                                            |'0'...'9'|'_'|'.'
                                            => {}
                                            _ => {
                                                offset = off;
                                                break;
                                            }
                                        }
                                    }
                                    None => break,
                                }
                                self.iter.next();
                            }
                            let value = &self.data[off..offset];
                            return Some((TokenType::Number, value, pos));
                        }
                        _ => {
                            return None; // Unexpected character
                        }
                    }
                }
                None => {
                    let pos = SourcePosition {
                        line: self.iter.line,
                        column: self.iter.column,
                        };
                    match self.indents.pop() {
                        Some(level) if level > 0 => {
                            return Some((TokenType::Dedent, "", pos));
                        }
                        _ => {
                            return Some((TokenType::Eof, "", pos));
                        }
                    }
                }
            }
        }
    }
}
<|endoftext|>


<commit_before>// C API for mp4parse module.
// Parses ISO Base Media Format aka video/mp4 streams.

// 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 https://mozilla.org/MPL/2.0/.

use std;
use std::io::Cursor;
use byteorder;

// Symbols we need from our rust api.
use MediaContext;
use read_box;

/// Allocate an opaque rust-side parser context.
#[no_mangle]
pub extern "C" fn mp4parse_new() -> *mut MediaContext {
    unsafe {
        std::mem::transmute(Box::new(MediaContext::new()))
    }
}

/// Free a rust-side parser context.
#[no_mangle]
pub extern "C" fn mp4parse_free(context: *mut MediaContext) {
    unsafe {
        assert!(!context.is_null());
        let _: Box<MediaContext> = std::mem::transmute(context);
    }
}

/// Feed a buffer through read_box(), returning the number of detected tracks.
#[no_mangle]
pub extern "C" fn mp4parse_read(context: *mut MediaContext, buffer: *const u8, size: usize) -> i32 {
    // Validate arguments from C.
    if context.is_null() {
        return -1;
    }
    if buffer.is_null() || size < 8 {
        return -1;
    }

    let mut context: &mut MediaContext = unsafe { &mut *context };

    // Wrap the buffer we've been give in a slice.
    let b = unsafe { std::slice::from_raw_parts(buffer, size) };
    let mut c = Cursor::new(b);

    // Parse in a subthread to catch any panics.
    let task = std::thread::spawn(move || {
        loop {
            match read_box(&mut c, &mut context) {
                Ok(_) => {},
                Err(byteorder::Error::UnexpectedEOF) => { break },
                Err(e) => { panic!(e); },
            }
        }
        // Make sure the track count fits in an i32 so we can use
        // negative values for failure.
        assert!(context.tracks.len() < std::i32::MAX as usize);
        context.tracks.len() as i32
    });
    task.join().unwrap_or(-1)
}

#[test]
fn new_context() {
    let context = mp4parse_new();
    assert!(!context.is_null());
    mp4parse_free(context);
}

#[test]
#[should_panic(expected = "assertion failed")]
fn free_null_context() {
    mp4parse_free(std::ptr::null_mut());
}

#[test]
fn arg_validation() {
    let null_buffer = std::ptr::null();
    let null_context = std::ptr::null_mut();
    assert_eq!(-1, mp4parse_read(null_context, null_buffer, 0));

    let context = mp4parse_new();
    assert!(!context.is_null());

    assert_eq!(-1, mp4parse_read(context, null_buffer, 0));

    let buffer = vec![0u8; 8];
    for size in (0..buffer.len()) {
        println!("testing buffer length {}", size);
        assert_eq!(-1, mp4parse_read(context, buffer.as_ptr(), size));
    }

    mp4parse_free(context);
}
<commit_msg>Restore unsafe to mp4parse_read and mp4parse_free.<commit_after>// C API for mp4parse module.
// Parses ISO Base Media Format aka video/mp4 streams.

// 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 https://mozilla.org/MPL/2.0/.

use std;
use std::io::Cursor;
use byteorder;

// Symbols we need from our rust api.
use MediaContext;
use read_box;

/// Allocate an opaque rust-side parser context.
#[no_mangle]
pub extern "C" fn mp4parse_new() -> *mut MediaContext {
    let context = Box::new(MediaContext::new());
    unsafe {
        // transmute is unsafe, but context is always valid.
        std::mem::transmute(context)
    }
}

/// Free a rust-side parser context.
#[no_mangle]
pub unsafe extern "C" fn mp4parse_free(context: *mut MediaContext) {
    assert!(!context.is_null());
    let _: Box<MediaContext> = std::mem::transmute(context);
}

/// Feed a buffer through read_box(), returning the number of detected tracks.
#[no_mangle]
pub unsafe extern "C" fn mp4parse_read(context: *mut MediaContext, buffer: *const u8, size: usize) -> i32 {
    // Validate arguments from C.
    if context.is_null() {
        return -1;
    }
    if buffer.is_null() || size < 8 {
        return -1;
    }

    let mut context: &mut MediaContext = &mut *context;

    // Wrap the buffer we've been give in a slice.
    let b = std::slice::from_raw_parts(buffer, size);
    let mut c = Cursor::new(b);

    // Parse in a subthread to catch any panics.
    let task = std::thread::spawn(move || {
        loop {
            match read_box(&mut c, &mut context) {
                Ok(_) => {},
                Err(byteorder::Error::UnexpectedEOF) => { break },
                Err(e) => { panic!(e); },
            }
        }
        // Make sure the track count fits in an i32 so we can use
        // negative values for failure.
        assert!(context.tracks.len() < std::i32::MAX as usize);
        context.tracks.len() as i32
    });
    task.join().unwrap_or(-1)
}

#[test]
fn new_context() {
    let context = mp4parse_new();
    assert!(!context.is_null());
    unsafe { mp4parse_free(context); }
}

#[test]
#[should_panic(expected = "assertion failed")]
fn free_null_context() {
    unsafe { mp4parse_free(std::ptr::null_mut()); }
}

#[test]
fn arg_validation() {
    let null_buffer = std::ptr::null();
    let null_context = std::ptr::null_mut();

    let context = mp4parse_new();
    assert!(!context.is_null());

    let buffer = vec![0u8; 8];

    unsafe {
        assert_eq!(-1, mp4parse_read(null_context, null_buffer, 0));
        assert_eq!(-1, mp4parse_read(context, null_buffer, 0));
    }

    for size in (0..buffer.len()) {
        println!("testing buffer length {}", size);
        unsafe {
            assert_eq!(-1, mp4parse_read(context, buffer.as_ptr(), size));
        }
    }

    unsafe { mp4parse_free(context); }
}
<|endoftext|>


<commit_before><commit_msg>Relax orderings of Serialize impl for atomic types to match the latest stable<commit_after><|endoftext|>


<commit_before>//! Library used by tidy and other tools.
//!
//! This library contains the tidy lints and exposes it
//! to be used by tools.

use std::fs::File;
use std::io::Read;
use walkdir::{DirEntry, WalkDir};

use std::path::Path;

macro_rules! t {
    ($e:expr, $p:expr) => {
        match $e {
            Ok(e) => e,
            Err(e) => panic!("{} failed on {} with {}", stringify!($e), ($p).display(), e),
        }
    };

    ($e:expr) => {
        match $e {
            Ok(e) => e,
            Err(e) => panic!("{} failed with {}", stringify!($e), e),
        }
    };
}

macro_rules! tidy_error {
    ($bad:expr, $fmt:expr) => ({
        *$bad = true;
        eprintln!("tidy error: {}", $fmt);
    });
    ($bad:expr, $fmt:expr, $($arg:tt)*) => ({
        *$bad = true;
        eprint!("tidy error: ");
        eprintln!($fmt, $($arg)*);
    });
}

pub mod bins;
pub mod debug_artifacts;
pub mod deps;
pub mod edition;
pub mod error_codes_check;
pub mod errors;
pub mod extdeps;
pub mod features;
pub mod pal;
pub mod style;
pub mod ui_tests;
pub mod unit_tests;
pub mod unstable_book;

fn filter_dirs(path: &Path) -> bool {
    let skip = [
        "compiler/rustc_codegen_cranelift",
        "src/llvm-project",
        "library/backtrace",
        "library/stdarch",
        "src/tools/cargo",
        "src/tools/clippy",
        "src/tools/miri",
        "src/tools/rls",
        "src/tools/rust-analyzer",
        "src/tools/rust-installer",
        "src/tools/rustfmt",
        "src/doc/book",
        // Filter RLS output directories
        "target/rls",
    ];
    skip.iter().any(|p| path.ends_with(p))
}

fn walk_many(
    paths: &[&Path],
    skip: &mut dyn FnMut(&Path) -> bool,
    f: &mut dyn FnMut(&DirEntry, &str),
) {
    for path in paths {
        walk(path, skip, f);
    }
}

fn walk(path: &Path, skip: &mut dyn FnMut(&Path) -> bool, f: &mut dyn FnMut(&DirEntry, &str)) {
    let mut contents = String::new();
    walk_no_read(path, skip, &mut |entry| {
        contents.clear();
        if t!(File::open(entry.path()), entry.path()).read_to_string(&mut contents).is_err() {
            contents.clear();
        }
        f(&entry, &contents);
    });
}

fn walk_no_read(path: &Path, skip: &mut dyn FnMut(&Path) -> bool, f: &mut dyn FnMut(&DirEntry)) {
    let walker = WalkDir::new(path).into_iter().filter_entry(|e| !skip(e.path()));
    for entry in walker {
        if let Ok(entry) = entry {
            if entry.file_type().is_dir() {
                continue;
            }
            f(&entry);
        }
    }
}
<commit_msg>Rollup merge of #83878 - the8472:fix-concurrent-tidy-access, r=Mark-Simulacrum<commit_after>//! Library used by tidy and other tools.
//!
//! This library contains the tidy lints and exposes it
//! to be used by tools.

use std::fs::File;
use std::io::Read;
use walkdir::{DirEntry, WalkDir};

use std::path::Path;

macro_rules! t {
    ($e:expr, $p:expr) => {
        match $e {
            Ok(e) => e,
            Err(e) => panic!("{} failed on {} with {}", stringify!($e), ($p).display(), e),
        }
    };

    ($e:expr) => {
        match $e {
            Ok(e) => e,
            Err(e) => panic!("{} failed with {}", stringify!($e), e),
        }
    };
}

macro_rules! tidy_error {
    ($bad:expr, $fmt:expr) => ({
        *$bad = true;
        eprintln!("tidy error: {}", $fmt);
    });
    ($bad:expr, $fmt:expr, $($arg:tt)*) => ({
        *$bad = true;
        eprint!("tidy error: ");
        eprintln!($fmt, $($arg)*);
    });
}

pub mod bins;
pub mod debug_artifacts;
pub mod deps;
pub mod edition;
pub mod error_codes_check;
pub mod errors;
pub mod extdeps;
pub mod features;
pub mod pal;
pub mod style;
pub mod ui_tests;
pub mod unit_tests;
pub mod unstable_book;

fn filter_dirs(path: &Path) -> bool {
    let skip = [
        "tidy-test-file",
        "compiler/rustc_codegen_cranelift",
        "src/llvm-project",
        "library/backtrace",
        "library/stdarch",
        "src/tools/cargo",
        "src/tools/clippy",
        "src/tools/miri",
        "src/tools/rls",
        "src/tools/rust-analyzer",
        "src/tools/rust-installer",
        "src/tools/rustfmt",
        "src/doc/book",
        // Filter RLS output directories
        "target/rls",
    ];
    skip.iter().any(|p| path.ends_with(p))
}

fn walk_many(
    paths: &[&Path],
    skip: &mut dyn FnMut(&Path) -> bool,
    f: &mut dyn FnMut(&DirEntry, &str),
) {
    for path in paths {
        walk(path, skip, f);
    }
}

fn walk(path: &Path, skip: &mut dyn FnMut(&Path) -> bool, f: &mut dyn FnMut(&DirEntry, &str)) {
    let mut contents = String::new();
    walk_no_read(path, skip, &mut |entry| {
        contents.clear();
        if t!(File::open(entry.path()), entry.path()).read_to_string(&mut contents).is_err() {
            contents.clear();
        }
        f(&entry, &contents);
    });
}

fn walk_no_read(path: &Path, skip: &mut dyn FnMut(&Path) -> bool, f: &mut dyn FnMut(&DirEntry)) {
    let walker = WalkDir::new(path).into_iter().filter_entry(|e| !skip(e.path()));
    for entry in walker {
        if let Ok(entry) = entry {
            if entry.file_type().is_dir() {
                continue;
            }
            f(&entry);
        }
    }
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2017-2018 Ryan Huang
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

use std::mem;

mod board;
mod constants;

pub use self::board::{Board, Outcome};
use ai::AIHandle;

pub struct Model {
    pub board: Board,
    pub players: ColorMap<PlayerType>,
    pub last_move: Option<Move>,
    pub selected_piece: Option<FieldCoord>,
    pub available_moves: Option<Vec<FieldCoord>>,
    pub exchanging: bool,
    pub ai_handle: Option<AIHandle>,
    undo_stack: Vec<(Board, Option<Move>)>,
    redo_stack: Vec<(Board, Option<Move>)>,
}

impl Model {
    pub fn new(players: ColorMap<PlayerType>) -> Self {
        Self {
            players,
            ..Default::default()
        }
    }
    pub fn can_undo(&self) -> bool {
        !self.undo_stack.is_empty()
    }
    pub fn can_redo(&self) -> bool {
        !self.redo_stack.is_empty()
    }
    pub fn commit_state(&mut self) {
        self.undo_stack.push((self.board, self.last_move));
        self.redo_stack.clear();
    }
    pub fn undo_move(&mut self) {
        if let Some((board, last_move)) = self.undo_stack.pop() {
            self.redo_stack.push((
                mem::replace(&mut self.board, board),
                mem::replace(&mut self.last_move, last_move),
            ));

            self.clear_selection();
            self.exchanging = false;
        }
    }
    pub fn redo_move(&mut self) {
        if let Some((board, last_move)) = self.redo_stack.pop() {
            self.undo_stack.push((
                mem::replace(&mut self.board, board),
                mem::replace(&mut self.last_move, last_move),
            ));

            self.clear_selection();
            self.exchanging = false;
        }
    }
    pub fn clear_selection(&mut self) {
        self.selected_piece = None;
        self.available_moves = None;
    }
    pub fn is_ai_turn(&self) -> bool {
        self.players.get_ref(self.board.turn()) == &PlayerType::Computer
    }
    pub fn is_game_over(&self) -> bool {
        self.board.outcome() != Outcome::InProgress
    }
}

impl Default for Model {
    fn default() -> Self {
        Self {
            board: Board::new(),
            players: ColorMap::new(PlayerType::Human, PlayerType::Human),
            selected_piece: None,
            last_move: None,
            available_moves: None,
            exchanging: false,
            ai_handle: None,
            undo_stack: vec![],
            redo_stack: vec![],
        }
    }
}

pub type BitBoard = u64;

pub fn pop_bit(bb: &mut BitBoard) -> BitBoard {
    // bb & -bb
    let bit = *bb & (1 + !*bb);
    *bb ^= bit;
    bit
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Color {
    White,
    Black,
}

impl Color {
    pub fn switch(&self) -> Self {
        match *self {
            Color::White => Color::Black,
            Color::Black => Color::White,
        }
    }
}

#[derive(Debug, PartialEq)]
pub enum PlayerType {
    Human,
    Computer,
}

#[derive(Clone, Copy, Debug)]
pub enum Move {
    Exchange(BitBoard, Color),
    Move(BitBoard, BitBoard, Color),
}

impl Move {
    pub fn move_from_field(from: &FieldCoord, to: &FieldCoord) -> Self {
        Move::Move(from.to_bitboard(), to.to_bitboard(), from.color())
    }
    pub fn exchange_from_field(field: &FieldCoord) -> Self {
        Move::Exchange(field.to_bitboard(), field.color())
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FieldCoord {
    x: i8,
    y: i8,
    f: u8,
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct HexCoord {
    x: i8,
    y: i8,
}

/// A map to associate any two values with the variants of the Color enum. Useful for keeping
/// track of player-specific information, which almost always comes in pairs.
#[derive(Clone, Copy, PartialEq)]
pub struct ColorMap<T> {
    pub white: T,
    pub black: T,
}

impl<T> ColorMap<T> {
    pub fn new(white: T, black: T) -> Self {
        Self { white, black }
    }
    pub fn get_ref(&self, color: Color) -> &T {
        match color {
            Color::White => &self.white,
            Color::Black => &self.black,
        }
    }
    pub fn get_mut(&mut self, color: Color) -> &mut T {
        match color {
            Color::White => &mut self.white,
            Color::Black => &mut self.black,
        }
    }
}

impl FieldCoord {
    pub fn new(x: i8, y: i8, f: u8) -> Self {
        assert!(Self::is_valid_coord(x, y, f));
        Self { x, y, f }
    }
    pub fn from_bitboard(bb: BitBoard, color: Color) -> Self {
        Self::from_index(bb.trailing_zeros() as u8, color)
    }
    pub fn from_index(index: u8, color: Color) -> Self {
        assert!(index < 57);

        let f = 2 * (index % 3) + match color {
            Color::White => 1,
            Color::Black => 0,
        };

        Self::from_hex_f(index / 3, f)
    }
    pub fn from_hex_f(hex: u8, f: u8) -> Self {
        assert!(hex < 19);
        assert!(f < 6);

        let hex = hex as i8 + match hex {
            0...2 => 2,
            3...15 => 3,
            16...18 => 4,
            _ => unreachable!(),
        };
        Self::new(hex % 5 - 2, hex / 5 - 2, f as u8)
    }
    pub fn to_index(&self) -> usize {
        self.to_bitboard().trailing_zeros() as usize
    }
    pub fn to_hex(&self) -> HexCoord {
        HexCoord {
            x: self.x,
            y: self.y,
        }
    }
    pub fn to_bitboard(&self) -> BitBoard {
        let hex = 5 * (self.y + 2) + self.x + 2;
        let hex = hex as u8 - match hex {
            2...4 => 2,
            6...18 => 3,
            20...22 => 4,
            _ => unreachable!(),
        };

        1 << (hex * 3 + self.f / 2)
    }
    pub fn f(&self) -> u8 {
        self.f
    }
    pub fn color(&self) -> Color {
        if self.f % 2 == 0 {
            Color::Black
        } else {
            Color::White
        }
    }
    fn is_valid_coord(x: i8, y: i8, f: u8) -> bool {
        (x + y).abs() <= 2 && x.abs() <= 2 && y.abs() <= 2 && f < 6
    }
}

impl HexCoord {
    pub fn try_new(x: i8, y: i8) -> Option<Self> {
        if Self::is_valid_coord(x, y) {
            Some(Self { x, y })
        } else {
            None
        }
    }
    pub fn x(&self) -> i8 {
        self.x
    }
    pub fn y(&self) -> i8 {
        self.y
    }
    pub fn to_field(&self, f: u8) -> FieldCoord {
        assert!(f < 6);
        FieldCoord {
            x: self.x,
            y: self.y,
            f,
        }
    }
    pub fn to_index(&self) -> usize {
        let hex = 5 * (self.y + 2) + self.x + 2;
        hex as usize - match hex {
            2...4 => 2,
            6...18 => 3,
            20...22 => 4,
            _ => unreachable!(),
        }
    }
    fn is_valid_coord(x: i8, y: i8) -> bool {
        (x + y).abs() <= 2 && x.abs() <= 2 && y.abs() <= 2
    }
}

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

    #[test]
    fn index_reflextivity() {
        for index in 0..57 {
            let white = FieldCoord::from_index(index as u8, Color::White);
            assert_eq!(index, white.to_index());

            let black = FieldCoord::from_index(index as u8, Color::Black);
            assert_eq!(index, black.to_index());
        }
    }
}
<commit_msg>Rearrange model structs and enums<commit_after>/*
 * Copyright (C) 2017-2018 Ryan Huang
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

use std::mem;

mod board;
mod constants;

pub use self::board::{Board, Outcome};
use ai::AIHandle;

pub struct Model {
    pub board: Board,
    pub players: ColorMap<PlayerType>,
    pub last_move: Option<Move>,
    pub selected_piece: Option<FieldCoord>,
    pub available_moves: Option<Vec<FieldCoord>>,
    pub exchanging: bool,
    pub ai_handle: Option<AIHandle>,
    undo_stack: Vec<(Board, Option<Move>)>,
    redo_stack: Vec<(Board, Option<Move>)>,
}

impl Model {
    pub fn new(players: ColorMap<PlayerType>) -> Self {
        Self {
            players,
            ..Default::default()
        }
    }
    pub fn can_undo(&self) -> bool {
        !self.undo_stack.is_empty()
    }
    pub fn can_redo(&self) -> bool {
        !self.redo_stack.is_empty()
    }
    pub fn commit_state(&mut self) {
        self.undo_stack.push((self.board, self.last_move));
        self.redo_stack.clear();
    }
    pub fn undo_move(&mut self) {
        if let Some((board, last_move)) = self.undo_stack.pop() {
            self.redo_stack.push((
                mem::replace(&mut self.board, board),
                mem::replace(&mut self.last_move, last_move),
            ));

            self.clear_selection();
            self.exchanging = false;
        }
    }
    pub fn redo_move(&mut self) {
        if let Some((board, last_move)) = self.redo_stack.pop() {
            self.undo_stack.push((
                mem::replace(&mut self.board, board),
                mem::replace(&mut self.last_move, last_move),
            ));

            self.clear_selection();
            self.exchanging = false;
        }
    }
    pub fn clear_selection(&mut self) {
        self.selected_piece = None;
        self.available_moves = None;
    }
    pub fn is_ai_turn(&self) -> bool {
        self.players.get_ref(self.board.turn()) == &PlayerType::Computer
    }
    pub fn is_game_over(&self) -> bool {
        self.board.outcome() != Outcome::InProgress
    }
}

impl Default for Model {
    fn default() -> Self {
        Self {
            board: Board::new(),
            players: ColorMap::new(PlayerType::Human, PlayerType::Human),
            selected_piece: None,
            last_move: None,
            available_moves: None,
            exchanging: false,
            ai_handle: None,
            undo_stack: vec![],
            redo_stack: vec![],
        }
    }
}

pub type BitBoard = u64;

pub fn pop_bit(bb: &mut BitBoard) -> BitBoard {
    // bb & -bb
    let bit = *bb & (1 + !*bb);
    *bb ^= bit;
    bit
}

#[derive(Debug, PartialEq)]
pub enum PlayerType {
    Human,
    Computer,
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Color {
    White,
    Black,
}

impl Color {
    pub fn switch(&self) -> Self {
        match *self {
            Color::White => Color::Black,
            Color::Black => Color::White,
        }
    }
}

/// A map to associate any two values with the variants of the Color enum. Useful for keeping
/// track of player-specific information, which almost always comes in pairs.
#[derive(Clone, Copy, PartialEq)]
pub struct ColorMap<T> {
    pub white: T,
    pub black: T,
}

impl<T> ColorMap<T> {
    pub fn new(white: T, black: T) -> Self {
        Self { white, black }
    }
    pub fn get_ref(&self, color: Color) -> &T {
        match color {
            Color::White => &self.white,
            Color::Black => &self.black,
        }
    }
    pub fn get_mut(&mut self, color: Color) -> &mut T {
        match color {
            Color::White => &mut self.white,
            Color::Black => &mut self.black,
        }
    }
}

#[derive(Clone, Copy, Debug)]
pub enum Move {
    Exchange(BitBoard, Color),
    Move(BitBoard, BitBoard, Color),
}

impl Move {
    pub fn move_from_field(from: &FieldCoord, to: &FieldCoord) -> Self {
        Move::Move(from.to_bitboard(), to.to_bitboard(), from.color())
    }
    pub fn exchange_from_field(field: &FieldCoord) -> Self {
        Move::Exchange(field.to_bitboard(), field.color())
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FieldCoord {
    x: i8,
    y: i8,
    f: u8,
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct HexCoord {
    x: i8,
    y: i8,
}

impl FieldCoord {
    pub fn new(x: i8, y: i8, f: u8) -> Self {
        assert!(Self::is_valid_coord(x, y, f));
        Self { x, y, f }
    }
    pub fn from_bitboard(bb: BitBoard, color: Color) -> Self {
        Self::from_index(bb.trailing_zeros() as u8, color)
    }
    pub fn from_index(index: u8, color: Color) -> Self {
        assert!(index < 57);

        let f = 2 * (index % 3) + match color {
            Color::White => 1,
            Color::Black => 0,
        };

        Self::from_hex_f(index / 3, f)
    }
    pub fn from_hex_f(hex: u8, f: u8) -> Self {
        assert!(hex < 19);
        assert!(f < 6);

        let hex = hex as i8 + match hex {
            0...2 => 2,
            3...15 => 3,
            16...18 => 4,
            _ => unreachable!(),
        };
        Self::new(hex % 5 - 2, hex / 5 - 2, f as u8)
    }
    pub fn to_index(&self) -> usize {
        self.to_bitboard().trailing_zeros() as usize
    }
    pub fn to_hex(&self) -> HexCoord {
        HexCoord {
            x: self.x,
            y: self.y,
        }
    }
    pub fn to_bitboard(&self) -> BitBoard {
        let hex = 5 * (self.y + 2) + self.x + 2;
        let hex = hex as u8 - match hex {
            2...4 => 2,
            6...18 => 3,
            20...22 => 4,
            _ => unreachable!(),
        };

        1 << (hex * 3 + self.f / 2)
    }
    pub fn f(&self) -> u8 {
        self.f
    }
    pub fn color(&self) -> Color {
        if self.f % 2 == 0 {
            Color::Black
        } else {
            Color::White
        }
    }
    fn is_valid_coord(x: i8, y: i8, f: u8) -> bool {
        (x + y).abs() <= 2 && x.abs() <= 2 && y.abs() <= 2 && f < 6
    }
}

impl HexCoord {
    pub fn try_new(x: i8, y: i8) -> Option<Self> {
        if Self::is_valid_coord(x, y) {
            Some(Self { x, y })
        } else {
            None
        }
    }
    pub fn x(&self) -> i8 {
        self.x
    }
    pub fn y(&self) -> i8 {
        self.y
    }
    pub fn to_field(&self, f: u8) -> FieldCoord {
        assert!(f < 6);
        FieldCoord {
            x: self.x,
            y: self.y,
            f,
        }
    }
    pub fn to_index(&self) -> usize {
        let hex = 5 * (self.y + 2) + self.x + 2;
        hex as usize - match hex {
            2...4 => 2,
            6...18 => 3,
            20...22 => 4,
            _ => unreachable!(),
        }
    }
    fn is_valid_coord(x: i8, y: i8) -> bool {
        (x + y).abs() <= 2 && x.abs() <= 2 && y.abs() <= 2
    }
}

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

    #[test]
    fn index_reflextivity() {
        for index in 0..57 {
            let white = FieldCoord::from_index(index as u8, Color::White);
            assert_eq!(index, white.to_index());

            let black = FieldCoord::from_index(index as u8, Color::Black);
            assert_eq!(index, black.to_index());
        }
    }
}
<|endoftext|>


<commit_before>use luminance::{Dim2, Flat, RGBA32F, Texture};

/// Time.
pub type Time = f32;

/// A clip is an object that implements a visual sequence.
pub struct Clip<'a, 'b> where 'a: 'b {
  act: Box<Fn(Time) -> &'a Texture<Flat, Dim2, RGBA32F> + 'b>
}

impl<'a, 'b> Clip<'a, 'b> {
  pub fn new<F>(act: F) -> Self where F: 'b + Fn(Time) -> &'a Texture<Flat, Dim2, RGBA32F> {
    Clip {
      act: Box::new(act)
    }
  }
}

/// A cut is an object that slices a `Clip` at an *input time* and *output time*. It is instantiated
/// in a `Track` at a given *instance time*.
#[derive(Clone)]
pub struct Cut<'a, 'b, 'c> where 'a: 'b, 'b: 'c {
  pub in_time: Time,
  pub out_time: Time,
  pub inst_time: Time,
  pub clip: &'c Clip<'a, 'b>
}

impl<'a, 'b, 'c> Cut<'a, 'b, 'c> where 'a: 'b, 'b: 'c {
  pub fn new(in_time: Time, out_time: Time, inst_time: Time, clip: &'c Clip<'a, 'b>) -> Self {
    assert!(in_time <= out_time);

    Cut {
      in_time: in_time,
      out_time: out_time,
      inst_time: inst_time,
      clip: clip
    }
  }

  /// Duration of the cut.
  pub fn dur(&self) -> Time {
    self.out_time - self.in_time
  }
}

/// A track gathers `Cut`s and its purpose is to be used inside a `Timeline`.
#[derive(Clone)]
pub struct Track<'a, 'b, 'c> where 'a: 'b, 'b: 'c {
  cuts: Vec<Cut<'a, 'b, 'c>>
}

impl<'a, 'b, 'c> Track<'a, 'b, 'c> where 'a: 'b, 'b: 'c {
  pub fn new() -> Self {
    Track {
      cuts: Vec::new()
    }
  }

  pub fn add_cut(&mut self, cut: Cut<'a, 'b, 'c>) {
    self.cuts.push(cut);
  }
}

impl<'a, 'b, 'c, 'd> From<&'d [Cut<'a, 'b, 'c>]> for Track<'a, 'b, 'c> {
  fn from(cuts: &'d [Cut<'a, 'b, 'c>]) -> Self {
    Track {
      cuts: cuts.to_vec()
    }
  }
}

/// A timeline gathers tracks used to build up the visual aspect of the demo.
#[derive(Clone)]
pub struct Timeline<'a, 'b, 'c> where 'a: 'b, 'b: 'c {
  tracks: Vec<Track<'a, 'b, 'c>>,
}

impl<'a, 'b, 'c> Timeline<'a, 'b, 'c> where 'a: 'b, 'b: 'c {
  pub fn new() -> Self {
    Timeline {
      tracks: Vec::new(),
    }
  }

  pub fn add_track(&mut self, track: Track<'a, 'b, 'c>) {
    self.tracks.push(track);
  }

  // TODO: currently, we play the first track we find; add transition support
  pub fn play(&self, t: Time) -> Option<&'a Texture<Flat, Dim2, RGBA32F>> {
    for track in &self.tracks {
      for cut in &track.cuts {
        if cut.inst_time <= t && t <= cut.inst_time + cut.dur() {
          return Some((cut.clip.act)(t));
        }
      }
    }

    None
  }
}

impl<'a, 'b, 'c, 'd> From<&'d [Track<'a, 'b, 'c>]> for Timeline<'a, 'b, 'c> {
  fn from(tracks: &'d [Track<'a, 'b, 'c>]) -> Self {
    Timeline {
      tracks: tracks.to_vec(),
    }
  }
}
<commit_msg>Added manifests in edit.rs.<commit_after>use luminance::{Dim2, Flat, RGBA32F, Texture};
use serde_json::from_reader;
use std::fs::File;
use std::path::Path;

use resource::{Load, LoadError, Result, ResCache};

/// Time.
pub type Time = f32;

/// A clip is an object that implements a visual sequence.
pub struct Clip<'a, 'b> where 'a: 'b {
  act: Box<Fn(Time) -> &'a Texture<Flat, Dim2, RGBA32F> + 'b>
}

impl<'a, 'b> Clip<'a, 'b> {
  pub fn new<F>(act: F) -> Self where F: 'b + Fn(Time) -> &'a Texture<Flat, Dim2, RGBA32F> {
    Clip {
      act: Box::new(act)
    }
  }
}

/// A cut is an object that slices a `Clip` at an *input time* and *output time*. It is instantiated
/// in a `Track` at a given *instance time*.
#[derive(Clone)]
pub struct Cut<'a, 'b, 'c> where 'a: 'b, 'b: 'c {
  pub in_time: Time,
  pub out_time: Time,
  pub inst_time: Time,
  pub clip: &'c Clip<'a, 'b>
}

impl<'a, 'b, 'c> Cut<'a, 'b, 'c> where 'a: 'b, 'b: 'c {
  pub fn new(in_time: Time, out_time: Time, inst_time: Time, clip: &'c Clip<'a, 'b>) -> Self {
    assert!(in_time <= out_time);

    Cut {
      in_time: in_time,
      out_time: out_time,
      inst_time: inst_time,
      clip: clip
    }
  }

  /// Duration of the cut.
  pub fn dur(&self) -> Time {
    self.out_time - self.in_time
  }
}

/// A track gathers `Cut`s and its purpose is to be used inside a `Timeline`.
#[derive(Clone)]
pub struct Track<'a, 'b, 'c> where 'a: 'b, 'b: 'c {
  cuts: Vec<Cut<'a, 'b, 'c>>
}

impl<'a, 'b, 'c> Track<'a, 'b, 'c> where 'a: 'b, 'b: 'c {
  pub fn new() -> Self {
    Track {
      cuts: Vec::new()
    }
  }

  pub fn add_cut(&mut self, cut: Cut<'a, 'b, 'c>) {
    self.cuts.push(cut);
  }
}

impl<'a, 'b, 'c, 'd> From<&'d [Cut<'a, 'b, 'c>]> for Track<'a, 'b, 'c> {
  fn from(cuts: &'d [Cut<'a, 'b, 'c>]) -> Self {
    Track {
      cuts: cuts.to_vec()
    }
  }
}

/// A timeline gathers tracks used to build up the visual aspect of the demo.
#[derive(Clone)]
pub struct Timeline<'a, 'b, 'c> where 'a: 'b, 'b: 'c {
  tracks: Vec<Track<'a, 'b, 'c>>,
}

impl<'a, 'b, 'c> Timeline<'a, 'b, 'c> where 'a: 'b, 'b: 'c {
  pub fn new() -> Self {
    Timeline {
      tracks: Vec::new(),
    }
  }

  pub fn add_track(&mut self, track: Track<'a, 'b, 'c>) {
    self.tracks.push(track);
  }

  // TODO: currently, we play the first track we find; add transition support
  pub fn play(&self, t: Time) -> Option<&'a Texture<Flat, Dim2, RGBA32F>> {
    for track in &self.tracks {
      for cut in &track.cuts {
        if cut.inst_time <= t && t <= cut.inst_time + cut.dur() {
          return Some((cut.clip.act)(t));
        }
      }
    }

    None
  }
}

impl<'a, 'b, 'c, 'd> From<&'d [Track<'a, 'b, 'c>]> for Timeline<'a, 'b, 'c> {
  fn from(tracks: &'d [Track<'a, 'b, 'c>]) -> Self {
    Timeline {
      tracks: tracks.to_vec(),
    }
  }
}

#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct TimelineManifest {
  pub tracks: Vec<TrackManifest>
}

impl Load for TimelineManifest {
  type Args = ();

  const TY_STR: &'static str = "edit/timelines";

  fn load<P>(path: P, cache: &mut ResCache, args: Self::Args) -> Result<Self> where P: AsRef<Path> {
    let path = path.as_ref();

    info!("loading timeline: {:?}", path);

    let file = File::open(path).map_err(|e| LoadError::FileNotFound(path.to_path_buf(), format!("{:?}", e)))?;
    let tracks = from_reader(file).map_err(|e| LoadError::ParseFailed(format!("{:?}", e)))?;

    Ok(TimelineManifest {
      tracks: tracks
    })
  }
}

#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct TrackManifest {
  pub cuts: Vec<CutManifest>
}

impl Load for TrackManifest {
  type Args = ();

  const TY_STR: &'static str = "edit/tracks";

  fn load<P>(path: P, cache: &mut ResCache, args: Self::Args) -> Result<Self> where P: AsRef<Path> {
    let path = path.as_ref();

    info!("loading track: {:?}", path);

    let file = File::open(path).map_err(|e| LoadError::FileNotFound(path.to_path_buf(), format!("{:?}", e)))?;
    let cuts = from_reader(file).map_err(|e| LoadError::ParseFailed(format!("{:?}", e)))?;

    Ok(TrackManifest {
      cuts: cuts
    })
  }
}

#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct CutManifest {
  pub in_time: Time,
  pub out_time: Time,
  pub inst_time: Time,
  pub clip: String
}

impl Load for CutManifest {
  type Args = ();

  const TY_STR: &'static str = "edit/cuts";

  fn load<P>(path: P, cache: &mut ResCache, args: Self::Args) -> Result<Self> where P: AsRef<Path> {
    let path = path.as_ref();

    info!("loading cut: {:?}", path);

    let file = File::open(path).map_err(|e| LoadError::FileNotFound(path.to_path_buf(), format!("{:?}", e)))?;
    from_reader(file).map_err(|e| LoadError::ParseFailed(format!("{:?}", e)))
  }
}
<|endoftext|>


<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::mpsc::{channel, Receiver};

use gtk::prelude::*;

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

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

#[derive(Clone, PartialEq)]
pub enum ControllerState {
    EOS,
    Ready,
    Paused,
    Playing,
    Stopped,
}

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,
    build_toc_btn: gtk::Button,

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

    context: Option<Context>,
    state: ControllerState,
    duration: Option<u64>, // duration is accurately known at eos
    seeking: bool,

    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 build_toc_btn: gtk::Button = builder.get_object("build_toc-btn").unwrap();
        build_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(),
            build_toc_btn: build_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,
            duration: None,
            seeking: 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.build_toc_btn.connect_clicked(move |_| {
                this_rc.borrow_mut().build_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 |_| { this_rc.borrow_mut().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 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)
        }
    }

    fn stop(&mut self) {
        if let Some(context) = self.context.as_mut() {
            context.stop();
        };

        // remove callbacks in order to avoid conflict on borrowing of self
        self.remove_listener();
        self.remove_tracker();

        self.audio_ctrl.borrow_mut().cleanup();

        self.play_pause_btn.set_icon_name("media-playback-start");

        self.state = ControllerState::Stopped;
        self.duration = None;
    }

    pub fn seek(&mut self, position: u64, accurate: bool) {
        if self.state != ControllerState::Stopped {
            self.seeking = true;

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

            if self.state == ControllerState::EOS || self.state == ControllerState::Ready {
                if self.state == ControllerState::Ready {
                    self.context
                        .as_ref()
                        .expect("MainController::seek no context")
                        .play()
                        .unwrap();
                }
                self.register_tracker();
                self.play_pause_btn.set_icon_name("media-playback-pause");
                self.state = ControllerState::Playing;
            }

            self.info_ctrl.borrow_mut().seek(position);
            self.audio_ctrl.borrow_mut().seek(position, &self.state);
        }
    }

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

    fn select_media(&mut self) {
        self.stop();

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

        self.build_toc_btn.set_sensitive(false);

        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() {
            self.open_media(file_dlg.get_filename().unwrap());
        }

        file_dlg.close();
    }

    fn build_toc(&mut self) {
        match self.context.take() {
            Some(mut context) => {
                self.stop();
                self.info_ctrl.borrow().export_chapters(&mut context);
                self.export_ctrl.borrow_mut().open(context);
            }
            None => (),
        }
    }

    fn handle_eos(&mut self) {
        #[cfg(feature = "trace-main-controller")]
        println!("MainController::handle_eos");

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

    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 => {
                        this_rc.borrow_mut().seeking = false;
                    }
                    InitDone => {
                        let mut this_mut = this_rc.borrow_mut();

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

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

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

                        this_mut.context = Some(context);

                        this_mut.state = ControllerState::Ready;
                    }
                    Eos => {
                        let mut this_mut = this_rc.borrow_mut();

                        let position = this_mut.get_position();
                        this_mut.duration = Some(position);

                        {
                            let mut info_ctrl = this_mut.info_ctrl.borrow_mut();
                            info_ctrl.update_duration(position);
                            info_ctrl.tick(position, true);
                        }

                        this_mut.audio_ctrl.borrow_mut().tick();

                        this_mut.handle_eos();

                        // Remove listener and tracker.
                        // Note: tracker will be register again in case of
                        // a seek. Listener is of no use anymore because
                        // the context won't send any more Eos nor AsyncDone
                        // after an EOS.
                        keep_going = false;
                    }
                    FailedToOpenMedia => {
                        eprintln!("ERROR: failed to open media");

                        let mut this_mut = this_rc.borrow_mut();

                        this_mut.context = None;
                        this_mut.keep_going = false;
                        keep_going = false;
                    }
                };

                if !keep_going {
                    break;
                }
            }

            if !keep_going {
                let mut this_mut = this_rc.borrow_mut();
                this_mut.listener_src = None;
                this_mut.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 keep_going = true;

            let mut this_mut = this_rc.borrow_mut();

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

            let position = this_mut
                .context
                .as_mut()
                .expect("MainController::tracker no context while getting position")
                .get_position();

            let is_eos = if let Some(duration) = this_mut.duration {
                if position >= duration {
                    if !this_mut.seeking {
                        // this check is necessary as EOS is not sent
                        // in case of a seek after EOS
                        this_mut.handle_eos();
                        this_mut.tracker_src = None;
                        keep_going = false;
                    }
                    true
                } else if this_mut.seeking {
                    // this check is necessary as AsyncDone is not sent
                    // in case of a seek after EOS
                    this_mut.seeking = false;
                    false
                } else {
                    false
                }
            } else {
                false
            };

            if !this_mut.seeking {
                this_mut.info_ctrl.borrow_mut().tick(position, is_eos);
                this_mut.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_mut.keep_going && keep_going)
        }));
    }

    fn open_media(&mut self, filepath: PathBuf) {
        assert_eq!(self.listener_src, None);

        let (ctx_tx, ui_rx) = channel();

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

        match Context::new(
            filepath,
            self.audio_ctrl.borrow().get_dbl_buffer_mtx(),
            ctx_tx,
        ) {
            Ok(context) => {
                self.context = Some(context);
                self.build_toc_btn.set_sensitive(true);
            }
            Err(error) => eprintln!("Error opening media: {}", error),
        };
    }
}
<commit_msg>MainController: fix cleanup might conflict with tracker (#56)<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::mpsc::{channel, Receiver};

use gtk::prelude::*;

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

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

#[derive(Clone, PartialEq)]
pub enum ControllerState {
    EOS,
    Ready,
    Paused,
    Playing,
    Stopped,
}

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,
    build_toc_btn: gtk::Button,

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

    context: Option<Context>,
    state: ControllerState,
    duration: Option<u64>, // duration is accurately known at eos
    seeking: bool,

    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 build_toc_btn: gtk::Button = builder.get_object("build_toc-btn").unwrap();
        build_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(),
            build_toc_btn: build_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,
            duration: None,
            seeking: 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.build_toc_btn.connect_clicked(move |_| {
                this_rc.borrow_mut().build_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 |_| { this_rc.borrow_mut().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 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)
        }
    }

    fn stop(&mut self) {
        if let Some(context) = self.context.as_mut() {
            context.stop();
        };

        // remove callbacks in order to avoid conflict on borrowing of self
        self.remove_listener();
        self.remove_tracker();

        self.audio_ctrl.borrow_mut().cleanup();

        self.play_pause_btn.set_icon_name("media-playback-start");

        self.state = ControllerState::Stopped;
        self.duration = None;
    }

    pub fn seek(&mut self, position: u64, accurate: bool) {
        if self.state != ControllerState::Stopped {
            self.seeking = true;

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

            if self.state == ControllerState::EOS || self.state == ControllerState::Ready {
                if self.state == ControllerState::Ready {
                    self.context
                        .as_ref()
                        .expect("MainController::seek no context")
                        .play()
                        .unwrap();
                }
                self.register_tracker();
                self.play_pause_btn.set_icon_name("media-playback-pause");
                self.state = ControllerState::Playing;
            }

            self.info_ctrl.borrow_mut().seek(position);
            self.audio_ctrl.borrow_mut().seek(position, &self.state);
        }
    }

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

    fn select_media(&mut self) {
        self.stop();

        // can't cleanup controllers here because ticks might be still pending
        // => need to wait for the main loop to terminate

        self.build_toc_btn.set_sensitive(false);

        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() {
            self.open_media(file_dlg.get_filename().unwrap());
        } else {
            self.info_ctrl.borrow_mut().cleanup();
            self.audio_ctrl.borrow_mut().cleanup();
            self.header_bar.set_subtitle("");
        }

        file_dlg.close();
    }

    fn build_toc(&mut self) {
        match self.context.take() {
            Some(mut context) => {
                self.stop();
                self.info_ctrl.borrow().export_chapters(&mut context);
                self.export_ctrl.borrow_mut().open(context);
            }
            None => (),
        }
    }

    fn handle_eos(&mut self) {
        #[cfg(feature = "trace-main-controller")]
        println!("MainController::handle_eos");

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

    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 => {
                        this_rc.borrow_mut().seeking = false;
                    }
                    InitDone => {
                        let mut this_mut = this_rc.borrow_mut();

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

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

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

                        this_mut.context = Some(context);

                        this_mut.state = ControllerState::Ready;
                    }
                    Eos => {
                        let mut this_mut = this_rc.borrow_mut();

                        let position = this_mut.get_position();
                        this_mut.duration = Some(position);

                        {
                            let mut info_ctrl = this_mut.info_ctrl.borrow_mut();
                            info_ctrl.update_duration(position);
                            info_ctrl.tick(position, true);
                        }

                        this_mut.audio_ctrl.borrow_mut().tick();

                        this_mut.handle_eos();

                        // Remove listener and tracker.
                        // Note: tracker will be register again in case of
                        // a seek. Listener is of no use anymore because
                        // the context won't send any more Eos nor AsyncDone
                        // after an EOS.
                        keep_going = false;
                    }
                    FailedToOpenMedia => {
                        eprintln!("ERROR: failed to open media");

                        let mut this_mut = this_rc.borrow_mut();

                        this_mut.context = None;
                        this_mut.keep_going = false;
                        keep_going = false;
                    }
                };

                if !keep_going {
                    break;
                }
            }

            if !keep_going {
                let mut this_mut = this_rc.borrow_mut();
                this_mut.listener_src = None;
                this_mut.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 keep_going = true;

            let mut this_mut = this_rc.borrow_mut();

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

            let position = this_mut
                .context
                .as_mut()
                .expect("MainController::tracker no context while getting position")
                .get_position();

            let is_eos = if let Some(duration) = this_mut.duration {
                if position >= duration {
                    if !this_mut.seeking {
                        // this check is necessary as EOS is not sent
                        // in case of a seek after EOS
                        this_mut.handle_eos();
                        this_mut.tracker_src = None;
                        keep_going = false;
                    }
                    true
                } else if this_mut.seeking {
                    // this check is necessary as AsyncDone is not sent
                    // in case of a seek after EOS
                    this_mut.seeking = false;
                    false
                } else {
                    false
                }
            } else {
                false
            };

            if !this_mut.seeking {
                this_mut.info_ctrl.borrow_mut().tick(position, is_eos);
                this_mut.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_mut.keep_going && keep_going)
        }));
    }

    fn open_media(&mut self, filepath: PathBuf) {
        assert_eq!(self.listener_src, None);

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

        let (ctx_tx, ui_rx) = channel();

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

        match Context::new(
            filepath,
            self.audio_ctrl.borrow().get_dbl_buffer_mtx(),
            ctx_tx,
        ) {
            Ok(context) => {
                self.context = Some(context);
                self.build_toc_btn.set_sensitive(true);
            }
            Err(error) => eprintln!("Error opening media: {}", error),
        };
    }
}
<|endoftext|>