<commit_before>use tasks::RunsTask;
use bot::BotEnv;
use commands::tag::Tagger;
use lalafell::error::*;
use database::models::Tag;

use serenity::model::id::{UserId, GuildId};

use chrono::Duration;
use chrono::Utc;

use diesel::prelude::*;

use std::sync::Arc;
use std::thread;

pub struct AutoTagTask {
  pub next_sleep: i64
}

impl AutoTagTask {
  pub fn new() -> AutoTagTask {
    AutoTagTask {
      next_sleep: 30
    }
  }

  pub fn update_tag(env: &BotEnv, user: UserId, guild: GuildId, character: u64) -> Result<Option<String>> {
    Tagger::tag(env, user, guild, character, false)
  }

  pub fn run_once(&mut self, env: &BotEnv) {
    thread::sleep(Duration::seconds(self.next_sleep).to_std().unwrap());
    self.next_sleep = Duration::minutes(10).num_seconds();
    info!("Autotag task running");
    let users: Vec<Tag> = match ::bot::with_connection(|c| {
      use database::schema::tags::dsl;
      let twelve_hours_ago = Utc::now().timestamp() - Duration::hours(12).num_seconds();
      dsl::tags
        .filter(dsl::last_updated.lt(twelve_hours_ago))
        .load(c)
    }) {
      Ok(t) => t,
      Err(e) => {
        warn!("could not load tags: {}", e);
        return;
      }
    };
    info!("{} tag{} to update", users.len(), if users.len() == 1 { "" } else { "s" });
    for mut tag in users {
      match AutoTagTask::update_tag(env, UserId(*tag.user_id), GuildId(*tag.server_id), *tag.character_id) {
        Err(e) => {
          warn!("Couldn't update tag for user ID {}: {}", *tag.user_id, e);
          continue;
        },
        Ok(Some(s)) => {
          warn!("Couldn't update tag for user ID {}: {}", *tag.user_id, s);
          continue;
        },
        _ => {},
      }
      tag.last_updated = Utc::now().timestamp();
      let res: ::std::result::Result<Tag, _> = ::bot::with_connection(|c| tag.save_changes(c));
      if let Err(e) = res {
        warn!("could not update tag last_updated: {}", e);
      }
    }
    info!("Done updating autotags");
  }
}

impl RunsTask for AutoTagTask {
  fn start(mut self, env: Arc<BotEnv>) {
    info!("Autotag task waiting {} seconds", self.next_sleep);
    loop {
      self.run_once(env.as_ref());
    }
  }
}
<commit_msg>refactor: autotag in batches<commit_after>use tasks::RunsTask;
use bot::BotEnv;
use commands::tag::Tagger;
use lalafell::error::*;
use database::models::Tag;

use serenity::model::id::{UserId, GuildId};

use chrono::Duration;
use chrono::Utc;

use diesel::prelude::*;

use std::sync::Arc;
use std::thread;

pub struct AutoTagTask {
  pub next_sleep: i64
}

impl AutoTagTask {
  pub fn new() -> AutoTagTask {
    AutoTagTask {
      next_sleep: 30
    }
  }

  pub fn update_tag(env: &BotEnv, user: UserId, guild: GuildId, character: u64) -> Result<Option<String>> {
    Tagger::tag(env, user, guild, character, false)
  }

  pub fn run_once(&mut self, env: &BotEnv) {
    thread::sleep(Duration::seconds(self.next_sleep).to_std().unwrap());
    self.next_sleep = Duration::minutes(10).num_seconds();
    info!("Autotag task running");
    let mut users: Vec<Tag> = match ::bot::with_connection(|c| {
      use database::schema::tags::dsl;
      let twelve_hours_ago = Utc::now().timestamp() - Duration::hours(12).num_seconds();
      dsl::tags
        .filter(dsl::last_updated.lt(twelve_hours_ago))
        .load(c)
    }) {
      Ok(t) => t,
      Err(e) => {
        warn!("could not load tags: {}", e);
        return;
      }
    };
    info!("{} tag{} to update", users.len(), if users.len() == 1 { "" } else { "s" });
    for mut tags in users.chunks_mut(20) {
      for mut tag in tags {
        match AutoTagTask::update_tag(env, UserId(*tag.user_id), GuildId(*tag.server_id), *tag.character_id) {
          Err(e) => {
            warn!("Couldn't update tag for user ID {}: {}", *tag.user_id, e);
            continue;
          },
          Ok(Some(s)) => {
            warn!("Couldn't update tag for user ID {}: {}", *tag.user_id, s);
            continue;
          },
          _ => {},
        }
        tag.last_updated = Utc::now().timestamp();
        let res: ::std::result::Result<Tag, _> = ::bot::with_connection(|c| tag.save_changes(c));
        if let Err(e) = res {
          warn!("could not update tag last_updated: {}", e);
        }
      }
      thread::sleep(Duration::seconds(1).to_std().unwrap());
    }
    info!("Done updating autotags");
  }
}

impl RunsTask for AutoTagTask {
  fn start(mut self, env: Arc<BotEnv>) {
    info!("Autotag task waiting {} seconds", self.next_sleep);
    loop {
      self.run_once(env.as_ref());
    }
  }
}
<|endoftext|>


<commit_before>//
//  SOS: the Stupid Operating System
//  by Hawk Weisman (hi@hawkweisman.me)
//
//  Copyright (c) 2015 Hawk Weisman
//  Released under the terms of the MIT license. See `LICENSE` in the root
//  directory of this repository for more information.
//
//! `x86_64` architecture-specific implementation.
pub mod cpu;
pub mod drivers;
pub mod memory;

use memory::PAddr;
use multiboot2;

pub const ARCH_BITS: u8 = 64;

/// Entry point for architecture-specific kernel init
pub fn arch_init(multiboot_addr: PAddr) {

    // -- Unpack multiboot tag ------------------------------------------------
    let boot_info
        = unsafe { multiboot2::Info::from(multiboot_addr)
                    .expect("Could not unpack multiboot2 information!") };

    let mmap_tag // Extract the memory map tag from the multiboot info
        = boot_info.mem_map()
                   .expect("Memory map tag required!");

    println!(" . Detected memory areas:");
    for a in mmap_tag.areas() {
        println!(" . . start: {:#08x}, end: {:#08x}"
                , a.base, a.length );
    }

    let elf_sections_tag // Extract ELF sections tag from the multiboot info
        = boot_info.elf_sections()
                   .expect("ELF sections tag required!");

    println!(" . Detecting kernel ELF sections:");

    let kernel_begin    // Extract kernel ELF sections from  multiboot info
        = elf_sections_tag.sections()
            .map(|s| {
                println!(" . . address: {:#08x}, size: {:#08x}, flags: {:#08x}"
                        , s.addr()
                        , s.length()
                        , s.flags() );
                s.addr() })
            .min()
            .expect("Could not find kernel start section!\
                    \nSomething is deeply wrong.");

    let mut n_elf_sections = 0;
    let kernel_end
        = elf_sections_tag.sections()
            .map(|s| { n_elf_sections += 1; s.addr() })
            .max()
            .expect("Could not find kernel end section!\
                    \nSomething is deeply wrong.");

    println!( " . Detected {} kernel ELF sections.", n_elf_sections);
    println!( " . . Kernel begins at {:#x} and ends at {:#x}."
             , kernel_begin, kernel_end );

    let multiboot_end = multiboot_addr + boot_info.length as u64;

    println!( " . . Multiboot info begins at {:#x} and ends at {:#x}."
             , multiboot_addr, multiboot_end);

}
<commit_msg>[x86_64] arch_init() sets flags for paging<commit_after>//
//  SOS: the Stupid Operating System
//  by Hawk Weisman (hi@hawkweisman.me)
//
//  Copyright (c) 2015 Hawk Weisman
//  Released under the terms of the MIT license. See `LICENSE` in the root
//  directory of this repository for more information.
//
//! `x86_64` architecture-specific implementation.
pub mod cpu;
pub mod drivers;
pub mod memory;

use memory::PAddr;

pub const ARCH_BITS: u8 = 64;

/// Entry point for architecture-specific kernel init
pub fn arch_init(multiboot_addr: PAddr) {
    use multiboot2;
    use self::cpu::{control_regs, msr};

    // -- Unpack multiboot tag ------------------------------------------------
    let boot_info
        = unsafe { multiboot2::Info::from(multiboot_addr)
                    .expect("Could not unpack multiboot2 information!") };

    let mmap_tag // Extract the memory map tag from the multiboot info
        = boot_info.mem_map()
                   .expect("Memory map tag required!");

    println!(" . Detected memory areas:");
    for a in mmap_tag.areas() {
        println!(" . . start: {:#08x}, end: {:#08x}"
                , a.base, a.length );
    }

    let elf_sections_tag // Extract ELF sections tag from the multiboot info
        = boot_info.elf_sections()
                   .expect("ELF sections tag required!");

    println!(" . Detecting kernel ELF sections:");

    let kernel_begin    // Extract kernel ELF sections from  multiboot info
        = elf_sections_tag.sections()
            .map(|s| {
                println!(" . . address: {:#08x}, size: {:#08x}, flags: {:#08x}"
                        , s.addr()
                        , s.length()
                        , s.flags() );
                s.addr() })
            .min()
            .expect("Could not find kernel start section!\
                    \nSomething is deeply wrong.");

    let mut n_elf_sections = 0;
    let kernel_end
        = elf_sections_tag.sections()
            .map(|s| { n_elf_sections += 1; s.addr() })
            .max()
            .expect("Could not find kernel end section!\
                    \nSomething is deeply wrong.");

    println!( " . Detected {} kernel ELF sections.", n_elf_sections);
    println!( " . . Kernel begins at {:#x} and ends at {:#x}."
             , kernel_begin, kernel_end );

    let multiboot_end = multiboot_addr + boot_info.length as u64;

    println!( " . . Multiboot info begins at {:#x} and ends at {:#x}."
             , multiboot_addr, multiboot_end);

    // enable flags needed for paging
    unsafe {
        control_regs::cr0::enable_write_protect(true);
        println!( " . Page write protect ENABED" );

        msr::enable_nxe();
        println!( " . Page no execute bit ENABLED");
    }

}
<|endoftext|>


<commit_before>//! Issues interface

extern crate serde_json;

use super::{Github, Result};
use comments::Comments;
use users::User;
use labels::Label;
use super::SortDirection;
use std::fmt;
use std::collections::HashMap;
use url::form_urlencoded;

/// enum representation of github pull and issue state
#[derive(Clone, Debug, PartialEq)]
pub enum State {
    /// Only open issues
    Open,
    /// Only closed issues
    Closed,
    /// All issues, open or closed
    All,
}

impl fmt::Display for State {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            State::Open => "open",
            State::Closed => "closed",
            State::All => "all",
        }.fmt(f)
    }
}

impl Default for State {
    fn default() -> State {
        State::Open
    }
}

/// Sort options available for github issues
#[derive(Clone, Debug, PartialEq)]
pub enum Sort {
    /// sort by creation time of issue
    Created,
    /// sort by the last time issue was updated
    Updated,
    /// sort by number of comments
    Comments,
}

impl fmt::Display for Sort {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Sort::Created => "created",
            Sort::Updated => "updated",
            Sort::Comments => "comments",
        }.fmt(f)
    }
}

impl Default for Sort {
    fn default() -> Sort {
        Sort::Created
    }
}

/// Provides access to label operations available for an individual issues
pub struct IssueLabels<'a> {
    github: &'a Github,
    owner: String,
    repo: String,
    number: u64,
}

impl<'a> IssueLabels<'a> {
    #[doc(hidden)]
    pub fn new<O, R>(github: &'a Github, owner: O, repo: R, number: u64) -> IssueLabels<'a>
    where
        O: Into<String>,
        R: Into<String>,
    {
        IssueLabels {
            github: github,
            owner: owner.into(),
            repo: repo.into(),
            number: number,
        }
    }

    fn path(&self, more: &str) -> String {
        format!(
            "/repos/{}/{}/issues/{}/labels{}",
            self.owner,
            self.repo,
            self.number,
            more
        )
    }

    /// add a set of labels to this issue ref
    pub fn add(&self, labels: Vec<&str>) -> Result<Vec<Label>> {
        self.github.post::<Vec<Label>>(
            &self.path(""),
            serde_json::to_string(&labels)?.as_bytes(),
        )
    }

    /// remove a label from this issue
    pub fn remove(&self, label: &str) -> Result<()> {
        self.github.delete(&self.path(&format!("/{}", label)))
    }

    /// replace all labels associated with this issue with a new set.
    /// providing an empty set of labels is the same as clearing the
    /// current labels
    pub fn set(&self, labels: Vec<&str>) -> Result<Vec<Label>> {
        self.github.put::<Vec<Label>>(
            &self.path(""),
            serde_json::to_string(&labels)?.as_bytes(),
        )
    }

    /// remove all labels from an issue
    pub fn clear(&self) -> Result<()> {
        self.github.delete(&self.path(""))
    }
}

/// Provides access to operations available for a single issue
/// Typically accessed from `github.repo(.., ..).issues().get(number)`
pub struct IssueRef<'a> {
    github: &'a Github,
    owner: String,
    repo: String,
    number: u64,
}

impl<'a> IssueRef<'a> {
    /// create a new instance of a github repo issue ref
    pub fn new<O, R>(github: &'a Github, owner: O, repo: R, number: u64) -> IssueRef<'a>
    where
        O: Into<String>,
        R: Into<String>,
    {
        IssueRef {
            github: github,
            owner: owner.into(),
            repo: repo.into(),
            number: number,
        }
    }

    fn path(&self, more: &str) -> String {
        format!(
            "/repos/{}/{}/issues/{}{}",
            self.owner,
            self.repo,
            self.number,
            more
        )
    }

    pub fn labels(&self) -> IssueLabels {
        IssueLabels::new(
            self.github,
            self.owner.as_str(),
            self.repo.as_str(),
            self.number,
        )
    }

    pub fn edit(&self, is: &IssueOptions) -> Result<Issue> {
        let data = serde_json::to_string(&is)?;
        self.github.patch::<Issue>(&self.path(""), data.as_bytes())
    }

    pub fn comments(&self) -> Comments {
        Comments::new(
            self.github,
            self.owner.clone(),
            self.repo.clone(),
            self.number,
        )
    }
}

/// Provides access to operations available for a repository issues
/// Typically accessed via `github.repo(..., ...).issues()`
pub struct Issues<'a> {
    github: &'a Github,
    owner: String,
    repo: String,
}

impl<'a> Issues<'a> {
    /// create a new instance of a github repo issue ref
    pub fn new<O, R>(github: &'a Github, owner: O, repo: R) -> Issues<'a>
    where
        O: Into<String>,
        R: Into<String>,
    {
        Issues {
            github: github,
            owner: owner.into(),
            repo: repo.into(),
        }
    }

    fn path(&self, more: &str) -> String {
        format!("/repos/{}/{}/issues{}", self.owner, self.repo, more)
    }

    pub fn get(&self, number: u64) -> IssueRef {
        IssueRef::new(self.github, self.owner.as_str(), self.repo.as_str(), number)
    }

    pub fn create(&self, is: &IssueOptions) -> Result<Issue> {
        let data = serde_json::to_string(&is)?;
        self.github.post::<Issue>(&self.path(""), data.as_bytes())
    }

    pub fn list(&self, options: &IssueListOptions) -> Result<Vec<Issue>> {
        let mut uri = vec![self.path("")];
        if let Some(query) = options.serialize() {
            uri.push(query);
        }
        self.github.get::<Vec<Issue>>(&uri.join("?"))
    }
}

// representations

// todo: simplify with param
#[derive(Default)]
pub struct IssueListOptions {
    params: HashMap<&'static str, String>,
}

impl IssueListOptions {
    pub fn builder() -> IssueListOptionsBuilder {
        IssueListOptionsBuilder::new()
    }

    pub fn serialize(&self) -> Option<String> {
        if self.params.is_empty() {
            None
        } else {
            let encoded: String = form_urlencoded::Serializer::new(String::new())
                .extend_pairs(&self.params)
                .finish();
            Some(encoded)
        }
    }
}

/// a mutable issue list builder
pub struct IssueListOptionsBuilder(IssueListOptions);

impl IssueListOptionsBuilder {
    pub fn new() -> IssueListOptionsBuilder {
        IssueListOptionsBuilder(IssueListOptions { ..Default::default() })
    }

    pub fn state(&mut self, state: State) -> &mut IssueListOptionsBuilder {
        self.0.params.insert("state", state.to_string());
        self
    }

    pub fn sort(&mut self, sort: Sort) -> &mut IssueListOptionsBuilder {
        self.0.params.insert("sort", sort.to_string());
        self
    }

    pub fn asc(&mut self) -> &mut IssueListOptionsBuilder {
        self.direction(SortDirection::Asc)
    }

    pub fn desc(&mut self) -> &mut IssueListOptionsBuilder {
        self.direction(SortDirection::Desc)
    }

    pub fn direction(&mut self, direction: SortDirection) -> &mut IssueListOptionsBuilder {
        self.0.params.insert("direction", direction.to_string());
        self
    }

    pub fn assignee<A>(&mut self, assignee: A) -> &mut IssueListOptionsBuilder
    where
        A: Into<String>,
    {
        self.0.params.insert("assignee", assignee.into());
        self
    }

    pub fn creator<C>(&mut self, creator: C) -> &mut IssueListOptionsBuilder
    where
        C: Into<String>,
    {
        self.0.params.insert("creator", creator.into());
        self
    }

    pub fn mentioned<M>(&mut self, mentioned: M) -> &mut IssueListOptionsBuilder
    where
        M: Into<String>,
    {
        self.0.params.insert("mentioned", mentioned.into());
        self
    }

    pub fn labels<L>(&mut self, labels: Vec<L>) -> &mut IssueListOptionsBuilder
    where
        L: Into<String>,
    {
        self.0.params.insert(
            "labels",
            labels
                .into_iter()
                .map(|l| l.into())
                .collect::<Vec<_>>()
                .join(","),
        );
        self
    }

    pub fn since<S>(&mut self, since: S) -> &mut IssueListOptionsBuilder
    where
        S: Into<String>,
    {
        self.0.params.insert("since", since.into());
        self
    }

    pub fn build(&self) -> IssueListOptions {
        IssueListOptions { params: self.0.params.clone() }
    }
}

#[derive(Debug, Serialize)]
pub struct IssueOptions {
    pub title: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub body: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub assignee: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub milestone: Option<u64>,
    pub labels: Vec<String>,
}

impl IssueOptions {
    pub fn new<T, B, A, L>(
        title: T,
        body: Option<B>,
        assignee: Option<A>,
        milestone: Option<u64>,
        labels: Vec<L>,
    ) -> IssueOptions
    where
        T: Into<String>,
        B: Into<String>,
        A: Into<String>,
        L: Into<String>,
    {
        IssueOptions {
            title: title.into(),
            body: body.map(|b| b.into()),
            assignee: assignee.map(|a| a.into()),
            milestone: milestone,
            labels: labels
                .into_iter()
                .map(|l| l.into())
                .collect::<Vec<String>>(),
        }
    }
}

#[derive(Debug, Deserialize)]
pub struct Issue {
    pub id: u64,
    pub url: String,
    pub labels_url: String,
    pub comments_url: String,
    pub events_url: String,
    pub html_url: String,
    pub number: u64,
    pub state: String,
    pub title: String,
    pub body: String,
    pub user: User,
    pub labels: Vec<Label>,
    pub assignee: Option<User>,
    pub locked: bool,
    pub comments: u64,
    pub closed_at: Option<String>,
    pub created_at: String,
    pub updated_at: String,
}

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

    #[test]
    fn default_state() {
        let default: State = Default::default();
        assert_eq!(default, State::Open)
    }

    #[test]
    fn issue_list_reqs() {
        fn test_serialize(tests: Vec<(IssueListOptions, Option<String>)>) {
            for test in tests {
                match test {
                    (k, v) => assert_eq!(k.serialize(), v),
                }
            }
        }
        let tests = vec![
            (IssueListOptions::builder().build(), None),
            (
                IssueListOptions::builder().state(State::Closed).build(),
                Some("state=closed".to_owned())
            ),
            (
                IssueListOptions::builder()
                    .labels(vec!["foo", "bar"])
                    .build(),
                Some("labels=foo%2Cbar".to_owned())
            ),
        ];
        test_serialize(tests)
    }

    #[test]
    fn sort_default() {
        let default: Sort = Default::default();
        assert_eq!(default, Sort::Created)
    }

    #[test]
    fn sort_display() {
        for (k, v) in vec![
            (Sort::Created, "created"),
            (Sort::Updated, "updated"),
            (Sort::Comments, "comments"),
        ]
        {
            assert_eq!(k.to_string(), v)
        }
    }
}
<commit_msg>issues: Support getting an Issue from an IssueRef<commit_after>//! Issues interface

extern crate serde_json;

use super::{Github, Result};
use comments::Comments;
use users::User;
use labels::Label;
use super::SortDirection;
use std::fmt;
use std::collections::HashMap;
use url::form_urlencoded;

/// enum representation of github pull and issue state
#[derive(Clone, Debug, PartialEq)]
pub enum State {
    /// Only open issues
    Open,
    /// Only closed issues
    Closed,
    /// All issues, open or closed
    All,
}

impl fmt::Display for State {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            State::Open => "open",
            State::Closed => "closed",
            State::All => "all",
        }.fmt(f)
    }
}

impl Default for State {
    fn default() -> State {
        State::Open
    }
}

/// Sort options available for github issues
#[derive(Clone, Debug, PartialEq)]
pub enum Sort {
    /// sort by creation time of issue
    Created,
    /// sort by the last time issue was updated
    Updated,
    /// sort by number of comments
    Comments,
}

impl fmt::Display for Sort {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Sort::Created => "created",
            Sort::Updated => "updated",
            Sort::Comments => "comments",
        }.fmt(f)
    }
}

impl Default for Sort {
    fn default() -> Sort {
        Sort::Created
    }
}

/// Provides access to label operations available for an individual issues
pub struct IssueLabels<'a> {
    github: &'a Github,
    owner: String,
    repo: String,
    number: u64,
}

impl<'a> IssueLabels<'a> {
    #[doc(hidden)]
    pub fn new<O, R>(github: &'a Github, owner: O, repo: R, number: u64) -> IssueLabels<'a>
    where
        O: Into<String>,
        R: Into<String>,
    {
        IssueLabels {
            github: github,
            owner: owner.into(),
            repo: repo.into(),
            number: number,
        }
    }

    fn path(&self, more: &str) -> String {
        format!(
            "/repos/{}/{}/issues/{}/labels{}",
            self.owner,
            self.repo,
            self.number,
            more
        )
    }

    /// add a set of labels to this issue ref
    pub fn add(&self, labels: Vec<&str>) -> Result<Vec<Label>> {
        self.github.post::<Vec<Label>>(
            &self.path(""),
            serde_json::to_string(&labels)?.as_bytes(),
        )
    }

    /// remove a label from this issue
    pub fn remove(&self, label: &str) -> Result<()> {
        self.github.delete(&self.path(&format!("/{}", label)))
    }

    /// replace all labels associated with this issue with a new set.
    /// providing an empty set of labels is the same as clearing the
    /// current labels
    pub fn set(&self, labels: Vec<&str>) -> Result<Vec<Label>> {
        self.github.put::<Vec<Label>>(
            &self.path(""),
            serde_json::to_string(&labels)?.as_bytes(),
        )
    }

    /// remove all labels from an issue
    pub fn clear(&self) -> Result<()> {
        self.github.delete(&self.path(""))
    }
}

/// Provides access to operations available for a single issue
/// Typically accessed from `github.repo(.., ..).issues().get(number)`
pub struct IssueRef<'a> {
    github: &'a Github,
    owner: String,
    repo: String,
    number: u64,
}

impl<'a> IssueRef<'a> {
    /// create a new instance of a github repo issue ref
    pub fn new<O, R>(github: &'a Github, owner: O, repo: R, number: u64) -> IssueRef<'a>
    where
        O: Into<String>,
        R: Into<String>,
    {
        IssueRef {
            github: github,
            owner: owner.into(),
            repo: repo.into(),
            number: number,
        }
    }

    /// Request an issue's information
    pub fn get(&self) -> Result<Issue> {
        self.github.get::<Issue>(&self.path(""))
    }

    fn path(&self, more: &str) -> String {
        format!(
            "/repos/{}/{}/issues/{}{}",
            self.owner,
            self.repo,
            self.number,
            more
        )
    }

    pub fn labels(&self) -> IssueLabels {
        IssueLabels::new(
            self.github,
            self.owner.as_str(),
            self.repo.as_str(),
            self.number,
        )
    }

    pub fn edit(&self, is: &IssueOptions) -> Result<Issue> {
        let data = serde_json::to_string(&is)?;
        self.github.patch::<Issue>(&self.path(""), data.as_bytes())
    }

    pub fn comments(&self) -> Comments {
        Comments::new(
            self.github,
            self.owner.clone(),
            self.repo.clone(),
            self.number,
        )
    }
}

/// Provides access to operations available for a repository issues
/// Typically accessed via `github.repo(..., ...).issues()`
pub struct Issues<'a> {
    github: &'a Github,
    owner: String,
    repo: String,
}

impl<'a> Issues<'a> {
    /// create a new instance of a github repo issue ref
    pub fn new<O, R>(github: &'a Github, owner: O, repo: R) -> Issues<'a>
    where
        O: Into<String>,
        R: Into<String>,
    {
        Issues {
            github: github,
            owner: owner.into(),
            repo: repo.into(),
        }
    }

    fn path(&self, more: &str) -> String {
        format!("/repos/{}/{}/issues{}", self.owner, self.repo, more)
    }

    pub fn get(&self, number: u64) -> IssueRef {
        IssueRef::new(self.github, self.owner.as_str(), self.repo.as_str(), number)
    }

    pub fn create(&self, is: &IssueOptions) -> Result<Issue> {
        let data = serde_json::to_string(&is)?;
        self.github.post::<Issue>(&self.path(""), data.as_bytes())
    }

    pub fn list(&self, options: &IssueListOptions) -> Result<Vec<Issue>> {
        let mut uri = vec![self.path("")];
        if let Some(query) = options.serialize() {
            uri.push(query);
        }
        self.github.get::<Vec<Issue>>(&uri.join("?"))
    }
}

// representations

// todo: simplify with param
#[derive(Default)]
pub struct IssueListOptions {
    params: HashMap<&'static str, String>,
}

impl IssueListOptions {
    pub fn builder() -> IssueListOptionsBuilder {
        IssueListOptionsBuilder::new()
    }

    pub fn serialize(&self) -> Option<String> {
        if self.params.is_empty() {
            None
        } else {
            let encoded: String = form_urlencoded::Serializer::new(String::new())
                .extend_pairs(&self.params)
                .finish();
            Some(encoded)
        }
    }
}

/// a mutable issue list builder
pub struct IssueListOptionsBuilder(IssueListOptions);

impl IssueListOptionsBuilder {
    pub fn new() -> IssueListOptionsBuilder {
        IssueListOptionsBuilder(IssueListOptions { ..Default::default() })
    }

    pub fn state(&mut self, state: State) -> &mut IssueListOptionsBuilder {
        self.0.params.insert("state", state.to_string());
        self
    }

    pub fn sort(&mut self, sort: Sort) -> &mut IssueListOptionsBuilder {
        self.0.params.insert("sort", sort.to_string());
        self
    }

    pub fn asc(&mut self) -> &mut IssueListOptionsBuilder {
        self.direction(SortDirection::Asc)
    }

    pub fn desc(&mut self) -> &mut IssueListOptionsBuilder {
        self.direction(SortDirection::Desc)
    }

    pub fn direction(&mut self, direction: SortDirection) -> &mut IssueListOptionsBuilder {
        self.0.params.insert("direction", direction.to_string());
        self
    }

    pub fn assignee<A>(&mut self, assignee: A) -> &mut IssueListOptionsBuilder
    where
        A: Into<String>,
    {
        self.0.params.insert("assignee", assignee.into());
        self
    }

    pub fn creator<C>(&mut self, creator: C) -> &mut IssueListOptionsBuilder
    where
        C: Into<String>,
    {
        self.0.params.insert("creator", creator.into());
        self
    }

    pub fn mentioned<M>(&mut self, mentioned: M) -> &mut IssueListOptionsBuilder
    where
        M: Into<String>,
    {
        self.0.params.insert("mentioned", mentioned.into());
        self
    }

    pub fn labels<L>(&mut self, labels: Vec<L>) -> &mut IssueListOptionsBuilder
    where
        L: Into<String>,
    {
        self.0.params.insert(
            "labels",
            labels
                .into_iter()
                .map(|l| l.into())
                .collect::<Vec<_>>()
                .join(","),
        );
        self
    }

    pub fn since<S>(&mut self, since: S) -> &mut IssueListOptionsBuilder
    where
        S: Into<String>,
    {
        self.0.params.insert("since", since.into());
        self
    }

    pub fn build(&self) -> IssueListOptions {
        IssueListOptions { params: self.0.params.clone() }
    }
}

#[derive(Debug, Serialize)]
pub struct IssueOptions {
    pub title: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub body: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub assignee: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub milestone: Option<u64>,
    pub labels: Vec<String>,
}

impl IssueOptions {
    pub fn new<T, B, A, L>(
        title: T,
        body: Option<B>,
        assignee: Option<A>,
        milestone: Option<u64>,
        labels: Vec<L>,
    ) -> IssueOptions
    where
        T: Into<String>,
        B: Into<String>,
        A: Into<String>,
        L: Into<String>,
    {
        IssueOptions {
            title: title.into(),
            body: body.map(|b| b.into()),
            assignee: assignee.map(|a| a.into()),
            milestone: milestone,
            labels: labels
                .into_iter()
                .map(|l| l.into())
                .collect::<Vec<String>>(),
        }
    }
}

#[derive(Debug, Deserialize)]
pub struct Issue {
    pub id: u64,
    pub url: String,
    pub labels_url: String,
    pub comments_url: String,
    pub events_url: String,
    pub html_url: String,
    pub number: u64,
    pub state: String,
    pub title: String,
    pub body: String,
    pub user: User,
    pub labels: Vec<Label>,
    pub assignee: Option<User>,
    pub locked: bool,
    pub comments: u64,
    pub closed_at: Option<String>,
    pub created_at: String,
    pub updated_at: String,
}

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

    #[test]
    fn default_state() {
        let default: State = Default::default();
        assert_eq!(default, State::Open)
    }

    #[test]
    fn issue_list_reqs() {
        fn test_serialize(tests: Vec<(IssueListOptions, Option<String>)>) {
            for test in tests {
                match test {
                    (k, v) => assert_eq!(k.serialize(), v),
                }
            }
        }
        let tests = vec![
            (IssueListOptions::builder().build(), None),
            (
                IssueListOptions::builder().state(State::Closed).build(),
                Some("state=closed".to_owned())
            ),
            (
                IssueListOptions::builder()
                    .labels(vec!["foo", "bar"])
                    .build(),
                Some("labels=foo%2Cbar".to_owned())
            ),
        ];
        test_serialize(tests)
    }

    #[test]
    fn sort_default() {
        let default: Sort = Default::default();
        assert_eq!(default, Sort::Created)
    }

    #[test]
    fn sort_display() {
        for (k, v) in vec![
            (Sort::Created, "created"),
            (Sort::Updated, "updated"),
            (Sort::Comments, "comments"),
        ]
        {
            assert_eq!(k.to_string(), v)
        }
    }
}
<|endoftext|>


<commit_before>// During tests, we disable the renderer and that causes a bunch of warnings
// See Cargo.toml for an explanation of this attribute
#![cfg_attr(any(feature = "test", test), allow(dead_code, unused_variables, unused_imports))]

use std::env;
use std::thread;
use std::process::{self, Stdio};
use std::time::Instant;
use std::cell::RefCell;
use std::sync::mpsc;

use piston_window::math;

use client;
use canvas;
use animation::{Animation, MoveAnimation, RotateAnimation, AnimationStatus};
use state::{TurtleState, DrawingState, Path};
use query::{Query, Request, StateUpdate, DrawingCommand, Response};
use radians::{self, Radians};
use {Point, Distance, Event};

use self::DrawingCommand::*;

#[cfg(any(feature = "test", test))]
fn renderer_client(_: mpsc::Sender<Response>) -> (process::Child, thread::JoinHandle<()>) {
    let command = if cfg!(windows) {
        "dir"
    } else {
        "ls"
    };
    let mut test_proc = process::Command::new(command)
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::inherit())
        .spawn()
        .expect("test process failed to start");
    test_proc.kill().expect("test process could not be killed!");
    let handle = thread::spawn(move || {});
    (test_proc, handle)
}

#[cfg(not(any(feature = "test", test)))]
fn renderer_client(response_tx: mpsc::Sender<Response>) -> (process::Child, thread::JoinHandle<()>) {
    let current_exe = env::current_exe()
        .expect("Could not read path of the currently running executable")
        .into_os_string();
    let mut renderer_process = process::Command::new(current_exe)
        .env("RUN_TURTLE_CANVAS", "true")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::inherit())
        .spawn()
        .expect("renderer process failed to start");

    let renderer_stdout = renderer_process.stdout.take()
        .expect("renderer process was not opened with stdout");
    let handle = thread::spawn(move || {
        client::run(renderer_stdout, response_tx);
    });

    (renderer_process, handle)
}

pub struct TurtleWindow {
    renderer: RefCell<process::Child>,
    thread_handle: RefCell<Option<thread::JoinHandle<()>>>,
    /// Channel for receiving responses from the rendering process
    response_channel: mpsc::Receiver<Response>,
}

impl TurtleWindow {
    pub fn new() -> TurtleWindow {
        // This needs to be called as close to the start of the program as possible
        // Since Turtle::new() is called at the beginning of many turtle programs, we do so here
        // to make sure this call occurs when it should.
        canvas::start();

        let (response_tx, response_rx) = mpsc::channel();
        let (renderer_process, handle) = renderer_client(response_tx);

        Self {
            renderer: RefCell::new(renderer_process),
            thread_handle: RefCell::new(Some(handle)),
            response_channel: response_rx,
        }
    }

    pub fn fetch_turtle(&self) -> TurtleState {
        self.send_query(Query::Request(Request::TurtleState));
        match self.wait_for_response() {
            Response::TurtleState(state) => {
                state
            },
            _ => panic!("bug: the renderer process did not sent back TurtleState"),
        }
    }

    pub fn update_turtle(&mut self, turtle: TurtleState) {
        self.send_query(Query::Update(StateUpdate::TurtleState(turtle)));
    }

    pub fn fetch_drawing(&self) -> DrawingState {
        unimplemented!();
    }

    pub fn update_drawing(&mut self, drawing: DrawingState) {
        unimplemented!();
    }

    fn fetch_temporary_path(&self) -> Option<Path> {
        unimplemented!();
    }

    fn set_temporary_path(&mut self, path: Option<Path>) {
        unimplemented!();
    }

    /// See [`Turtle::poll_event()`](struct.Turtle.html#method.poll_event).
    pub fn poll_event(&mut self) -> Option<Event> {
        //TODO: query for an event, then read the response
        unimplemented!();
        //match self.response_channel.try_recv() {
        //    Ok(event) => Some(event),
        //    Err(TryRecvError::Empty) => None, // Do nothing
        //    // The window has been closed
        //    Err(TryRecvError::Disconnected) => self.exit_process(), // Quit
        //}
    }

    /// Begin filling the shape drawn by the turtle's movements.
    pub fn begin_fill(&mut self) {
        let fill_color = self.fetch_turtle().fill_color;
        self.send_drawing_command(BeginFill(fill_color));
    }

    /// Stop filling the current shape
    pub fn end_fill(&mut self) {
        self.send_drawing_command(EndFill);
    }

    /// Clear the turtle's drawings
    pub fn clear(&mut self) {
        assert!(self.fetch_temporary_path().is_none(),
            "bug: The temporary path was still set when the renderer was asked to clear the drawing");
        self.send_drawing_command(Clear);
    }

    /// Move the turtle to the given position without changing its heading.
    pub fn go_to(&mut self, end: Point) {
        let (start, speed, pen) = {
            let turtle = self.fetch_turtle();
            (turtle.position, turtle.speed, turtle.pen.clone())
        };

        let distance = math::square_len(math::sub(start, end)).sqrt();
        let speed = speed.to_absolute(); // px per second
        // We take the absolute value because the time is always positive, even if distance is negative
        let total_millis = (distance / speed * 1000.).abs();

        let animation = MoveAnimation {
            path: Path {start, end, pen},
            timer: Instant::now(),
            total_millis,
        };

        self.play_animation(animation);
    }

    /// Move the turtle forward by the given distance. To move backwards, use a negative distance.
    ///
    /// The turtle's motion will be animated based on the speed
    pub fn forward(&mut self, distance: Distance) {
        if distance == 0. {
            return;
        }

        let (start, speed, heading, pen) = {
            let turtle = self.fetch_turtle();
            (turtle.position, turtle.speed, turtle.heading, turtle.pen.clone())
        };
        let x = distance * heading.cos();
        let y = distance * heading.sin();
        let end = math::add(start, [x, y]);

        let speed = speed.to_absolute(); // px per second
        // We take the absolute value because the time is always positive, even if distance is negative
        let total_millis = (distance / speed * 1000.).abs();

        let animation = MoveAnimation {
            path: Path {start, end, pen},
            timer: Instant::now(),
            total_millis,
        };

        self.play_animation(animation);
    }

    /// Rotate the turtle in place by the given angle in the given direction of rotation
    pub fn rotate(&mut self, angle: Radians, clockwise: bool) {
        if angle == radians::ZERO {
            return;
        }

        let TurtleState {heading, speed, ..} = self.fetch_turtle();
        let speed = speed.to_rotation(); // radians per second
        let total_millis = angle / speed * 1000.;
        // We take the absolute value because the time is always positive, even if angle is negative
        let total_millis = total_millis.to_radians().abs();

        let animation = RotateAnimation {
            start: heading,
            delta_angle: angle,
            clockwise,
            timer: Instant::now(),
            total_millis,
        };

        self.play_animation(animation);
    }

    fn play_animation<A: Animation>(&mut self, animation: A) {
        loop {
            // We want to keep the lock for as little time as possible
            let status = {
                let mut turtle = self.fetch_turtle();
                let status = animation.advance(&mut turtle);
                self.update_turtle(turtle);
                status
            };
            match status {
                AnimationStatus::Running(path) => self.set_temporary_path(path),
                AnimationStatus::Complete(path) => {
                    if let Some(path) = path {
                        self.set_temporary_path(None);
                        self.send_drawing_command(StorePath(path));
                    }

                    break;
                },
            }
        }
    }

    fn send_drawing_command(&self, command: DrawingCommand) {
        self.send_query(Query::Drawing(command));
    }

    // During tests, we disable the renderer. That means that if we let this code run, it will
    // quit the application during the tests and make it look like everything passes.
    // We disable this code so that none of that happens.
    #[cfg(any(feature = "test", test))]
    fn send_query(&self, _: Query) {}

    #[inline]
    #[cfg(not(any(feature = "test", test)))]
    fn send_query(&self, query: Query) {
        let result = if let Some(ref mut stdin) = self.renderer.borrow_mut().stdin {
            client::send_query(stdin, &query)
        }
        else {
            unreachable!("bug: renderer process was not opened with stdin");
        };

        result.unwrap_or_else(|_| {
            // Something went wrong while sending the query, check if the renderer process
            // panicked (exited with an error)
            match self.renderer.borrow_mut().try_wait() {
                Ok(Some(status)) => {
                    if status.success() {
                        // The window/renderer process was closed normally
                        process::exit(0);
                    }
                    else {
                        // Something went wrong, likely the other thread panicked
                        process::exit(1);
                    }
                },
                Ok(None) => panic!("bug: failed to send query even though renderer process was still running"),
                Err(_) => panic!("bug: unable to check the exit status of the renderer process"),
            }
        });
    }

    fn wait_for_response(&self) -> Response {
        match self.response_channel.recv() {
            Ok(response) => response,
            // The client thread has exited, that means that the renderer process has exited
            // and the window has closed
            Err(_) => self.exit_process(), // Quit
        }
    }

    /// Exits the current process with the correct error code
    ///
    /// Panics if the thread handle has already been consumed
    #[inline]
    fn exit_process(&self) -> ! {
        if let Some(handle) = self.thread_handle.borrow_mut().take() {
            // First check if the other thread panicked before it quit
            match handle.join() {
                Ok(_) => match self.renderer.borrow_mut().try_wait() {
                    Ok(Some(status)) => {
                        if status.success() {
                            // The window/renderer process was closed normally
                            process::exit(0);
                        }
                        else {
                            // Something went wrong, likely the other thread panicked
                            process::exit(1);
                        }
                    },
                    Ok(None) => match self.renderer.borrow_mut().wait() {
                        Ok(status) => {
                            if status.success() {
                                process::exit(0);
                            }
                            else {
                                process::exit(1);
                            }
                        },
                        Err(_) => unreachable!("bug: renderer process never ran even though we exited"),
                    },
                    Err(_) => panic!("bug: unable to check the exit status of the renderer process after client thread quit"),
                },
                // If this returns an error, the other thread panicked
                Err(_) => process::exit(1),
            }
        }
        else {
            unreachable!("bug: the thread handle was used but the process did not end");
        }
    }
}

#[cfg(not(any(feature = "test", test)))]
impl Drop for TurtleWindow {
    fn drop(&mut self) {
        // If the current thread is panicking, we want to abort right away
        // because otherwise there is code in the rendering thread that will call
        // process::exit(0) and then the exit code will be 0 instead of 1
        if thread::panicking() {
            process::exit(1);
        }

        // If this is just a normal ending of the main thread, we want to leave the renderer
        // running so that the user can see their drawing as long as they keep the window open
        //TODO: Maybe we should wait on the renderer_process instead of (or in addition to?) this thread
        if let Some(handle) = self.thread_handle.borrow_mut().take() {
            handle.join().unwrap_or_else(|_| {
                // If this returns an error, the other thread panicked
                process::exit(1);
            });
        }
    }
}
<commit_msg>Finished re-implementing all the methods for fetching and updating state in turtle window<commit_after>// During tests, we disable the renderer and that causes a bunch of warnings
// See Cargo.toml for an explanation of this attribute
#![cfg_attr(any(feature = "test", test), allow(dead_code, unused_variables, unused_imports))]

use std::env;
use std::thread;
use std::process::{self, Stdio};
use std::time::Instant;
use std::cell::RefCell;
use std::sync::mpsc;

use piston_window::math;

use client;
use canvas;
use animation::{Animation, MoveAnimation, RotateAnimation, AnimationStatus};
use state::{TurtleState, DrawingState, Path};
use query::{Query, Request, StateUpdate, DrawingCommand, Response};
use radians::{self, Radians};
use {Point, Distance, Event};

use self::DrawingCommand::*;

#[cfg(any(feature = "test", test))]
fn renderer_client(_: mpsc::Sender<Response>) -> (process::Child, thread::JoinHandle<()>) {
    let command = if cfg!(windows) {
        "dir"
    } else {
        "ls"
    };
    let mut test_proc = process::Command::new(command)
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::inherit())
        .spawn()
        .expect("test process failed to start");
    test_proc.kill().expect("test process could not be killed!");
    let handle = thread::spawn(move || {});
    (test_proc, handle)
}

#[cfg(not(any(feature = "test", test)))]
fn renderer_client(response_tx: mpsc::Sender<Response>) -> (process::Child, thread::JoinHandle<()>) {
    let current_exe = env::current_exe()
        .expect("Could not read path of the currently running executable")
        .into_os_string();
    let mut renderer_process = process::Command::new(current_exe)
        .env("RUN_TURTLE_CANVAS", "true")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::inherit())
        .spawn()
        .expect("renderer process failed to start");

    let renderer_stdout = renderer_process.stdout.take()
        .expect("renderer process was not opened with stdout");
    let handle = thread::spawn(move || {
        client::run(renderer_stdout, response_tx);
    });

    (renderer_process, handle)
}

pub struct TurtleWindow {
    renderer: RefCell<process::Child>,
    thread_handle: RefCell<Option<thread::JoinHandle<()>>>,
    /// Channel for receiving responses from the rendering process
    response_channel: mpsc::Receiver<Response>,
}

impl TurtleWindow {
    pub fn new() -> TurtleWindow {
        // This needs to be called as close to the start of the program as possible
        // Since Turtle::new() is called at the beginning of many turtle programs, we do so here
        // to make sure this call occurs when it should.
        canvas::start();

        let (response_tx, response_rx) = mpsc::channel();
        let (renderer_process, handle) = renderer_client(response_tx);

        Self {
            renderer: RefCell::new(renderer_process),
            thread_handle: RefCell::new(Some(handle)),
            response_channel: response_rx,
        }
    }

    pub fn fetch_turtle(&self) -> TurtleState {
        self.send_query(Query::Request(Request::TurtleState));
        match self.wait_for_response() {
            Response::TurtleState(state) => state,
            _ => panic!("bug: the renderer process did not sent back TurtleState"),
        }
    }

    pub fn update_turtle(&mut self, turtle: TurtleState) {
        self.send_query(Query::Update(StateUpdate::TurtleState(turtle)));
    }

    pub fn fetch_drawing(&self) -> DrawingState {
        self.send_query(Query::Request(Request::DrawingState));
        match self.wait_for_response() {
            Response::DrawingState(state) => state,
            _ => panic!("bug: the renderer process did not sent back DrawingState"),
        }
    }

    pub fn update_drawing(&mut self, drawing: DrawingState) {
        self.send_query(Query::Update(StateUpdate::DrawingState(drawing)));
    }

    fn set_temporary_path(&mut self, path: Option<Path>) {
        self.send_query(Query::Update(StateUpdate::TemporaryPath(path)));
    }

    /// See [`Turtle::poll_event()`](struct.Turtle.html#method.poll_event).
    pub fn poll_event(&mut self) -> Option<Event> {
        //TODO: query for an event, then read the response
        unimplemented!();
        //match self.response_channel.try_recv() {
        //    Ok(event) => Some(event),
        //    Err(TryRecvError::Empty) => None, // Do nothing
        //    // The window has been closed
        //    Err(TryRecvError::Disconnected) => self.exit_process(), // Quit
        //}
    }

    /// Begin filling the shape drawn by the turtle's movements.
    pub fn begin_fill(&mut self) {
        let fill_color = self.fetch_turtle().fill_color;
        self.send_drawing_command(BeginFill(fill_color));
    }

    /// Stop filling the current shape
    pub fn end_fill(&mut self) {
        self.send_drawing_command(EndFill);
    }

    /// Clear the turtle's drawings
    pub fn clear(&mut self) {
        self.send_drawing_command(Clear);
    }

    /// Move the turtle to the given position without changing its heading.
    pub fn go_to(&mut self, end: Point) {
        let (start, speed, pen) = {
            let turtle = self.fetch_turtle();
            (turtle.position, turtle.speed, turtle.pen.clone())
        };

        let distance = math::square_len(math::sub(start, end)).sqrt();
        let speed = speed.to_absolute(); // px per second
        // We take the absolute value because the time is always positive, even if distance is negative
        let total_millis = (distance / speed * 1000.).abs();

        let animation = MoveAnimation {
            path: Path {start, end, pen},
            timer: Instant::now(),
            total_millis,
        };

        self.play_animation(animation);
    }

    /// Move the turtle forward by the given distance. To move backwards, use a negative distance.
    ///
    /// The turtle's motion will be animated based on the speed
    pub fn forward(&mut self, distance: Distance) {
        if distance == 0. {
            return;
        }

        let (start, speed, heading, pen) = {
            let turtle = self.fetch_turtle();
            (turtle.position, turtle.speed, turtle.heading, turtle.pen.clone())
        };
        let x = distance * heading.cos();
        let y = distance * heading.sin();
        let end = math::add(start, [x, y]);

        let speed = speed.to_absolute(); // px per second
        // We take the absolute value because the time is always positive, even if distance is negative
        let total_millis = (distance / speed * 1000.).abs();

        let animation = MoveAnimation {
            path: Path {start, end, pen},
            timer: Instant::now(),
            total_millis,
        };

        self.play_animation(animation);
    }

    /// Rotate the turtle in place by the given angle in the given direction of rotation
    pub fn rotate(&mut self, angle: Radians, clockwise: bool) {
        if angle == radians::ZERO {
            return;
        }

        let TurtleState {heading, speed, ..} = self.fetch_turtle();
        let speed = speed.to_rotation(); // radians per second
        let total_millis = angle / speed * 1000.;
        // We take the absolute value because the time is always positive, even if angle is negative
        let total_millis = total_millis.to_radians().abs();

        let animation = RotateAnimation {
            start: heading,
            delta_angle: angle,
            clockwise,
            timer: Instant::now(),
            total_millis,
        };

        self.play_animation(animation);
    }

    fn play_animation<A: Animation>(&mut self, animation: A) {
        loop {
            // We want to keep the lock for as little time as possible
            let status = {
                let mut turtle = self.fetch_turtle();
                let status = animation.advance(&mut turtle);
                self.update_turtle(turtle);
                status
            };
            match status {
                AnimationStatus::Running(path) => self.set_temporary_path(path),
                AnimationStatus::Complete(path) => {
                    if let Some(path) = path {
                        self.set_temporary_path(None);
                        self.send_drawing_command(StorePath(path));
                    }

                    break;
                },
            }
        }
    }

    fn send_drawing_command(&self, command: DrawingCommand) {
        self.send_query(Query::Drawing(command));
    }

    // During tests, we disable the renderer. That means that if we let this code run, it will
    // quit the application during the tests and make it look like everything passes.
    // We disable this code so that none of that happens.
    #[cfg(any(feature = "test", test))]
    fn send_query(&self, _: Query) {}

    #[inline]
    #[cfg(not(any(feature = "test", test)))]
    fn send_query(&self, query: Query) {
        let result = if let Some(ref mut stdin) = self.renderer.borrow_mut().stdin {
            client::send_query(stdin, &query)
        }
        else {
            unreachable!("bug: renderer process was not opened with stdin");
        };

        result.unwrap_or_else(|_| {
            // Something went wrong while sending the query, check if the renderer process
            // panicked (exited with an error)
            match self.renderer.borrow_mut().try_wait() {
                Ok(Some(status)) => {
                    if status.success() {
                        // The window/renderer process was closed normally
                        process::exit(0);
                    }
                    else {
                        // Something went wrong, likely the other thread panicked
                        process::exit(1);
                    }
                },
                Ok(None) => panic!("bug: failed to send query even though renderer process was still running"),
                Err(_) => panic!("bug: unable to check the exit status of the renderer process"),
            }
        });
    }

    fn wait_for_response(&self) -> Response {
        match self.response_channel.recv() {
            Ok(response) => response,
            // The client thread has exited, that means that the renderer process has exited
            // and the window has closed
            Err(_) => self.exit_process(), // Quit
        }
    }

    /// Exits the current process with the correct error code
    ///
    /// Panics if the thread handle has already been consumed
    #[inline]
    fn exit_process(&self) -> ! {
        if let Some(handle) = self.thread_handle.borrow_mut().take() {
            // First check if the other thread panicked before it quit
            match handle.join() {
                Ok(_) => match self.renderer.borrow_mut().try_wait() {
                    Ok(Some(status)) => {
                        if status.success() {
                            // The window/renderer process was closed normally
                            process::exit(0);
                        }
                        else {
                            // Something went wrong, likely the other thread panicked
                            process::exit(1);
                        }
                    },
                    Ok(None) => match self.renderer.borrow_mut().wait() {
                        Ok(status) => {
                            if status.success() {
                                process::exit(0);
                            }
                            else {
                                process::exit(1);
                            }
                        },
                        Err(_) => unreachable!("bug: renderer process never ran even though we exited"),
                    },
                    Err(_) => panic!("bug: unable to check the exit status of the renderer process after client thread quit"),
                },
                // If this returns an error, the other thread panicked
                Err(_) => process::exit(1),
            }
        }
        else {
            unreachable!("bug: the thread handle was used but the process did not end");
        }
    }
}

#[cfg(not(any(feature = "test", test)))]
impl Drop for TurtleWindow {
    fn drop(&mut self) {
        // If the current thread is panicking, we want to abort right away
        // because otherwise there is code in the rendering thread that will call
        // process::exit(0) and then the exit code will be 0 instead of 1
        if thread::panicking() {
            process::exit(1);
        }

        // If this is just a normal ending of the main thread, we want to leave the renderer
        // running so that the user can see their drawing as long as they keep the window open
        //TODO: Maybe we should wait on the renderer_process instead of (or in addition to?) this thread
        if let Some(handle) = self.thread_handle.borrow_mut().take() {
            handle.join().unwrap_or_else(|_| {
                // If this returns an error, the other thread panicked
                process::exit(1);
            });
        }
    }
}
<|endoftext|>


<commit_before>// During tests, we disable the renderer and that causes a bunch of warnings
// See Cargo.toml for an explanation of this attribute
#![cfg_attr(any(feature = "test", test), allow(dead_code, unused_variables, unused_imports))]

use std::env;
use std::thread;
use std::process::{self, Stdio};
use std::time::Instant;
use std::cell::RefCell;
use std::sync::mpsc;

use piston_window::math;

use client;
use canvas;
use animation::{Animation, MoveAnimation, RotateAnimation, AnimationStatus};
use state::{TurtleState, DrawingState, Path};
use query::{Query, Request, DrawingCommand, Response};
use radians::{self, Radians};
use {Point, Distance, Event};

use self::DrawingCommand::*;

#[cfg(any(feature = "test", test))]
fn renderer_client(_: mpsc::Sender<Response>) -> (process::Child, thread::JoinHandle<()>) {
    let command = if cfg!(windows) {
        "dir"
    } else {
        "ls"
    };
    let mut test_proc = process::Command::new(command)
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::inherit())
        .spawn()
        .expect("test process failed to start");
    test_proc.kill().expect("test process could not be killed!");
    let handle = thread::spawn(move || {});
    (test_proc, handle)
}

#[cfg(not(any(feature = "test", test)))]
fn renderer_client(response_tx: mpsc::Sender<Response>) -> (process::Child, thread::JoinHandle<()>) {
    let current_exe = env::current_exe()
        .expect("Could not read path of the currently running executable")
        .into_os_string();
    let mut renderer_process = process::Command::new(current_exe)
        .env("RUN_TURTLE_CANVAS", "true")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::inherit())
        .spawn()
        .expect("renderer process failed to start");

    let renderer_stdout = renderer_process.stdout.take()
        .expect("renderer process was not opened with stdout");
    let handle = thread::spawn(move || {
        client::run(renderer_stdout, response_tx);
    });

    (renderer_process, handle)
}

pub struct TurtleWindow {
    renderer: RefCell<process::Child>,
    thread_handle: RefCell<Option<thread::JoinHandle<()>>>,
    /// Channel for receiving responses from the rendering process
    response_channel: mpsc::Receiver<Response>,
}

impl TurtleWindow {
    pub fn new() -> TurtleWindow {
        // This needs to be called as close to the start of the program as possible
        // Since Turtle::new() is called at the beginning of many turtle programs, we do so here
        // to make sure this call occurs when it should.
        canvas::start();

        let (response_tx, response_rx) = mpsc::channel();
        let (renderer_process, handle) = renderer_client(response_tx);

        Self {
            renderer: RefCell::new(renderer_process),
            thread_handle: RefCell::new(Some(handle)),
            response_channel: response_rx,
        }
    }

    pub fn fetch_turtle(&self) -> TurtleState {
        self.send_query(Query::Request(Request::TurtleState));
        match self.wait_for_response() {
            Response::TurtleState(state) => {
                println!("{:?}", state);
                state
            },
            _ => panic!("bug: the renderer process did not sent back TurtleState"),
        }
    }

    pub fn update_turtle(&mut self, turtle: TurtleState) {
        unimplemented!();
    }

    pub fn fetch_drawing(&self) -> DrawingState {
        unimplemented!();
    }

    pub fn update_drawing(&mut self, drawing: DrawingState) {
        unimplemented!();
    }

    fn fetch_temporary_path(&self) -> Option<Path> {
        unimplemented!();
    }

    fn set_temporary_path(&mut self, path: Option<Path>) {
        unimplemented!();
    }

    /// See [`Turtle::poll_event()`](struct.Turtle.html#method.poll_event).
    pub fn poll_event(&mut self) -> Option<Event> {
        //TODO: query for an event, then read the response
        unimplemented!();
        //match self.response_channel.try_recv() {
        //    Ok(event) => Some(event),
        //    Err(TryRecvError::Empty) => None, // Do nothing
        //    // The window has been closed
        //    Err(TryRecvError::Disconnected) => self.exit_process(), // Quit
        //}
    }

    /// Begin filling the shape drawn by the turtle's movements.
    pub fn begin_fill(&mut self) {
        let fill_color = self.fetch_turtle().fill_color;
        self.send_drawing_command(BeginFill(fill_color));
    }

    /// Stop filling the current shape
    pub fn end_fill(&mut self) {
        self.send_drawing_command(EndFill);
    }

    /// Clear the turtle's drawings
    pub fn clear(&mut self) {
        assert!(self.fetch_temporary_path().is_none(),
            "bug: The temporary path was still set when the renderer was asked to clear the drawing");
        self.send_drawing_command(Clear);
    }

    /// Move the turtle to the given position without changing its heading.
    pub fn go_to(&mut self, end: Point) {
        let (start, speed, pen) = {
            let turtle = self.fetch_turtle();
            (turtle.position, turtle.speed, turtle.pen.clone())
        };

        let distance = math::square_len(math::sub(start, end)).sqrt();
        let speed = speed.to_absolute(); // px per second
        // We take the absolute value because the time is always positive, even if distance is negative
        let total_millis = (distance / speed * 1000.).abs();

        let animation = MoveAnimation {
            path: Path {start, end, pen},
            timer: Instant::now(),
            total_millis,
        };

        self.play_animation(animation);
    }

    /// Move the turtle forward by the given distance. To move backwards, use a negative distance.
    ///
    /// The turtle's motion will be animated based on the speed
    pub fn forward(&mut self, distance: Distance) {
        if distance == 0. {
            return;
        }

        let (start, speed, heading, pen) = {
            let turtle = self.fetch_turtle();
            (turtle.position, turtle.speed, turtle.heading, turtle.pen.clone())
        };
        let x = distance * heading.cos();
        let y = distance * heading.sin();
        let end = math::add(start, [x, y]);

        let speed = speed.to_absolute(); // px per second
        // We take the absolute value because the time is always positive, even if distance is negative
        let total_millis = (distance / speed * 1000.).abs();

        let animation = MoveAnimation {
            path: Path {start, end, pen},
            timer: Instant::now(),
            total_millis,
        };

        self.play_animation(animation);
    }

    /// Rotate the turtle in place by the given angle in the given direction of rotation
    pub fn rotate(&mut self, angle: Radians, clockwise: bool) {
        if angle == radians::ZERO {
            return;
        }

        let TurtleState {heading, speed, ..} = self.fetch_turtle();
        let speed = speed.to_rotation(); // radians per second
        let total_millis = angle / speed * 1000.;
        // We take the absolute value because the time is always positive, even if angle is negative
        let total_millis = total_millis.to_radians().abs();

        let animation = RotateAnimation {
            start: heading,
            delta_angle: angle,
            clockwise,
            timer: Instant::now(),
            total_millis,
        };

        self.play_animation(animation);
    }

    fn play_animation<A: Animation>(&mut self, animation: A) {
        loop {
            // We want to keep the lock for as little time as possible
            let status = {
                let mut turtle = self.fetch_turtle();
                let status = animation.advance(&mut turtle);
                self.update_turtle(turtle);
                status
            };
            match status {
                AnimationStatus::Running(path) => self.set_temporary_path(path),
                AnimationStatus::Complete(path) => {
                    if let Some(path) = path {
                        self.set_temporary_path(None);
                        self.send_drawing_command(StorePath(path));
                    }

                    break;
                },
            }
        }
    }

    fn send_drawing_command(&self, command: DrawingCommand) {
        self.send_query(Query::Drawing(command));
    }

    // During tests, we disable the renderer. That means that if we let this code run, it will
    // quit the application during the tests and make it look like everything passes.
    // We disable this code so that none of that happens.
    #[cfg(any(feature = "test", test))]
    fn send_query(&self, _: Query) {}

    #[inline]
    #[cfg(not(any(feature = "test", test)))]
    fn send_query(&self, query: Query) {
        let result = if let Some(ref mut stdin) = self.renderer.borrow_mut().stdin {
            client::send_query(stdin, &query)
        }
        else {
            unreachable!("bug: renderer process was not opened with stdin");
        };

        result.unwrap_or_else(|_| {
            // Something went wrong while sending the query, check if the renderer process
            // panicked (exited with an error)
            match self.renderer.borrow_mut().try_wait() {
                Ok(Some(status)) => {
                    if status.success() {
                        // The window/renderer process was closed normally
                        process::exit(0);
                    }
                    else {
                        // Something went wrong, likely the other thread panicked
                        process::exit(1);
                    }
                },
                Ok(None) => panic!("bug: failed to send query even though renderer process was still running"),
                Err(_) => panic!("bug: unable to check the exit status of the renderer process"),
            }
        });
    }

    fn wait_for_response(&self) -> Response {
        match self.response_channel.recv() {
            Ok(response) => response,
            // The client thread has exited, that means that the renderer process has exited
            // and the window has closed
            Err(_) => self.exit_process(), // Quit
        }
    }

    /// Exits the current process with the correct error code
    ///
    /// Panics if the thread handle has already been consumed
    #[inline]
    fn exit_process(&self) -> ! {
        if let Some(handle) = self.thread_handle.borrow_mut().take() {
            // First check if the other thread panicked before it quit
            match handle.join() {
                Ok(_) => match self.renderer.borrow_mut().try_wait() {
                    Ok(Some(status)) => {
                        if status.success() {
                            // The window/renderer process was closed normally
                            process::exit(0);
                        }
                        else {
                            // Something went wrong, likely the other thread panicked
                            process::exit(1);
                        }
                    },
                    Ok(None) => panic!("bug: client thread quit even though renderer process was still running"),
                    Err(_) => panic!("bug: unable to check the exit status of the renderer process after client thread quit"),
                },
                // If this returns an error, the other thread panicked
                Err(_) => process::exit(1),
            }
        }
        else {
            unreachable!("bug: the thread handle was used but the process did not end");
        }
    }
}

#[cfg(not(any(feature = "test", test)))]
impl Drop for TurtleWindow {
    fn drop(&mut self) {
        // If the current thread is panicking, we want to abort right away
        // because otherwise there is code in the rendering thread that will call
        // process::exit(0) and then the exit code will be 0 instead of 1
        if thread::panicking() {
            process::exit(1);
        }

        // If this is just a normal ending of the main thread, we want to leave the renderer
        // running so that the user can see their drawing as long as they keep the window open
        //TODO: Maybe we should wait on the renderer_process instead of (or in addition to?) this thread
        if let Some(handle) = self.thread_handle.borrow_mut().take() {
            handle.join().unwrap_or_else(|_| {
                // If this returns an error, the other thread panicked
                process::exit(1);
            });
        }
    }
}
<commit_msg>Trying to marshal the correct error code when the renderer process closes<commit_after>// During tests, we disable the renderer and that causes a bunch of warnings
// See Cargo.toml for an explanation of this attribute
#![cfg_attr(any(feature = "test", test), allow(dead_code, unused_variables, unused_imports))]

use std::env;
use std::thread;
use std::process::{self, Stdio};
use std::time::Instant;
use std::cell::RefCell;
use std::sync::mpsc;

use piston_window::math;

use client;
use canvas;
use animation::{Animation, MoveAnimation, RotateAnimation, AnimationStatus};
use state::{TurtleState, DrawingState, Path};
use query::{Query, Request, DrawingCommand, Response};
use radians::{self, Radians};
use {Point, Distance, Event};

use self::DrawingCommand::*;

#[cfg(any(feature = "test", test))]
fn renderer_client(_: mpsc::Sender<Response>) -> (process::Child, thread::JoinHandle<()>) {
    let command = if cfg!(windows) {
        "dir"
    } else {
        "ls"
    };
    let mut test_proc = process::Command::new(command)
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::inherit())
        .spawn()
        .expect("test process failed to start");
    test_proc.kill().expect("test process could not be killed!");
    let handle = thread::spawn(move || {});
    (test_proc, handle)
}

#[cfg(not(any(feature = "test", test)))]
fn renderer_client(response_tx: mpsc::Sender<Response>) -> (process::Child, thread::JoinHandle<()>) {
    let current_exe = env::current_exe()
        .expect("Could not read path of the currently running executable")
        .into_os_string();
    let mut renderer_process = process::Command::new(current_exe)
        .env("RUN_TURTLE_CANVAS", "true")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::inherit())
        .spawn()
        .expect("renderer process failed to start");

    let renderer_stdout = renderer_process.stdout.take()
        .expect("renderer process was not opened with stdout");
    let handle = thread::spawn(move || {
        client::run(renderer_stdout, response_tx);
    });

    (renderer_process, handle)
}

pub struct TurtleWindow {
    renderer: RefCell<process::Child>,
    thread_handle: RefCell<Option<thread::JoinHandle<()>>>,
    /// Channel for receiving responses from the rendering process
    response_channel: mpsc::Receiver<Response>,
}

impl TurtleWindow {
    pub fn new() -> TurtleWindow {
        // This needs to be called as close to the start of the program as possible
        // Since Turtle::new() is called at the beginning of many turtle programs, we do so here
        // to make sure this call occurs when it should.
        canvas::start();

        let (response_tx, response_rx) = mpsc::channel();
        let (renderer_process, handle) = renderer_client(response_tx);

        Self {
            renderer: RefCell::new(renderer_process),
            thread_handle: RefCell::new(Some(handle)),
            response_channel: response_rx,
        }
    }

    pub fn fetch_turtle(&self) -> TurtleState {
        self.send_query(Query::Request(Request::TurtleState));
        match self.wait_for_response() {
            Response::TurtleState(state) => {
                println!("{:?}", state);
                state
            },
            _ => panic!("bug: the renderer process did not sent back TurtleState"),
        }
    }

    pub fn update_turtle(&mut self, turtle: TurtleState) {
        unimplemented!();
    }

    pub fn fetch_drawing(&self) -> DrawingState {
        unimplemented!();
    }

    pub fn update_drawing(&mut self, drawing: DrawingState) {
        unimplemented!();
    }

    fn fetch_temporary_path(&self) -> Option<Path> {
        unimplemented!();
    }

    fn set_temporary_path(&mut self, path: Option<Path>) {
        unimplemented!();
    }

    /// See [`Turtle::poll_event()`](struct.Turtle.html#method.poll_event).
    pub fn poll_event(&mut self) -> Option<Event> {
        //TODO: query for an event, then read the response
        unimplemented!();
        //match self.response_channel.try_recv() {
        //    Ok(event) => Some(event),
        //    Err(TryRecvError::Empty) => None, // Do nothing
        //    // The window has been closed
        //    Err(TryRecvError::Disconnected) => self.exit_process(), // Quit
        //}
    }

    /// Begin filling the shape drawn by the turtle's movements.
    pub fn begin_fill(&mut self) {
        let fill_color = self.fetch_turtle().fill_color;
        self.send_drawing_command(BeginFill(fill_color));
    }

    /// Stop filling the current shape
    pub fn end_fill(&mut self) {
        self.send_drawing_command(EndFill);
    }

    /// Clear the turtle's drawings
    pub fn clear(&mut self) {
        assert!(self.fetch_temporary_path().is_none(),
            "bug: The temporary path was still set when the renderer was asked to clear the drawing");
        self.send_drawing_command(Clear);
    }

    /// Move the turtle to the given position without changing its heading.
    pub fn go_to(&mut self, end: Point) {
        let (start, speed, pen) = {
            let turtle = self.fetch_turtle();
            (turtle.position, turtle.speed, turtle.pen.clone())
        };

        let distance = math::square_len(math::sub(start, end)).sqrt();
        let speed = speed.to_absolute(); // px per second
        // We take the absolute value because the time is always positive, even if distance is negative
        let total_millis = (distance / speed * 1000.).abs();

        let animation = MoveAnimation {
            path: Path {start, end, pen},
            timer: Instant::now(),
            total_millis,
        };

        self.play_animation(animation);
    }

    /// Move the turtle forward by the given distance. To move backwards, use a negative distance.
    ///
    /// The turtle's motion will be animated based on the speed
    pub fn forward(&mut self, distance: Distance) {
        if distance == 0. {
            return;
        }

        let (start, speed, heading, pen) = {
            let turtle = self.fetch_turtle();
            (turtle.position, turtle.speed, turtle.heading, turtle.pen.clone())
        };
        let x = distance * heading.cos();
        let y = distance * heading.sin();
        let end = math::add(start, [x, y]);

        let speed = speed.to_absolute(); // px per second
        // We take the absolute value because the time is always positive, even if distance is negative
        let total_millis = (distance / speed * 1000.).abs();

        let animation = MoveAnimation {
            path: Path {start, end, pen},
            timer: Instant::now(),
            total_millis,
        };

        self.play_animation(animation);
    }

    /// Rotate the turtle in place by the given angle in the given direction of rotation
    pub fn rotate(&mut self, angle: Radians, clockwise: bool) {
        if angle == radians::ZERO {
            return;
        }

        let TurtleState {heading, speed, ..} = self.fetch_turtle();
        let speed = speed.to_rotation(); // radians per second
        let total_millis = angle / speed * 1000.;
        // We take the absolute value because the time is always positive, even if angle is negative
        let total_millis = total_millis.to_radians().abs();

        let animation = RotateAnimation {
            start: heading,
            delta_angle: angle,
            clockwise,
            timer: Instant::now(),
            total_millis,
        };

        self.play_animation(animation);
    }

    fn play_animation<A: Animation>(&mut self, animation: A) {
        loop {
            // We want to keep the lock for as little time as possible
            let status = {
                let mut turtle = self.fetch_turtle();
                let status = animation.advance(&mut turtle);
                self.update_turtle(turtle);
                status
            };
            match status {
                AnimationStatus::Running(path) => self.set_temporary_path(path),
                AnimationStatus::Complete(path) => {
                    if let Some(path) = path {
                        self.set_temporary_path(None);
                        self.send_drawing_command(StorePath(path));
                    }

                    break;
                },
            }
        }
    }

    fn send_drawing_command(&self, command: DrawingCommand) {
        self.send_query(Query::Drawing(command));
    }

    // During tests, we disable the renderer. That means that if we let this code run, it will
    // quit the application during the tests and make it look like everything passes.
    // We disable this code so that none of that happens.
    #[cfg(any(feature = "test", test))]
    fn send_query(&self, _: Query) {}

    #[inline]
    #[cfg(not(any(feature = "test", test)))]
    fn send_query(&self, query: Query) {
        let result = if let Some(ref mut stdin) = self.renderer.borrow_mut().stdin {
            client::send_query(stdin, &query)
        }
        else {
            unreachable!("bug: renderer process was not opened with stdin");
        };

        result.unwrap_or_else(|_| {
            // Something went wrong while sending the query, check if the renderer process
            // panicked (exited with an error)
            match self.renderer.borrow_mut().try_wait() {
                Ok(Some(status)) => {
                    if status.success() {
                        // The window/renderer process was closed normally
                        process::exit(0);
                    }
                    else {
                        // Something went wrong, likely the other thread panicked
                        process::exit(1);
                    }
                },
                Ok(None) => panic!("bug: failed to send query even though renderer process was still running"),
                Err(_) => panic!("bug: unable to check the exit status of the renderer process"),
            }
        });
    }

    fn wait_for_response(&self) -> Response {
        match self.response_channel.recv() {
            Ok(response) => response,
            // The client thread has exited, that means that the renderer process has exited
            // and the window has closed
            Err(_) => self.exit_process(), // Quit
        }
    }

    /// Exits the current process with the correct error code
    ///
    /// Panics if the thread handle has already been consumed
    #[inline]
    fn exit_process(&self) -> ! {
        if let Some(handle) = self.thread_handle.borrow_mut().take() {
            // First check if the other thread panicked before it quit
            match handle.join() {
                Ok(_) => match self.renderer.borrow_mut().try_wait() {
                    Ok(Some(status)) => {
                        if status.success() {
                            // The window/renderer process was closed normally
                            process::exit(0);
                        }
                        else {
                            // Something went wrong, likely the other thread panicked
                            process::exit(1);
                        }
                    },
                    Ok(None) => match self.renderer.borrow_mut().wait() {
                        Ok(status) => {
                            if status.success() {
                                process::exit(0);
                            }
                            else {
                                process::exit(1);
                            }
                        },
                        Err(_) => unreachable!("bug: renderer process never ran even though we exited"),
                    },
                    Err(_) => panic!("bug: unable to check the exit status of the renderer process after client thread quit"),
                },
                // If this returns an error, the other thread panicked
                Err(_) => process::exit(1),
            }
        }
        else {
            unreachable!("bug: the thread handle was used but the process did not end");
        }
    }
}

#[cfg(not(any(feature = "test", test)))]
impl Drop for TurtleWindow {
    fn drop(&mut self) {
        // If the current thread is panicking, we want to abort right away
        // because otherwise there is code in the rendering thread that will call
        // process::exit(0) and then the exit code will be 0 instead of 1
        if thread::panicking() {
            process::exit(1);
        }

        // If this is just a normal ending of the main thread, we want to leave the renderer
        // running so that the user can see their drawing as long as they keep the window open
        //TODO: Maybe we should wait on the renderer_process instead of (or in addition to?) this thread
        if let Some(handle) = self.thread_handle.borrow_mut().take() {
            handle.join().unwrap_or_else(|_| {
                // If this returns an error, the other thread panicked
                process::exit(1);
            });
        }
    }
}
<|endoftext|>


<commit_before>// Copyright (c) The Diem Core Contributors
// SPDX-License-Identifier: Apache-2.0

use crate::{Coffer, PublicInfo};
use diem_sdk::{
    client::BlockingClient,
    transaction_builder::TransactionFactory,
    types::{chain_id::ChainId, LocalAccount},
};

#[derive(Debug)]
pub struct ChainInfo<'t> {
    pub root_account: &'t mut LocalAccount,
    pub treasury_compliance_account: &'t mut LocalAccount,
    pub designated_dealer_account: &'t mut LocalAccount,
    pub json_rpc_url: String,
    pub chain_id: ChainId,
}

impl<'t> ChainInfo<'t> {
    pub fn new(
        root_account: &'t mut LocalAccount,
        treasury_compliance_account: &'t mut LocalAccount,
        designated_dealer_account: &'t mut LocalAccount,
        json_rpc_url: String,
        chain_id: ChainId,
    ) -> Self {
        Self {
            root_account,
            treasury_compliance_account,
            designated_dealer_account,
            json_rpc_url,
            chain_id,
        }
    }

    pub fn designated_dealer_account(&mut self) -> &mut LocalAccount {
        self.designated_dealer_account
    }

    pub fn root_account(&mut self) -> &mut LocalAccount {
        self.root_account
    }

    pub fn treasury_compliance_account(&mut self) -> &mut LocalAccount {
        self.treasury_compliance_account
    }

    pub fn json_rpc(&self) -> &str {
        &self.json_rpc_url
    }

    pub fn chain_id(&self) -> ChainId {
        self.chain_id
    }

    pub fn into_public_info(self) -> PublicInfo<'t> {
        PublicInfo::new(
            self.json_rpc_url.clone(),
            self.chain_id,
            Coffer::TreasuryCompliance {
                transaction_factory: TransactionFactory::new(self.chain_id),
                json_rpc_client: BlockingClient::new(self.json_rpc_url),
                treasury_compliance_account: self.treasury_compliance_account,
                designated_dealer_account: self.designated_dealer_account,
            },
        )
    }
}
<commit_msg>forge: add more helper methods to ChainInfo<commit_after>// Copyright (c) The Diem Core Contributors
// SPDX-License-Identifier: Apache-2.0

use crate::{Coffer, PublicInfo, Result};
use diem_sdk::{
    client::BlockingClient,
    transaction_builder::{Currency, TransactionFactory},
    types::{
        account_address::AccountAddress, chain_id::ChainId,
        transaction::authenticator::AuthenticationKey, LocalAccount,
    },
};

#[derive(Debug)]
pub struct ChainInfo<'t> {
    pub root_account: &'t mut LocalAccount,
    pub treasury_compliance_account: &'t mut LocalAccount,
    pub designated_dealer_account: &'t mut LocalAccount,
    pub json_rpc_url: String,
    pub chain_id: ChainId,
}

impl<'t> ChainInfo<'t> {
    pub fn new(
        root_account: &'t mut LocalAccount,
        treasury_compliance_account: &'t mut LocalAccount,
        designated_dealer_account: &'t mut LocalAccount,
        json_rpc_url: String,
        chain_id: ChainId,
    ) -> Self {
        Self {
            root_account,
            treasury_compliance_account,
            designated_dealer_account,
            json_rpc_url,
            chain_id,
        }
    }

    pub fn designated_dealer_account(&mut self) -> &mut LocalAccount {
        self.designated_dealer_account
    }

    pub fn root_account(&mut self) -> &mut LocalAccount {
        self.root_account
    }

    pub fn treasury_compliance_account(&mut self) -> &mut LocalAccount {
        self.treasury_compliance_account
    }

    pub fn json_rpc(&self) -> &str {
        &self.json_rpc_url
    }

    pub fn json_rpc_client(&self) -> BlockingClient {
        BlockingClient::new(&self.json_rpc_url)
    }

    pub fn chain_id(&self) -> ChainId {
        self.chain_id
    }

    pub fn transaction_factory(&self) -> TransactionFactory {
        TransactionFactory::new(self.chain_id())
    }

    pub fn create_parent_vasp_account(
        &mut self,
        currency: Currency,
        authentication_key: AuthenticationKey,
    ) -> Result<()> {
        let factory = self.transaction_factory();
        let client = self.json_rpc_client();
        let treasury_compliance_account = self.treasury_compliance_account();

        let create_account_txn = treasury_compliance_account.sign_with_transaction_builder(
            factory.create_parent_vasp_account(
                currency,
                0,
                authentication_key,
                &format!("No. {} VASP", treasury_compliance_account.sequence_number()),
                false,
            ),
        );
        client.submit(&create_account_txn)?;
        client.wait_for_signed_transaction(&create_account_txn, None, None)?;
        Ok(())
    }

    pub fn create_designated_dealer_account(
        &mut self,
        currency: Currency,
        authentication_key: AuthenticationKey,
    ) -> Result<()> {
        let factory = self.transaction_factory();
        let client = self.json_rpc_client();
        let treasury_compliance_account = self.treasury_compliance_account();

        let create_account_txn = treasury_compliance_account.sign_with_transaction_builder(
            factory.create_designated_dealer(
                currency,
                0, // sliding_nonce
                authentication_key,
                &format!("No. {} DD", treasury_compliance_account.sequence_number()),
                false, // add all currencies
            ),
        );
        client.submit(&create_account_txn)?;
        client.wait_for_signed_transaction(&create_account_txn, None, None)?;
        Ok(())
    }

    pub fn fund(&mut self, currency: Currency, address: AccountAddress, amount: u64) -> Result<()> {
        let factory = self.transaction_factory();
        let client = self.json_rpc_client();
        let designated_dealer_account = self.designated_dealer_account();
        let fund_account_txn = designated_dealer_account
            .sign_with_transaction_builder(factory.peer_to_peer(currency, address, amount));
        client.submit(&fund_account_txn)?;
        client.wait_for_signed_transaction(&fund_account_txn, None, None)?;
        Ok(())
    }

    pub fn into_public_info(self) -> PublicInfo<'t> {
        PublicInfo::new(
            self.json_rpc_url.clone(),
            self.chain_id,
            Coffer::TreasuryCompliance {
                transaction_factory: TransactionFactory::new(self.chain_id),
                json_rpc_client: BlockingClient::new(self.json_rpc_url),
                treasury_compliance_account: self.treasury_compliance_account,
                designated_dealer_account: self.designated_dealer_account,
            },
        )
    }
}
<|endoftext|>