<commit_before>use std::{
    cmp::PartialEq,
    error::Error as StdError,
    fmt::{self, Display},
    io,
};

#[cfg(feature = "testing")]
use backtrace::Backtrace;

use crate::{
    pagecache::{DiskPtr, PageView},
    IVec,
};

/// The top-level result type for dealing with
/// fallible operations. The errors tend to
/// be fail-stop, and nested results are used
/// in cases where the outer fail-stop error can
/// have try `?` used on it, exposing the inner
/// operation that is expected to fail under
/// normal operation. The philosophy behind this
/// is detailed [on the sled blog](https://sled.rs/errors).
pub type Result<T> = std::result::Result<T, Error>;

/// A compare and swap result.  If the CAS is successful,
/// the new `PagePtr` will be returned as `Ok`.  Otherwise,
/// the `Err` will contain a tuple of the current `PagePtr`
/// and the old value that could not be set atomically.
pub(crate) type CasResult<'a, R> =
    std::result::Result<PageView<'a>, Option<(PageView<'a>, R)>>;

/// An Error type encapsulating various issues that may come up
/// in the operation of a `Db`.
#[derive(Debug)]
pub enum Error {
    /// The underlying collection no longer exists.
    CollectionNotFound(IVec),
    /// The system has been used in an unsupported way.
    Unsupported(String),
    /// An unexpected bug has happened. Please open an issue on github!
    ReportableBug(String),
    /// A read or write error has happened when interacting with the file
    /// system.
    Io(io::Error),
    /// Corruption has been detected in the storage file.
    Corruption {
        /// The file location that corrupted data was found at.
        at: Option<DiskPtr>,
        /// A backtrace for where the corruption was encountered.
        #[cfg(feature = "testing")]
        bt: Backtrace,
        /// A backtrace for where the corruption was encountered.
        #[cfg(not(feature = "testing"))]
        bt: (),
    },
    // a failpoint has been triggered for testing purposes
    #[doc(hidden)]
    #[cfg(feature = "failpoints")]
    FailPoint,
}

impl Error {
    pub(crate) fn corruption(at: Option<DiskPtr>) -> Error {
        Error::Corruption {
            at,
            #[cfg(feature = "testing")]
            bt: Backtrace::new(),
            #[cfg(not(feature = "testing"))]
            bt: (),
        }
    }
}

impl Clone for Error {
    fn clone(&self) -> Self {
        use self::Error::*;

        match self {
            Io(ioe) => Io(io::Error::new(ioe.kind(), format!("{:?}", ioe))),
            CollectionNotFound(name) => CollectionNotFound(name.clone()),
            Unsupported(why) => Unsupported(why.clone()),
            ReportableBug(what) => ReportableBug(what.clone()),
            Corruption { at, bt } => Corruption { at: *at, bt: bt.clone() },
            #[cfg(feature = "failpoints")]
            FailPoint => FailPoint,
        }
    }
}

impl Eq for Error {}

impl PartialEq for Error {
    fn eq(&self, other: &Self) -> bool {
        use self::Error::*;

        match *self {
            CollectionNotFound(ref l) => {
                if let CollectionNotFound(ref r) = *other {
                    l == r
                } else {
                    false
                }
            }
            Unsupported(ref l) => {
                if let Unsupported(ref r) = *other {
                    l == r
                } else {
                    false
                }
            }
            ReportableBug(ref l) => {
                if let ReportableBug(ref r) = *other {
                    l == r
                } else {
                    false
                }
            }
            #[cfg(feature = "failpoints")]
            FailPoint => {
                if let FailPoint = *other {
                    true
                } else {
                    false
                }
            }
            Corruption { at: l, .. } => {
                if let Corruption { at: r, .. } = *other {
                    l == r
                } else {
                    false
                }
            }
            Io(_) => false,
        }
    }
}

impl From<io::Error> for Error {
    #[inline]
    fn from(io_error: io::Error) -> Self {
        Error::Io(io_error)
    }
}

impl StdError for Error {}

impl Display for Error {
    fn fmt(
        &self,
        f: &mut fmt::Formatter<'_>,
    ) -> std::result::Result<(), fmt::Error> {
        use self::Error::*;

        match *self {
            CollectionNotFound(ref name) => {
                write!(f, "Collection {:?} does not exist", name,)
            }
            Unsupported(ref e) => write!(f, "Unsupported: {}", e),
            ReportableBug(ref e) => write!(
                f,
                "Unexpected bug has happened: {}. \
                 PLEASE REPORT THIS BUG!",
                e
            ),
            #[cfg(feature = "failpoints")]
            FailPoint => write!(f, "Fail point has been triggered."),
            Io(ref e) => write!(f, "IO error: {}", e),
            Corruption { at, ref bt } => write!(
                f,
                "Read corrupted data at file offset {:?} backtrace {:?}",
                at, bt
            ),
        }
    }
}
<commit_msg>Implement From<sled::Error> for io::Error<commit_after>use std::{
    cmp::PartialEq,
    error::Error as StdError,
    fmt::{self, Display},
    io,
};

#[cfg(feature = "testing")]
use backtrace::Backtrace;

use crate::{
    pagecache::{DiskPtr, PageView},
    IVec,
};

/// The top-level result type for dealing with
/// fallible operations. The errors tend to
/// be fail-stop, and nested results are used
/// in cases where the outer fail-stop error can
/// have try `?` used on it, exposing the inner
/// operation that is expected to fail under
/// normal operation. The philosophy behind this
/// is detailed [on the sled blog](https://sled.rs/errors).
pub type Result<T> = std::result::Result<T, Error>;

/// A compare and swap result.  If the CAS is successful,
/// the new `PagePtr` will be returned as `Ok`.  Otherwise,
/// the `Err` will contain a tuple of the current `PagePtr`
/// and the old value that could not be set atomically.
pub(crate) type CasResult<'a, R> =
    std::result::Result<PageView<'a>, Option<(PageView<'a>, R)>>;

/// An Error type encapsulating various issues that may come up
/// in the operation of a `Db`.
#[derive(Debug)]
pub enum Error {
    /// The underlying collection no longer exists.
    CollectionNotFound(IVec),
    /// The system has been used in an unsupported way.
    Unsupported(String),
    /// An unexpected bug has happened. Please open an issue on github!
    ReportableBug(String),
    /// A read or write error has happened when interacting with the file
    /// system.
    Io(io::Error),
    /// Corruption has been detected in the storage file.
    Corruption {
        /// The file location that corrupted data was found at.
        at: Option<DiskPtr>,
        /// A backtrace for where the corruption was encountered.
        #[cfg(feature = "testing")]
        bt: Backtrace,
        /// A backtrace for where the corruption was encountered.
        #[cfg(not(feature = "testing"))]
        bt: (),
    },
    // a failpoint has been triggered for testing purposes
    #[doc(hidden)]
    #[cfg(feature = "failpoints")]
    FailPoint,
}

impl Error {
    pub(crate) fn corruption(at: Option<DiskPtr>) -> Error {
        Error::Corruption {
            at,
            #[cfg(feature = "testing")]
            bt: Backtrace::new(),
            #[cfg(not(feature = "testing"))]
            bt: (),
        }
    }
}

impl Clone for Error {
    fn clone(&self) -> Self {
        use self::Error::*;

        match self {
            Io(ioe) => Io(io::Error::new(ioe.kind(), format!("{:?}", ioe))),
            CollectionNotFound(name) => CollectionNotFound(name.clone()),
            Unsupported(why) => Unsupported(why.clone()),
            ReportableBug(what) => ReportableBug(what.clone()),
            Corruption { at, bt } => Corruption { at: *at, bt: bt.clone() },
            #[cfg(feature = "failpoints")]
            FailPoint => FailPoint,
        }
    }
}

impl Eq for Error {}

impl PartialEq for Error {
    fn eq(&self, other: &Self) -> bool {
        use self::Error::*;

        match *self {
            CollectionNotFound(ref l) => {
                if let CollectionNotFound(ref r) = *other {
                    l == r
                } else {
                    false
                }
            }
            Unsupported(ref l) => {
                if let Unsupported(ref r) = *other {
                    l == r
                } else {
                    false
                }
            }
            ReportableBug(ref l) => {
                if let ReportableBug(ref r) = *other {
                    l == r
                } else {
                    false
                }
            }
            #[cfg(feature = "failpoints")]
            FailPoint => {
                if let FailPoint = *other {
                    true
                } else {
                    false
                }
            }
            Corruption { at: l, .. } => {
                if let Corruption { at: r, .. } = *other {
                    l == r
                } else {
                    false
                }
            }
            Io(_) => false,
        }
    }
}

impl From<io::Error> for Error {
    #[inline]
    fn from(io_error: io::Error) -> Self {
        Error::Io(io_error)
    }
}

impl From<Error> for io::Error {
    fn from(error: Error) -> io::Error {
        use self::Error::*;
        use std::io::ErrorKind;
        match error {
            Io(ioe) => ioe,
            CollectionNotFound(name) =>
                io::Error::new(
                ErrorKind::NotFound,
                format!("collection not found: {:?}", name),
            ),
            Unsupported(why) => io::Error::new(
                ErrorKind::InvalidInput,
                format!("operation not supported: {:?}", why),
            ),
            ReportableBug(what) => io::Error::new(
                ErrorKind::Other,
                format!("unexpected bug! please report this bug at <github.rs/spacejam/sled>: {:?}", what),
            ),
            Corruption { .. } => io::Error::new(
                ErrorKind::InvalidData,
                format!("corruption encountered: {:?}", error),
            ),
            #[cfg(feature = "failpoints")]
            FailPoint => io::Error::new(
                ErrorKind::Other,
                "failpoint"
            ),
        }
    }
}

impl StdError for Error {}

impl Display for Error {
    fn fmt(
        &self,
        f: &mut fmt::Formatter<'_>,
    ) -> std::result::Result<(), fmt::Error> {
        use self::Error::*;

        match *self {
            CollectionNotFound(ref name) => {
                write!(f, "Collection {:?} does not exist", name,)
            }
            Unsupported(ref e) => write!(f, "Unsupported: {}", e),
            ReportableBug(ref e) => write!(
                f,
                "Unexpected bug has happened: {}. \
                 PLEASE REPORT THIS BUG!",
                e
            ),
            #[cfg(feature = "failpoints")]
            FailPoint => write!(f, "Fail point has been triggered."),
            Io(ref e) => write!(f, "IO error: {}", e),
            Corruption { at, ref bt } => write!(
                f,
                "Read corrupted data at file offset {:?} backtrace {:?}",
                at, bt
            ),
        }
    }
}
<|endoftext|>


<commit_before>use std::collections::{BTreeMap, HashMap};
use std::env;
use std::fs::File;
use std::process;

use applescript::{run as run_applescript};

use plist::Plist;

use tera::{Context, Tera};

use urls;

macro_rules! error(
  ($($arg:tt)*) => { { return Err(format!($($arg)*)) } }
);

/// Returns true/false if Safari is running.
pub fn is_safari_running() -> bool {
  let cmd_result = process::Command::new("ps")
    .arg("-eaf")
    .output()
    .expect("Unable to test if Safari is running.");
  for line in String::from_utf8(cmd_result.stdout).unwrap().lines() {
    if line.contains("Safari.app/Contents/MacOS/Safari") {
      return true
    }
  }
  false
}

/// Return a URL from a Safari window.
///
/// Given a (window, tab) pair, this function looks up the URL of the tab.
/// Note that it doesn't do any error handling, so will throw an execution
/// error if it fails.
///
/// * `window` - Window index.  1 is frontmost.  If None, assumes the
///              frontmost window.
/// * `tab` - Tab index.  1 is leftmost.  If None, assumes the frontmost tab.
///
pub fn get_url(window: Option<i32>, tab: Option<i32>) -> Result<String, String> {
  // If a tab isn't specified, assume the user wants the frontmost tab.
  let command = match window {
    Some(w_idx) => {
      match tab {
        Some(t_idx) => format!("tell application \"Safari\" to get tab {} of window {}", t_idx, w_idx),
        None => format!("tell application \"Safari\" to get URL of document {}", w_idx),
      }
    },
    None => "tell application \"Safari\" to get URL of document 1".to_string(),
  };
  let output = run_applescript(&command);

  if output.status.success() {
    Ok(urls::tidy_url(output.stdout.trim()))
  } else {
    if output.stderr.contains("Invalid index") {
      error!("Invalid index: no such window or tab.")
    } else {
      error!("Unexpected error from osascript: {:?}", output.stderr)
    }
  }
}

/// Return a list of URLs from Safari.
///
/// This returns a list of URLs, one for every tab in Safari.  Iteration
/// order depends on AppleScript, which I don't think is guaranteed to be
/// stable (in particular, I think it depends on which window is frontmost).
///
pub fn get_all_urls() -> Result<Vec<String>, String> {
  let script = include_str!("scripts/list-open-tabs.scpt");
  let output = run_applescript(&script);
  if output.status.success() {
    Ok(parse_list_open_tabs_output(&output.stdout))
  } else {
    error!("Unexpected error from osascript: {:?}", output.stderr);
  }
}

/// Given AppleScript output from list-open-tabs.scpt, return a list of URLs.
fn parse_list_open_tabs_output(stdout: &str) -> Vec<String> {
  stdout
    .trim()
    .split(", ")
    .map(|url| urls::tidy_url(url))
    .filter(|url| url != "favorites://")
    .filter(|url| url != "://missing value")
    .collect()
}

macro_rules! parse_list_open_tabs_output_tests {
  ($($name:ident: $value:expr,)*) => {
    $(
      #[test]
      fn $name() {
        let (input, expected) = $value;
        assert_eq!(expected, parse_list_open_tabs_output(input));
      }
    )*
  }
}

parse_list_open_tabs_output_tests! {

  // I don't have a good way to put an empty vector on the right-hand side of
  // this comparison, so no test checks for nothing (yet).

  favorites_with_url: (
    "favorites://, http://example.org",
    vec!["http://example.org"]
  ),

  url_with_missing_value: (
    "://missing value, https://www.example.net",
    vec!["https://www.example.net"]
  ),

  single_url: (
    "http://foo_bar.com",
    vec!["http://foo_bar.com"]
  ),

  multiple_urls: (
    "http://example.org, https://www.example.net, http://test.co.uk",
    vec!["http://example.org", "https://www.example.net", "http://test.co.uk"]
  ),

  with_extra_whitespace: (
    "    http://space.org, https://www.nasa.gov   ",
    vec!["http://space.org", "https://www.nasa.gov"]
  ),

  applies_tidy_url_transform: (
    "https://mobile.twitter.com",
    vec!["https://twitter.com"]
  ),
}

/// Convert URL patterns into AppleScript conditions.
///
/// These conditions can be used in an `if` statement in AppleScript to
/// decide whether a tab should be closed.  Three patterns are supported,
/// a limited regex syntax:
///
///     example.com             matches anywhere in the URL
///     ^http://examples.com    matches at the start of the URL
///     example.com/$           matches at the end of the URL
///
fn parse_conditions(url_patterns: Vec<&str>) -> Vec<String> {
  url_patterns.iter().map(|p|
    if p.starts_with("^") {
      format!("starts with \"{}\"", p.replace("^", ""))
    } else if p.ends_with("$") {
      format!("ends with \"{}\"", p.replace("$", ""))
    } else {
      format!("contains \"{}\"", p)
    }
  ).collect()
}

/// Tests for parse_conditions().
#[cfg(test)]
mod tests {
  use safari::parse_conditions;

  #[test]
  fn test_parse_conditions() {
    let patterns = vec!["github.com", "^facebook.com", "twitter.com$"];
    let expected = vec!["contains \"github.com\"", "starts with \"facebook.com\"", "ends with \"twitter.com\""];
    let actual = parse_conditions(patterns);
    assert_eq!(actual, expected);
  }
}

/// Close tabs in Safari that match URL patterns.
///
/// Takes a list of URL patterns, and tries to close any matching Safari tabs.
/// Bugginess in AppleScript means this isn't always perfect, but we can
/// have a go.  In general it will fail to close tabs, rather than close
/// the wrong tabs.
///
pub fn close_tabs(url_patterns: Vec<&str>) {
  let conditions = parse_conditions(url_patterns);

  let clean_tabs_template = include_str!("scripts/clean-tabs.scpt");
  let mut context = Context::new();
  context.add("conditions", &conditions);
  let script = Tera::one_off(&clean_tabs_template, context, false).unwrap();

  // Run it twice to get around weird AppleScript bugs.
  run_applescript(&script);
  run_applescript(&script);
}

/// Get the Bookmarks.plist dict for a given title
fn read_bookmarks_plist(title: &str) -> Result<Plist, String> {

  // All Safari data lives at ~/Library/Safari/Bookmarks.plist
  // TODO: There's probably a more idiomatic Rust-like way to write this.
  let mut plist_path = match env::home_dir() {
    Some(v) => v,
    None => error!("Unable to get home directory?"),
  };
  plist_path.push("Library/Safari/Bookmarks.plist");

  let file = match File::open(plist_path) {
    Ok(v) => v,
    Err(e) => error!("Unable to open ~/Library/Safari/Bookmarks.plist: {:?}", e),
  };

  let plist = match Plist::read(file) {
    Ok(v) => v,
    Err(e) => error!("Unable to read ~/Library/Safari/Bookmarks.plist: {:?}", e),
  };

  let data = match plist.as_dictionary() {
    Some(v) => v,
    None => error!("Unable to parse ~/Library/Safari/Bookmarks.plist as dictionary?"),
  };

  // The structure of Bookmarks.plist is as follows:
  //
  //     <dict>
  //       <key>Children</key>
  //       <array>
  //         <dict>
  //           <key>Title</key><string>History</string>
  //           ... dict data ...
  //         </dict>
  //         <dict>
  //           <key>Title</key><string>com.apple.ReadingList</string>
  //           ... dict data ...
  //         </dict>
  //         ... other array items ...
  //       </array>
  //     </dict>
  //
  let children = match data.get("Children") {
    Some(child_key) => match child_key.as_array() {
      Some(v) => v,
      None => error!("Top-level children key in ~/Library/Safari/Bookmarks.plist isn't an array?"),
    },
    None => error!("Unable to find top-level Children key in ~/Library/Safari/Bookmarks.plist"),
  };

  let mut matching_children = children.iter().filter(|d|
    match d.as_dictionary() {
      Some(dict) => match dict.get("Title") {
        Some(title_elem) => match title_elem.as_string() {
          Some(v) => (v == title),
          None => false,
        },
        None => false,
      },
      None => false,
    }
  );

  // Check we got one, and only one result.
  let result = match matching_children.next() {
    Some(v) => v,
    None => error!("Unable to find key {} in Bookmarks.plist", title),
  };

  match matching_children.next() {
    Some(_) => error!("Got more than one result for {} in Bookmarks.plist", title),
    None => {},
  };

  Ok(result.to_owned())
}

/// Return a list of URLs from Reading List.
///
/// Iteration order depends on the order in which they're stored in
/// Bookmarks.plist, which is usually (but not guaranteed to be) newest first.
///
pub fn get_reading_list_urls() -> Result<Vec<String>, String> {
  let plist = match read_bookmarks_plist("com.apple.ReadingList") {
    Ok(v) => v,
    Err(e) => return Err(e),
  };

  // TODO: All these unwrap() calls should probably be handled better
  let children = plist
    .as_dictionary().unwrap()
    .get("Children").unwrap()
    .as_array().unwrap();

  Ok(children
    .iter()
    .map(|child|
      child
        .as_dictionary().unwrap()
        .get("URLString").unwrap()
        .as_string().unwrap()
    )
    .map(|url| urls::tidy_url(url))
    .collect()
  )
}

/// Get the com.apple.Safari.plist preferences file
fn read_safari_plist() -> Result<BTreeMap<String, Plist>, String> {

  // All Safari data lives at ~/Library/Safari/Bookmarks.plist
  // TODO: There's probably a more idiomatic Rust-like way to write this.
  let mut plist_path = match env::home_dir() {
    Some(v) => v,
    None => error!("Unable to get home directory?"),
  };
  plist_path.push("Library/SyncedPreferences/com.apple.Safari.plist");

  let file = match File::open(plist_path) {
    Ok(v) => v,
    Err(e) => error!("Unable to open com.apple.Safari.plist: {:?}", e),
  };

  let plist = match Plist::read(file) {
    Ok(v) => v,
    Err(e) => error!("Unable to read com.apple.Safari.plist: {:?}", e),
  };

  let data = match plist.as_dictionary() {
    Some(v) => v,
    None => error!("Unable to parse com.apple.Safari.plist as dictionary?"),
  };

  // The structure of com.apple.Safari.plist is as follows:
  //
  //      <dict>
  //        <key>values</key>
  //        <dict>
  //          <dict>
  //            <key>[[ UUID ]]</key>
  //            <dict>
  //              ... dict data ...
  //              <key>value</key>
  //              <dict>
  //                ... dict data ...
  //                <key>DeviceName</key><string>[[ Device name ]]</string>
  //                <key>Tabs</key>
  //                <array>
  //                  <dict>
  //                    <key>Title</key><string>[[ Title ]]</string>
  //                    <key>URL</key><string>[[ URL ]]</string>
  //                  </dict>
  //                  ... array items for other tabs ...
  //                </array>
  //              </dict>
  //            </dict>
  //            ... dicts for other devices ...
  //          </dict>
  //        </dict>
  //      </dict>
  //
  match data.get("values") {
    Some(child_key) => match child_key.as_dictionary() {
      Some(v) => Ok(v.to_owned()),
      None => error!("Top-level values key in com.apple.Safari.plist isn't an dictionary?"),
    },
    None => error!("Unable to find top-level values key in com.apple.Safari.plist"),
  }
}

/// Return a list of devices in iCloud Tabs.
pub fn list_icloud_tabs_devices() -> Result<Vec<String>, String> {
  let plist = match read_safari_plist() {
    Ok(plist) => plist,
    Err(e) => return Err(e),
  };

  Ok(plist
    .values()
    .map(|value| value
      .as_dictionary().unwrap()
      .get("value").unwrap()
      .as_dictionary().unwrap()
      .get("DeviceName").unwrap()
      .as_string().unwrap()
      .to_owned()
    )
    .collect()
  )
}

/// Return a list of URLs from iCloud Tabs.
pub fn get_icloud_tabs_urls() -> Result<HashMap<String, Vec<String>>, String> {
  let plist = match read_safari_plist() {
    Ok(plist) => plist,
    Err(e) => return Err(e),
  };

  // Within the `values` dictionary, each device is as follows:
  //
  //    <key>[[ device UUID ]]</key>
  //    <dict>
  //      [[ device data ]]
  //      <dict>
  //        <key>Tabs</key>
  //        <key>DeviceName</key><string>[[ device name ]]</key>
  //        <key>Tabs</key>
  //        <array>
  //          <dict>
  //            <key>URL</key><string>[[ URL ]]</string>
  //            [[ other tab data ]]
  //          </dict>
  //          [[ other tabs ]]
  //
  let mut result: HashMap<String, Vec<String>> = HashMap::new();
  let devices_with_tabs = plist
    .values()
    .map(|value| value
      .as_dictionary().unwrap()
      .get("value").unwrap()
      .as_dictionary().unwrap()
    );
  for data in devices_with_tabs {
    let name: String = data
      .get("DeviceName").unwrap()
      .as_string().unwrap()
      .to_owned();

    // If a device is registered with iCloud but Safari isn't running or
    // there aren't any tabs open, there isn't a Tabs field.
    if !data.contains_key("Tabs") {
      continue;
    }

    let urls: Vec<String> = data
      .get("Tabs").unwrap()
      .as_array().unwrap()
      .iter()
      .map(|tab| tab
        .as_dictionary().unwrap()
        .get("URL").unwrap()
        .as_string().unwrap()
      )
      .map(|url| urls::tidy_url(url))
      .collect();
    result.insert(name, urls);
  }
  Ok(result)
}
<commit_msg>Add a test that :// is ignored in results<commit_after>use std::collections::{BTreeMap, HashMap};
use std::env;
use std::fs::File;
use std::process;

use applescript::{run as run_applescript};

use plist::Plist;

use tera::{Context, Tera};

use urls;

macro_rules! error(
  ($($arg:tt)*) => { { return Err(format!($($arg)*)) } }
);

/// Returns true/false if Safari is running.
pub fn is_safari_running() -> bool {
  let cmd_result = process::Command::new("ps")
    .arg("-eaf")
    .output()
    .expect("Unable to test if Safari is running.");
  for line in String::from_utf8(cmd_result.stdout).unwrap().lines() {
    if line.contains("Safari.app/Contents/MacOS/Safari") {
      return true
    }
  }
  false
}

/// Return a URL from a Safari window.
///
/// Given a (window, tab) pair, this function looks up the URL of the tab.
/// Note that it doesn't do any error handling, so will throw an execution
/// error if it fails.
///
/// * `window` - Window index.  1 is frontmost.  If None, assumes the
///              frontmost window.
/// * `tab` - Tab index.  1 is leftmost.  If None, assumes the frontmost tab.
///
pub fn get_url(window: Option<i32>, tab: Option<i32>) -> Result<String, String> {
  // If a tab isn't specified, assume the user wants the frontmost tab.
  let command = match window {
    Some(w_idx) => {
      match tab {
        Some(t_idx) => format!("tell application \"Safari\" to get tab {} of window {}", t_idx, w_idx),
        None => format!("tell application \"Safari\" to get URL of document {}", w_idx),
      }
    },
    None => "tell application \"Safari\" to get URL of document 1".to_string(),
  };
  let output = run_applescript(&command);

  if output.status.success() {
    Ok(urls::tidy_url(output.stdout.trim()))
  } else {
    if output.stderr.contains("Invalid index") {
      error!("Invalid index: no such window or tab.")
    } else {
      error!("Unexpected error from osascript: {:?}", output.stderr)
    }
  }
}

/// Return a list of URLs from Safari.
///
/// This returns a list of URLs, one for every tab in Safari.  Iteration
/// order depends on AppleScript, which I don't think is guaranteed to be
/// stable (in particular, I think it depends on which window is frontmost).
///
pub fn get_all_urls() -> Result<Vec<String>, String> {
  let script = include_str!("scripts/list-open-tabs.scpt");
  let output = run_applescript(&script);
  if output.status.success() {
    Ok(parse_list_open_tabs_output(&output.stdout))
  } else {
    error!("Unexpected error from osascript: {:?}", output.stderr);
  }
}

/// Given AppleScript output from list-open-tabs.scpt, return a list of URLs.
fn parse_list_open_tabs_output(stdout: &str) -> Vec<String> {
  stdout
    .trim()
    .split(", ")
    .map(|url| urls::tidy_url(url))
    .filter(|url| url != "favorites://")
    .filter(|url| url != "://missing value")
    .collect()
}

macro_rules! parse_list_open_tabs_output_tests {
  ($($name:ident: $value:expr,)*) => {
    $(
      #[test]
      fn $name() {
        let (input, expected) = $value;
        assert_eq!(expected, parse_list_open_tabs_output(input));
      }
    )*
  }
}

parse_list_open_tabs_output_tests! {

  // I don't have a good way to put an empty vector on the right-hand side of
  // this comparison, so no test checks for nothing (yet).

  favorites_with_url: (
    "favorites://, http://example.org",
    vec!["http://example.org"]
  ),

  url_with_missing_value: (
    "://missing value, https://www.example.net",
    vec!["https://www.example.net"]
  ),

  single_url: (
    "http://foo_bar.com",
    vec!["http://foo_bar.com"]
  ),

  multiple_urls: (
    "http://example.org, https://www.example.net, http://test.co.uk",
    vec!["http://example.org", "https://www.example.net", "http://test.co.uk"]
  ),

  with_extra_whitespace: (
    "    http://space.org, https://www.nasa.gov   ",
    vec!["http://space.org", "https://www.nasa.gov"]
  ),

  applies_tidy_url_transform: (
    "https://mobile.twitter.com",
    vec!["https://twitter.com"]
  ),

  colon_slash_slash_with_url: (
    "://, http://example.org",
    vec!["http://example.org"]
  ),
}

/// Convert URL patterns into AppleScript conditions.
///
/// These conditions can be used in an `if` statement in AppleScript to
/// decide whether a tab should be closed.  Three patterns are supported,
/// a limited regex syntax:
///
///     example.com             matches anywhere in the URL
///     ^http://examples.com    matches at the start of the URL
///     example.com/$           matches at the end of the URL
///
fn parse_conditions(url_patterns: Vec<&str>) -> Vec<String> {
  url_patterns.iter().map(|p|
    if p.starts_with("^") {
      format!("starts with \"{}\"", p.replace("^", ""))
    } else if p.ends_with("$") {
      format!("ends with \"{}\"", p.replace("$", ""))
    } else {
      format!("contains \"{}\"", p)
    }
  ).collect()
}

/// Tests for parse_conditions().
#[cfg(test)]
mod tests {
  use safari::parse_conditions;

  #[test]
  fn test_parse_conditions() {
    let patterns = vec!["github.com", "^facebook.com", "twitter.com$"];
    let expected = vec!["contains \"github.com\"", "starts with \"facebook.com\"", "ends with \"twitter.com\""];
    let actual = parse_conditions(patterns);
    assert_eq!(actual, expected);
  }
}

/// Close tabs in Safari that match URL patterns.
///
/// Takes a list of URL patterns, and tries to close any matching Safari tabs.
/// Bugginess in AppleScript means this isn't always perfect, but we can
/// have a go.  In general it will fail to close tabs, rather than close
/// the wrong tabs.
///
pub fn close_tabs(url_patterns: Vec<&str>) {
  let conditions = parse_conditions(url_patterns);

  let clean_tabs_template = include_str!("scripts/clean-tabs.scpt");
  let mut context = Context::new();
  context.add("conditions", &conditions);
  let script = Tera::one_off(&clean_tabs_template, context, false).unwrap();

  // Run it twice to get around weird AppleScript bugs.
  run_applescript(&script);
  run_applescript(&script);
}

/// Get the Bookmarks.plist dict for a given title
fn read_bookmarks_plist(title: &str) -> Result<Plist, String> {

  // All Safari data lives at ~/Library/Safari/Bookmarks.plist
  // TODO: There's probably a more idiomatic Rust-like way to write this.
  let mut plist_path = match env::home_dir() {
    Some(v) => v,
    None => error!("Unable to get home directory?"),
  };
  plist_path.push("Library/Safari/Bookmarks.plist");

  let file = match File::open(plist_path) {
    Ok(v) => v,
    Err(e) => error!("Unable to open ~/Library/Safari/Bookmarks.plist: {:?}", e),
  };

  let plist = match Plist::read(file) {
    Ok(v) => v,
    Err(e) => error!("Unable to read ~/Library/Safari/Bookmarks.plist: {:?}", e),
  };

  let data = match plist.as_dictionary() {
    Some(v) => v,
    None => error!("Unable to parse ~/Library/Safari/Bookmarks.plist as dictionary?"),
  };

  // The structure of Bookmarks.plist is as follows:
  //
  //     <dict>
  //       <key>Children</key>
  //       <array>
  //         <dict>
  //           <key>Title</key><string>History</string>
  //           ... dict data ...
  //         </dict>
  //         <dict>
  //           <key>Title</key><string>com.apple.ReadingList</string>
  //           ... dict data ...
  //         </dict>
  //         ... other array items ...
  //       </array>
  //     </dict>
  //
  let children = match data.get("Children") {
    Some(child_key) => match child_key.as_array() {
      Some(v) => v,
      None => error!("Top-level children key in ~/Library/Safari/Bookmarks.plist isn't an array?"),
    },
    None => error!("Unable to find top-level Children key in ~/Library/Safari/Bookmarks.plist"),
  };

  let mut matching_children = children.iter().filter(|d|
    match d.as_dictionary() {
      Some(dict) => match dict.get("Title") {
        Some(title_elem) => match title_elem.as_string() {
          Some(v) => (v == title),
          None => false,
        },
        None => false,
      },
      None => false,
    }
  );

  // Check we got one, and only one result.
  let result = match matching_children.next() {
    Some(v) => v,
    None => error!("Unable to find key {} in Bookmarks.plist", title),
  };

  match matching_children.next() {
    Some(_) => error!("Got more than one result for {} in Bookmarks.plist", title),
    None => {},
  };

  Ok(result.to_owned())
}

/// Return a list of URLs from Reading List.
///
/// Iteration order depends on the order in which they're stored in
/// Bookmarks.plist, which is usually (but not guaranteed to be) newest first.
///
pub fn get_reading_list_urls() -> Result<Vec<String>, String> {
  let plist = match read_bookmarks_plist("com.apple.ReadingList") {
    Ok(v) => v,
    Err(e) => return Err(e),
  };

  // TODO: All these unwrap() calls should probably be handled better
  let children = plist
    .as_dictionary().unwrap()
    .get("Children").unwrap()
    .as_array().unwrap();

  Ok(children
    .iter()
    .map(|child|
      child
        .as_dictionary().unwrap()
        .get("URLString").unwrap()
        .as_string().unwrap()
    )
    .map(|url| urls::tidy_url(url))
    .collect()
  )
}

/// Get the com.apple.Safari.plist preferences file
fn read_safari_plist() -> Result<BTreeMap<String, Plist>, String> {

  // All Safari data lives at ~/Library/Safari/Bookmarks.plist
  // TODO: There's probably a more idiomatic Rust-like way to write this.
  let mut plist_path = match env::home_dir() {
    Some(v) => v,
    None => error!("Unable to get home directory?"),
  };
  plist_path.push("Library/SyncedPreferences/com.apple.Safari.plist");

  let file = match File::open(plist_path) {
    Ok(v) => v,
    Err(e) => error!("Unable to open com.apple.Safari.plist: {:?}", e),
  };

  let plist = match Plist::read(file) {
    Ok(v) => v,
    Err(e) => error!("Unable to read com.apple.Safari.plist: {:?}", e),
  };

  let data = match plist.as_dictionary() {
    Some(v) => v,
    None => error!("Unable to parse com.apple.Safari.plist as dictionary?"),
  };

  // The structure of com.apple.Safari.plist is as follows:
  //
  //      <dict>
  //        <key>values</key>
  //        <dict>
  //          <dict>
  //            <key>[[ UUID ]]</key>
  //            <dict>
  //              ... dict data ...
  //              <key>value</key>
  //              <dict>
  //                ... dict data ...
  //                <key>DeviceName</key><string>[[ Device name ]]</string>
  //                <key>Tabs</key>
  //                <array>
  //                  <dict>
  //                    <key>Title</key><string>[[ Title ]]</string>
  //                    <key>URL</key><string>[[ URL ]]</string>
  //                  </dict>
  //                  ... array items for other tabs ...
  //                </array>
  //              </dict>
  //            </dict>
  //            ... dicts for other devices ...
  //          </dict>
  //        </dict>
  //      </dict>
  //
  match data.get("values") {
    Some(child_key) => match child_key.as_dictionary() {
      Some(v) => Ok(v.to_owned()),
      None => error!("Top-level values key in com.apple.Safari.plist isn't an dictionary?"),
    },
    None => error!("Unable to find top-level values key in com.apple.Safari.plist"),
  }
}

/// Return a list of devices in iCloud Tabs.
pub fn list_icloud_tabs_devices() -> Result<Vec<String>, String> {
  let plist = match read_safari_plist() {
    Ok(plist) => plist,
    Err(e) => return Err(e),
  };

  Ok(plist
    .values()
    .map(|value| value
      .as_dictionary().unwrap()
      .get("value").unwrap()
      .as_dictionary().unwrap()
      .get("DeviceName").unwrap()
      .as_string().unwrap()
      .to_owned()
    )
    .collect()
  )
}

/// Return a list of URLs from iCloud Tabs.
pub fn get_icloud_tabs_urls() -> Result<HashMap<String, Vec<String>>, String> {
  let plist = match read_safari_plist() {
    Ok(plist) => plist,
    Err(e) => return Err(e),
  };

  // Within the `values` dictionary, each device is as follows:
  //
  //    <key>[[ device UUID ]]</key>
  //    <dict>
  //      [[ device data ]]
  //      <dict>
  //        <key>Tabs</key>
  //        <key>DeviceName</key><string>[[ device name ]]</key>
  //        <key>Tabs</key>
  //        <array>
  //          <dict>
  //            <key>URL</key><string>[[ URL ]]</string>
  //            [[ other tab data ]]
  //          </dict>
  //          [[ other tabs ]]
  //
  let mut result: HashMap<String, Vec<String>> = HashMap::new();
  let devices_with_tabs = plist
    .values()
    .map(|value| value
      .as_dictionary().unwrap()
      .get("value").unwrap()
      .as_dictionary().unwrap()
    );
  for data in devices_with_tabs {
    let name: String = data
      .get("DeviceName").unwrap()
      .as_string().unwrap()
      .to_owned();

    // If a device is registered with iCloud but Safari isn't running or
    // there aren't any tabs open, there isn't a Tabs field.
    if !data.contains_key("Tabs") {
      continue;
    }

    let urls: Vec<String> = data
      .get("Tabs").unwrap()
      .as_array().unwrap()
      .iter()
      .map(|tab| tab
        .as_dictionary().unwrap()
        .get("URL").unwrap()
        .as_string().unwrap()
      )
      .map(|url| urls::tidy_url(url))
      .collect();
    result.insert(name, urls);
  }
  Ok(result)
}
<|endoftext|>


<commit_before>#![feature(phase)]
#[phase(syntax, link)] extern crate log;
extern crate collections;
extern crate sync;
extern crate rand;
extern crate serialize;
use std::io::{IoResult, IoError, IoUnavailable, SeekStyle, MemWriter};
use serialize::ebml::{reader,writer};
use serialize::{Encodable, Decodable};
mod buffer;

#[deriving(Encodable, Decodable)]
enum SqlType {
	Char(uint),
	Varchar(uint),
	Integer,
}

#[deriving(Encodable, Decodable)]
enum SqlAttribute {
	Null,
	NotNull,
}

#[deriving(Encodable, Decodable)]
struct Column {
	name: ~str,
	datatype: SqlType,
	attributes: Vec<SqlAttribute>,
}

#[deriving(Encodable, Decodable)]
struct Relation {
	name: ~str,
	columns: Vec<Column>,
}

impl Relation {
	pub fn new(name: ~str) -> Relation {
		Relation {name: name, columns: Vec::new()}
	}

	pub fn add_column(&mut self, column: Column) {
		self.columns.push(column);
	}
}

#[deriving(Encodable, Decodable)]
struct Schema {
	relations: Vec<Relation>,
}

struct SchemaWriter{
	buffer_manager: buffer::BufferManager,
}

impl Writer for SchemaWriter {
	fn write(&mut self, buf: &[u8]) -> IoResult<()> {
		Err(IoError {kind: IoUnavailable, desc: "Unavailable", detail: None})
	}
}

impl Seek for SchemaWriter {
	fn tell(&self) -> IoResult<u64> {
		Err(IoError {kind: IoUnavailable, desc: "Unavailable", detail: None})
	}

	fn seek(&mut self, pos: i64, style: SeekStyle) -> IoResult<()> {
		Err(IoError {kind: IoUnavailable, desc: "Unavailable", detail: None})
	}
}

impl Schema {
	pub fn new() -> Schema {
		Schema {relations: Vec::new()}
	}

	pub fn new_from_disk(bufmanager: buffer::BufferManager) -> Schema {
		// TODO
		Schema {relations: Vec::new()}
	}

	pub fn add_relation(&mut self, relation: Relation) {
		self.relations.push(relation);
	}

	pub fn save_to_disk(&self) {
		let mut wr = MemWriter::new();
		let v: u64 = 42;
		{
			let mut ebml_w = writer::Encoder(&mut wr);
			let _ = self.encode(&mut ebml_w);
		}
		let ebml_doc = reader::Doc(wr.get_ref());
		let mut deser = reader::Decoder(ebml_doc);
		let v1: Schema = Decodable::decode(&mut deser).unwrap();
		println!("v1 == {:?}", v1);

		for rel in self.relations.iter() {
			//rel.save_to_disk()
		}
	}
}

struct Record {
	// TODO: do we even need this?
	len: uint,
	data: Vec<u8>,
}

impl Record {
	/* move semantics, no copy */
	pub fn new(len: uint, data: Vec<u8>) -> Record {
		Record {len: len, data: data}
	}

	pub fn get_data<'a>(&'a self) -> &'a [u8] {
		self.data.as_slice()
	}
}

struct SPSegment;

/* our newtype struct: create a TID type as an alias to u64 */
struct TID(u64);

impl SPSegment {
	pub fn insert(r: Record) -> TID {
		TID(0_u64)
	}

	pub fn remove(tid: TID) -> bool {
		false
	}

	pub fn lookup(tid: TID) -> Record {
		// TODO
		Record {len: 1, data: vec!(1)}
	}

	pub fn update(tid: TID, r: Record) -> bool {
		false
	}
}

#[test]
fn main() {
}

#[test]
fn create_schema() {
	let name = Column {name: ~"name", datatype: Varchar(128), attributes: vec!(NotNull)};
	let age = Column {name: ~"age", datatype: Integer, attributes: vec!(NotNull)};
	let mut relation = Relation::new(~"Person");
	relation.add_column(name);
	relation.add_column(age);
	let mut schema = Schema::new();
	schema.add_relation(relation);
	schema.save_to_disk();
	assert!(false);
}
<commit_msg>Added start of a file implementation on top of BufferManager<commit_after>#![feature(phase)]
#[phase(syntax, link)] extern crate log;
extern crate collections;
extern crate sync;
extern crate rand;
extern crate serialize;
use std::io::{IoResult, IoError, IoUnavailable, SeekStyle, MemWriter};
use serialize::ebml::{reader,writer};
use serialize::{Encodable, Decodable};
mod buffer;

#[deriving(Encodable, Decodable)]
enum SqlType {
	Char(uint),
	Varchar(uint),
	Integer,
}

#[deriving(Encodable, Decodable)]
enum SqlAttribute {
	Null,
	NotNull,
}

#[deriving(Encodable, Decodable)]
struct Column {
	name: ~str,
	datatype: SqlType,
	attributes: Vec<SqlAttribute>,
}

#[deriving(Encodable, Decodable)]
struct Relation {
	name: ~str,
	columns: Vec<Column>,
}

impl Relation {
	pub fn new(name: ~str) -> Relation {
		Relation {name: name, columns: Vec::new()}
	}

	pub fn add_column(&mut self, column: Column) {
		self.columns.push(column);
	}
}

#[deriving(Encodable, Decodable)]
struct Schema {
	relations: Vec<Relation>,
}

struct SchemaWriter{
	buffer_manager: buffer::BufferManager,
	location: u64,
}

impl Writer for SchemaWriter {
	fn write(&mut self, buf: &[u8]) -> IoResult<()> {
		Err(IoError {kind: IoUnavailable, desc: "Unavailable", detail: None})
	}
}

impl Seek for SchemaWriter {
	fn tell(&self) -> IoResult<u64> {
		Ok(self.location)
	}

	fn seek(&mut self, pos: i64, style: SeekStyle) -> IoResult<()> {
		Err(IoError {kind: IoUnavailable, desc: "Unavailable", detail: None})
	}
}

impl SchemaWriter {
	pub fn new(bufman: buffer::BufferManager) -> SchemaWriter {
		SchemaWriter {buffer_manager: bufman, location: 0}
	}
}

impl Schema {
	pub fn new() -> Schema {
		Schema {relations: Vec::new()}
	}

	pub fn new_from_disk(bufmanager: buffer::BufferManager) -> Schema {
		// TODO
		Schema {relations: Vec::new()}
	}

	pub fn add_relation(&mut self, relation: Relation) {
		self.relations.push(relation);
	}

	pub fn save_to_disk(&self) {
		let mut wr = MemWriter::new();
		let v: u64 = 42;
		{
			let mut ebml_w = writer::Encoder(&mut wr);
			let _ = self.encode(&mut ebml_w);
		}
		let ebml_doc = reader::Doc(wr.get_ref());
		let mut deser = reader::Decoder(ebml_doc);
		let v1: Schema = Decodable::decode(&mut deser).unwrap();
		println!("v1 == {:?}", v1);

		for rel in self.relations.iter() {
			//rel.save_to_disk()
		}
	}
}

struct Record {
	// TODO: do we even need this?
	len: uint,
	data: Vec<u8>,
}

impl Record {
	/* move semantics, no copy */
	pub fn new(len: uint, data: Vec<u8>) -> Record {
		Record {len: len, data: data}
	}

	pub fn get_data<'a>(&'a self) -> &'a [u8] {
		self.data.as_slice()
	}
}

struct SPSegment;

/* our newtype struct: create a TID type as an alias to another type */
struct TID(u8);

impl SPSegment {
	pub fn insert(r: Record) -> TID {
		TID(0)
	}

	pub fn remove(tid: TID) -> bool {
		false
	}

	pub fn lookup(tid: TID) -> Record {
		// TODO
		Record {len: 1, data: vec!(1)}
	}

	pub fn update(tid: TID, r: Record) -> bool {
		false
	}
}

#[test]
fn main() {
}

#[test]
fn create_schema() {
	let name = Column {name: ~"name", datatype: Varchar(128), attributes: vec!(NotNull)};
	let age = Column {name: ~"age", datatype: Integer, attributes: vec!(NotNull)};
	let mut relation = Relation::new(~"Person");
	relation.add_column(name);
	relation.add_column(age);
	let mut schema = Schema::new();
	schema.add_relation(relation);
	schema.save_to_disk();
	assert!(false);
}
<|endoftext|>


<commit_before>use configuration::Configuration;
use std::slice::SliceExt;
use std::cmp::Ordering;
use score::Score;

struct Search {
    config: Configuration,
    current: uint,
    query: String,
    selection: Option<String>,
    result: Vec<String>,
}

impl Search {
    fn blank(config: Configuration) -> Search {
        let query = config.initial_search.clone();
        let previous_result = config.choices.clone();
        Search::new(config, query, previous_result, 0)
    }

    fn new(config: Configuration, query: String, prev_result: Vec<String>, index: uint) -> Search {
        let result = Search::filter(query.as_slice(), &prev_result);
        let select = Search::select(&result, index);
        Search { config: config,
                 current: index,
                 query: query,
                 selection: select,
                 result: result }
    }

    fn new_for_index(self, index: uint) -> Search {
        Search::new(self.config, self.query, self.result, index)
    }

    fn filter(query: &str, choices: &Vec<String>) -> Vec<String> {
        let mut filtered = choices.iter().filter_map( |choice| {
            let quality = Score::score(choice.as_slice(), query);
            if quality > 0.0 {
                Some((quality, choice.to_string()))
            } else {
                None
            }
        }).collect::<Vec<(f32, String)>>();

        filtered.sort_by( |&(quality_a, _), &(quality_b, _)| {
            quality_a.partial_cmp(&quality_b).unwrap_or(Ordering::Equal).reverse()
        });

        filtered.iter().map( |&(_, ref choice)| choice.to_string() ).collect::<Vec<String>>()
    }

    fn select(result: &Vec<String>, index: uint) -> Option<String> {
        if result.len() > 0 {
            Some(result[index].to_string())
        } else {
            None
        }
    }

    fn down(self) -> Search {
        let next_index = self.next_index();
        self.new_for_index(next_index)
    }

    fn up(self) -> Search {
        let next_index = self.prev_index();
        self.new_for_index(next_index)
    }

    fn append_to_search(self, input: &str) -> Search {
        let mut new_query = self.query;
        new_query.push_str(input.as_slice());
        Search::new(self.config, new_query, self.result, self.current)
    }

    fn next_index(&self) -> uint {
        let next_index = self.current + 1;

        if next_index >= self.config.visible_limit { 0 } else { next_index }
    }

    fn prev_index(&self) -> uint {
        if self.current == 0 { self.config.visible_limit - 1 } else  { self.current - 1 }
    }
}

#[cfg(test)]

fn one_two_three() -> Vec<String> {
    vec!["one".to_string(),
         "two".to_string(),
         "three".to_string()]
}

#[test]
fn it_selects_the_first_choice_by_default() {
    let input =  one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);
    assert_eq!(search.selection, Some("one".to_string()));
}

#[test]
fn it_selets_the_second_when_down_is_called() {
    let input =  one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);
    assert_eq!(search.down().selection, Some("two".to_string()));
}

#[test]
fn it_loop_around_when_reaching_end_of_list() {
    let input =  one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);
    assert_eq!(search.down().down().down().down().selection, Some("two".to_string()));
}

#[test]
fn it_loop_around_when_reaching_top_of_list() {
    let input =  one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);
    assert_eq!(search.up().up().selection, Some("two".to_string()));
}

#[test]
fn it_loop_around_when_reaching_visible_limit() {
    let input =  one_two_three();

    let config = Configuration::from_inputs(input, None, Some(2));
    let search = Search::blank(config);
    assert_eq!(search.down().down().down().selection, Some("two".to_string()));
}

#[test]
fn it_moves_down_the_filtered_search_results() {
    let input =  one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);
    assert_eq!(search.append_to_search("t").down().selection, Some("three".to_string()));
}

#[test]
fn it_moves_down_the_filtered_search_results_twice() {
    let input =  one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);
    assert_eq!(search.append_to_search("t").append_to_search("w").selection, Some("two".to_string()));
}

#[test]
fn it_handles_not_matching_anything() {
    let input = one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);
    assert_eq!(search.append_to_search("x").selection, None);
}
<commit_msg>Moves filtering and selecting out of constructor<commit_after>use configuration::Configuration;
use std::slice::SliceExt;
use std::cmp::Ordering;
use score::Score;

struct Search {
    config: Configuration,
    current: uint,
    query: String,
    selection: Option<String>,
    result: Vec<String>,
}

impl Search {
    fn blank(config: Configuration) -> Search {
        let query = config.initial_search.clone();
        let previous_result = config.choices.clone();
        let selection = Some(previous_result[0].clone());
        Search::new(config, query, previous_result, 0, selection)
    }

    fn new(config: Configuration, query: String, result: Vec<String>, index: uint, selection: Option<String>) -> Search {
        Search { config: config,
                 current: index,
                 query: query,
                 selection: selection,
                 result: result }
    }

    fn new_for_index(self, index: uint) -> Search {
        let new_selection = Search::select(&self.result, index);

        Search::new(self.config, self.query, self.result, index, new_selection)
    }

    fn filter(query: &str, choices: &Vec<String>) -> Vec<String> {
        let mut filtered = choices.iter().filter_map( |choice| {
            let quality = Score::score(choice.as_slice(), query);
            if quality > 0.0 {
                Some((quality, choice.to_string()))
            } else {
                None
            }
        }).collect::<Vec<(f32, String)>>();

        filtered.sort_by( |&(quality_a, _), &(quality_b, _)| {
            quality_a.partial_cmp(&quality_b).unwrap_or(Ordering::Equal).reverse()
        });

        filtered.iter().map( |&(_, ref choice)| choice.to_string() ).collect::<Vec<String>>()
    }

    fn select(result: &Vec<String>, index: uint) -> Option<String> {
        if result.len() > 0 {
            Some(result[index].to_string())
        } else {
            None
        }
    }

    fn down(self) -> Search {
        let next_index = self.next_index();
        self.new_for_index(next_index)
    }

    fn up(self) -> Search {
        let next_index = self.prev_index();
        self.new_for_index(next_index)
    }

    fn append_to_search(self, input: &str) -> Search {
        let mut new_query = self.query;
        new_query.push_str(input.as_slice());

        let new_result    = Search::filter(new_query.as_slice(), &self.result);
        let new_selection = Search::select(&new_result, self.current);

        Search::new(self.config, new_query, new_result, self.current, new_selection)
    }

    fn next_index(&self) -> uint {
        let next_index = self.current + 1;

        if next_index >= self.config.visible_limit { 0 } else { next_index }
    }

    fn prev_index(&self) -> uint {
        if self.current == 0 { self.config.visible_limit - 1 } else  { self.current - 1 }
    }
}

#[cfg(test)]

fn one_two_three() -> Vec<String> {
    vec!["one".to_string(),
         "two".to_string(),
         "three".to_string()]
}

#[test]
fn it_selects_the_first_choice_by_default() {
    let input =  one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);
    assert_eq!(search.selection, Some("one".to_string()));
}

#[test]
fn it_selets_the_second_when_down_is_called() {
    let input =  one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);
    assert_eq!(search.down().selection, Some("two".to_string()));
}

#[test]
fn it_loop_around_when_reaching_end_of_list() {
    let input =  one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);
    assert_eq!(search.down().down().down().down().selection, Some("two".to_string()));
}

#[test]
fn it_loop_around_when_reaching_top_of_list() {
    let input =  one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);
    assert_eq!(search.up().up().selection, Some("two".to_string()));
}

#[test]
fn it_loop_around_when_reaching_visible_limit() {
    let input =  one_two_three();

    let config = Configuration::from_inputs(input, None, Some(2));
    let search = Search::blank(config);
    assert_eq!(search.down().down().down().selection, Some("two".to_string()));
}

#[test]
fn it_moves_down_the_filtered_search_results() {
    let input =  one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);
    assert_eq!(search.append_to_search("t").down().selection, Some("three".to_string()));
}

#[test]
fn it_moves_down_the_filtered_search_results_twice() {
    let input =  one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);
    assert_eq!(search.append_to_search("t").append_to_search("w").selection, Some("two".to_string()));
}

#[test]
fn it_handles_not_matching_anything() {
    let input = one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);
    assert_eq!(search.append_to_search("x").selection, None);
}
<|endoftext|>


<commit_before>extern crate dbus;
use self::dbus::{Connection, ConnectionItem, Message, NameFlag, ReleaseNameReply};
use self::dbus::obj::ObjectPath;

use super::error::DBusError;
use super::target::{DBusTarget, extract_target};

use std::collections::btree_map::{BTreeMap, Entry};
use std::error::Error;

pub struct DBusServer<'a> {
    conn: &'a Connection,
    name: String,

    objects: BTreeMap<String, ObjectPath<'a>>,
    signals: BTreeMap<DBusTarget, Vec<fn (&Connection, &DBusTarget) -> ()>>
}

impl<'a> DBusServer<'a> {
    pub fn new(conn: &'a Connection, name: &str) -> Result<DBusServer<'a>, dbus::Error> {
        try!(conn.register_name(name, NameFlag::DoNotQueue as u32));

        Ok(DBusServer {
            conn: conn,
            name: name.to_string(),

            objects: BTreeMap::new(),
            signals: BTreeMap::new(),
        })
    }

    pub fn add_object(&mut self, path: &str, add_interfaces: fn (&mut ObjectPath<'a>) -> ()) -> Result<(), Box<Error>> {
        match self.objects.entry(path.to_string()) {
            Entry::Vacant(v)    => {
                let mut obj = ObjectPath::new(self.conn, path, true);
                try!(obj.set_registered(true));

                // Add interfaces.
                add_interfaces(&mut obj);

                v.insert(obj);

                Ok(())
            },
            Entry::Occupied(_)  => Err(Box::new(DBusError::PathAlreadyRegistered(path.to_string()))),
        }
    }

    pub fn remove_object(&mut self, path: &str) -> Result<(), DBusError> {
        match self.objects.remove(path) {
            Some(_) => Ok(()),
            None    => Err(DBusError::NoSuchPath(path.to_string())),
        }
    }

    pub fn connect(&mut self, signal: DBusTarget, callback: fn (&Connection, &DBusTarget) -> ()) -> () {
        match self.signals.entry(signal) {
            Entry::Vacant(v)    => { v.insert(vec![callback]); },
            Entry::Occupied(o)  => o.into_mut().push(callback),
        };
    }

    pub fn run(&mut self) -> () {
        self.conn.iter(100).fold((), |_, item| {
            match item {
                ConnectionItem::MethodCall(m)   => self._call_method(m),
                ConnectionItem::Signal(s)       => self._match_signal(s),
                ConnectionItem::Nothing         => (),
            }
        });
    }

    fn _call_method(&mut self, m: Message) -> () {
        self.objects.iter_mut().fold(Some(m), |opt_m, (_, object)| {
            opt_m.and_then(|mut m| {
                match object.handle_message(&mut m) {
                    None          => Some(m),
                    Some(Ok(()))  => None,
                    Some(Err(())) => {
                        println!("failed to send a reply for {:?}", m);
                        None
                    },
                }
            })
        });
    }

    fn _match_signal(&self, m: Message) -> () {
        let ref signals = self.signals;
        let conn = self.conn;

        extract_target(&m).and_then(|signal| {
            signals.get(&signal).map(|fs| {
                fs.iter().fold((conn, signal), |(conn, signal), f| {
                    f(&conn, &signal);
                    (conn, signal)
                })
            })
        });
    }
}

impl<'a> Drop for DBusServer<'a> {
    fn drop(&mut self) {
        let res = self.conn.release_name(&self.name);
        match res {
            Ok(reply) =>
                match reply {
                    ReleaseNameReply::Released    => (),
                    ReleaseNameReply::NonExistent => panic!("internal error: non-existent name {}?!", self.name),
                    ReleaseNameReply::NotOwner    => panic!("internal error: not the owner of {}?!", self.name),
                },
            Err(err) => println!("failed to release {}: {:?}: {:?}", self.name, err.name(), err.message()),
        }
    }
}
<commit_msg>clippy: address str_to_string warnings<commit_after>extern crate dbus;
use self::dbus::{Connection, ConnectionItem, Message, NameFlag, ReleaseNameReply};
use self::dbus::obj::ObjectPath;

use super::error::DBusError;
use super::target::{DBusTarget, extract_target};

use std::collections::btree_map::{BTreeMap, Entry};
use std::error::Error;

pub struct DBusServer<'a> {
    conn: &'a Connection,
    name: String,

    objects: BTreeMap<String, ObjectPath<'a>>,
    signals: BTreeMap<DBusTarget, Vec<fn (&Connection, &DBusTarget) -> ()>>
}

impl<'a> DBusServer<'a> {
    pub fn new(conn: &'a Connection, name: &str) -> Result<DBusServer<'a>, dbus::Error> {
        try!(conn.register_name(name, NameFlag::DoNotQueue as u32));

        Ok(DBusServer {
            conn: conn,
            name: name.to_owned(),

            objects: BTreeMap::new(),
            signals: BTreeMap::new(),
        })
    }

    pub fn add_object(&mut self, path: &str, add_interfaces: fn (&mut ObjectPath<'a>) -> ()) -> Result<(), Box<Error>> {
        match self.objects.entry(path.to_owned()) {
            Entry::Vacant(v)    => {
                let mut obj = ObjectPath::new(self.conn, path, true);
                try!(obj.set_registered(true));

                // Add interfaces.
                add_interfaces(&mut obj);

                v.insert(obj);

                Ok(())
            },
            Entry::Occupied(_)  => Err(Box::new(DBusError::PathAlreadyRegistered(path.to_owned()))),
        }
    }

    pub fn remove_object(&mut self, path: &str) -> Result<(), DBusError> {
        match self.objects.remove(path) {
            Some(_) => Ok(()),
            None    => Err(DBusError::NoSuchPath(path.to_owned())),
        }
    }

    pub fn connect(&mut self, signal: DBusTarget, callback: fn (&Connection, &DBusTarget) -> ()) -> () {
        match self.signals.entry(signal) {
            Entry::Vacant(v)    => { v.insert(vec![callback]); },
            Entry::Occupied(o)  => o.into_mut().push(callback),
        };
    }

    pub fn run(&mut self) -> () {
        self.conn.iter(100).fold((), |_, item| {
            match item {
                ConnectionItem::MethodCall(m)   => self._call_method(m),
                ConnectionItem::Signal(s)       => self._match_signal(s),
                ConnectionItem::Nothing         => (),
            }
        });
    }

    fn _call_method(&mut self, m: Message) -> () {
        self.objects.iter_mut().fold(Some(m), |opt_m, (_, object)| {
            opt_m.and_then(|mut m| {
                match object.handle_message(&mut m) {
                    None          => Some(m),
                    Some(Ok(()))  => None,
                    Some(Err(())) => {
                        println!("failed to send a reply for {:?}", m);
                        None
                    },
                }
            })
        });
    }

    fn _match_signal(&self, m: Message) -> () {
        let ref signals = self.signals;
        let conn = self.conn;

        extract_target(&m).and_then(|signal| {
            signals.get(&signal).map(|fs| {
                fs.iter().fold((conn, signal), |(conn, signal), f| {
                    f(&conn, &signal);
                    (conn, signal)
                })
            })
        });
    }
}

impl<'a> Drop for DBusServer<'a> {
    fn drop(&mut self) {
        let res = self.conn.release_name(&self.name);
        match res {
            Ok(reply) =>
                match reply {
                    ReleaseNameReply::Released    => (),
                    ReleaseNameReply::NonExistent => panic!("internal error: non-existent name {}?!", self.name),
                    ReleaseNameReply::NotOwner    => panic!("internal error: not the owner of {}?!", self.name),
                },
            Err(err) => println!("failed to release {}: {:?}: {:?}", self.name, err.name(), err.message()),
        }
    }
}
<|endoftext|>


<commit_before>extern crate rand;

use self::rand::{thread_rng,Rng};

use std::collections::HashMap;
use std::sync::{Arc,RwLock,Mutex,mpsc};

use super::libcoinche::{bid,cards,pos,game};

pub enum ServerError {
    BadPlayerId,
    BadEventId,
    PlayInAuction,
    BidInGame,
    Bid(bid::BidError),
    Play(game::PlayError),
}

pub enum Action {
    Bid(bid::Contract),
    Coinche,
    Pass,
    Play(cards::Card),
}

#[derive(Clone)]
pub enum PlayerEvent {
    Bidded(bid::Contract),
    Coinched,
    Passed,
    CardPlayed(cards::Card),
}

// Player just joined a new party. He's given a player id, and his position.
pub struct NewPartyInfo {
    pub player_id: u32,
    pub player_pos: pos::PlayerPos,
}

// Represents an event that can happen during the game.
#[derive(Clone)]
pub enum EventType {
    // The party is cancelled. Contains an optional explanation.
    PartyCancelled(String),

    // A player did something!
    FromPlayer(pos::PlayerPos, PlayerEvent),

    // Bid over: contains the contract and the author
    BidOver(bid::Contract),
    // The bid was cancelled, probably because no one bidded anything.
    // A new game is probably on its way.
    BidCancelled,

    // Trick over: contains the winner
    TrickOver(pos::PlayerPos),

    // New game: contains the first player, and the player's hand
    NewGame(pos::PlayerPos, [cards::Hand;4]),

    // Game over: contains scores
    GameOver([i32;2], pos::Team, [i32;2]),
    GameCancelled,
}

#[derive(Clone)]
pub struct Event {
    pub event: EventType,
    pub id: usize,
}

pub struct Order {
    pub author: pos::PlayerPos,
    pub action: Action
}

pub struct Server {
    party_list: RwLock<PartyList>,

    waiting_list: Mutex<Vec<mpsc::Sender<NewPartyInfo>>>,
}

pub enum Game {
    Bidding(bid::Auction),
    Playing(game::GameState),
}

fn make_game(first: pos::PlayerPos) -> (bid::Auction, EventType) {
    let auction = bid::new_auction(first);
    let hands = auction.hands();

    let event = EventType::NewGame(first, hands);

    (auction,event)
}

pub struct Party {
    game: Game,
    first: pos::PlayerPos,

    scores: [i32; 2],

    events: Vec<EventType>,
    observers: Vec<mpsc::Sender<Event>>,
}

fn new_party(first: pos::PlayerPos) -> Party {
    let (auction,event) = make_game(first);
    Party {
        first: first,
        game: Game::Bidding(auction),
        scores: [0;2],
        events: vec![event],
        observers: Vec::new(),
    }
}

impl Party {
    fn add_event(&mut self, event: EventType) -> Event {
        let ev = Event{
            event: event.clone(),
            id: self.events.len(),
        };
        for sender in self.observers.iter() {
            // TODO: handle cancelled wait?
            sender.send(ev.clone()).unwrap();
        }
        self.observers.clear();
        self.events.push(event);

        ev
    }

    fn new_game(&mut self) {
        // TODO: Maybe keep the current game in the history?

        let (auction, event) = make_game(self.first);

        self.first = self.first.next();
        self.game = Game::Bidding(auction);
        self.add_event(event);
    }

    fn bid(&mut self, pos: pos::PlayerPos, contract: bid::Contract) -> Result<Event,ServerError> {
        let state = match &mut self.game {
            &mut Game::Bidding(ref mut auction) => {
                match auction.bid(contract.clone()) {
                    Ok(state) => state,
                    Err(err) => return Err(ServerError::Bid(err)),
                }

            },
            &mut Game::Playing(_) => return Err(ServerError::BidInGame),
        };

        let main_event = self.add_event(EventType::FromPlayer(pos, PlayerEvent::Bidded(contract.clone())));
        match state {
            bid::AuctionState::Over => self.complete_auction(),
            _ => (),
        }

        Ok(main_event)
    }

    fn pass(&mut self, pos: pos::PlayerPos) -> Result<Event,ServerError> {
        let state = match &mut self.game {
            &mut Game::Bidding(ref mut auction) => auction.pass(),
            &mut Game::Playing(_) => return Err(ServerError::BidInGame),
        };

        let main_event = self.add_event(EventType::FromPlayer(pos, PlayerEvent::Passed));
        match state {
            bid::AuctionState::Over => self.complete_auction(),
            bid::AuctionState::Cancelled => {
                self.add_event(EventType::GameCancelled);
                self.new_game();
            },
            _ => (),
        }

        Ok(main_event)
    }

    fn complete_auction(&mut self) {
        let game = match &mut self.game {
            &mut Game::Playing(_) => unreachable!(),
            &mut Game::Bidding(ref mut auction) => {
                match auction.complete() {
                    Ok(game) => game,
                    Err(err) => panic!(err),
                }
            }
        };

        self.add_event(EventType::BidOver(game.contract().clone()));

        self.game = Game::Playing(game);
    }

    fn play_card(&mut self, pos: pos::PlayerPos, card: cards::Card) -> Result<Event,ServerError> {
        let result = match &mut self.game {
            &mut Game::Playing(ref mut game) => {
                match game.play_card(pos, card) {
                    Ok(result) => result,
                    Err(err) => return Err(ServerError::Play(err)),
                }
            },
            &mut Game::Bidding(_) => {
                // Bad time for a card play!
                return Err(ServerError::PlayInAuction);
            },
        };
        // This is the main event we want to send.
        // TODO: Batch event dispatch, and send all those together.
        let main_event = self.add_event(EventType::FromPlayer(pos, PlayerEvent::CardPlayed(card)));
        match result {
            game::TrickResult::Nothing => (),
            game::TrickResult::TrickOver(winner, game_result) => {
                self.add_event(EventType::TrickOver(winner));
                match game_result {
                    game::GameResult::Nothing => (),
                    game::GameResult::GameOver(points, winners, scores) => {
                        for i in 0..2 { self.scores[i] += scores[i]; }
                        let total_scores = self.scores;
                        self.add_event(EventType::GameOver(points, winners, total_scores));
                        self.new_game();
                    }
                }
            },
        }

        Ok(main_event)
    }
}

pub struct PlayerInfo {
    pub party: Arc<RwLock<Party>>,
    pub pos: pos::PlayerPos,
}

pub struct PartyList {
    pub player_map: HashMap<u32,PlayerInfo>,
}

impl PartyList {
    fn make_ids(&self) -> [u32; 4] {
        // Expect self.player_map to be locked
        let mut result = [0;4];

        for i in 0..4 {
            loop {
                let id = thread_rng().next_u32();
                if self.player_map.contains_key(&id) {
                    continue;
                }
                let mut ok = true;
                for j in 0..i {
                    if result[j] == id {
                        ok = false;
                        break;
                    }
                }
                if !ok {
                    continue;
                }

                result[i] = id;
            }
        }

        result
    }
}

enum WaitResult {
    Ready(Event),
    Waiting(mpsc::Receiver<Event>),
}

enum JoinResult {
    Ready(NewPartyInfo),
    Waiting(mpsc::Receiver<NewPartyInfo>),
}

impl Server {
    pub fn join(&self) -> Option<NewPartyInfo> {
        match self.get_join_result() {
            // TODO: add a timeout (max: 20s)
            // TODO: handle cancelled join?
            JoinResult::Ready(info) => Some(info),
            JoinResult::Waiting(rx) => Some(rx.recv().unwrap()),
        }
    }

    fn get_join_result(&self) -> JoinResult {
        let mut waiters = self.waiting_list.lock().unwrap();
        if waiters.len() >= 3 {
            // It's a PARTEY!
            let info = self.make_party([
                                       waiters.pop().unwrap(),
                                       waiters.pop().unwrap(),
                                       waiters.pop().unwrap(),
            ]);
            return JoinResult::Ready(info);
        } else {
            let (tx,rx) = mpsc::channel();
            waiters.push(tx);
            return JoinResult::Waiting(rx);
        }
    }

    fn make_party(&self, others: [mpsc::Sender<NewPartyInfo>; 3]) -> NewPartyInfo {
        let mut list = self.party_list.write().unwrap();

        // Generate 4 new IDS
        let ids = list.make_ids();

        let party = Arc::new(RwLock::new(new_party(pos::P0)));
        // Kickstart it with a new game!

        // Prepare the players info
        for i in 0..4 {
            list.player_map.insert(ids[i], PlayerInfo {
                party: party.clone(),
                pos: pos::PlayerPos(i),
            });
        }

        // Tell everyone. They'll love it.
        // TODO: handle cancelled channels (?)
        for i in 0..3 {
            others[i].send(NewPartyInfo{
                player_id: ids[i],
                player_pos: pos::PlayerPos(i),
            }).unwrap();
        }

        // Even you, weird 4th dude.
        NewPartyInfo{
            player_id: ids[3],
            player_pos: pos::P3,
        }
    }

    // Play a card in the current game
    pub fn play_card(&self, player_id: u32, card: cards::Card) -> Result<Event,ServerError> {
        let list = self.party_list.read().unwrap();

        let info = match list.player_map.get(&player_id) {
            Some(info) => info,
            None => return Err(ServerError::BadPlayerId),
        };

        let mut party = info.party.write().unwrap();
        party.play_card(info.pos, card)

    }

    // TODO: add bidding and stuff?
    pub fn bid(&self, player_id: u32, contract: bid::Contract) -> Result<Event,ServerError> {
        let list = self.party_list.read().unwrap();

        let info = match list.player_map.get(&player_id) {
            Some(info) => info,
            None => return Err(ServerError::BadPlayerId),
        };

        let mut party = info.party.write().unwrap();
        party.bid(info.pos, contract)
    }

    pub fn pass(&self, player_id: u32) -> Result<Event, ServerError> {
        let list = self.party_list.read().unwrap();

        let info = match list.player_map.get(&player_id) {
            Some(info) => info,
            None => return Err(ServerError::BadPlayerId),
        };

        let mut party = info.party.write().unwrap();
        party.pass(info.pos)
    }

    // Waits until the given event_id happens
    pub fn wait(&self, player_id: u32, event_id: usize) -> Result<Event,ServerError> {
        let res = self.get_wait_result(player_id, event_id);

        // TODO: add a timeout (~15s?)

        match res {
            Err(err) => Err(err),
            Ok(WaitResult::Ready(event)) => Ok(event),
            // TODO: handle case where the wait is cancelled
            // (don't unwrap, return an error instead?)
            Ok(WaitResult::Waiting(rx)) => Ok(rx.recv().unwrap()),
        }
    }

    // Check if the event ID is already available. If not, returns a channel that will produce it one
    // day, so that we don't keep the locks while waiting.
    fn get_wait_result(&self, player_id: u32, event_id: usize) -> Result<WaitResult,ServerError> {
        let list = self.party_list.read().unwrap();

        let info = match list.player_map.get(&player_id) {
            Some(info) => info,
            None => return Err(ServerError::BadPlayerId),
        };

        let mut party = info.party.write().unwrap();

        if party.events.len() > event_id {
            return Ok(WaitResult::Ready(Event {
                event: party.events[event_id].clone(),
                id: event_id,
            }));
        } else if event_id > party.events.len() {
            // We are too ambitious! One event at a time!
            return Err(ServerError::BadEventId);
        }

        let (tx, rx) = mpsc::channel();
        party.observers.push(tx);

        Ok(WaitResult::Waiting(rx))
    }
}

<commit_msg>Updated TODOs<commit_after>extern crate rand;

use self::rand::{thread_rng,Rng};

use std::collections::HashMap;
use std::sync::{Arc,RwLock,Mutex,mpsc};

use super::libcoinche::{bid,cards,pos,game};

pub enum ServerError {
    BadPlayerId,
    BadEventId,
    PlayInAuction,
    BidInGame,
    Bid(bid::BidError),
    Play(game::PlayError),
}

pub enum Action {
    Bid(bid::Contract),
    Coinche,
    Pass,
    Play(cards::Card),
}

#[derive(Clone)]
pub enum PlayerEvent {
    Bidded(bid::Contract),
    Coinched,
    Passed,
    CardPlayed(cards::Card),
}

// Player just joined a new party. He's given a player id, and his position.
pub struct NewPartyInfo {
    pub player_id: u32,
    pub player_pos: pos::PlayerPos,
}

// Represents an event that can happen during the game.
#[derive(Clone)]
pub enum EventType {
    // The party is cancelled. Contains an optional explanation.
    PartyCancelled(String),

    // A player did something!
    FromPlayer(pos::PlayerPos, PlayerEvent),

    // Bid over: contains the contract and the author
    BidOver(bid::Contract),
    // The bid was cancelled, probably because no one bidded anything.
    // A new game is probably on its way.
    BidCancelled,

    // Trick over: contains the winner
    TrickOver(pos::PlayerPos),

    // New game: contains the first player, and the player's hand
    NewGame(pos::PlayerPos, [cards::Hand;4]),

    // Game over: contains scores
    GameOver([i32;2], pos::Team, [i32;2]),
    GameCancelled,
}

#[derive(Clone)]
pub struct Event {
    pub event: EventType,
    pub id: usize,
}

pub struct Order {
    pub author: pos::PlayerPos,
    pub action: Action
}

pub struct Server {
    party_list: RwLock<PartyList>,

    waiting_list: Mutex<Vec<mpsc::Sender<NewPartyInfo>>>,
}

pub enum Game {
    Bidding(bid::Auction),
    Playing(game::GameState),
}

fn make_game(first: pos::PlayerPos) -> (bid::Auction, EventType) {
    let auction = bid::new_auction(first);
    let hands = auction.hands();

    let event = EventType::NewGame(first, hands);

    (auction,event)
}

pub struct Party {
    game: Game,
    first: pos::PlayerPos,

    scores: [i32; 2],

    events: Vec<EventType>,
    observers: Vec<mpsc::Sender<Event>>,
}

fn new_party(first: pos::PlayerPos) -> Party {
    let (auction,event) = make_game(first);
    Party {
        first: first,
        game: Game::Bidding(auction),
        scores: [0;2],
        events: vec![event],
        observers: Vec::new(),
    }
}

impl Party {
    fn add_event(&mut self, event: EventType) -> Event {
        let ev = Event{
            event: event.clone(),
            id: self.events.len(),
        };
        for sender in self.observers.iter() {
            // TODO: handle cancelled wait?
            sender.send(ev.clone()).unwrap();
        }
        self.observers.clear();
        self.events.push(event);

        ev
    }

    fn new_game(&mut self) {
        // TODO: Maybe keep the current game in the history?

        let (auction, event) = make_game(self.first);

        self.first = self.first.next();
        self.game = Game::Bidding(auction);
        self.add_event(event);
    }

    fn bid(&mut self, pos: pos::PlayerPos, contract: bid::Contract) -> Result<Event,ServerError> {
        let state = match &mut self.game {
            &mut Game::Bidding(ref mut auction) => {
                match auction.bid(contract.clone()) {
                    Ok(state) => state,
                    Err(err) => return Err(ServerError::Bid(err)),
                }

            },
            &mut Game::Playing(_) => return Err(ServerError::BidInGame),
        };

        let main_event = self.add_event(EventType::FromPlayer(pos, PlayerEvent::Bidded(contract.clone())));
        match state {
            bid::AuctionState::Over => self.complete_auction(),
            _ => (),
        }

        Ok(main_event)
    }

    fn pass(&mut self, pos: pos::PlayerPos) -> Result<Event,ServerError> {
        let state = match &mut self.game {
            &mut Game::Bidding(ref mut auction) => auction.pass(),
            &mut Game::Playing(_) => return Err(ServerError::BidInGame),
        };

        let main_event = self.add_event(EventType::FromPlayer(pos, PlayerEvent::Passed));
        match state {
            bid::AuctionState::Over => self.complete_auction(),
            bid::AuctionState::Cancelled => {
                self.add_event(EventType::GameCancelled);
                self.new_game();
            },
            _ => (),
        }

        Ok(main_event)
    }

    fn complete_auction(&mut self) {
        let game = match &mut self.game {
            &mut Game::Playing(_) => unreachable!(),
            &mut Game::Bidding(ref mut auction) => {
                match auction.complete() {
                    Ok(game) => game,
                    Err(err) => panic!(err),
                }
            }
        };

        self.add_event(EventType::BidOver(game.contract().clone()));

        self.game = Game::Playing(game);
    }

    fn play_card(&mut self, pos: pos::PlayerPos, card: cards::Card) -> Result<Event,ServerError> {
        let result = match &mut self.game {
            &mut Game::Playing(ref mut game) => {
                match game.play_card(pos, card) {
                    Ok(result) => result,
                    Err(err) => return Err(ServerError::Play(err)),
                }
            },
            &mut Game::Bidding(_) => {
                // Bad time for a card play!
                return Err(ServerError::PlayInAuction);
            },
        };
        // This is the main event we want to send.
        // TODO: Batch event dispatch, and send all those together.
        let main_event = self.add_event(EventType::FromPlayer(pos, PlayerEvent::CardPlayed(card)));
        match result {
            game::TrickResult::Nothing => (),
            game::TrickResult::TrickOver(winner, game_result) => {
                self.add_event(EventType::TrickOver(winner));
                match game_result {
                    game::GameResult::Nothing => (),
                    game::GameResult::GameOver(points, winners, scores) => {
                        for i in 0..2 { self.scores[i] += scores[i]; }
                        let total_scores = self.scores;
                        self.add_event(EventType::GameOver(points, winners, total_scores));
                        self.new_game();
                    }
                }
            },
        }

        Ok(main_event)
    }
}

pub struct PlayerInfo {
    pub party: Arc<RwLock<Party>>,
    pub pos: pos::PlayerPos,
}

pub struct PartyList {
    pub player_map: HashMap<u32,PlayerInfo>,
}

impl PartyList {
    fn make_ids(&self) -> [u32; 4] {
        // Expect self.player_map to be locked
        let mut result = [0;4];

        for i in 0..4 {
            loop {
                let id = thread_rng().next_u32();
                if self.player_map.contains_key(&id) {
                    continue;
                }
                let mut ok = true;
                for j in 0..i {
                    if result[j] == id {
                        ok = false;
                        break;
                    }
                }
                if !ok {
                    continue;
                }

                result[i] = id;
            }
        }

        result
    }
}

enum WaitResult {
    Ready(Event),
    Waiting(mpsc::Receiver<Event>),
}

enum JoinResult {
    Ready(NewPartyInfo),
    Waiting(mpsc::Receiver<NewPartyInfo>),
}

impl Server {
    pub fn join(&self) -> Option<NewPartyInfo> {
        match self.get_join_result() {
            // TODO: add a timeout (max: 20s)
            // TODO: handle cancelled join?
            JoinResult::Ready(info) => Some(info),
            JoinResult::Waiting(rx) => Some(rx.recv().unwrap()),
        }
    }

    fn get_join_result(&self) -> JoinResult {
        let mut waiters = self.waiting_list.lock().unwrap();
        if waiters.len() >= 3 {
            // It's a PARTEY!
            let info = self.make_party([
                                       waiters.pop().unwrap(),
                                       waiters.pop().unwrap(),
                                       waiters.pop().unwrap(),
            ]);
            return JoinResult::Ready(info);
        } else {
            let (tx,rx) = mpsc::channel();
            waiters.push(tx);
            return JoinResult::Waiting(rx);
        }
    }

    fn make_party(&self, others: [mpsc::Sender<NewPartyInfo>; 3]) -> NewPartyInfo {
        let mut list = self.party_list.write().unwrap();

        // Generate 4 new IDS
        let ids = list.make_ids();

        let party = Arc::new(RwLock::new(new_party(pos::P0)));
        // Kickstart it with a new game!

        // Prepare the players info
        for i in 0..4 {
            list.player_map.insert(ids[i], PlayerInfo {
                party: party.clone(),
                pos: pos::PlayerPos(i),
            });
        }

        // Tell everyone. They'll love it.
        // TODO: handle cancelled channels (?)
        for i in 0..3 {
            others[i].send(NewPartyInfo{
                player_id: ids[i],
                player_pos: pos::PlayerPos(i),
            }).unwrap();
        }

        // Even you, weird 4th dude.
        NewPartyInfo{
            player_id: ids[3],
            player_pos: pos::P3,
        }
    }

    // Play a card in the current game
    pub fn play_card(&self, player_id: u32, card: cards::Card) -> Result<Event,ServerError> {
        let list = self.party_list.read().unwrap();

        let info = match list.player_map.get(&player_id) {
            Some(info) => info,
            None => return Err(ServerError::BadPlayerId),
        };

        let mut party = info.party.write().unwrap();
        party.play_card(info.pos, card)

    }

    pub fn bid(&self, player_id: u32, contract: bid::Contract) -> Result<Event,ServerError> {
        let list = self.party_list.read().unwrap();

        let info = match list.player_map.get(&player_id) {
            Some(info) => info,
            None => return Err(ServerError::BadPlayerId),
        };

        let mut party = info.party.write().unwrap();
        party.bid(info.pos, contract)
    }

    pub fn pass(&self, player_id: u32) -> Result<Event, ServerError> {
        let list = self.party_list.read().unwrap();

        let info = match list.player_map.get(&player_id) {
            Some(info) => info,
            None => return Err(ServerError::BadPlayerId),
        };

        let mut party = info.party.write().unwrap();
        party.pass(info.pos)
    }

    // TODO: add coinche()

    // TODO: add getter methods: get_hand, ...

    // Waits until the given event_id happens
    pub fn wait(&self, player_id: u32, event_id: usize) -> Result<Event,ServerError> {
        let res = self.get_wait_result(player_id, event_id);

        // TODO: add a timeout (~15s?)

        match res {
            Err(err) => Err(err),
            Ok(WaitResult::Ready(event)) => Ok(event),
            // TODO: handle case where the wait is cancelled
            // (don't unwrap, return an error instead?)
            Ok(WaitResult::Waiting(rx)) => Ok(rx.recv().unwrap()),
        }
    }

    // Check if the event ID is already available. If not, returns a channel that will produce it one
    // day, so that we don't keep the locks while waiting.
    fn get_wait_result(&self, player_id: u32, event_id: usize) -> Result<WaitResult,ServerError> {
        let list = self.party_list.read().unwrap();

        let info = match list.player_map.get(&player_id) {
            Some(info) => info,
            None => return Err(ServerError::BadPlayerId),
        };

        let mut party = info.party.write().unwrap();

        if party.events.len() > event_id {
            return Ok(WaitResult::Ready(Event {
                event: party.events[event_id].clone(),
                id: event_id,
            }));
        } else if event_id > party.events.len() {
            // We are too ambitious! One event at a time!
            return Err(ServerError::BadEventId);
        }

        let (tx, rx) = mpsc::channel();
        party.observers.push(tx);

        Ok(WaitResult::Waiting(rx))
    }
}

<|endoftext|>


<commit_before>use gl;
use gl::types::*;
use gl_context::GLContext;
use std::collections::HashMap;
use std::ptr;
use std::str;

pub struct ProgramHandle<'a> {
  pub gl_id: GLuint,
}

impl<'a> ProgramHandle<'a> {
  pub fn new<'b: 'a>(_gl: &'b GLContext) -> ProgramHandle<'a> {
    let gl_id = unsafe {
      gl::CreateProgram()
    };

    ProgramHandle {
      gl_id: gl_id,
    }
  }
}

#[unsafe_destructor]
impl<'a> Drop for ProgramHandle<'a> {
  fn drop(&mut self) {
    unsafe {
      gl::DeleteProgram(self.gl_id);
    }
  }
}

pub struct ShaderHandle<'a> {
  pub gl_id: GLuint,
}

impl<'a> ShaderHandle<'a> {
  pub fn compile_from<'b: 'a>(
    _gl: &'b GLContext,
    shader_source: String,
    typ: GLenum
  ) -> ShaderHandle<'a> {
    let gl_id = unsafe {
      gl::CreateShader(typ)
    };
    // Attempt to compile the shader
    shader_source.with_c_str(|ptr| unsafe { gl::ShaderSource(gl_id, 1, &ptr, ptr::null()) });
    unsafe {
      gl::CompileShader(gl_id);
    }

    // Get the compile status
    let mut status = gl::FALSE as GLint;
    unsafe {
      gl::GetShaderiv(gl_id, gl::COMPILE_STATUS, &mut status);
    }

    // Fail on error
    if status != (gl::TRUE as GLint) {
      let mut len = 0;
      unsafe {
        gl::GetShaderiv(gl_id, gl::INFO_LOG_LENGTH, &mut len);
      }
      let mut buf = Vec::from_elem(len as uint - 1, 0u8); // subtract 1 to skip the trailing null character
      unsafe {
        gl::GetShaderInfoLog(gl_id, len, ptr::null_mut(), buf.as_mut_ptr() as *mut GLchar);
      }
      panic!("error compiling 0x{:x} shader: {}", typ, str::from_utf8(buf.as_slice()).expect("ShaderInfoLog not valid utf8"));
    }

    ShaderHandle {
      gl_id: gl_id,
    }
  }
}

#[unsafe_destructor]
impl<'a> Drop for ShaderHandle<'a> {
  fn drop(&mut self) {
    unsafe {
      gl::DeleteShader(self.gl_id);
    }
  }
}

pub struct Shader<'a> {
  pub handle: ProgramHandle<'a>,
  pub components: Vec<ShaderHandle<'a>>,
  pub uniforms: HashMap<String, GLint>,
}

impl<'a> Shader<'a> {
  pub fn new<'b: 'a, T: Iterator<(String, GLenum)>>(
    gl: &'b GLContext,
    shader_components: T,
  ) -> Shader<'a> {
    let mut shader_components = shader_components;
    let handle = ProgramHandle::new(gl);

    let mut components = Vec::new();
    for (content, component) in shader_components {
      let s = ShaderHandle::compile_from(gl, content, component);
      unsafe { gl::AttachShader(handle.gl_id, s.gl_id) };
      components.push(s);
    }

    unsafe {
      gl::LinkProgram(handle.gl_id);
    }

    // Get the link status
    let mut status = gl::FALSE as GLint;
    unsafe {
      gl::GetProgramiv(handle.gl_id, gl::LINK_STATUS, &mut status);
    }

    // Fail on error
    if status != (gl::TRUE as GLint) {
      let mut len: GLint = 0;
      unsafe {
        gl::GetProgramiv(handle.gl_id, gl::INFO_LOG_LENGTH, &mut len);
      }
      let mut buf = Vec::from_elem(len as uint - 1, 0u8); // subtract 1 to skip the trailing null character
      unsafe {
        gl::GetProgramInfoLog(handle.gl_id, len, ptr::null_mut(), buf.as_mut_ptr() as *mut GLchar);
      }
      panic!("{}", str::from_utf8(buf.as_slice()).expect("ProgramInfoLog not valid utf8"));
    }

    Shader {
      handle: handle,
      components: components,
      uniforms: HashMap::new(),
    }
  }

  pub fn use_shader(&self, _gl: &mut GLContext) {
    unsafe {
      gl::UseProgram(self.handle.gl_id)
    }
  }

  pub fn get_uniform_location(
    &mut self,
    gl: &mut GLContext,
    name: &'static str,
  ) -> GLint {
    let s_name = String::from_str(name);
    let name = name.to_c_str().as_ptr();
    match self.uniforms.get(&s_name) {
      None => {
        self.use_shader(gl);
        let loc = unsafe { gl::GetUniformLocation(self.handle.gl_id, name) };
        assert!(loc != -1, "couldn't find shader uniform: {}", s_name);

        self.uniforms.insert(s_name, loc);
        loc
      },
      Some(&loc) => loc,
    }
  }
}
<commit_msg>remove unnecessary parameter from Shader::get_uniform_location<commit_after>use gl;
use gl::types::*;
use gl_context::GLContext;
use std::collections::HashMap;
use std::ptr;
use std::str;

pub struct ProgramHandle<'a> {
  pub gl_id: GLuint,
}

impl<'a> ProgramHandle<'a> {
  pub fn new<'b: 'a>(_gl: &'b GLContext) -> ProgramHandle<'a> {
    let gl_id = unsafe {
      gl::CreateProgram()
    };

    ProgramHandle {
      gl_id: gl_id,
    }
  }
}

#[unsafe_destructor]
impl<'a> Drop for ProgramHandle<'a> {
  fn drop(&mut self) {
    unsafe {
      gl::DeleteProgram(self.gl_id);
    }
  }
}

pub struct ShaderHandle<'a> {
  pub gl_id: GLuint,
}

impl<'a> ShaderHandle<'a> {
  pub fn compile_from<'b: 'a>(
    _gl: &'b GLContext,
    shader_source: String,
    typ: GLenum
  ) -> ShaderHandle<'a> {
    let gl_id = unsafe {
      gl::CreateShader(typ)
    };
    // Attempt to compile the shader
    shader_source.with_c_str(|ptr| unsafe { gl::ShaderSource(gl_id, 1, &ptr, ptr::null()) });
    unsafe {
      gl::CompileShader(gl_id);
    }

    // Get the compile status
    let mut status = gl::FALSE as GLint;
    unsafe {
      gl::GetShaderiv(gl_id, gl::COMPILE_STATUS, &mut status);
    }

    // Fail on error
    if status != (gl::TRUE as GLint) {
      let mut len = 0;
      unsafe {
        gl::GetShaderiv(gl_id, gl::INFO_LOG_LENGTH, &mut len);
      }
      let mut buf = Vec::from_elem(len as uint - 1, 0u8); // subtract 1 to skip the trailing null character
      unsafe {
        gl::GetShaderInfoLog(gl_id, len, ptr::null_mut(), buf.as_mut_ptr() as *mut GLchar);
      }
      panic!("error compiling 0x{:x} shader: {}", typ, str::from_utf8(buf.as_slice()).expect("ShaderInfoLog not valid utf8"));
    }

    ShaderHandle {
      gl_id: gl_id,
    }
  }
}

#[unsafe_destructor]
impl<'a> Drop for ShaderHandle<'a> {
  fn drop(&mut self) {
    unsafe {
      gl::DeleteShader(self.gl_id);
    }
  }
}

pub struct Shader<'a> {
  pub handle: ProgramHandle<'a>,
  pub components: Vec<ShaderHandle<'a>>,
  pub uniforms: HashMap<String, GLint>,
}

impl<'a> Shader<'a> {
  pub fn new<'b: 'a, T: Iterator<(String, GLenum)>>(
    gl: &'b GLContext,
    shader_components: T,
  ) -> Shader<'a> {
    let mut shader_components = shader_components;
    let handle = ProgramHandle::new(gl);

    let mut components = Vec::new();
    for (content, component) in shader_components {
      let s = ShaderHandle::compile_from(gl, content, component);
      unsafe { gl::AttachShader(handle.gl_id, s.gl_id) };
      components.push(s);
    }

    unsafe {
      gl::LinkProgram(handle.gl_id);
    }

    // Get the link status
    let mut status = gl::FALSE as GLint;
    unsafe {
      gl::GetProgramiv(handle.gl_id, gl::LINK_STATUS, &mut status);
    }

    // Fail on error
    if status != (gl::TRUE as GLint) {
      let mut len: GLint = 0;
      unsafe {
        gl::GetProgramiv(handle.gl_id, gl::INFO_LOG_LENGTH, &mut len);
      }
      let mut buf = Vec::from_elem(len as uint - 1, 0u8); // subtract 1 to skip the trailing null character
      unsafe {
        gl::GetProgramInfoLog(handle.gl_id, len, ptr::null_mut(), buf.as_mut_ptr() as *mut GLchar);
      }
      panic!("{}", str::from_utf8(buf.as_slice()).expect("ProgramInfoLog not valid utf8"));
    }

    Shader {
      handle: handle,
      components: components,
      uniforms: HashMap::new(),
    }
  }

  pub fn use_shader(&self, _gl: &mut GLContext) {
    unsafe {
      gl::UseProgram(self.handle.gl_id)
    }
  }

  pub fn get_uniform_location(
    &mut self,
    name: &'static str,
  ) -> GLint {
    let s_name = String::from_str(name);
    let name = name.to_c_str().as_ptr();
    match self.uniforms.get(&s_name) {
      None => {
        let loc = unsafe { gl::GetUniformLocation(self.handle.gl_id, name) };
        assert!(loc != -1, "couldn't find shader uniform: {}", s_name);

        self.uniforms.insert(s_name, loc);
        loc
      },
      Some(&loc) => loc,
    }
  }
}
<|endoftext|>