<commit_before>use crate::Msg;
use seed::prelude::*;

pub fn render() -> seed::dom_types::Node<Msg> {
    div![
        h1!["News"],
        br![],
        br![],
        br![],
        table![
            attrs!{At::Class => "text"},
            tbody![
                tr![
                    //TODO: change this to styles
                    td![attrs!{At::Width => "100"},"Date"],
                    td![attrs!{At::Width => "300"},"News"],
                ],
                tr![
                    td!["28th July 2019"],
                    td![El::from_html("bigprimes.net has been rewritten from the ground up using rust!
All archives have been retired in favour of client side processing in web assembly.")],
                ],
                tr![
                    td!["22nd January 2019"],
                    td![El::from_html("Ethereum bounty contracts have now been added for anyone that would like to try to push the boundaries of human knowledge. The first contract is a competition for the correct solution to a³+b³+c³ = 33. Tweet me if you need assistance using these contracts!")],
                ],
                tr![
                    td!["10th March 2018"],
                    td![El::from_html("Added torrents for the first million primes and first billion prime numbers.")],
                ],
                tr![
                    td!["12th November 2017"],
                    td![El::from_html("Site is now running on AWS Lambda, but can still fallback to regular VM hosting if ever needed. Site is now ~40% covered by unit tests.")],
                ],
                tr![
                    td!["13th November 2016"],
                    td![El::from_html("Site source code has been converted to PHP7 with Silex framework. Some methods have also been moved to cpp from PHP https://github.com/craigmayhew/bigprimes.net/")],
                ],
                tr![
                    td!["28th February 2014"],
                    td![El::from_html("You can now find the websites php source code here https://github.com/craigmayhew/bigprimes.net/")],
                ],
                tr![
                    td!["19th November 2011"],
                    td![El::from_html("Thank you to Yu Kwong Yiu Wilson for the donation!")],
                ],
                tr![
                    td!["13th July 2011"],
                    td![El::from_html("We are retiring the distributed computing client. Thank you all for your help and processing cycles.
        Watch this space for an open source GPU client coming soon...")],
                ],
                tr![
                    td!["10th February 2010"],
                    td![El::from_html("We have added a page that shows the sum of the digits of prime numbers (now retired).")],
                ],
                tr![
                    td!["1st February 2010"],
                    td![El::from_html("Thanks to everyone for donating computer time, we are now upto 1.4 billion primes. We now also have a facebook group where you get news updates and show your support for the project!")],
                ],
                tr![
                    td!["28th September 2009"],
                    td![El::from_html("Thanks for all your help! We made it to one billion prime numbers! Please keep on donating processor time as we still have plenty of storage for the prime numbers. Our database is now more than double the size (300 million primes) and we've put in the capacity to keep growing. To view our current progress please visit the status page.")],
                ],
                tr![
                    td!["18th August 2009"],
                    td![El::from_html("It's been a while but we have been very busy. We've put together a distributed computing client written in javascript. Our database is now more than double the size (300 million primes) and we've put in the capacity to keep growing. To view our current progress please visit the status page.")],
                ],
                tr![
                    td!["17th July 2008"],
                    td![El::from_html("We've now added a Forum (now retired) which can be found on the navigation links on the left.")],
                ],
                tr![
                    td!["14th July 2008"],
                    td![El::from_html("Someone named Kirk just emailed me to say the download links wern't working. I've now fixed this. Thanks Kirk!")],
                ],
                tr![
                    td!["4th July 2008"],
                    td![El::from_html("Added much faster code to the cruncher so that we can handle more of your page loads! Lots of little updates and some reorganizing of the site.")],
                ],
                tr![
                    td!["23rd June 2007"],
                    td![El::from_html("Added the Downloads section.")],
                ],
                tr![
                    td!["11th February 2007"],
                    td![El::from_html("Added the Fibonacci number archive containing the first 70331 Fibonacci numbers.")],
                ],
                tr![
                    td!["16th January 2007"],
                    td![El::from_html("Added the 44th Mersenne Prime number to the archive.")],
                ],
                tr![
                    td!["12th January 2007"],
                    td![El::from_html("The Archives are now more organized.")],
                ],
                tr![
                    td!["28th September 2005"],
                    td![El::from_html("We have managed to archive the first 100 million prime numbers.")],
                ],
                tr![
                    td!["20th May 2005"],
                    td![El::from_html("We have managed to archive the first 75 million prime numbers.")],
                ],
                tr![
                    td!["16th May 2005"],
                    td![El::from_html("We have managed to archive the first 50 million prime numbers.")],
                ],
                tr![
                    td!["14th May 2005"],
                    td![El::from_html("We have managed to archive the first 10 million prime numbers.")],
                ],
                tr![
                    td!["13th May 2005"],
                    td![El::from_html("We have managed to archive the first 1 million prime numbers.")],
                ],
            ],
        ],
    ]
}<commit_msg>Added Gavin shoutout<commit_after>use crate::Msg;
use seed::prelude::*;

pub fn render() -> seed::dom_types::Node<Msg> {
    div![
        h1!["News"],
        br![],
        br![],
        br![],
        table![
            attrs!{At::Class => "text"},
            tbody![
                tr![
                    //TODO: change this to styles
                    td![attrs!{At::Width => "100"},"Date"],
                    td![attrs!{At::Width => "300"},"News"],
                ],
                tr![
                    td!["28th July 2019"],
                    td![El::from_html("bigprimes.net has been rewritten from the ground up using rust!
All archives have been retired in favour of client side processing in web assembly.")],
                ],
                tr![
                    td!["28th July 2019"],
                    td![El::from_html("Shout out to Gavin, who took the time to memorise all the news entries on the website!")],
                ],
                tr![
                    td!["22nd January 2019"],
                    td![El::from_html("Ethereum bounty contracts have now been added for anyone that would like to try to push the boundaries of human knowledge. The first contract is a competition for the correct solution to a³+b³+c³ = 33. Tweet me if you need assistance using these contracts!")],
                ],
                tr![
                    td!["10th March 2018"],
                    td![El::from_html("Added torrents for the first million primes and first billion prime numbers.")],
                ],
                tr![
                    td!["12th November 2017"],
                    td![El::from_html("Site is now running on AWS Lambda, but can still fallback to regular VM hosting if ever needed. Site is now ~40% covered by unit tests.")],
                ],
                tr![
                    td!["13th November 2016"],
                    td![El::from_html("Site source code has been converted to PHP7 with Silex framework. Some methods have also been moved to cpp from PHP https://github.com/craigmayhew/bigprimes.net/")],
                ],
                tr![
                    td!["28th February 2014"],
                    td![El::from_html("You can now find the websites php source code here https://github.com/craigmayhew/bigprimes.net/")],
                ],
                tr![
                    td!["19th November 2011"],
                    td![El::from_html("Thank you to Yu Kwong Yiu Wilson for the donation!")],
                ],
                tr![
                    td!["13th July 2011"],
                    td![El::from_html("We are retiring the distributed computing client. Thank you all for your help and processing cycles.
        Watch this space for an open source GPU client coming soon...")],
                ],
                tr![
                    td!["10th February 2010"],
                    td![El::from_html("We have added a page that shows the sum of the digits of prime numbers (now retired).")],
                ],
                tr![
                    td!["1st February 2010"],
                    td![El::from_html("Thanks to everyone for donating computer time, we are now upto 1.4 billion primes. We now also have a facebook group where you get news updates and show your support for the project!")],
                ],
                tr![
                    td!["28th September 2009"],
                    td![El::from_html("Thanks for all your help! We made it to one billion prime numbers! Please keep on donating processor time as we still have plenty of storage for the prime numbers. Our database is now more than double the size (300 million primes) and we've put in the capacity to keep growing. To view our current progress please visit the status page.")],
                ],
                tr![
                    td!["18th August 2009"],
                    td![El::from_html("It's been a while but we have been very busy. We've put together a distributed computing client written in javascript. Our database is now more than double the size (300 million primes) and we've put in the capacity to keep growing. To view our current progress please visit the status page.")],
                ],
                tr![
                    td!["17th July 2008"],
                    td![El::from_html("We've now added a Forum (now retired) which can be found on the navigation links on the left.")],
                ],
                tr![
                    td!["14th July 2008"],
                    td![El::from_html("Someone named Kirk just emailed me to say the download links wern't working. I've now fixed this. Thanks Kirk!")],
                ],
                tr![
                    td!["4th July 2008"],
                    td![El::from_html("Added much faster code to the cruncher so that we can handle more of your page loads! Lots of little updates and some reorganizing of the site.")],
                ],
                tr![
                    td!["23rd June 2007"],
                    td![El::from_html("Added the Downloads section.")],
                ],
                tr![
                    td!["11th February 2007"],
                    td![El::from_html("Added the Fibonacci number archive containing the first 70331 Fibonacci numbers.")],
                ],
                tr![
                    td!["16th January 2007"],
                    td![El::from_html("Added the 44th Mersenne Prime number to the archive.")],
                ],
                tr![
                    td!["12th January 2007"],
                    td![El::from_html("The Archives are now more organized.")],
                ],
                tr![
                    td!["28th September 2005"],
                    td![El::from_html("We have managed to archive the first 100 million prime numbers.")],
                ],
                tr![
                    td!["20th May 2005"],
                    td![El::from_html("We have managed to archive the first 75 million prime numbers.")],
                ],
                tr![
                    td!["16th May 2005"],
                    td![El::from_html("We have managed to archive the first 50 million prime numbers.")],
                ],
                tr![
                    td!["14th May 2005"],
                    td![El::from_html("We have managed to archive the first 10 million prime numbers.")],
                ],
                tr![
                    td!["13th May 2005"],
                    td![El::from_html("We have managed to archive the first 1 million prime numbers.")],
                ],
            ],
        ],
    ]
}<|endoftext|>


<commit_before>use std::sync::mpsc::Receiver;
use std::sync::{Arc, Weak, RwLock};
use std::thread::Thread;
use subject::Subject;

/// Thread keeps internal subject alive
fn spawn_over<A, B, C, F>(source: Receiver<A>, subject: Weak<RwLock<B>>, keep_alive: Arc<RwLock<C>>, f: F)
    where F: Fn(&mut B, A) + Send,
          A: Send + Sync,
          B: Send + Sync,
          C: Send + Sync,
{
    Thread::spawn(move || {
        // This reference is only here to keep the subject observed by our
        // source alive, as long as this thread is running.
        let _ = keep_alive;
        for a in source.iter() {
            match subject.upgrade() {
                Some(subject) => f(&mut *subject.write().unwrap(), a),
                None => break,
            }
        }
    });
}

pub struct Event<A> {
    subject: Arc<RwLock<Subject<A>>>,
}

impl<A: Send + Sync + Clone> Event<A> {
    pub fn new() -> Event<A> {
        Event { subject: Arc::new(RwLock::new(Subject::new())) }
    }

    pub fn send(&self, a: A) {
        self.subject.write().unwrap().send(a);
    }

    pub fn listen(&self) -> Receiver<A> {
        self.subject.write().unwrap().listen()
    }

    fn spawn_over<B, F>(&self, subject: Weak<RwLock<B>>, f: F)
        where F: Fn(&mut B, A) + Send,
              B: Send + Sync,
    {
        spawn_over(self.listen(), subject, self.subject.clone(), f);
    }

    pub fn map<B: Send + Sync + Clone, F: Fn(A) -> B + Send>(&self, f: F) -> Event<B> {
        let event = Event::new();
        self.spawn_over(event.subject.downgrade(), move |mut subject, a| subject.send(f(a)));
        event
    }

    pub fn filter<F: Fn(&A) -> bool + Send>(&self, f: F) -> Event<A> {
        let event = Event::new();
        self.spawn_over(event.subject.downgrade(), move |mut subject, a| if f(&a) { subject.send(a) });
        event
    }

    /// Note: the specific order of the merge is not guaranteed to be consistent
    /// with the order, in which they were fired.
    pub fn merge(&self, other: &Event<A>) -> Event<A> {
        let event = Event::new();
        self.spawn_over(event.subject.downgrade(), |mut subject, a| subject.send(a));
        other.spawn_over(event.subject.downgrade(), |mut subject, a| subject.send(a));
        event
    }

    pub fn hold(&self, a: A) -> Behaviour<A> {
        Behaviour::hold(a, self)
    }
}

pub struct Behaviour<A> {
    state: Arc<RwLock<(A, Subject<A>)>>,
}

impl<A: Send + Sync + Clone> Behaviour<A> {
    pub fn constant(initial: A) -> Behaviour<A> {
        Behaviour {
            state: Arc::new(RwLock::new((initial, Subject::new())))
        }
    }

    pub fn hold(initial: A, event: &Event<A>) -> Behaviour<A> {
        let behaviour = Behaviour::constant(initial);
        event.spawn_over(
            behaviour.state.downgrade(),
            move |&mut (ref mut state, ref mut subject), a| {
                *state = a.clone();
                subject.send(a);
            }
        );
        behaviour
    }

    pub fn snapshot<B: Send + Sync + Clone>(&self, event: &Event<B>) -> Event<(A, B)> {
        let snap = Event::new();
        let b_state = self.state.clone();
        event.spawn_over(
            snap.subject.downgrade(),
            move |subject, a| {
                subject.send((b_state.read().unwrap().0.clone(), a))
            },
        );
        snap
    }

    pub fn sample(&self) -> A {
        self.state.read().unwrap().0.clone()
    }
}

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

    #[test]
    fn sink() {
        let sink = Event::new();
        let r = sink.listen();
        sink.send(1);
        sink.send(2);
        assert_eq!(r.recv(), Ok(1));
        assert_eq!(r.recv(), Ok(2));
    }

    #[test]
    fn map() {
        let sink = Event::new();
        let triple = sink.map(|x| 3 * x);
        let r = triple.listen();
        sink.send(1);
        assert_eq!(r.recv(), Ok(3));
    }

    #[test]
    fn filter() {
        let sink: Event<i32> = Event::new();
        let positive = sink.filter(|&x| x > 0);
        let r = positive.listen();
        sink.send(-2);
        sink.send(3);
        assert_eq!(r.recv(), Ok(3));
    }

    #[test]
    fn merge() {
        let sink1 = Event::new();
        let sink2 = Event::new();
        let merge = sink1.merge(&sink2);
        let r = merge.listen();
        sink1.send(3);
        sink2.send(4);
        let result = (r.recv(), r.recv());
        // We cannot be certain about the ordering
        assert!((result == (Ok(3), Ok(4))) || (result == (Ok(4), Ok(3))));
    }

    #[test]
    fn snapshot() {
        let sink = Event::new();
        let behaviour = Event::new().hold(1);
        let snap = behaviour.snapshot(&sink);
        let r = snap.listen();
        sink.send(4);
        assert_eq!(r.recv(), Ok((1, 4)));
    }

    #[test]
    fn chain() {
        let sink: Event<i32> = Event::new();
        let chain = sink.map(|x| x + 2).filter(|&x| x > 10);
        let r = chain.listen();
        sink.send(9);
        assert_eq!(r.recv(), Ok(11));
    }

    #[test]
    fn chain_more() {
        let sink: Event<i32> = Event::new();
        let chain = sink
            .map(|x| x + 2)
            .filter(|&x| x > 10)
            .merge(&sink.filter(|&x| x < -3))
            .map(|x| x - 4);
        let r = chain.listen();
        sink.send(9);
        assert_eq!(r.recv(), Ok(7));
        sink.send(-5);
        assert_eq!(r.recv(), Ok(-9));
    }
}
<commit_msg>Implement unary lift<commit_after>use std::sync::mpsc::Receiver;
use std::sync::{Arc, Weak, RwLock};
use std::thread::Thread;
use subject::Subject;

/// Thread keeps internal subject alive
fn spawn_over<A, B, C, F>(source: Receiver<A>, subject: Weak<RwLock<B>>, keep_alive: Arc<RwLock<C>>, f: F)
    where F: Fn(&mut B, A) + Send,
          A: Send + Sync,
          B: Send + Sync,
          C: Send + Sync,
{
    Thread::spawn(move || {
        // This reference is only here to keep the subject observed by our
        // source alive, as long as this thread is running.
        let _ = keep_alive;
        for a in source.iter() {
            match subject.upgrade() {
                Some(subject) => f(&mut *subject.write().unwrap(), a),
                None => break,
            }
        }
    });
}

pub struct Event<A> {
    subject: Arc<RwLock<Subject<A>>>,
}

impl<A: Send + Sync + Clone> Event<A> {
    pub fn new() -> Event<A> {
        Event { subject: Arc::new(RwLock::new(Subject::new())) }
    }

    pub fn send(&self, a: A) {
        self.subject.write().unwrap().send(a);
    }

    pub fn listen(&self) -> Receiver<A> {
        self.subject.write().unwrap().listen()
    }

    fn spawn_over<B, F>(&self, subject: Weak<RwLock<B>>, f: F)
        where F: Fn(&mut B, A) + Send,
              B: Send + Sync,
    {
        spawn_over(self.listen(), subject, self.subject.clone(), f);
    }

    pub fn map<B: Send + Sync + Clone, F: Fn(A) -> B + Send>(&self, f: F) -> Event<B> {
        let event = Event::new();
        self.spawn_over(event.subject.downgrade(), move |mut subject, a| subject.send(f(a)));
        event
    }

    pub fn filter<F: Fn(&A) -> bool + Send>(&self, f: F) -> Event<A> {
        let event = Event::new();
        self.spawn_over(event.subject.downgrade(), move |mut subject, a| if f(&a) { subject.send(a) });
        event
    }

    /// Note: the specific order of the merge is not guaranteed to be consistent
    /// with the order, in which they were fired.
    pub fn merge(&self, other: &Event<A>) -> Event<A> {
        let event = Event::new();
        for src_ev in [self, other].iter() {
            src_ev.spawn_over(event.subject.downgrade(), |mut subject, a| subject.send(a));
        }
        event
    }

    pub fn hold(&self, a: A) -> Behaviour<A> {
        Behaviour::hold(a, self)
    }
}

pub struct Behaviour<A> {
    state: Arc<RwLock<(A, Subject<A>)>>,
}

impl<A: Send + Sync + Clone> Behaviour<A> {
    pub fn constant(initial: A) -> Behaviour<A> {
        Behaviour {
            state: Arc::new(RwLock::new((initial, Subject::new())))
        }
    }

    pub fn hold(initial: A, event: &Event<A>) -> Behaviour<A> {
        let behaviour = Behaviour::constant(initial);
        event.spawn_over(
            behaviour.state.downgrade(),
            move |&mut (ref mut state, ref mut subject), a| {
                *state = a.clone();
                subject.send(a);
            }
        );
        behaviour
    }

    pub fn snapshot<B: Send + Sync + Clone>(&self, event: &Event<B>) -> Event<(A, B)> {
        let snap = Event::new();
        let b_state = self.state.clone();
        event.spawn_over(
            snap.subject.downgrade(),
            move |subject, a| {
                subject.send((b_state.read().unwrap().0.clone(), a))
            },
        );
        snap
    }

    pub fn sample(&self) -> A {
        self.state.read().unwrap().0.clone()
    }

    pub fn updates(&self) -> Receiver<A> {
        self.state.write().unwrap().1.listen()
    }

    pub fn lift<B, F>(&self, f: F) -> Behaviour<B>
        where B: Send + Sync + Clone,
              F: Fn(A) -> B + Send,
    {
        let lifted = Behaviour::constant(f(self.sample()));
        let self_state = self.state.clone();
        spawn_over(
            self.updates(), lifted.state.downgrade(), self_state,
            move |&mut (ref mut state, ref mut subject), a| {
                *state = f(a);
                subject.send(state.clone())
            },
        );
        lifted
    }
}

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

    #[test]
    fn sink() {
        let sink = Event::new();
        let r = sink.listen();
        sink.send(1);
        sink.send(2);
        assert_eq!(r.recv(), Ok(1));
        assert_eq!(r.recv(), Ok(2));
    }

    #[test]
    fn map() {
        let sink = Event::new();
        let triple = sink.map(|x| 3 * x);
        let r = triple.listen();
        sink.send(1);
        assert_eq!(r.recv(), Ok(3));
    }

    #[test]
    fn filter() {
        let sink: Event<i32> = Event::new();
        let positive = sink.filter(|&x| x > 0);
        let r = positive.listen();
        sink.send(-2);
        sink.send(3);
        assert_eq!(r.recv(), Ok(3));
    }

    #[test]
    fn merge() {
        let sink1 = Event::new();
        let sink2 = Event::new();
        let merge = sink1.merge(&sink2);
        let r = merge.listen();
        sink1.send(3);
        sink2.send(4);
        let result = (r.recv(), r.recv());
        // We cannot be certain about the ordering
        assert!((result == (Ok(3), Ok(4))) || (result == (Ok(4), Ok(3))));
    }

    #[test]
    fn snapshot() {
        let sink = Event::new();
        let behaviour = Event::new().hold(1);
        let snap = behaviour.snapshot(&sink);
        let r = snap.listen();
        sink.send(4);
        assert_eq!(r.recv(), Ok((1, 4)));
    }

    #[test]
    fn chain() {
        let sink: Event<i32> = Event::new();
        let chain = sink.map(|x| x + 2).filter(|&x| x > 10);
        let r = chain.listen();
        sink.send(9);
        assert_eq!(r.recv(), Ok(11));
    }

    #[test]
    fn chain_more() {
        let sink: Event<i32> = Event::new();
        let chain = sink
            .map(|x| x + 2)
            .filter(|&x| x > 10)
            .merge(&sink.filter(|&x| x < -3))
            .map(|x| x - 4);
        let r = chain.listen();
        sink.send(9);
        assert_eq!(r.recv(), Ok(7));
        sink.send(-5);
        assert_eq!(r.recv(), Ok(-9));
    }

    #[test]
    fn lift() {
        let sink = Event::new();
        let lifted = sink.hold(3).lift(|x| x * 3);
        assert_eq!(lifted.sample(), 9);
        let r = lifted.updates();
        sink.send(4);
        assert_eq!(r.recv(), Ok(12));
    }
}
<|endoftext|>


<commit_before>/* Copyright (C)  2016 nokaa <nokaa@cock.li>
 * This software is licensed under the terms of the
 * GNU Affero General Public License. You should have
 * received a copy of this license with this software.
 * The license may also be found at https://gnu.org/licenses/agpl.txt
 * */
pub mod types;

use {Context, db};
use errors::*;
use self::types::*;

use rocket::State;
use rocket::request::Form;
use rocket::response::Redirect;
use rocket_contrib::Template;

#[get("/")]
pub fn home(ctx: State<Context>) -> Template {
    Template::render("home", &ctx.config)
}

#[get("/b/<board_name>")]
pub fn board(ctx: State<Context>, board_name: &str) -> Result<Template> {
    let board = ctx.config.boards.get(board_name);
    if board.is_none() {
        return Ok(not_found());
    }
    let pool = ctx.db_pool.clone();
    let mut catalog = db::read::catalog(pool, board_name)?;
    catalog.reverse();
    Ok(Template::render("board", &(board.unwrap(), catalog)))
}

#[post("/b/<board>", data = "<new_thread_form>")]
pub fn create_thread(ctx: State<Context>,
                     board: &str,
                     new_thread_form: Form<NewThread>)
                     -> Result<Option<Redirect>> {
    if ctx.config.boards.get(board).is_none() {
        return Ok(None);
    }

    let new_thread_form = new_thread_form.get();
    let pool = ctx.db_pool.clone();
    let thread_number = db::create::thread(pool, board, new_thread_form)?;
    let redirect = format!("/b/{}/{}", board, thread_number);
    Ok(Some(Redirect::to(&redirect)))
}

#[get("/b/<board_name>/<thread>")]
pub fn thread(ctx: State<Context>, board_name: &str, thread: i64) -> Result<Template> {
    let board = ctx.config.boards.get(board_name);
    if board.is_none() {
        return Ok(not_found());
    }

    let pool = ctx.db_pool.clone();
    let thread = db::read::thread(pool, board_name, thread)?;
    Ok(Template::render("thread", &(board.unwrap(), thread)))
}

#[post("/b/<board>/<thread>", data = "<new_post_form>")]
pub fn create_post(ctx: State<Context>,
                   board: &str,
                   thread: i64,
                   new_post_form: Form<NewPost>)
                   -> Result<Option<Redirect>> {
    if ctx.config.boards.get(board).is_none() {
        return Ok(None);
    }

    let new_post_form = new_post_form.get();
    let pool = ctx.db_pool.clone();
    let post_number = db::create::post(pool, board, thread, new_post_form)?;

    let thread = format!("/b/{}/{}#{}", board, thread, post_number);
    Ok(Some(Redirect::to(&thread)))
}

#[error(404)]
pub fn not_found() -> Template {
    Template::render("404", &())
}
<commit_msg>Check that forms are not empty<commit_after>/* Copyright (C)  2016 nokaa <nokaa@cock.li>
 * This software is licensed under the terms of the
 * GNU Affero General Public License. You should have
 * received a copy of this license with this software.
 * The license may also be found at https://gnu.org/licenses/agpl.txt
 * */
pub mod types;

use {Context, db};
use errors::*;
use self::types::*;

use rocket::State;
use rocket::request::Form;
use rocket::response::Redirect;
use rocket_contrib::Template;

#[get("/")]
pub fn home(ctx: State<Context>) -> Template {
    Template::render("home", &ctx.config)
}

#[get("/b/<board_name>")]
pub fn board(ctx: State<Context>, board_name: &str) -> Result<Template> {
    let board = ctx.config.boards.get(board_name);
    if board.is_none() {
        return Ok(not_found());
    }
    let pool = ctx.db_pool.clone();
    let mut catalog = db::read::catalog(pool, board_name)?;
    catalog.reverse();
    Ok(Template::render("board", &(board.unwrap(), catalog)))
}

#[post("/b/<board>", data = "<new_thread_form>")]
pub fn create_thread(ctx: State<Context>,
                     board: &str,
                     new_thread_form: Form<NewThread>)
                     -> Result<Option<Redirect>> {
    if ctx.config.boards.get(board).is_none() {
        return Ok(None);
    }

    let new_thread_form = new_thread_form.get();
    if new_thread_form.content.is_empty() && new_thread_form.subject.is_empty() {
        return Ok(None);
    }

    let pool = ctx.db_pool.clone();
    let thread_number = db::create::thread(pool, board, new_thread_form)?;
    let redirect = format!("/b/{}/{}", board, thread_number);
    Ok(Some(Redirect::to(&redirect)))
}

#[get("/b/<board_name>/<thread>")]
pub fn thread(ctx: State<Context>, board_name: &str, thread: i64) -> Result<Template> {
    let board = ctx.config.boards.get(board_name);
    if board.is_none() {
        return Ok(not_found());
    }

    let pool = ctx.db_pool.clone();
    let thread = db::read::thread(pool, board_name, thread)?;
    Ok(Template::render("thread", &(board.unwrap(), thread)))
}

#[post("/b/<board>/<thread>", data = "<new_post_form>")]
pub fn create_post(ctx: State<Context>,
                   board: &str,
                   thread: i64,
                   new_post_form: Form<NewPost>)
                   -> Result<Option<Redirect>> {
    if ctx.config.boards.get(board).is_none() {
        return Ok(None);
    }

    let new_post_form = new_post_form.get();
    if new_post_form.content.is_empty() {
        return Ok(None);
    }

    let pool = ctx.db_pool.clone();
    let post_number = db::create::post(pool, board, thread, new_post_form)?;

    let thread = format!("/b/{}/{}#{}", board, thread, post_number);
    Ok(Some(Redirect::to(&thread)))
}

#[error(404)]
pub fn not_found() -> Template {
    Template::render("404", &())
}
<|endoftext|>


<commit_before>pub mod parse;

use self::parse::{EpisodeData, SaveData, SeasonState};
use backend::{AnimeEntry, AnimeInfo, Status, SyncBackend};
use chrono::Local;
use error::SeriesError;
use input::{self, Answer};
use process;
use std::borrow::Cow;
use std::ops::Range;
use std::path::Path;

pub struct SeriesConfig<B>
where
    B: SyncBackend,
{
    pub offline_mode: bool,
    pub sync_service: B,
    pub season_num: usize,
}

pub struct FolderData {
    pub episodes: EpisodeData,
    pub savefile: SaveData,
}

impl FolderData {
    pub fn load_dir(path: &Path) -> Result<FolderData, SeriesError> {
        let mut savefile = SaveData::from_dir(path)?;
        let episodes = EpisodeData::parse_until_valid_pattern(path, &mut savefile.episode_matcher)?;

        Ok(FolderData { episodes, savefile })
    }

    pub fn save(&self) -> Result<(), SeriesError> {
        self.savefile.write_to_file()
    }

    pub fn populate_season_data<B>(
        &mut self,
        config: &SeriesConfig<B>,
        desired_season: usize,
    ) -> Result<(), SeriesError>
    where
        B: SyncBackend,
    {
        let num_seasons = self.seasons().len();

        if num_seasons > desired_season {
            return Ok(());
        }

        for cur_season in num_seasons..=desired_season {
            let info = self.fetch_series_info(config, cur_season)?;
            let entry = AnimeEntry::new(info);

            let season = SeasonState {
                state: entry,
                needs_info: config.offline_mode,
                needs_sync: config.offline_mode,
            };

            self.seasons_mut().push(season);
        }

        Ok(())
    }

    pub fn fetch_series_info<B>(
        &mut self,
        config: &SeriesConfig<B>,
        season: usize,
    ) -> Result<AnimeInfo, SeriesError>
    where
        B: SyncBackend,
    {
        if config.offline_mode {
            // Return existing data if we already have it, otherwise return barebones info
            if self.seasons().len() > season {
                let info = self.seasons()[season].state.info.clone();
                Ok(info)
            } else {
                let mut info = AnimeInfo::default();
                info.title = self.episodes.series_name.clone();

                Ok(info)
            }
        } else {
            search_for_series_info(&config.sync_service, &self.episodes.series_name, season)
        }
    }

    pub fn sync_remote_season_info<B>(
        &mut self,
        config: &SeriesConfig<B>,
        season: usize,
    ) -> Result<(), SeriesError>
    where
        B: SyncBackend,
    {
        if season >= self.seasons().len() {
            return Ok(());
        }

        let mut season_data = self.seasons_mut()[season].clone();

        if season_data.needs_info {
            season_data.state.info = self.fetch_series_info(config, season)?;

            // We want to stay in a needs-sync state in offline mode so the "real" info
            // can be inserted when the series is played in online mode
            if !config.offline_mode {
                season_data.needs_info = false;
            }
        }

        // Sync data from the backend when not offline
        if !config.offline_mode {
            let entry = config
                .sync_service
                .get_list_entry(season_data.state.info.clone())?;

            if let Some(entry) = entry {
                // If we don't have new data to report, we should sync the data from the backend to keep up with
                // any changes made outside of the program
                if !season_data.needs_sync {
                    season_data.state = entry;
                }
            }
        }

        self.seasons_mut()[season] = season_data;
        Ok(())
    }

    pub fn calculate_season_offset(&self, mut range: Range<usize>) -> u32 {
        let num_seasons = self.savefile.season_states.len();
        range.start = num_seasons.min(range.start);
        range.end = num_seasons.min(range.end);

        let mut offset = 0;

        for i in range {
            let season = &self.savefile.season_states[i];

            match season.state.info.episodes {
                Some(eps) => offset += eps,
                None => return offset,
            }
        }

        offset
    }

    pub fn seasons(&self) -> &Vec<SeasonState> {
        &self.savefile.season_states
    }

    pub fn seasons_mut(&mut self) -> &mut Vec<SeasonState> {
        &mut self.savefile.season_states
    }
}

pub struct Series<B>
where
    B: SyncBackend,
{
    config: SeriesConfig<B>,
    dir: FolderData,
    ep_offset: u32,
}

impl<B> Series<B>
where
    B: SyncBackend,
{
    pub fn new(config: SeriesConfig<B>, dir: FolderData) -> Series<B> {
        Series {
            config,
            dir,
            ep_offset: 0,
        }
    }

    pub fn prepare(&mut self) -> Result<(), SeriesError> {
        self.dir
            .populate_season_data(&self.config, self.config.season_num)?;
        self.dir
            .sync_remote_season_info(&self.config, self.config.season_num)?;

        self.ep_offset = self.dir.calculate_season_offset(0..self.config.season_num);
        self.dir.save()?;

        self.prepare_list_entry()?;

        Ok(())
    }

    fn prepare_list_entry(&mut self) -> Result<(), SeriesError> {
        let status = self.cur_season().state.status;

        match status {
            Status::Watching | Status::Rewatching => Ok(()),
            Status::PlanToWatch => self.update_list_entry_status(Status::Watching),
            Status::Completed => self.update_list_entry_status(Status::Rewatching),
            Status::OnHold | Status::Dropped => self.prompt_to_watch_paused_series(),
        }
    }

    fn update_list_entry_status(&mut self, status: Status) -> Result<(), SeriesError> {
        match status {
            Status::Watching => {
                let entry = &mut self.cur_season_mut().state;

                // A series that was on hold probably already has a starting date, and it would make
                // more sense to use that one instead of replacing it
                if entry.status != Status::OnHold {
                    entry.start_date = Some(Local::today().naive_local());
                }

                entry.finish_date = None;
            }
            Status::Rewatching => {
                let entry = &mut self.cur_season_mut().state;

                println!("[{}] already completed", entry.info.title);
                println!("do you want to reset the start and end dates of the series? (Y/n)");

                if input::read_yn(Answer::Yes)? {
                    entry.start_date = Some(Local::today().naive_local());
                    entry.finish_date = None;
                }

                entry.watched_episodes = 0;
            }
            Status::Completed => {
                let entry = &mut self.cur_season_mut().state;

                if entry.finish_date.is_none() {
                    entry.finish_date = Some(Local::today().naive_local());
                }

                println!("[{}] completed!", entry.info.title);

                self.prompt_to_update_score();
            }
            Status::Dropped => {
                let entry = &mut self.cur_season_mut().state;

                if entry.finish_date.is_none() {
                    entry.finish_date = Some(Local::today().naive_local());
                }
            }
            Status::OnHold | Status::PlanToWatch => (),
        }

        let entry = &mut self.cur_season_mut().state;
        entry.status = status;

        self.update_list_entry()?;

        Ok(())
    }

    pub fn update_list_entry(&mut self) -> Result<(), SeriesError> {
        self.cur_season_mut().needs_sync = self.config.offline_mode;
        self.dir.save()?;

        if self.config.offline_mode {
            return Ok(());
        }

        self.config
            .sync_service
            .update_list_entry(&self.cur_season().state)?;

        Ok(())
    }

    pub fn play_episode(&mut self, episode: u32) -> Result<(), SeriesError> {
        let absolute_ep = self.ep_offset + episode;
        let path = self.dir.episodes.get_episode(absolute_ep)?.clone();

        let status = process::open_with_default(path).map_err(SeriesError::FailedToOpenPlayer)?;

        let entry = &mut self.cur_season_mut().state;
        entry.watched_episodes = episode.max(entry.watched_episodes);

        if !status.success() {
            eprintln!("video player not exited normally");
            eprintln!("do you still want to count the episode as completed? (y/N)");

            if !input::read_yn(Answer::No)? {
                return Ok(());
            }
        }

        match entry.info.episodes {
            Some(total_eps) if episode >= total_eps => {
                self.update_list_entry_status(Status::Completed)?;
                return Err(SeriesError::RequestExit);
            }
            _ => self.prompt_episode_completed()?,
        }

        Ok(())
    }

    pub fn play_all_episodes(&mut self) -> Result<(), SeriesError> {
        self.prepare()?;

        loop {
            let next_episode = self.cur_season().state.watched_episodes + 1;

            self.play_episode(next_episode)?;
            self.prompt_next_episode_options()?;
        }
    }

    fn prompt_episode_completed(&mut self) -> Result<(), SeriesError> {
        let entry = &self.cur_season().state;

        let total_episodes = entry
            .info
            .episodes
            .map(|e| Cow::Owned(e.to_string()))
            .unwrap_or_else(|| Cow::Borrowed("?"));

        println!(
            "[{}] episode {}/{} completed",
            entry.info.title, entry.watched_episodes, total_episodes
        );

        self.update_list_entry()?;
        Ok(())
    }

    fn prompt_next_episode_options(&mut self) -> Result<(), SeriesError> {
        let current_score_text: Cow<str> = match self.format_entry_score() {
            Some(score) => Cow::Owned(format!(" [{}]", score)),
            None => Cow::Borrowed(""),
        };

        println!("series options:");
        println!("\t[d] drop\n\t[h] put on hold\n\t[r] rate{}\n\t[x] exit\n\t[n] watch next episode (default)", current_score_text);

        let input = input::read_line()?.to_lowercase();

        match input.as_str() {
            "d" => {
                self.update_list_entry_status(Status::Dropped)?;
                Err(SeriesError::RequestExit)
            }
            "h" => {
                self.update_list_entry_status(Status::OnHold)?;
                Err(SeriesError::RequestExit)
            }
            "r" => {
                self.prompt_to_update_score();
                self.update_list_entry()?;

                self.prompt_next_episode_options()
            }
            "x" => Err(SeriesError::RequestExit),
            _ => Ok(()),
        }
    }

    fn prompt_to_update_score(&mut self) {
        let (min_score, max_score) = self.config.sync_service.formatted_score_range();

        println!(
            "enter your score between {} and {} (press return to skip):",
            min_score, max_score
        );

        // TODO: use read_range() with empty line bypassing
        let input = match input::read_line() {
            Ok(ref input) if input.is_empty() => return,
            Ok(input) => input,
            Err(err) => {
                eprintln!("failed to read score input: {}", err);
                return;
            }
        };

        match self.config.sync_service.parse_score(&input) {
            Ok(score) => {
                let entry = &mut self.cur_season_mut().state;
                entry.score = Some(score);
            }
            Err(err) => eprintln!("failed to parse score: {}", err),
        }
    }

    fn prompt_to_watch_paused_series(&mut self) -> Result<(), SeriesError> {
        let entry = &mut self.cur_season_mut().state;

        println!(
            "[{}] was previously put on hold or dropped",
            entry.info.title
        );

        println!("do you want to watch it from the beginning? (Y/n)");

        if input::read_yn(Answer::Yes)? {
            entry.watched_episodes = 0;
        }

        self.update_list_entry_status(Status::Watching)?;
        Ok(())
    }

    fn format_entry_score(&self) -> Option<String> {
        let entry = &self.cur_season().state;

        match entry.score {
            Some(score) => {
                let formatted_score = self.config.sync_service.format_score(score);

                match formatted_score {
                    Ok(score) => Some(score),
                    Err(err) => {
                        eprintln!("failed to read existing list entry score: {}", err);
                        None
                    }
                }
            }
            None => None,
        }
    }

    pub fn cur_season(&self) -> &SeasonState {
        &self.dir.seasons()[self.config.season_num]
    }

    pub fn cur_season_mut(&mut self) -> &mut SeasonState {
        &mut self.dir.seasons_mut()[self.config.season_num]
    }
}

pub fn search_for_series_info<B>(
    backend: &B,
    name: &str,
    season: usize,
) -> Result<AnimeInfo, SeriesError>
where
    B: SyncBackend,
{
    println!("[{}] searching on {}..", name, B::name());

    let mut found = backend.search_by_name(name)?;

    if !found.is_empty() {
        println!(
            "select season {} by entering the number next to its name:\n",
            1 + season
        );

        println!("0 [custom search]");

        for (i, series) in found.iter().enumerate() {
            println!("{} [{}]", 1 + i, series.title);
        }

        let index = input::read_range(0, found.len())?;

        if index == 0 {
            println!("enter the name you want to search for:");

            let name = input::read_line()?;
            search_for_series_info(backend, &name, season)
        } else {
            let info = found.swap_remove(index - 1);
            Ok(info)
        }
    } else {
        println!("no results found\nplease enter a custom search term:");

        let name = input::read_line()?;
        search_for_series_info(backend, &name, season)
    }
}
<commit_msg>[series] Remove redundant season parameter from some functions<commit_after>pub mod parse;

use self::parse::{EpisodeData, SaveData, SeasonState};
use backend::{AnimeEntry, AnimeInfo, Status, SyncBackend};
use chrono::Local;
use error::SeriesError;
use input::{self, Answer};
use process;
use std::borrow::Cow;
use std::ops::Range;
use std::path::Path;

pub struct SeriesConfig<B>
where
    B: SyncBackend,
{
    pub offline_mode: bool,
    pub sync_service: B,
    pub season_num: usize,
}

pub struct FolderData {
    pub episodes: EpisodeData,
    pub savefile: SaveData,
}

impl FolderData {
    pub fn load_dir(path: &Path) -> Result<FolderData, SeriesError> {
        let mut savefile = SaveData::from_dir(path)?;
        let episodes = EpisodeData::parse_until_valid_pattern(path, &mut savefile.episode_matcher)?;

        Ok(FolderData { episodes, savefile })
    }

    pub fn save(&self) -> Result<(), SeriesError> {
        self.savefile.write_to_file()
    }

    pub fn populate_season_data<B>(&mut self, config: &SeriesConfig<B>) -> Result<(), SeriesError>
    where
        B: SyncBackend,
    {
        let num_seasons = self.seasons().len();

        if num_seasons > config.season_num {
            return Ok(());
        }

        for cur_season in num_seasons..=config.season_num {
            let info = self.fetch_series_info(config, cur_season)?;
            let entry = AnimeEntry::new(info);

            let season = SeasonState {
                state: entry,
                needs_info: config.offline_mode,
                needs_sync: config.offline_mode,
            };

            self.seasons_mut().push(season);
        }

        Ok(())
    }

    pub fn fetch_series_info<B>(
        &mut self,
        config: &SeriesConfig<B>,
        cur_season: usize,
    ) -> Result<AnimeInfo, SeriesError>
    where
        B: SyncBackend,
    {
        if config.offline_mode {
            // Return existing data if we already have it, otherwise return barebones info
            if self.seasons().len() > config.season_num {
                let info = self.seasons()[config.season_num].state.info.clone();
                Ok(info)
            } else {
                let mut info = AnimeInfo::default();
                info.title = self.episodes.series_name.clone();

                Ok(info)
            }
        } else {
            search_for_series_info(&config.sync_service, &self.episodes.series_name, cur_season)
        }
    }

    pub fn sync_remote_season_info<B>(
        &mut self,
        config: &SeriesConfig<B>,
    ) -> Result<(), SeriesError>
    where
        B: SyncBackend,
    {
        if config.season_num >= self.seasons().len() {
            return Ok(());
        }

        let mut season_data = self.seasons_mut()[config.season_num].clone();

        if season_data.needs_info {
            season_data.state.info = self.fetch_series_info(config, config.season_num)?;

            // We want to stay in a needs-sync state in offline mode so the "real" info
            // can be inserted when the series is played in online mode
            if !config.offline_mode {
                season_data.needs_info = false;
            }
        }

        // Sync data from the backend when not offline
        if !config.offline_mode {
            let entry = config
                .sync_service
                .get_list_entry(season_data.state.info.clone())?;

            if let Some(entry) = entry {
                // If we don't have new data to report, we should sync the data from the backend to keep up with
                // any changes made outside of the program
                if !season_data.needs_sync {
                    season_data.state = entry;
                }
            }
        }

        self.seasons_mut()[config.season_num] = season_data;
        Ok(())
    }

    pub fn calculate_season_offset(&self, mut range: Range<usize>) -> u32 {
        let num_seasons = self.savefile.season_states.len();
        range.start = num_seasons.min(range.start);
        range.end = num_seasons.min(range.end);

        let mut offset = 0;

        for i in range {
            let season = &self.savefile.season_states[i];

            match season.state.info.episodes {
                Some(eps) => offset += eps,
                None => return offset,
            }
        }

        offset
    }

    pub fn seasons(&self) -> &Vec<SeasonState> {
        &self.savefile.season_states
    }

    pub fn seasons_mut(&mut self) -> &mut Vec<SeasonState> {
        &mut self.savefile.season_states
    }
}

pub struct Series<B>
where
    B: SyncBackend,
{
    config: SeriesConfig<B>,
    dir: FolderData,
    ep_offset: u32,
}

impl<B> Series<B>
where
    B: SyncBackend,
{
    pub fn new(config: SeriesConfig<B>, dir: FolderData) -> Series<B> {
        Series {
            config,
            dir,
            ep_offset: 0,
        }
    }

    pub fn prepare(&mut self) -> Result<(), SeriesError> {
        self.dir.populate_season_data(&self.config)?;
        self.dir.sync_remote_season_info(&self.config)?;

        self.ep_offset = self.dir.calculate_season_offset(0..self.config.season_num);
        self.dir.save()?;

        self.prepare_list_entry()?;

        Ok(())
    }

    fn prepare_list_entry(&mut self) -> Result<(), SeriesError> {
        let status = self.cur_season().state.status;

        match status {
            Status::Watching | Status::Rewatching => Ok(()),
            Status::PlanToWatch => self.update_list_entry_status(Status::Watching),
            Status::Completed => self.update_list_entry_status(Status::Rewatching),
            Status::OnHold | Status::Dropped => self.prompt_to_watch_paused_series(),
        }
    }

    fn update_list_entry_status(&mut self, status: Status) -> Result<(), SeriesError> {
        match status {
            Status::Watching => {
                let entry = &mut self.cur_season_mut().state;

                // A series that was on hold probably already has a starting date, and it would make
                // more sense to use that one instead of replacing it
                if entry.status != Status::OnHold {
                    entry.start_date = Some(Local::today().naive_local());
                }

                entry.finish_date = None;
            }
            Status::Rewatching => {
                let entry = &mut self.cur_season_mut().state;

                println!("[{}] already completed", entry.info.title);
                println!("do you want to reset the start and end dates of the series? (Y/n)");

                if input::read_yn(Answer::Yes)? {
                    entry.start_date = Some(Local::today().naive_local());
                    entry.finish_date = None;
                }

                entry.watched_episodes = 0;
            }
            Status::Completed => {
                let entry = &mut self.cur_season_mut().state;

                if entry.finish_date.is_none() {
                    entry.finish_date = Some(Local::today().naive_local());
                }

                println!("[{}] completed!", entry.info.title);

                self.prompt_to_update_score();
            }
            Status::Dropped => {
                let entry = &mut self.cur_season_mut().state;

                if entry.finish_date.is_none() {
                    entry.finish_date = Some(Local::today().naive_local());
                }
            }
            Status::OnHold | Status::PlanToWatch => (),
        }

        let entry = &mut self.cur_season_mut().state;
        entry.status = status;

        self.update_list_entry()?;

        Ok(())
    }

    pub fn update_list_entry(&mut self) -> Result<(), SeriesError> {
        self.cur_season_mut().needs_sync = self.config.offline_mode;
        self.dir.save()?;

        if self.config.offline_mode {
            return Ok(());
        }

        self.config
            .sync_service
            .update_list_entry(&self.cur_season().state)?;

        Ok(())
    }

    pub fn play_episode(&mut self, episode: u32) -> Result<(), SeriesError> {
        let absolute_ep = self.ep_offset + episode;
        let path = self.dir.episodes.get_episode(absolute_ep)?.clone();

        let status = process::open_with_default(path).map_err(SeriesError::FailedToOpenPlayer)?;

        let entry = &mut self.cur_season_mut().state;
        entry.watched_episodes = episode.max(entry.watched_episodes);

        if !status.success() {
            eprintln!("video player not exited normally");
            eprintln!("do you still want to count the episode as completed? (y/N)");

            if !input::read_yn(Answer::No)? {
                return Ok(());
            }
        }

        match entry.info.episodes {
            Some(total_eps) if episode >= total_eps => {
                self.update_list_entry_status(Status::Completed)?;
                return Err(SeriesError::RequestExit);
            }
            _ => self.prompt_episode_completed()?,
        }

        Ok(())
    }

    pub fn play_all_episodes(&mut self) -> Result<(), SeriesError> {
        self.prepare()?;

        loop {
            let next_episode = self.cur_season().state.watched_episodes + 1;

            self.play_episode(next_episode)?;
            self.prompt_next_episode_options()?;
        }
    }

    fn prompt_episode_completed(&mut self) -> Result<(), SeriesError> {
        let entry = &self.cur_season().state;

        let total_episodes = entry
            .info
            .episodes
            .map(|e| Cow::Owned(e.to_string()))
            .unwrap_or_else(|| Cow::Borrowed("?"));

        println!(
            "[{}] episode {}/{} completed",
            entry.info.title, entry.watched_episodes, total_episodes
        );

        self.update_list_entry()?;
        Ok(())
    }

    fn prompt_next_episode_options(&mut self) -> Result<(), SeriesError> {
        let current_score_text: Cow<str> = match self.format_entry_score() {
            Some(score) => Cow::Owned(format!(" [{}]", score)),
            None => Cow::Borrowed(""),
        };

        println!("series options:");
        println!("\t[d] drop\n\t[h] put on hold\n\t[r] rate{}\n\t[x] exit\n\t[n] watch next episode (default)", current_score_text);

        let input = input::read_line()?.to_lowercase();

        match input.as_str() {
            "d" => {
                self.update_list_entry_status(Status::Dropped)?;
                Err(SeriesError::RequestExit)
            }
            "h" => {
                self.update_list_entry_status(Status::OnHold)?;
                Err(SeriesError::RequestExit)
            }
            "r" => {
                self.prompt_to_update_score();
                self.update_list_entry()?;

                self.prompt_next_episode_options()
            }
            "x" => Err(SeriesError::RequestExit),
            _ => Ok(()),
        }
    }

    fn prompt_to_update_score(&mut self) {
        let (min_score, max_score) = self.config.sync_service.formatted_score_range();

        println!(
            "enter your score between {} and {} (press return to skip):",
            min_score, max_score
        );

        // TODO: use read_range() with empty line bypassing
        let input = match input::read_line() {
            Ok(ref input) if input.is_empty() => return,
            Ok(input) => input,
            Err(err) => {
                eprintln!("failed to read score input: {}", err);
                return;
            }
        };

        match self.config.sync_service.parse_score(&input) {
            Ok(score) => {
                let entry = &mut self.cur_season_mut().state;
                entry.score = Some(score);
            }
            Err(err) => eprintln!("failed to parse score: {}", err),
        }
    }

    fn prompt_to_watch_paused_series(&mut self) -> Result<(), SeriesError> {
        let entry = &mut self.cur_season_mut().state;

        println!(
            "[{}] was previously put on hold or dropped",
            entry.info.title
        );

        println!("do you want to watch it from the beginning? (Y/n)");

        if input::read_yn(Answer::Yes)? {
            entry.watched_episodes = 0;
        }

        self.update_list_entry_status(Status::Watching)?;
        Ok(())
    }

    fn format_entry_score(&self) -> Option<String> {
        let entry = &self.cur_season().state;

        match entry.score {
            Some(score) => {
                let formatted_score = self.config.sync_service.format_score(score);

                match formatted_score {
                    Ok(score) => Some(score),
                    Err(err) => {
                        eprintln!("failed to read existing list entry score: {}", err);
                        None
                    }
                }
            }
            None => None,
        }
    }

    pub fn cur_season(&self) -> &SeasonState {
        &self.dir.seasons()[self.config.season_num]
    }

    pub fn cur_season_mut(&mut self) -> &mut SeasonState {
        &mut self.dir.seasons_mut()[self.config.season_num]
    }
}

pub fn search_for_series_info<B>(
    backend: &B,
    name: &str,
    season: usize,
) -> Result<AnimeInfo, SeriesError>
where
    B: SyncBackend,
{
    println!("[{}] searching on {}..", name, B::name());

    let mut found = backend.search_by_name(name)?;

    if !found.is_empty() {
        println!(
            "select season {} by entering the number next to its name:\n",
            1 + season
        );

        println!("0 [custom search]");

        for (i, series) in found.iter().enumerate() {
            println!("{} [{}]", 1 + i, series.title);
        }

        let index = input::read_range(0, found.len())?;

        if index == 0 {
            println!("enter the name you want to search for:");

            let name = input::read_line()?;
            search_for_series_info(backend, &name, season)
        } else {
            let info = found.swap_remove(index - 1);
            Ok(info)
        }
    } else {
        println!("no results found\nplease enter a custom search term:");

        let name = input::read_line()?;
        search_for_series_info(backend, &name, season)
    }
}
<|endoftext|>


<commit_before>//! HTTP Server
//!
//! A `Server` is created to listen on a port, parse HTTP requests, and hand
//! them off to a `Service`.
//!
//! There are two levels of APIs provide for constructing HTTP servers:
//!
//! - The higher-level [`Server`](Server) type.
//! - The lower-level [`conn`](conn) module.
//!
//! # Server
//!
//! The [`Server`](Server) is main way to start listening for HTTP requests.
//! It wraps a listener with a [`MakeService`](crate::service), and then should
//! be executed to start serving requests.
//!
//! [`Server`](Server) accepts connections in both HTTP1 and HTTP2 by default.
//!
//! ## Example
//!
//! ```no_run
//! use std::convert::Infallible;
//! use std::net::SocketAddr;
//! use hyper::{Body, Request, Response, Server};
//! use hyper::service::{make_service_fn, service_fn};
//!
//! async fn handle(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
//!     Ok(Response::new(Body::from("Hello World")))
//! }
//!
//! # #[cfg(feature = "runtime")]
//! #[tokio::main]
//! async fn main() {
//!     // Construct our SocketAddr to listen on...
//!     let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
//!
//!     // And a MakeService to handle each connection...
//!     let make_service = make_service_fn(|_conn| async {
//!         Ok::<_, Infallible>(service_fn(handle))
//!     });
//!
//!     // Then bind and serve...
//!     let server = Server::bind(&addr).serve(make_service);
//!
//!     // And run forever...
//!     if let Err(e) = server.await {
//!         eprintln!("server error: {}", e);
//!     }
//! }
//! # #[cfg(not(feature = "runtime"))]
//! # fn main() {}
//! ```

pub mod accept;

cfg_feature! {
    #![any(feature = "http1", feature = "http2")]

    pub use self::server::{Builder, Server};

    pub mod conn;
    mod server;
    mod shutdown;
    #[cfg(feature = "tcp")]
    mod tcp;
}
<commit_msg>docs(server): use cfg_feature! on hyper::server::conn::tcp instead of #[cfg]<commit_after>//! HTTP Server
//!
//! A `Server` is created to listen on a port, parse HTTP requests, and hand
//! them off to a `Service`.
//!
//! There are two levels of APIs provide for constructing HTTP servers:
//!
//! - The higher-level [`Server`](Server) type.
//! - The lower-level [`conn`](conn) module.
//!
//! # Server
//!
//! The [`Server`](Server) is main way to start listening for HTTP requests.
//! It wraps a listener with a [`MakeService`](crate::service), and then should
//! be executed to start serving requests.
//!
//! [`Server`](Server) accepts connections in both HTTP1 and HTTP2 by default.
//!
//! ## Example
//!
//! ```no_run
//! use std::convert::Infallible;
//! use std::net::SocketAddr;
//! use hyper::{Body, Request, Response, Server};
//! use hyper::service::{make_service_fn, service_fn};
//!
//! async fn handle(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
//!     Ok(Response::new(Body::from("Hello World")))
//! }
//!
//! # #[cfg(feature = "runtime")]
//! #[tokio::main]
//! async fn main() {
//!     // Construct our SocketAddr to listen on...
//!     let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
//!
//!     // And a MakeService to handle each connection...
//!     let make_service = make_service_fn(|_conn| async {
//!         Ok::<_, Infallible>(service_fn(handle))
//!     });
//!
//!     // Then bind and serve...
//!     let server = Server::bind(&addr).serve(make_service);
//!
//!     // And run forever...
//!     if let Err(e) = server.await {
//!         eprintln!("server error: {}", e);
//!     }
//! }
//! # #[cfg(not(feature = "runtime"))]
//! # fn main() {}
//! ```

pub mod accept;

cfg_feature! {
    #![any(feature = "http1", feature = "http2")]

    pub use self::server::{Builder, Server};

    pub mod conn;
    mod server;
    mod shutdown;

    cfg_feature! {
        #![feature = "tcp"]

        mod tcp;
    }
}
<|endoftext|>


<commit_before>use std::collections::HashMap;
use super::terminal::Key;

const DEFAULT_KEYBINDS: &'static str = r#"^Q: Quit
^W: CloseFile
^O: OpenFile
^S: SaveFile
^X: Cut
^C: Copy
^V: Paste
^F: Find
^H: FindReplace
^Z: Undo
^Y: Redo
^R: Refresh
^T: NewTab
"#;

macro_attr! {
    #[derive(Clone, EnumDisplay!, EnumFromStr!)]
    pub enum Command {
        Quit,
        CloseFile,
        OpenFile,
        SaveFile,
        Cut,
        Copy,
        Paste,
        Find,
        FindReplace,
        Undo,
        Redo,
        Refresh,
        NewTab,
    }
}

pub struct KeybindTable {
    table: HashMap<Key, Command>
}

impl KeybindTable {
    pub fn lookup(&self, key: Key) -> Option<Command> {
        self.table.get(&key).cloned()
    }

    pub fn entries(&self) -> Vec<(String, String)> {
        self.table.iter().map(|(key, command)| {
            let key = match key {
                &Key::Ctrl(ref k) => match **k {
                    Key::Char(ref k) => format!("^{}", *k).to_uppercase(),
                    _ => String::from("???")
                },
                _ => String::from("???")
            };
            (key, format!("{}", command))
        }).collect()
    }
}

impl Default for KeybindTable {
    fn default() -> Self {
        Self::from(DEFAULT_KEYBINDS)
    }
}

fn decode_key_spec(spec: &str) -> Option<Key> {
    if spec.len() != 2 {
        println!("Bad key specifier: {}", spec);
        return None;
    }
    let (modifier, key) = spec.split_at(1);
    let modifier = modifier.chars().next().unwrap();
    let key = key.chars().next().unwrap();
    if modifier != '^' {
        println!("Bad key specifier: {}", spec);
        return None;
    }
    Some(Key::Ctrl(Box::new(Key::Char(key))))
}

impl<'a> From<&'a str> for KeybindTable {
    fn from(text: &'a str) -> Self {
        let mut result = KeybindTable {
            table: HashMap::new()
        };
        for line in text.lines() {
            let data: Vec<_> = line.split(": ").collect();
            if data.len() != 2 {
                println!("Bad keybind specifier: {}", line);
                continue
            }
            if let Ok(command) = data[1].parse::<Command>() {
                let key_spec = data[0].to_lowercase();
                match decode_key_spec(&key_spec) {
                    Some(key) => {
                        result.table.insert(key, command);
                    },
                    _ => {}
                }
            } else {
                println!("Bad command: {}", data[1]);
            }
        }
        result
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn keybind_parsing_works() {
        let keys = KeybindTable::from("^Q: Quit");
        match keys.lookup(Key::Ctrl('q')) {
            Some(Command::Quit) => (),
            _ => panic!("Looking up ^Q failed!")
        }
        match keys.lookup(Key::Ctrl('x')) {
            None => (),
            _ => panic!("Looking up ^X succeeded!")
        }
        let keys = KeybindTable::from("^X: Quit");
        match keys.lookup(Key::Ctrl('x')) {
            Some(Command::Quit) => (),
            _ => panic!("Looking up ^X failed!")
        }
        match keys.lookup(Key::Ctrl('q')) {
            None => (),
            _ => panic!("Looking up ^Q succeeded!")
        }
    }
}
<commit_msg>Don't break all the tests<commit_after>use std::collections::HashMap;
use super::terminal::Key;

const DEFAULT_KEYBINDS: &'static str = r#"^Q: Quit
^W: CloseFile
^O: OpenFile
^S: SaveFile
^X: Cut
^C: Copy
^V: Paste
^F: Find
^H: FindReplace
^Z: Undo
^Y: Redo
^R: Refresh
^T: NewTab
"#;

macro_attr! {
    #[derive(Clone, EnumDisplay!, EnumFromStr!)]
    pub enum Command {
        Quit,
        CloseFile,
        OpenFile,
        SaveFile,
        Cut,
        Copy,
        Paste,
        Find,
        FindReplace,
        Undo,
        Redo,
        Refresh,
        NewTab,
    }
}

pub struct KeybindTable {
    table: HashMap<Key, Command>
}

impl KeybindTable {
    pub fn lookup(&self, key: Key) -> Option<Command> {
        self.table.get(&key).cloned()
    }

    pub fn entries(&self) -> Vec<(String, String)> {
        self.table.iter().map(|(key, command)| {
            let key = match key {
                &Key::Ctrl(ref k) => match **k {
                    Key::Char(ref k) => format!("^{}", *k).to_uppercase(),
                    _ => String::from("???")
                },
                _ => String::from("???")
            };
            (key, format!("{}", command))
        }).collect()
    }
}

impl Default for KeybindTable {
    fn default() -> Self {
        Self::from(DEFAULT_KEYBINDS)
    }
}

fn decode_key_spec(spec: &str) -> Option<Key> {
    if spec.len() != 2 {
        println!("Bad key specifier: {}", spec);
        return None;
    }
    let (modifier, key) = spec.split_at(1);
    let modifier = modifier.chars().next().unwrap();
    let key = key.chars().next().unwrap();
    if modifier != '^' {
        println!("Bad key specifier: {}", spec);
        return None;
    }
    Some(Key::Ctrl(Box::new(Key::Char(key))))
}

impl<'a> From<&'a str> for KeybindTable {
    fn from(text: &'a str) -> Self {
        let mut result = KeybindTable {
            table: HashMap::new()
        };
        for line in text.lines() {
            let data: Vec<_> = line.split(": ").collect();
            if data.len() != 2 {
                println!("Bad keybind specifier: {}", line);
                continue
            }
            if let Ok(command) = data[1].parse::<Command>() {
                let key_spec = data[0].to_lowercase();
                match decode_key_spec(&key_spec) {
                    Some(key) => {
                        result.table.insert(key, command);
                    },
                    _ => {}
                }
            } else {
                println!("Bad command: {}", data[1]);
            }
        }
        result
    }
}

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

    fn ctrl(c: char) -> Key {
        Key::Ctrl(Box::new(Key::Char(c)))
    }

    #[test]
    fn keybind_parsing_works() {
        let keys = KeybindTable::from("^Q: Quit");
        match keys.lookup(ctrl('q')) {
            Some(Command::Quit) => (),
            _ => panic!("Looking up ^Q failed!")
        }
        match keys.lookup(ctrl('x')) {
            None => (),
            _ => panic!("Looking up ^X succeeded!")
        }
        let keys = KeybindTable::from("^X: Quit");
        match keys.lookup(ctrl('x')) {
            Some(Command::Quit) => (),
            _ => panic!("Looking up ^X failed!")
        }
        match keys.lookup(ctrl('q')) {
            None => (),
            _ => panic!("Looking up ^Q succeeded!")
        }
    }
}
<|endoftext|>


<commit_before>use crate::i18n::LocalText;
use colored::{ColoredString, Colorize};
use git2::Repository;
use std::io::prelude::*;
use std::sync::{Arc, RwLock};
use std::{env, error, fs, result};
use subprocess::{Exec, NullFile, Redirection};

type Result<T> = result::Result<T, Box<dyn error::Error>>;

/// Dump what we know about the repo
pub fn run() -> Result<()> {
    crate::header("status-header");
    is_setup()?;
    Ok(())
}

#[allow(dead_code)]
enum RunAsMode {
    RunAsSubmodule,
    RunAsDirectory,
    RunAsDocker,
    RunAsRunner,
    RunAsSystem,
}

#[allow(dead_code)]
/// Determine the runtime mode
fn run_as() -> RunAsMode {
    RunAsMode::RunAsDocker {}
}

/// Evaluate whether this project is pis_setup()?roperly configured
pub fn is_setup() -> Result<bool> {
    let results = Arc::new(RwLock::new(Vec::new()));

    // First round of tests, entirely independent
    rayon::scope(|s| {
        s.spawn(|_| {
            let ret = is_repo().unwrap();
            results.write().unwrap().push(ret);
        });
        s.spawn(|_| {
            let ret = is_make_exectuable().unwrap();
            results.write().unwrap().push(ret);
        });
    });

    // Second round of tests, dependent on first set
    if results.read().unwrap().iter().all(|&v| v) {
        rayon::scope(|s| {
            s.spawn(|_| {
                let ret = is_writable().unwrap();
                results.write().unwrap().push(ret);
            });
            s.spawn(|_| {
                let ret = is_make_gnu().unwrap();
                results.write().unwrap().push(ret);
            });
        });
    }

    let ret = results.read().unwrap().iter().all(|&v| v);
    let msg = LocalText::new(if ret { "status-good" } else { "status-bad" }).fmt();
    eprintln!(
        "{} {}",
        "┗━".cyan(),
        if ret { msg.green() } else { msg.red() }
    );
    Ok(ret)
}

/// Are we in a git repo?
pub fn is_repo() -> Result<bool> {
    let cwd = env::current_dir()?;
    let ret = Repository::discover(cwd).is_ok();
    display_check("status-is-repo", ret);
    Ok(ret)
}

/// Is the git repo we are in writable?
pub fn is_writable() -> Result<bool> {
    let cwd = env::current_dir()?;
    let repo = Repository::discover(cwd)?;
    let workdir = repo.workdir().unwrap();
    let testfile = workdir.join(".casile-write-test");
    let mut file = fs::File::create(&testfile)?;
    file.write_all(b"test")?;
    fs::remove_file(&testfile)?;
    let ret = true;
    display_check("status-is-writable", ret);
    Ok(true)
}

/// Check if we can execute the system's `make` utility
pub fn is_make_exectuable() -> Result<bool> {
    let ret = Exec::cmd("make")
        .arg("-v")
        .stdout(NullFile)
        .stderr(NullFile)
        .join()
        .is_ok();
    display_check("status-is-make-executable", ret);
    Ok(true)
}

/// Check that the system's `make` utility is GNU Make
pub fn is_make_gnu() -> Result<bool> {
    let out = Exec::cmd("make")
        .arg("-v")
        .stdout(Redirection::Pipe)
        .stderr(NullFile)
        .capture()?
        .stdout_str();
    let ret = out.starts_with("GNU Make 4.");
    display_check("status-is-make-gnu", ret);
    Ok(true)
}

fn display_check(key: &str, val: bool) {
    eprintln!(
        "{} {} {}",
        "┠─".cyan(),
        LocalText::new(key).fmt(),
        fmt_t_f(val)
    );
}

/// Format a localized string just for true / false status prints
fn fmt_t_f(val: bool) -> ColoredString {
    let key = if val { "status-true" } else { "status-false" };
    let text = LocalText::new(key).fmt();
    if val {
        text.green()
    } else {
        text.red()
    }
}
<commit_msg>chore: Work towards outputting run time mode from status subcommand<commit_after>use crate::i18n::LocalText;
use colored::{ColoredString, Colorize};
use git2::Repository;
use std::io::prelude::*;
use std::sync::{Arc, RwLock};
use std::{env, error, fs, result};
use subprocess::{Exec, NullFile, Redirection};

type Result<T> = result::Result<T, Box<dyn error::Error>>;

/// Dump what we know about the repo
pub fn run() -> Result<()> {
    crate::header("status-header");
    is_setup()?;
    // println!("{:?}", run_as());
    Ok(())
}

#[allow(dead_code)]
#[derive(Debug)]
enum RunAsMode {
    RunAsSubmodule,
    RunAsDirectory,
    RunAsDocker,
    RunAsRunner,
    RunAsSystem,
}

#[allow(dead_code)]
/// Determine the runtime mode
fn run_as() -> RunAsMode {
    RunAsMode::RunAsDocker {}
}

/// Evaluate whether this project is pis_setup()?roperly configured
pub fn is_setup() -> Result<bool> {
    let results = Arc::new(RwLock::new(Vec::new()));

    // First round of tests, entirely independent
    rayon::scope(|s| {
        s.spawn(|_| {
            let ret = is_repo().unwrap();
            results.write().unwrap().push(ret);
        });
        s.spawn(|_| {
            let ret = is_make_exectuable().unwrap();
            results.write().unwrap().push(ret);
        });
    });

    // Second round of tests, dependent on first set
    if results.read().unwrap().iter().all(|&v| v) {
        rayon::scope(|s| {
            s.spawn(|_| {
                let ret = is_writable().unwrap();
                results.write().unwrap().push(ret);
            });
            s.spawn(|_| {
                let ret = is_make_gnu().unwrap();
                results.write().unwrap().push(ret);
            });
        });
    }

    let ret = results.read().unwrap().iter().all(|&v| v);
    let msg = LocalText::new(if ret { "status-good" } else { "status-bad" }).fmt();
    eprintln!(
        "{} {}",
        "┗━".cyan(),
        if ret { msg.green() } else { msg.red() }
    );
    Ok(ret)
}

/// Are we in a git repo?
pub fn is_repo() -> Result<bool> {
    let cwd = env::current_dir()?;
    let ret = Repository::discover(cwd).is_ok();
    display_check("status-is-repo", ret);
    Ok(ret)
}

/// Is the git repo we are in writable?
pub fn is_writable() -> Result<bool> {
    let cwd = env::current_dir()?;
    let repo = Repository::discover(cwd)?;
    let workdir = repo.workdir().unwrap();
    let testfile = workdir.join(".casile-write-test");
    let mut file = fs::File::create(&testfile)?;
    file.write_all(b"test")?;
    fs::remove_file(&testfile)?;
    let ret = true;
    display_check("status-is-writable", ret);
    Ok(true)
}

/// Check if we can execute the system's `make` utility
pub fn is_make_exectuable() -> Result<bool> {
    let ret = Exec::cmd("make")
        .arg("-v")
        .stdout(NullFile)
        .stderr(NullFile)
        .join()
        .is_ok();
    display_check("status-is-make-executable", ret);
    Ok(true)
}

/// Check that the system's `make` utility is GNU Make
pub fn is_make_gnu() -> Result<bool> {
    let out = Exec::cmd("make")
        .arg("-v")
        .stdout(Redirection::Pipe)
        .stderr(NullFile)
        .capture()?
        .stdout_str();
    let ret = out.starts_with("GNU Make 4.");
    display_check("status-is-make-gnu", ret);
    Ok(true)
}

fn display_check(key: &str, val: bool) {
    eprintln!(
        "{} {} {}",
        "┠─".cyan(),
        LocalText::new(key).fmt(),
        fmt_t_f(val)
    );
}

/// Format a localized string just for true / false status prints
fn fmt_t_f(val: bool) -> ColoredString {
    let key = if val { "status-true" } else { "status-false" };
    let text = LocalText::new(key).fmt();
    if val {
        text.green()
    } else {
        text.red()
    }
}
<|endoftext|>


<commit_before>use {Error, Result};
use errno::Errno;
use libc::{pid_t, c_void, c_long};

#[cfg(all(target_os = "linux",
          any(target_arch = "x86",
              target_arch = "x86_64",
              target_arch = "arm")),
          )]
pub mod ptrace {
    use libc::c_int;

    pub type PtraceRequest = c_int;

    pub const PTRACE_TRACEME:     PtraceRequest = 0;
    pub const PTRACE_PEEKTEXT:    PtraceRequest = 1;
    pub const PTRACE_PEEKDATA:    PtraceRequest = 2;
    pub const PTRACE_PEEKUSER:    PtraceRequest = 3;
    pub const PTRACE_POKETEXT:    PtraceRequest = 4;
    pub const PTRACE_POKEDATA:    PtraceRequest = 5;
    pub const PTRACE_POKEUSER:    PtraceRequest = 6;
    pub const PTRACE_CONT:        PtraceRequest = 7;
    pub const PTRACE_KILL:        PtraceRequest = 8;
    pub const PTRACE_SINGLESTEP:  PtraceRequest = 9;
    pub const PTRACE_GETREGS:     PtraceRequest = 12;
    pub const PTRACE_SETREGS:     PtraceRequest = 13;
    pub const PTRACE_GETFPREGS:   PtraceRequest = 14;
    pub const PTRACE_SETFPREGS:   PtraceRequest = 15;
    pub const PTRACE_ATTACH:      PtraceRequest = 16;
    pub const PTRACE_DETACH:      PtraceRequest = 17;
    pub const PTRACE_GETFPXREGS:  PtraceRequest = 18;
    pub const PTRACE_SETFPXREGS:  PtraceRequest = 19;
    pub const PTRACE_SYSCALL:     PtraceRequest = 24;
    pub const PTRACE_SETOPTIONS:  PtraceRequest = 0x4200;
    pub const PTRACE_GETEVENTMSG: PtraceRequest = 0x4201;
    pub const PTRACE_GETSIGINFO:  PtraceRequest = 0x4202;
    pub const PTRACE_SETSIGINFO:  PtraceRequest = 0x4203;
    pub const PTRACE_GETREGSET:   PtraceRequest = 0x4204;
    pub const PTRACE_SETREGSET:   PtraceRequest = 0x4205;
    pub const PTRACE_SEIZE:       PtraceRequest = 0x4206;
    pub const PTRACE_INTERRUPT:   PtraceRequest = 0x4207;
    pub const PTRACE_LISTEN:      PtraceRequest = 0x4208;
    pub const PTRACE_PEEKSIGINFO: PtraceRequest = 0x4209;
}

mod ffi {
    use libc::{pid_t, c_int, c_long, c_void};

    extern {
        pub fn ptrace(request: c_int, pid: pid_t, addr: * const c_void, data: * const c_void) -> c_long;
    }
}

pub fn ptrace(request: ptrace::PtraceRequest, pid: pid_t, addr: *mut c_void, data: *mut c_void) -> Result<c_long> {
    use self::ptrace::*;

    match request {
        PTRACE_PEEKTEXT | PTRACE_PEEKDATA | PTRACE_PEEKUSER => ptrace_peek(request, pid, addr, data),
        _ => ptrace_other(request, pid, addr, data)
    }
}

fn ptrace_peek(request: ptrace::PtraceRequest, pid: pid_t, addr: *mut c_void, data: *mut c_void) -> Result<c_long> {
    let ret = unsafe {
        Errno::clear();
        ffi::ptrace(request, pid, addr, data)
    };
    if ret == -1 && Errno::last() != Errno::UnknownErrno {
        return Err(Error::Sys(Errno::last()));
    }
    Ok::<c_long, Error>(ret)
}

fn ptrace_other(request: ptrace::PtraceRequest, pid: pid_t, addr: *mut c_void, data: *mut c_void) -> Result<c_long> {
    match unsafe { ffi::ptrace(request, pid, addr, data) } {
        -1 => Err(Error::Sys(Errno::last())),
        _  => Ok(0)
    }
}
<commit_msg>add function for handling PTRACE_SETOPTIONS nicely<commit_after>use {Error, Result};
use errno::Errno;
use libc::{pid_t, c_void, c_long};

#[cfg(all(target_os = "linux",
          any(target_arch = "x86",
              target_arch = "x86_64",
              target_arch = "arm")),
          )]
pub mod ptrace {
    use libc::c_int;

    pub type PtraceRequest = c_int;

    pub const PTRACE_TRACEME:     PtraceRequest = 0;
    pub const PTRACE_PEEKTEXT:    PtraceRequest = 1;
    pub const PTRACE_PEEKDATA:    PtraceRequest = 2;
    pub const PTRACE_PEEKUSER:    PtraceRequest = 3;
    pub const PTRACE_POKETEXT:    PtraceRequest = 4;
    pub const PTRACE_POKEDATA:    PtraceRequest = 5;
    pub const PTRACE_POKEUSER:    PtraceRequest = 6;
    pub const PTRACE_CONT:        PtraceRequest = 7;
    pub const PTRACE_KILL:        PtraceRequest = 8;
    pub const PTRACE_SINGLESTEP:  PtraceRequest = 9;
    pub const PTRACE_GETREGS:     PtraceRequest = 12;
    pub const PTRACE_SETREGS:     PtraceRequest = 13;
    pub const PTRACE_GETFPREGS:   PtraceRequest = 14;
    pub const PTRACE_SETFPREGS:   PtraceRequest = 15;
    pub const PTRACE_ATTACH:      PtraceRequest = 16;
    pub const PTRACE_DETACH:      PtraceRequest = 17;
    pub const PTRACE_GETFPXREGS:  PtraceRequest = 18;
    pub const PTRACE_SETFPXREGS:  PtraceRequest = 19;
    pub const PTRACE_SYSCALL:     PtraceRequest = 24;
    pub const PTRACE_SETOPTIONS:  PtraceRequest = 0x4200;
    pub const PTRACE_GETEVENTMSG: PtraceRequest = 0x4201;
    pub const PTRACE_GETSIGINFO:  PtraceRequest = 0x4202;
    pub const PTRACE_SETSIGINFO:  PtraceRequest = 0x4203;
    pub const PTRACE_GETREGSET:   PtraceRequest = 0x4204;
    pub const PTRACE_SETREGSET:   PtraceRequest = 0x4205;
    pub const PTRACE_SEIZE:       PtraceRequest = 0x4206;
    pub const PTRACE_INTERRUPT:   PtraceRequest = 0x4207;
    pub const PTRACE_LISTEN:      PtraceRequest = 0x4208;
    pub const PTRACE_PEEKSIGINFO: PtraceRequest = 0x4209;

    pub type PtraceEvent = c_int;

    pub const PTRACE_EVENT_FORK:       PtraceEvent = 1;
    pub const PTRACE_EVENT_VFORK:      PtraceEvent = 2;
    pub const PTRACE_EVENT_CLONE:      PtraceEvent = 3;
    pub const PTRACE_EVENT_EXEC:       PtraceEvent = 4;
    pub const PTRACE_EVENT_VFORK_DONE: PtraceEvent = 5;
    pub const PTRACE_EVENT_EXIT:       PtraceEvent = 6;
    pub const PTRACE_EVENT_SECCOMP:    PtraceEvent = 6;
    pub const PTRACE_EVENT_STOP:       PtraceEvent = 128;

    pub type PtraceOptions = c_int;
    pub const PTRACE_O_TRACESYSGOOD: PtraceOptions   = 1;
    pub const PTRACE_O_TRACEFORK: PtraceOptions      = (1 << PTRACE_EVENT_FORK);
    pub const PTRACE_O_TRACEVFORK: PtraceOptions     = (1 << PTRACE_EVENT_VFORK);
    pub const PTRACE_O_TRACECLONE: PtraceOptions     = (1 << PTRACE_EVENT_CLONE);
    pub const PTRACE_O_TRACEEXEC: PtraceOptions      = (1 << PTRACE_EVENT_EXEC);
    pub const PTRACE_O_TRACEVFORKDONE: PtraceOptions = (1 << PTRACE_EVENT_VFORK_DONE);
    pub const PTRACE_O_TRACEEXIT: PtraceOptions      = (1 << PTRACE_EVENT_EXIT);
    pub const PTRACE_O_TRACESECCOMP: PtraceOptions   = (1 << PTRACE_EVENT_SECCOMP);
}

mod ffi {
    use libc::{pid_t, c_int, c_long, c_void};

    extern {
        pub fn ptrace(request: c_int, pid: pid_t, addr: * const c_void, data: * const c_void) -> c_long;
    }
}

pub fn ptrace(request: ptrace::PtraceRequest, pid: pid_t, addr: *mut c_void, data: *mut c_void) -> Result<c_long> {
    use self::ptrace::*;

    match request {
        PTRACE_PEEKTEXT | PTRACE_PEEKDATA | PTRACE_PEEKUSER => ptrace_peek(request, pid, addr, data),
        _ => ptrace_other(request, pid, addr, data)
    }
}

fn ptrace_peek(request: ptrace::PtraceRequest, pid: pid_t, addr: *mut c_void, data: *mut c_void) -> Result<c_long> {
    let ret = unsafe {
        Errno::clear();
        ffi::ptrace(request, pid, addr, data)
    };
    if ret == -1 && Errno::last() != Errno::UnknownErrno {
        return Err(Error::Sys(Errno::last()));
    }
    Ok::<c_long, Error>(ret)
}

fn ptrace_other(request: ptrace::PtraceRequest, pid: pid_t, addr: *mut c_void, data: *mut c_void) -> Result<c_long> {
    match unsafe { ffi::ptrace(request, pid, addr, data) } {
        -1 => Err(Error::Sys(Errno::last())),
        _  => Ok(0)
    }
}

/// Set options, as with ptrace(PTRACE_SETOPTIONS,...).
pub fn ptrace_setoptions(pid: pid_t, options: ptrace::PtraceOptions) -> Result<()> {
    use self::ptrace::*;
    use std::ptr;

    try!(ptrace(PTRACE_SETOPTIONS, pid, ptr::null_mut(), options as *mut c_void));
    Ok(())
}

<|endoftext|>