<commit_before>use ffi::h5::{hsize_t, hbool_t};
use ffi::h5i::{hid_t, H5I_INVALID_HID};
use ffi::h5p::{H5Pcreate, H5Pset_userblock, H5Pget_userblock};
use ffi::h5f::{H5F_ACC_RDONLY, H5F_ACC_RDWR, H5F_ACC_EXCL, H5F_ACC_TRUNC,
               H5Fopen, H5Fcreate, H5Fget_filesize, H5Fget_intent,
               H5Fget_access_plist, H5Fget_create_plist, H5Fget_freespace};
use ffi::h5fd::{H5Pset_fapl_sec2, H5Pset_fapl_stdio, H5Pset_fapl_core};

use globals::{H5P_FILE_CREATE, H5P_FILE_ACCESS};

use container::Container;
use error::Result;
use location::Location;
use object::{Handle, Object};
use plist::PropertyList;
use util::to_cstring;

use std::path::Path;
use std::process::Command;

use libc::{size_t, c_uint};

#[derive(Clone)]
pub struct File {
    handle: Handle,
}

impl Object for File {
    fn id(&self) -> hid_t {
        self.handle.id()
    }

    fn from_id(id: hid_t) -> File {
        File { handle: Handle::new(id) }
    }
}

impl Location for File {}

impl Container for File {}

impl File {
    /// Create a new file object.
    ///
    /// | `mode`    | File access mode
    /// |-----------|-----------------
    /// | `r`       | Read-only, file must exist
    /// | `r+`      | Read/write, file must exist
    /// | `w`       | Create file, truncate if exists
    /// | `w-`, `x` | Create file, fail if exists
    /// | `a`       | Read/write if exists, create otherwise
    pub fn open<P: AsRef<Path>, S: Into<String>>(filename: P, mode: S) -> Result<File> {
        FileBuilder::new().mode(mode).open(filename)
    }

    /// Returns the file size in bytes (or 0 if the file handle is invalid).
    pub fn size(&self) -> u64 {
        unsafe {
            let size: *mut hsize_t = &mut 0;
            h5lock_s!(H5Fget_filesize(self.id(), size));
            if *size > 0 { *size as u64 } else { 0 }
        }
    }

    /// Returns the free space in the file in bytes (or 0 if the file handle is invalid).
    pub fn free_space(&self) -> u64 {
        match h5call!(H5Fget_freespace(self.id())) {
            Ok(size) => size as u64,
            _        => 0,
        }
    }

    /// Returns true if the file was opened in a read-only mode.
    pub fn is_read_only(&self) -> bool {
        unsafe {
            let mode: *mut c_uint = &mut 0;
            h5lock_s!(H5Fget_intent(self.id(), mode));
            *mode != H5F_ACC_RDWR
        }
    }

    #[allow(dead_code)]
    fn fapl(&self) -> PropertyList {
        PropertyList::from_id(h5call!(H5Fget_access_plist(self.id())).unwrap_or(H5I_INVALID_HID))
    }

    fn fcpl(&self) -> PropertyList {
        PropertyList::from_id(h5call!(H5Fget_create_plist(self.id())).unwrap_or(H5I_INVALID_HID))
    }

    /// Returns the output of the `h5dump` tool. Note that this wouldn't work with core driver.
    pub fn dump(&self) -> Option<String> {
        self.flush(true).ok().and(Command::new("h5dump").arg(self.filename()).output().ok()
                                  .map(|out| String::from_utf8_lossy(&out.stdout).to_string()))
    }

    /// Returns the userblock size in bytes (or 0 if the file handle is invalid).
    pub fn userblock(&self) -> size_t {
        unsafe {
            let userblock: *mut hsize_t = &mut 0;
            h5lock_s!(H5Pget_userblock(self.fcpl().id(), userblock));
            *userblock as size_t
        }
    }
}

pub struct FileBuilder {
    driver: String,
    mode: String,
    userblock: size_t,
    filebacked: bool,
    increment: size_t,
}

impl FileBuilder {
    pub fn new() -> FileBuilder {
        FileBuilder {
            driver: "sec2".to_string(),
            mode: "r".to_string(),
            userblock: 0,
            filebacked: false,
            increment: 64 * 1024 * 1024,
        }
    }

    pub fn driver<S: Into<String>>(&mut self, driver: S) -> &mut FileBuilder {
        self.driver = driver.into(); self
    }

    pub fn mode<S: Into<String>>(&mut self, mode: S) -> &mut FileBuilder {
        self.mode = mode.into(); self
    }

    pub fn userblock(&mut self, userblock: size_t) -> &mut FileBuilder {
        self.userblock = userblock; self
    }

    pub fn filebacked(&mut self, filebacked: bool) -> &mut FileBuilder {
        self.filebacked = filebacked; self
    }

    pub fn increment(&mut self, increment: size_t) -> &mut FileBuilder {
        self.increment = increment; self
    }

    fn make_fapl(&self) -> Result<PropertyList> {
        h5lock_s!({
            let fapl = PropertyList::from_id(h5try!(H5Pcreate(*H5P_FILE_ACCESS)));
            match self.driver.as_ref() {
                "sec2"  => h5try!(H5Pset_fapl_sec2(fapl.id())),
                "stdio" => h5try!(H5Pset_fapl_stdio(fapl.id())),
                "core"  => h5try!(H5Pset_fapl_core(fapl.id(), self.increment,
                                                   self.filebacked as hbool_t)),
                _       => fail!(format!("Invalid file driver: {}", self.driver)),
            };
            Ok(fapl)
        })
    }

    fn open_file<P: AsRef<Path>>(&self, filename: P, write: bool) -> Result<File> {
        ensure!(self.userblock == 0, "Cannot specify userblock when opening a file");
        h5lock_s!({
            let fapl = try!(self.make_fapl());
            let flags = if write { H5F_ACC_RDWR } else { H5F_ACC_RDONLY };
            let filename = filename.as_ref();
            match filename.to_str() {
                Some(filename) => {
                    let c_filename = to_cstring(filename).as_ptr();
                    let file = File::from_id(h5try!(H5Fopen(c_filename, flags, fapl.id())));
                    ensure!(file.is_valid(), "Invalid id for opened file");
                    Ok(file)
                },
                None          => fail!("Invalid UTF-8 in file name: {:?}", filename)
            }
        })
    }

    fn create_file<P: AsRef<Path>>(&self, filename: P, exclusive: bool) -> Result<File> {
        h5lock_s!({
            let fcpl = PropertyList::from_id(h5try!(H5Pcreate(*H5P_FILE_CREATE)));
            h5try!(H5Pset_userblock(fcpl.id(), self.userblock));
            let fapl = try!(self.make_fapl());
            let flags = if exclusive { H5F_ACC_EXCL } else { H5F_ACC_TRUNC };
            let filename = filename.as_ref();
            match filename.to_str() {
                Some(filename) => {
                    let c_filename = to_cstring(filename).as_ptr();
                    let file = File::from_id(h5try!(H5Fcreate(c_filename, flags,
                                                              fcpl.id(), fapl.id())));
                    ensure!(file.is_valid(), "Invalid id for created file");
                    Ok(file)
                },
                None          => fail!("Invalid UTF-8 in file name: {:?}", filename)
            }
        })
    }

    pub fn open<P: AsRef<Path>>(&self, filename: P) -> Result<File> {
        match self.mode.as_ref() {
            "r"        => self.open_file(&filename, false),
            "r+"       => self.open_file(&filename, true),
            "w"        => self.create_file(&filename, false),
            "w-" | "x" => self.create_file(&filename, true),
            "a"        => match self.open_file(&filename, true) {
                            Ok(file) => Ok(file),
                            _        => self.create_file(&filename, true),
                          },
            _          => fail!("Invalid file access mode, expected r|r+|w|w-|x|a"),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::File;
    use std::fs;
    use std::io::Write;
    use test::{with_tmp_dir, with_tmp_path};
    use error::silence_errors;

    #[test]
    pub fn test_invalid_mode() {
        silence_errors();
        with_tmp_dir(|dir| {
            assert_err!(File::open(&dir, "foo"), "Invalid file access mode");
        })
    }

    #[test]
    pub fn test_non_hdf5_file() {
        silence_errors();
        with_tmp_path("foo.h5", |path| {
            fs::File::create(&path).unwrap().write_all(b"foo").unwrap();
            assert!(fs::metadata(&path).is_ok());
            assert_err!(File::open(&path, "r"), "unable to open file");
        })
    }

    #[test]
    pub fn test_is_read_only() {
        silence_errors();
        with_tmp_path("foo.h5", |path| {
            assert!(!File::open(&path, "w").unwrap().is_read_only());
            assert!(File::open(&path, "r").unwrap().is_read_only());
            assert!(!File::open(&path, "r+").unwrap().is_read_only());
            assert!(!File::open(&path, "a").unwrap().is_read_only());
        });
        with_tmp_path("foo.h5", |path| {
            assert!(!File::open(&path, "a").unwrap().is_read_only());
        });
        with_tmp_path("foo.h5", |path| {
            assert!(!File::open(&path, "x").unwrap().is_read_only());
        });
    }

    #[test]
    pub fn test_unable_to_open() {
        silence_errors();
        with_tmp_dir(|dir| {
            assert_err!(File::open(&dir, "r"), "unable to open file");
            assert_err!(File::open(&dir, "r+"), "unable to open file");
            assert_err!(File::open(&dir, "x"), "unable to create file");
            assert_err!(File::open(&dir, "w-"), "unable to create file");
            assert_err!(File::open(&dir, "w"), "unable to create file");
            assert_err!(File::open(&dir, "a"), "unable to create file");
        });
    }
}
<commit_msg>Add file access mode tests<commit_after>use ffi::h5::{hsize_t, hbool_t};
use ffi::h5i::{hid_t, H5I_INVALID_HID};
use ffi::h5p::{H5Pcreate, H5Pset_userblock, H5Pget_userblock};
use ffi::h5f::{H5F_ACC_RDONLY, H5F_ACC_RDWR, H5F_ACC_EXCL, H5F_ACC_TRUNC,
               H5Fopen, H5Fcreate, H5Fget_filesize, H5Fget_intent,
               H5Fget_access_plist, H5Fget_create_plist, H5Fget_freespace};
use ffi::h5fd::{H5Pset_fapl_sec2, H5Pset_fapl_stdio, H5Pset_fapl_core};

use globals::{H5P_FILE_CREATE, H5P_FILE_ACCESS};

use container::Container;
use error::Result;
use location::Location;
use object::{Handle, Object};
use plist::PropertyList;
use util::to_cstring;

use std::path::Path;
use std::process::Command;

use libc::{size_t, c_uint};

#[derive(Clone)]
pub struct File {
    handle: Handle,
}

impl Object for File {
    fn id(&self) -> hid_t {
        self.handle.id()
    }

    fn from_id(id: hid_t) -> File {
        File { handle: Handle::new(id) }
    }
}

impl Location for File {}

impl Container for File {}

impl File {
    /// Create a new file object.
    ///
    /// | `mode`    | File access mode
    /// |-----------|-----------------
    /// | `r`       | Read-only, file must exist
    /// | `r+`      | Read/write, file must exist
    /// | `w`       | Create file, truncate if exists
    /// | `w-`, `x` | Create file, fail if exists
    /// | `a`       | Read/write if exists, create otherwise
    pub fn open<P: AsRef<Path>, S: Into<String>>(filename: P, mode: S) -> Result<File> {
        FileBuilder::new().mode(mode).open(filename)
    }

    /// Returns the file size in bytes (or 0 if the file handle is invalid).
    pub fn size(&self) -> u64 {
        unsafe {
            let size: *mut hsize_t = &mut 0;
            h5lock_s!(H5Fget_filesize(self.id(), size));
            if *size > 0 { *size as u64 } else { 0 }
        }
    }

    /// Returns the free space in the file in bytes (or 0 if the file handle is invalid).
    pub fn free_space(&self) -> u64 {
        match h5call!(H5Fget_freespace(self.id())) {
            Ok(size) => size as u64,
            _        => 0,
        }
    }

    /// Returns true if the file was opened in a read-only mode.
    pub fn is_read_only(&self) -> bool {
        unsafe {
            let mode: *mut c_uint = &mut 0;
            h5lock_s!(H5Fget_intent(self.id(), mode));
            *mode != H5F_ACC_RDWR
        }
    }

    #[allow(dead_code)]
    fn fapl(&self) -> PropertyList {
        PropertyList::from_id(h5call!(H5Fget_access_plist(self.id())).unwrap_or(H5I_INVALID_HID))
    }

    fn fcpl(&self) -> PropertyList {
        PropertyList::from_id(h5call!(H5Fget_create_plist(self.id())).unwrap_or(H5I_INVALID_HID))
    }

    /// Returns the output of the `h5dump` tool. Note that this wouldn't work with core driver.
    pub fn dump(&self) -> Option<String> {
        self.flush(true).ok().and(Command::new("h5dump").arg(self.filename()).output().ok()
                                  .map(|out| String::from_utf8_lossy(&out.stdout).to_string()))
    }

    /// Returns the userblock size in bytes (or 0 if the file handle is invalid).
    pub fn userblock(&self) -> size_t {
        unsafe {
            let userblock: *mut hsize_t = &mut 0;
            h5lock_s!(H5Pget_userblock(self.fcpl().id(), userblock));
            *userblock as size_t
        }
    }
}

pub struct FileBuilder {
    driver: String,
    mode: String,
    userblock: size_t,
    filebacked: bool,
    increment: size_t,
}

impl FileBuilder {
    pub fn new() -> FileBuilder {
        FileBuilder {
            driver: "sec2".to_string(),
            mode: "r".to_string(),
            userblock: 0,
            filebacked: false,
            increment: 64 * 1024 * 1024,
        }
    }

    pub fn driver<S: Into<String>>(&mut self, driver: S) -> &mut FileBuilder {
        self.driver = driver.into(); self
    }

    pub fn mode<S: Into<String>>(&mut self, mode: S) -> &mut FileBuilder {
        self.mode = mode.into(); self
    }

    pub fn userblock(&mut self, userblock: size_t) -> &mut FileBuilder {
        self.userblock = userblock; self
    }

    pub fn filebacked(&mut self, filebacked: bool) -> &mut FileBuilder {
        self.filebacked = filebacked; self
    }

    pub fn increment(&mut self, increment: size_t) -> &mut FileBuilder {
        self.increment = increment; self
    }

    fn make_fapl(&self) -> Result<PropertyList> {
        h5lock_s!({
            let fapl = PropertyList::from_id(h5try!(H5Pcreate(*H5P_FILE_ACCESS)));
            match self.driver.as_ref() {
                "sec2"  => h5try!(H5Pset_fapl_sec2(fapl.id())),
                "stdio" => h5try!(H5Pset_fapl_stdio(fapl.id())),
                "core"  => h5try!(H5Pset_fapl_core(fapl.id(), self.increment,
                                                   self.filebacked as hbool_t)),
                _       => fail!(format!("Invalid file driver: {}", self.driver)),
            };
            Ok(fapl)
        })
    }

    fn open_file<P: AsRef<Path>>(&self, filename: P, write: bool) -> Result<File> {
        ensure!(self.userblock == 0, "Cannot specify userblock when opening a file");
        h5lock_s!({
            let fapl = try!(self.make_fapl());
            let flags = if write { H5F_ACC_RDWR } else { H5F_ACC_RDONLY };
            let filename = filename.as_ref();
            match filename.to_str() {
                Some(filename) => {
                    let c_filename = to_cstring(filename).as_ptr();
                    let file = File::from_id(h5try!(H5Fopen(c_filename, flags, fapl.id())));
                    ensure!(file.is_valid(), "Invalid id for opened file");
                    Ok(file)
                },
                None          => fail!("Invalid UTF-8 in file name: {:?}", filename)
            }
        })
    }

    fn create_file<P: AsRef<Path>>(&self, filename: P, exclusive: bool) -> Result<File> {
        h5lock_s!({
            let fcpl = PropertyList::from_id(h5try!(H5Pcreate(*H5P_FILE_CREATE)));
            h5try!(H5Pset_userblock(fcpl.id(), self.userblock));
            let fapl = try!(self.make_fapl());
            let flags = if exclusive { H5F_ACC_EXCL } else { H5F_ACC_TRUNC };
            let filename = filename.as_ref();
            match filename.to_str() {
                Some(filename) => {
                    let c_filename = to_cstring(filename).as_ptr();
                    let file = File::from_id(h5try!(H5Fcreate(c_filename, flags,
                                                              fcpl.id(), fapl.id())));
                    ensure!(file.is_valid(), "Invalid id for created file");
                    Ok(file)
                },
                None          => fail!("Invalid UTF-8 in file name: {:?}", filename)
            }
        })
    }

    pub fn open<P: AsRef<Path>>(&self, filename: P) -> Result<File> {
        match self.mode.as_ref() {
            "r"        => self.open_file(&filename, false),
            "r+"       => self.open_file(&filename, true),
            "w"        => self.create_file(&filename, false),
            "w-" | "x" => self.create_file(&filename, true),
            "a"        => match self.open_file(&filename, true) {
                            Ok(file) => Ok(file),
                            _        => self.create_file(&filename, true),
                          },
            _          => fail!("Invalid file access mode, expected r|r+|w|w-|x|a"),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::File;
    use std::fs;
    use std::io::Write;
    use test::{with_tmp_dir, with_tmp_path};
    use container::Container;
    use error::silence_errors;

    #[test]
    pub fn test_invalid_mode() {
        silence_errors();
        with_tmp_dir(|dir| {
            assert_err!(File::open(&dir, "foo"), "Invalid file access mode");
        })
    }

    #[test]
    pub fn test_is_read_only() {
        silence_errors();
        with_tmp_path("foo.h5", |path| {
            assert!(!File::open(&path, "w").unwrap().is_read_only());
            assert!(File::open(&path, "r").unwrap().is_read_only());
            assert!(!File::open(&path, "r+").unwrap().is_read_only());
            assert!(!File::open(&path, "a").unwrap().is_read_only());
        });
        with_tmp_path("foo.h5", |path| {
            assert!(!File::open(&path, "a").unwrap().is_read_only());
        });
        with_tmp_path("foo.h5", |path| {
            assert!(!File::open(&path, "x").unwrap().is_read_only());
        });
    }

    #[test]
    pub fn test_unable_to_open() {
        silence_errors();
        with_tmp_dir(|dir| {
            assert_err!(File::open(&dir, "r"), "unable to open file");
            assert_err!(File::open(&dir, "r+"), "unable to open file");
            assert_err!(File::open(&dir, "x"), "unable to create file");
            assert_err!(File::open(&dir, "w-"), "unable to create file");
            assert_err!(File::open(&dir, "w"), "unable to create file");
            assert_err!(File::open(&dir, "a"), "unable to create file");
        });

        with_tmp_path("foo.h5", |path| {
            fs::File::create(&path).unwrap().write_all(b"foo").unwrap();
            assert!(fs::metadata(&path).is_ok());
            assert_err!(File::open(&path, "r"), "unable to open file");
        })
    }

    #[test]
    pub fn test_access_modes() {
        silence_errors();

        // "w" means overwrite
        with_tmp_path("foo.h5", |path| {
            File::open(&path, "w").unwrap().create_group("foo").unwrap();
            assert_err!(File::open(&path, "w").unwrap().group("foo"), "unable to open group");
        });

        // "w-"/"x-" means exclusive write
        with_tmp_path("foo.h5", |path| {
            File::open(&path, "w-").unwrap();
            assert_err!(File::open(&path, "w-"), "unable to create file");
        });
        with_tmp_path("foo.h5", |path| {
            File::open(&path, "x").unwrap();
            assert_err!(File::open(&path, "x"), "unable to create file");
        });

        // "a" means append
        with_tmp_path("foo.h5", |path| {
            File::open(&path, "a").unwrap().create_group("foo").unwrap();
            File::open(&path, "a").unwrap().group("foo").unwrap();
        });

        // "r" means read-only
        with_tmp_path("foo.h5", |path| {
            File::open(&path, "w").unwrap().create_group("foo").unwrap();
            let file = File::open(&path, "r").unwrap();
            file.group("foo").unwrap();
            assert_err!(file.create_group("bar"),
                        "unable to create group: no write intent on file");
        });

        // "r+" means read-write
        with_tmp_path("foo.h5", |path| {
            File::open(&path, "w").unwrap().create_group("foo").unwrap();
            let file = File::open(&path, "r+").unwrap();
            file.group("foo").unwrap();
            file.create_group("bar").unwrap();
        });
    }
}
<|endoftext|>


<commit_before>use ffi::types::{hid_t, hsize_t, hbool_t};
use ffi::h5i::H5I_INVALID_HID;
use ffi::h5p::{H5P_FILE_CREATE, H5P_FILE_ACCESS, H5Pcreate, H5Pset_userblock};
use ffi::h5f::{H5F_ACC_RDONLY, H5F_ACC_RDWR, H5F_ACC_EXCL, H5F_ACC_TRUNC,
               H5Fopen, H5Fcreate, H5Fclose, H5Fget_filesize, H5Fget_intent,
               H5Fget_access_plist, H5Fget_create_plist};
use ffi::drivers::{H5Pset_fapl_sec2, H5Pset_fapl_stdio, H5Pset_fapl_core};

use object::{Handle, Object};
use error::Result;
use plist::PropertyList;
use ffi::util::string_to_cstr;
use std::path::{Path, PathBuf};

use libc::{size_t, c_uint};

#[derive(Clone)]
pub struct File {
    handle: Handle,
}

impl Object for File {
    fn id(&self) -> hid_t {
        self.handle.id()
    }

    fn from_id(id: hid_t) -> File {
        File { handle: Handle::new(id) }
    }
}

impl File {
    pub fn size(&self) -> u64 {
        unsafe {
            let size: *mut hsize_t = &mut 0;
            h5lock_s!(H5Fget_filesize(self.id(), size));
            if *size > 0 { *size as u64 } else { 0 }
        }
    }

    pub fn is_read_only(&self) -> bool {
        unsafe {
            let mode: *mut c_uint = &mut 0;
            h5lock_s!(H5Fget_intent(self.id(), mode));
            *mode == H5F_ACC_RDONLY
        }
    }

    pub fn open<P: AsRef<Path>, S: Into<String>>(filename: P, mode: S) -> Result<File> {
        FileBuilder::new(filename).mode(mode).open()
    }

    fn fapl(&self) -> PropertyList {
        PropertyList::from_id(h5call!(H5Fget_access_plist(self.id())).unwrap_or(H5I_INVALID_HID))
    }

    fn fcpl(&self) -> PropertyList {
        PropertyList::from_id(h5call!(H5Fget_create_plist(self.id())).unwrap_or(H5I_INVALID_HID))
    }
}

impl Drop for File {
    fn drop(&mut self) {
        if self.refcount() == 1 {
            h5lock!(H5Fclose(self.id()));
        }
    }
}

pub struct FileBuilder {
    filename: PathBuf,
    driver: String,
    mode: String,
    userblock: size_t,
    filebacked: bool,
    increment: size_t,
}

impl FileBuilder {
    pub fn new<P: AsRef<Path>>(filename: P) -> FileBuilder {
        FileBuilder {
            filename: filename.as_ref().to_path_buf(),
            driver: "sec2".to_string(),
            mode: "r".to_string(),
            userblock: 0,
            filebacked: false,
            increment: 64 * 1024 * 1024,
        }
    }

    pub fn driver<S: Into<String>>(&mut self, driver: S) -> &mut FileBuilder {
        self.driver = driver.into(); self
    }

    pub fn mode<S: Into<String>>(&mut self, mode: S) -> &mut FileBuilder {
        self.mode = mode.into(); self
    }

    pub fn userblock(&mut self, userblock: size_t) -> &mut FileBuilder {
        self.userblock = userblock; self
    }

    pub fn filebacked(&mut self, filebacked: bool) -> &mut FileBuilder {
        self.filebacked = filebacked; self
    }

    pub fn increment(&mut self, increment: size_t) -> &mut FileBuilder {
        self.increment = increment; self
    }

    fn make_fapl(&self) -> Result<PropertyList> {
        h5lock_s!({
            let fapl = PropertyList::from_id(h5try!(H5Pcreate(*H5P_FILE_ACCESS)));
            match self.driver.as_ref() {
                "sec2"  => h5try!(H5Pset_fapl_sec2(fapl.id())),
                "stdio" => h5try!(H5Pset_fapl_stdio(fapl.id())),
                "core"  => h5try!(H5Pset_fapl_core(fapl.id(), self.increment,
                                                   self.filebacked as hbool_t)),
                _       => fail!(format!("Invalid file driver: {}", self.driver)),
            };
            Ok(fapl)
        })
    }

    fn open_file(&self, write: bool) -> Result<File> {
        ensure!(self.userblock == 0, "Cannot specify userblock when opening a file");
        h5lock_s!({
            let fapl = try!(self.make_fapl());
            let flags = if write { H5F_ACC_RDWR } else { H5F_ACC_RDONLY };
            match self.filename.to_str() {
                Some(filename) => {
                    let c_filename = string_to_cstr(filename);
                    let file = File::from_id(h5try!(H5Fopen(c_filename, flags, fapl.id())));
                    ensure!(file.is_valid(), "Invalid id for opened file");
                    Ok(file)
                },
                None          => fail!("Invalid UTF-8 in file name: {:?}", self.filename)
            }
        })
    }

    fn create_file(&self, exclusive: bool) -> Result<File> {
        h5lock_s!({
            let fcpl = PropertyList::from_id(h5try!(H5Pcreate(*H5P_FILE_CREATE)));
            h5try!(H5Pset_userblock(fcpl.id(), self.userblock));
            let fapl = try!(self.make_fapl());
            let flags = if exclusive { H5F_ACC_EXCL } else { H5F_ACC_TRUNC };
            match self.filename.to_str() {
                Some(filename) => {
                    let c_filename = string_to_cstr(filename);
                    let file = File::from_id(h5try!(H5Fcreate(c_filename, flags,
                                                              fcpl.id(), fapl.id())));
                    ensure!(file.is_valid(), "Invalid id for created file");
                    Ok(file)
                },
                None          => fail!("Invalid UTF-8 in file name: {:?}", self.filename)
            }
        })
    }

    pub fn open(&self) -> Result<File> {
        match self.mode.as_ref() {
            "r"        => self.open_file(false),
            "r+"       => self.open_file(true),
            "w"        => self.create_file(false),
            "w-" | "x" => self.create_file(true),
            "a"        => match self.open_file(true) {
                            Ok(file) => Ok(file),
                            _        => self.create_file(true),
                          },
            _          => fail!("Invalid file access mode, expected r|r+|w|w-|x|a"),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::File;
    use std::path::PathBuf;
    use tempdir::TempDir;

    fn with_tmpdir<F: Fn(PathBuf)>(f: F) {
        let dir = TempDir::new_in(".", "tmp").unwrap();
        let path = dir.path().to_path_buf();
        f(path);
    }

    #[test]
    pub fn test_invalid_mode() {
        with_tmpdir(|dir| {
            assert_err!(File::open(&dir, "foo"), "Invalid file access mode");
        })
    }

    #[test]
    pub fn test_is_read_only() {
        with_tmpdir(|dir| {
            let path = dir.join("foo.h5");
            assert!(!File::open(&path, "w").unwrap().is_read_only());
            assert!(File::open(&path, "r").unwrap().is_read_only());
            assert!(!File::open(&path, "r+").unwrap().is_read_only());
            assert!(!File::open(&path, "a").unwrap().is_read_only());
        });
        with_tmpdir(|dir| {
            assert!(!File::open(dir.join("foo.h"), "a").unwrap().is_read_only());
        });
        with_tmpdir(|dir| {
            assert!(!File::open(dir.join("foo.h"), "x").unwrap().is_read_only());
        });
    }

    #[test]
    pub fn test_invalid_filename() {
        with_tmpdir(|dir| {
            assert_err!(File::open(&dir, "r"), "unable to open file");
            assert_err!(File::open(&dir, "r+"), "unable to open file");
            assert_err!(File::open(&dir, "x"), "unable to create file");
            assert_err!(File::open(&dir, "w-"), "unable to create file");
            assert_err!(File::open(&dir, "w"), "unable to create file");
            assert_err!(File::open(&dir, "a"), "unable to create file");
        });
    }
}
<commit_msg>Small fix in File::is_read_only()<commit_after>use ffi::types::{hid_t, hsize_t, hbool_t};
use ffi::h5i::H5I_INVALID_HID;
use ffi::h5p::{H5P_FILE_CREATE, H5P_FILE_ACCESS, H5Pcreate, H5Pset_userblock};
use ffi::h5f::{H5F_ACC_RDONLY, H5F_ACC_RDWR, H5F_ACC_EXCL, H5F_ACC_TRUNC,
               H5Fopen, H5Fcreate, H5Fclose, H5Fget_filesize, H5Fget_intent,
               H5Fget_access_plist, H5Fget_create_plist};
use ffi::drivers::{H5Pset_fapl_sec2, H5Pset_fapl_stdio, H5Pset_fapl_core};

use object::{Handle, Object};
use error::Result;
use plist::PropertyList;
use ffi::util::string_to_cstr;
use std::path::{Path, PathBuf};

use libc::{size_t, c_uint};

#[derive(Clone)]
pub struct File {
    handle: Handle,
}

impl Object for File {
    fn id(&self) -> hid_t {
        self.handle.id()
    }

    fn from_id(id: hid_t) -> File {
        File { handle: Handle::new(id) }
    }
}

impl File {
    pub fn size(&self) -> u64 {
        unsafe {
            let size: *mut hsize_t = &mut 0;
            h5lock_s!(H5Fget_filesize(self.id(), size));
            if *size > 0 { *size as u64 } else { 0 }
        }
    }

    pub fn is_read_only(&self) -> bool {
        unsafe {
            let mode: *mut c_uint = &mut 0;
            h5lock_s!(H5Fget_intent(self.id(), mode));
            *mode != H5F_ACC_RDWR
        }
    }

    pub fn open<P: AsRef<Path>, S: Into<String>>(filename: P, mode: S) -> Result<File> {
        FileBuilder::new(filename).mode(mode).open()
    }

    fn fapl(&self) -> PropertyList {
        PropertyList::from_id(h5call!(H5Fget_access_plist(self.id())).unwrap_or(H5I_INVALID_HID))
    }

    fn fcpl(&self) -> PropertyList {
        PropertyList::from_id(h5call!(H5Fget_create_plist(self.id())).unwrap_or(H5I_INVALID_HID))
    }
}

impl Drop for File {
    fn drop(&mut self) {
        if self.refcount() == 1 {
            h5lock!(H5Fclose(self.id()));
        }
    }
}

pub struct FileBuilder {
    filename: PathBuf,
    driver: String,
    mode: String,
    userblock: size_t,
    filebacked: bool,
    increment: size_t,
}

impl FileBuilder {
    pub fn new<P: AsRef<Path>>(filename: P) -> FileBuilder {
        FileBuilder {
            filename: filename.as_ref().to_path_buf(),
            driver: "sec2".to_string(),
            mode: "r".to_string(),
            userblock: 0,
            filebacked: false,
            increment: 64 * 1024 * 1024,
        }
    }

    pub fn driver<S: Into<String>>(&mut self, driver: S) -> &mut FileBuilder {
        self.driver = driver.into(); self
    }

    pub fn mode<S: Into<String>>(&mut self, mode: S) -> &mut FileBuilder {
        self.mode = mode.into(); self
    }

    pub fn userblock(&mut self, userblock: size_t) -> &mut FileBuilder {
        self.userblock = userblock; self
    }

    pub fn filebacked(&mut self, filebacked: bool) -> &mut FileBuilder {
        self.filebacked = filebacked; self
    }

    pub fn increment(&mut self, increment: size_t) -> &mut FileBuilder {
        self.increment = increment; self
    }

    fn make_fapl(&self) -> Result<PropertyList> {
        h5lock_s!({
            let fapl = PropertyList::from_id(h5try!(H5Pcreate(*H5P_FILE_ACCESS)));
            match self.driver.as_ref() {
                "sec2"  => h5try!(H5Pset_fapl_sec2(fapl.id())),
                "stdio" => h5try!(H5Pset_fapl_stdio(fapl.id())),
                "core"  => h5try!(H5Pset_fapl_core(fapl.id(), self.increment,
                                                   self.filebacked as hbool_t)),
                _       => fail!(format!("Invalid file driver: {}", self.driver)),
            };
            Ok(fapl)
        })
    }

    fn open_file(&self, write: bool) -> Result<File> {
        ensure!(self.userblock == 0, "Cannot specify userblock when opening a file");
        h5lock_s!({
            let fapl = try!(self.make_fapl());
            let flags = if write { H5F_ACC_RDWR } else { H5F_ACC_RDONLY };
            match self.filename.to_str() {
                Some(filename) => {
                    let c_filename = string_to_cstr(filename);
                    let file = File::from_id(h5try!(H5Fopen(c_filename, flags, fapl.id())));
                    ensure!(file.is_valid(), "Invalid id for opened file");
                    Ok(file)
                },
                None          => fail!("Invalid UTF-8 in file name: {:?}", self.filename)
            }
        })
    }

    fn create_file(&self, exclusive: bool) -> Result<File> {
        h5lock_s!({
            let fcpl = PropertyList::from_id(h5try!(H5Pcreate(*H5P_FILE_CREATE)));
            h5try!(H5Pset_userblock(fcpl.id(), self.userblock));
            let fapl = try!(self.make_fapl());
            let flags = if exclusive { H5F_ACC_EXCL } else { H5F_ACC_TRUNC };
            match self.filename.to_str() {
                Some(filename) => {
                    let c_filename = string_to_cstr(filename);
                    let file = File::from_id(h5try!(H5Fcreate(c_filename, flags,
                                                              fcpl.id(), fapl.id())));
                    ensure!(file.is_valid(), "Invalid id for created file");
                    Ok(file)
                },
                None          => fail!("Invalid UTF-8 in file name: {:?}", self.filename)
            }
        })
    }

    pub fn open(&self) -> Result<File> {
        match self.mode.as_ref() {
            "r"        => self.open_file(false),
            "r+"       => self.open_file(true),
            "w"        => self.create_file(false),
            "w-" | "x" => self.create_file(true),
            "a"        => match self.open_file(true) {
                            Ok(file) => Ok(file),
                            _        => self.create_file(true),
                          },
            _          => fail!("Invalid file access mode, expected r|r+|w|w-|x|a"),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::File;
    use std::path::PathBuf;
    use tempdir::TempDir;

    fn with_tmpdir<F: Fn(PathBuf)>(f: F) {
        let dir = TempDir::new_in(".", "tmp").unwrap();
        let path = dir.path().to_path_buf();
        f(path);
    }

    #[test]
    pub fn test_invalid_mode() {
        with_tmpdir(|dir| {
            assert_err!(File::open(&dir, "foo"), "Invalid file access mode");
        })
    }

    #[test]
    pub fn test_is_read_only() {
        with_tmpdir(|dir| {
            let path = dir.join("foo.h5");
            assert!(!File::open(&path, "w").unwrap().is_read_only());
            assert!(File::open(&path, "r").unwrap().is_read_only());
            assert!(!File::open(&path, "r+").unwrap().is_read_only());
            assert!(!File::open(&path, "a").unwrap().is_read_only());
        });
        with_tmpdir(|dir| {
            assert!(!File::open(dir.join("foo.h"), "a").unwrap().is_read_only());
        });
        with_tmpdir(|dir| {
            assert!(!File::open(dir.join("foo.h"), "x").unwrap().is_read_only());
        });
    }

    #[test]
    pub fn test_invalid_filename() {
        with_tmpdir(|dir| {
            assert_err!(File::open(&dir, "r"), "unable to open file");
            assert_err!(File::open(&dir, "r+"), "unable to open file");
            assert_err!(File::open(&dir, "x"), "unable to create file");
            assert_err!(File::open(&dir, "w-"), "unable to create file");
            assert_err!(File::open(&dir, "w"), "unable to create file");
            assert_err!(File::open(&dir, "a"), "unable to create file");
        });
    }
}
<|endoftext|>


<commit_before>use std::io::fs::File;
use std::io::{Open,Write};

pub fn read(path: &Path) -> ~str {
    match File::open(path) {
        Err(_) => fail!("failed to open {}", path.display()),
        Ok(mut file) => match file.read_to_str() {
            Err(_) => fail!("failed to read {}", path.display()),
            Ok(contents) => contents,
        }
    }
}

pub fn write(path: &Path, string: &str) {
    match File::open_mode(path, Open, Write) {
        Err(_) => fail!("couldn't open {}", path.display()),
        Ok(mut file) => if file.write_str(string).is_err() {
            fail!("couldn't write to {}", path.display());
        },
    }
}
<commit_msg>eulermark: truncate files when writing<commit_after>use std::io::fs::File;
use std::io::{Truncate,Write};

pub fn read(path: &Path) -> ~str {
    match File::open(path) {
        Err(_) => fail!("failed to open {}", path.display()),
        Ok(mut file) => match file.read_to_str() {
            Err(_) => fail!("failed to read {}", path.display()),
            Ok(contents) => contents,
        }
    }
}

pub fn write(path: &Path, string: &str) {
    match File::open_mode(path, Truncate, Write) {
        Err(_) => fail!("couldn't open {}", path.display()),
        Ok(mut file) => if file.write_str(string).is_err() {
            fail!("couldn't write to {}", path.display());
        },
    }
}
<|endoftext|>


<commit_before>use futures::{Future, future};
use futures::sync::oneshot;
use std::collections::HashMap;
use std::sync::{Arc, Mutex, RwLock};
use uuid::Uuid;

#[derive(Debug, PartialEq)]
pub enum Error {
    Invalid,
    NotFound,
    Dropped,
    NotReady,
    Eof,
    Other,
}

pub const MAX_SIZE: usize = 65536;
pub const MAX_CAPACITY: u64 = 16777216;

#[derive(Debug)]
pub enum Chunk {
    Data(u64, Vec<u8>),
    Eof(u64),
}

type SharedChunk = Arc<RwLock<Chunk>>;

#[derive(Clone, Debug)]
enum State {
    Streaming,
    Stop,
}

pub struct Config {
    pub length: Option<u64>,
    pub capacity: u64,
}

pub struct Statistic {
    pub pushed: u64,
    pub dropped: u64,
}

struct Buffer {
    buffering: u64,
    bucket: HashMap<u64, SharedChunk>,
    waiting_pull: HashMap<u64, Vec<oneshot::Sender<SharedChunk>>>,
}

pub struct Flow {
    pub id: String,
    pub config: Config,
    pub statistic: Statistic,
    state: State,
    next_index: u64,
    pub tail_index: u64,
    buffer: Arc<RwLock<Buffer>>,
}

type FlowFuture<T> = future::BoxFuture<T, Error>;

impl Flow {
    pub fn new(length: Option<u64>) -> Self {
        Flow {
            id: Uuid::new_v4().simple().to_string(),
            config: Config {
                length,
                capacity: MAX_CAPACITY,
            },
            statistic: Statistic {
                pushed: 0,
                dropped: 0,
            },
            state: State::Streaming,
            next_index: 0,
            tail_index: 0,
            buffer: Arc::new(RwLock::new(Buffer {
                                             buffering: 0,
                                             bucket: HashMap::new(),
                                             waiting_pull: HashMap::new(),
                                         })),
        }
    }

    fn is_streaming(&self) -> bool {
        match self.state {
            State::Streaming => true,
            _ => false,
        }
    }

    fn acquire_chunk(&mut self, chunk: Chunk) -> FlowFuture<u64> {
        // Check if the flow is EOF.
        if !self.is_streaming() {
            return future::err(Error::Invalid).boxed();
        }
        if let Chunk::Eof(_) = chunk {
            self.state = State::Stop;
        }

        let shared_chunk = Arc::new(RwLock::new(chunk));
        let chunk_index = self.next_index;
        self.next_index += 1;

        {
            let mut buffer = self.buffer.write().unwrap();
            // Wake up the waiting pulls.
            if let Some(waits) = buffer.waiting_pull.remove(&chunk_index) {
                for wait in waits {
                    // Don't unwrap, since the receiver can early quit.
                    wait.send(shared_chunk.clone()).is_ok();
                }
            }
            // Insert the chunk.
            buffer.bucket.insert(chunk_index, shared_chunk);
            future::ok(chunk_index).boxed()
        }
    }

    pub fn close(&mut self) -> FlowFuture<()> {
        if self.is_streaming() {
            self.acquire_chunk(Chunk::Eof(1)).map(|_| ()).boxed()
        } else {
            future::err(Error::Invalid).boxed()
        }
    }

    pub fn push(&mut self, data: &[u8]) -> FlowFuture<u64> {
        if data.len() > MAX_SIZE {
            return future::err(Error::Invalid).boxed();
        }
        match self.statistic.pushed.checked_add(data.len() as u64) {
            Some(new_pushed) => {
                if let Some(length) = self.config.length {
                    // Check if the length is over.
                    if new_pushed > length {
                        return future::err(Error::Invalid).boxed();
                    }
                }
                self.statistic.pushed = new_pushed;
            }
            None => return future::err(Error::Invalid).boxed(),
        };
        self.acquire_chunk(Chunk::Data(1, data.to_vec()))
    }

    fn deref_chunk(shared_chunk: &SharedChunk) -> Result<Vec<u8>, Error> {
        let mut chunk = shared_chunk.write().unwrap();
        match *chunk {
            Chunk::Data(ref mut count, ref data) => {
                *count += 1;
                Ok(data.to_vec())
            }
            Chunk::Eof(ref mut count) => {
                *count += 1;
                Err(Error::Eof)
            }
        }
    }

    pub fn pull(&self, chunk_index: u64, timeout: Option<u64>) -> FlowFuture<Vec<u8>> {
        // Fast check
        if let Some(chunk) = self.buffer.read().unwrap().bucket.get(&chunk_index) {
            return future::result(Flow::deref_chunk(chunk)).boxed();
        } else if chunk_index < self.next_index {
            return future::err(Error::Dropped).boxed();
        } else if let Some(0) = timeout {
            return future::err(Error::NotReady).boxed();
        }
        // Slow check
        {
            let mut buffer = self.buffer.write().unwrap();
            if let Some(chunk) = buffer.bucket.get(&chunk_index) {
                return future::result(Flow::deref_chunk(chunk)).boxed();
            }
            let (tx, rx) = oneshot::channel();
            let waits = buffer.waiting_pull.entry(chunk_index).or_insert(Vec::new());
            waits.push(tx);
            rx.map_err(|_| Error::Other)
                .and_then(|chunk| future::result(Flow::deref_chunk(&chunk)))
                .boxed()
        }
    }
}

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

    #[test]
    fn basic_flow_operations() {
        let mut flow = Flow::new(None);
        assert_eq!(flow.push(&[1u8; 1234]).wait(), Ok(0));
        assert_eq!(flow.push(&[2u8; MAX_SIZE]).wait(), Ok(1));
        assert_eq!(flow.push(b"hello").wait(), Ok(2));
        assert_eq!(flow.push(&[2u8; MAX_SIZE + 1]).wait(), Err(Error::Invalid));
        assert_eq!(flow.pull(2, Some(0)).wait(), Ok(Vec::from(b"hello" as &[u8])));
        assert_eq!(flow.pull(100, Some(0)).wait(), Err(Error::NotReady));
    }

    #[test]
    fn fixed_size_flow() {
        let mut flow = Flow::new(Some(10));
        assert_eq!(flow.push(b"hello").wait(), Ok(0));
        assert_eq!(flow.push(b"world").wait(), Ok(1));
        assert_eq!(flow.push(b"!").wait(), Err(Error::Invalid));
    }

    #[test]
    fn pull_chunk() {
        let mut flow = Flow::new(None);
        assert_eq!(flow.pull(0, Some(0)).wait(), Err(Error::NotReady));
        assert_eq!(flow.pull(1, None).join3(flow.push(b"ello"), flow.push(b"hello")).wait(),
                   Ok((Vec::from(b"hello" as &[u8]), 0, 1)));
    }

    #[test]
    fn close_flow() {
        let mut flow = Flow::new(None);
        assert_eq!(flow.push(b"hello").wait(), Ok(0));
        assert_eq!(flow.pull(0, Some(0)).wait(), Ok(Vec::from(b"hello" as &[u8])));
        assert_eq!(flow.close().wait(), Ok(()));
        assert_eq!(flow.push(b"hello").wait(), Err(Error::Invalid));
        assert_eq!(flow.pull(1, Some(0)).wait(), Err(Error::Eof));
        assert_eq!(flow.close().wait(), Err(Error::Invalid));
    }
}
<commit_msg>Simplify the channel<commit_after>use futures::{Future, future};
use futures::sync::oneshot;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use uuid::Uuid;

#[derive(Debug, PartialEq)]
pub enum Error {
    Invalid,
    NotFound,
    Dropped,
    NotReady,
    Eof,
    Other,
}

pub const MAX_SIZE: usize = 65536;
pub const MAX_CAPACITY: u64 = 16777216;

#[derive(Debug)]
pub enum Chunk {
    Data(u64, Vec<u8>),
    Eof(u64),
}

#[derive(Clone, Debug)]
enum State {
    Streaming,
    Stop,
}

pub struct Config {
    pub length: Option<u64>,
    pub capacity: u64,
    pub lifecount: u64,
}

pub struct Statistic {
    pub pushed: u64,
    pub dropped: u64,
}

struct Buffer {
    buffered: u64,
    bucket: HashMap<u64, Chunk>,
    waiting_pull: HashMap<u64, Vec<oneshot::Sender<()>>>,
}

pub struct Flow {
    pub id: String,
    pub config: Config,
    pub statistic: Statistic,
    state: State,
    next_index: u64,
    head_index: u64,
    pub tail_index: u64,
    buffer: Arc<Mutex<Buffer>>,
}

type FlowFuture<T> = future::BoxFuture<T, Error>;

impl Flow {
    pub fn new(length: Option<u64>) -> Self {
        Flow {
            id: Uuid::new_v4().simple().to_string(),
            config: Config {
                length,
                capacity: MAX_CAPACITY,
                lifecount: 1,
            },
            statistic: Statistic {
                pushed: 0,
                dropped: 0,
            },
            state: State::Streaming,
            next_index: 0,
            head_index: 0,
            tail_index: 0,
            buffer: Arc::new(Mutex::new(Buffer {
                                            buffered: 0,
                                            bucket: HashMap::new(),
                                            waiting_pull: HashMap::new(),
                                        })),
        }
    }

    fn is_streaming(&self) -> bool {
        match self.state {
            State::Streaming => true,
            _ => false,
        }
    }

    fn sanitize_chunk(&mut self) {
        let mut buffer = self.buffer.lock().unwrap();
        let mut tail_index = self.tail_index;
        let head_index = self.head_index;
        while tail_index < head_index {
            // Get should always success.
            let count = match *buffer.bucket.get(&tail_index).unwrap() {
                Chunk::Data(count, _) => count,
                Chunk::Eof(count) => count,
            };
            if count < self.config.lifecount {
                break;
            }
            // Remove should always success.
            buffer.bucket.remove(&tail_index).unwrap();
            tail_index += 1;
        }
    }

    fn acquire_chunk(&mut self, chunk: Chunk) -> FlowFuture<u64> {
        // Check if the flow is EOF.
        if !self.is_streaming() {
            return future::err(Error::Invalid).boxed();
        }
        if let Chunk::Eof(_) = chunk {
            self.state = State::Stop;
        }

        let chunk_index = self.next_index;
        self.next_index += 1;

        {
            let mut buffer = self.buffer.lock().unwrap();
            // Insert the chunk.
            buffer.bucket.insert(chunk_index, chunk);
            // Wake up the waiting pulls.
            if let Some(waits) = buffer.waiting_pull.remove(&chunk_index) {
                for wait in waits {
                    // Don't unwrap, since the receiver can early quit.
                    wait.send(()).is_ok();
                }
            }
        }

        future::ok(chunk_index).boxed()
    }

    pub fn close(&mut self) -> FlowFuture<()> {
        if self.is_streaming() {
            self.acquire_chunk(Chunk::Eof(1)).map(|_| ()).boxed()
        } else {
            future::err(Error::Invalid).boxed()
        }
    }

    pub fn push(&mut self, data: &[u8]) -> FlowFuture<u64> {
        if data.len() > MAX_SIZE {
            return future::err(Error::Invalid).boxed();
        }
        match self.statistic.pushed.checked_add(data.len() as u64) {
            Some(new_pushed) => {
                if let Some(length) = self.config.length {
                    // Check if the length is over.
                    if new_pushed > length {
                        return future::err(Error::Invalid).boxed();
                    }
                }
                self.statistic.pushed = new_pushed;
            }
            None => return future::err(Error::Invalid).boxed(),
        };
        self.acquire_chunk(Chunk::Data(1, data.to_vec()))
    }

    fn deref_chunk(chunk: &mut Chunk) -> Result<Vec<u8>, Error> {
        match *chunk {
            Chunk::Data(ref mut count, ref data) => {
                *count += 1;
                Ok(data.to_vec())
            }
            Chunk::Eof(ref mut count) => {
                *count += 1;
                Err(Error::Eof)
            }
        }
    }

    pub fn pull(&self, chunk_index: u64, timeout: Option<u64>) -> FlowFuture<Vec<u8>> {
        // Fast check
        {
            let mut buffer = self.buffer.lock().unwrap();
            if let Some(chunk) = buffer.bucket.get_mut(&chunk_index) {
                return future::result(Flow::deref_chunk(chunk)).boxed();
            }
        }
        // Error check
        if chunk_index < self.next_index {
            return future::err(Error::Dropped).boxed();
        } else if let Some(0) = timeout {
            return future::err(Error::NotReady).boxed();
        }
        // Slow check
        let rx = {
            let mut buffer = self.buffer.lock().unwrap();
            if let Some(chunk) = buffer.bucket.get_mut(&chunk_index) {
                return future::result(Flow::deref_chunk(chunk)).boxed();
            }
            let (tx, rx) = oneshot::channel();
            let waits = buffer.waiting_pull.entry(chunk_index).or_insert(Vec::new());
            waits.push(tx);
            rx
        };
        {
            let buffer = self.buffer.clone();
            rx.map_err(|_| Error::Other)
                .and_then(move |_| {
                    let mut buffer = buffer.lock().unwrap();
                    let chunk = buffer.bucket.get_mut(&chunk_index);
                    if let Some(chunk) = chunk {
                        future::result(Flow::deref_chunk(chunk)).boxed()
                    } else {
                        future::err(Error::Dropped).boxed()
                    }
                })
                .boxed()
        }
    }
}

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

    #[test]
    fn basic_flow_operations() {
        let mut flow = Flow::new(None);
        assert_eq!(flow.push(&[1u8; 1234]).wait(), Ok(0));
        assert_eq!(flow.push(&[2u8; MAX_SIZE]).wait(), Ok(1));
        assert_eq!(flow.push(b"hello").wait(), Ok(2));
        assert_eq!(flow.push(&[2u8; MAX_SIZE + 1]).wait(), Err(Error::Invalid));
        assert_eq!(flow.pull(2, Some(0)).wait(), Ok(Vec::from(b"hello" as &[u8])));
        assert_eq!(flow.pull(100, Some(0)).wait(), Err(Error::NotReady));
    }

    #[test]
    fn fixed_size_flow() {
        let mut flow = Flow::new(Some(10));
        assert_eq!(flow.push(b"hello").wait(), Ok(0));
        assert_eq!(flow.push(b"world").wait(), Ok(1));
        assert_eq!(flow.push(b"!").wait(), Err(Error::Invalid));
    }

    #[test]
    fn pull_chunk() {
        let mut flow = Flow::new(None);
        assert_eq!(flow.pull(0, Some(0)).wait(), Err(Error::NotReady));
        assert_eq!(flow.pull(1, None).join3(flow.push(b"ello"), flow.push(b"hello")).wait(),
                   Ok((Vec::from(b"hello" as &[u8]), 0, 1)));
    }

    #[test]
    fn close_flow() {
        let mut flow = Flow::new(None);
        assert_eq!(flow.push(b"hello").wait(), Ok(0));
        assert_eq!(flow.pull(0, Some(0)).wait(), Ok(Vec::from(b"hello" as &[u8])));
        assert_eq!(flow.close().wait(), Ok(()));
        assert_eq!(flow.push(b"hello").wait(), Err(Error::Invalid));
        assert_eq!(flow.pull(1, Some(0)).wait(), Err(Error::Eof));
        assert_eq!(flow.close().wait(), Err(Error::Invalid));
    }
}
<|endoftext|>


<commit_before>use std::io::{Read, Seek};
use std::mem;

use Result;
use primitive::*;
use table::*;
use tape::{Tape, Value};
use truetype::compound::{FontHeader, CharMapping, OffsetTable, OffsetTableRecord};

/// A font.
#[derive(Default)]
pub struct Font {
    pub offset_table: OffsetTable,
    pub char_mapping: Option<CharMapping>,
    pub font_header: Option<FontHeader>,
    pub horizontal_header: Option<HorizontalHeader>,
    pub horizontal_metrics: Option<HorizontalMetrics>,
    pub maximum_profile: Option<MaximumProfile>,
    pub naming_table: Option<NamingTable>,
    pub postscript: Option<PostScript>,
    pub windows_metrics: Option<WindowsMetrics>,
}

macro_rules! tag(
    ($value:expr) => (unsafe {
        mem::transmute::<_, [u8; 4]>(u32::from_be(mem::transmute($value)))
    });
);

macro_rules! verify_and_jump(
    ($record:ident, $tape:ident, $table:expr, $process:expr) => ({
        if !try!($record.checksum($tape, $process)) {
            raise!(concat!("the ", $table, " is corrupted"));
        }
        try!($tape.jump($record.offset as u64));
    });
    ($record:ident, $tape:ident, $table:expr) => (
        verify_and_jump!($record, $tape, $table, |_, word| word);
    );
);

impl Font {
    #[inline]
    pub fn read<T: Read + Seek>(reader: &mut T) -> Result<Font> {
        Value::read(reader)
    }
}

impl Value for Font {
    fn read<T: Tape>(tape: &mut T) -> Result<Font> {
        macro_rules! sort(
            ($records:expr) => ({
                let mut records = $records.iter().collect::<Vec<_>>();
                records.sort_by(|one, two| {
                    priority(&tag!(one.tag)).cmp(&priority(&tag!(two.tag)))
                });
                records
            });
        );

        let mut font = Font { offset_table: try!(read_offset_table(tape)), .. Font::default() };

        for record in sort!(font.offset_table.records) {
            macro_rules! set(
                ($name:ident, $read:ident $($argument:tt)*) => (
                    font.$name = Some(try!($read(tape, record $($argument)*)))
                );
            );
            match &tag!(record.tag) {
                b"cmap" => set!(char_mapping, read_char_mapping),
                b"head" => set!(font_header, read_font_header),
                b"hhea" => set!(horizontal_header, read_horizontal_header),
                b"hmtx" => {
                    let header = match font.horizontal_header {
                        Some(ref table) => table,
                        _ => continue,
                    };
                    let profile = match font.maximum_profile {
                        Some(ref table) => table,
                        _ => continue,
                    };
                    set!(horizontal_metrics, read_horizontal_metrics, header, profile);
                },
                b"maxp" => set!(maximum_profile, read_maximum_profile),
                b"name" => set!(naming_table, read_naming_table),
                b"post" => set!(postscript, read_postscript),
                b"OS/2" => set!(windows_metrics, read_windows_metrics),
                _ => {},
            }
        }

        Ok(font)
    }
}

fn read_offset_table<T: Tape>(tape: &mut T) -> Result<OffsetTable> {
    use truetype::Value;

    match &tag!(try!(tape.peek::<Fixed>())) {
        b"OTTO" => OffsetTable::read(tape),
        _ => raise!("the format of a font is not supported"),
    }
}

fn read_char_mapping<T: Tape>(tape: &mut T, record: &OffsetTableRecord) -> Result<CharMapping> {
    use truetype::Value;

    verify_and_jump!(record, tape, "character-to-glyph mapping");
    match try!(tape.peek::<UShort>()) {
        0 => CharMapping::read(tape),
        _ => raise!("the format of the character-to-glyph mapping header is not supported"),
    }
}

fn read_font_header<T: Tape>(tape: &mut T, record: &OffsetTableRecord) -> Result<FontHeader> {
    use truetype::Value;

    verify_and_jump!(record, tape, "font header", |i, word| if i == 2 { 0 } else { word });
    let table = match try!(tape.peek::<Fixed>()) {
        Fixed(0x00010000) => try!(FontHeader::read(tape)),
        _ => raise!("the format of the font header is not supported"),
    };
    Ok(table)
}

fn read_horizontal_header<T: Tape>(tape: &mut T, record: &OffsetTableRecord)
                                   -> Result<HorizontalHeader> {

    verify_and_jump!(record, tape, "horizontal header");
    Ok(match try!(tape.peek::<Fixed>()) {
        Fixed(0x00010000) => try!(HorizontalHeader::read(tape)),
        _ => raise!("the format of the horizontal header is not supported"),
    })
}

fn read_horizontal_metrics<T: Tape>(tape: &mut T, record: &OffsetTableRecord,
                                    header: &HorizontalHeader, profile: &MaximumProfile)
                                    -> Result<HorizontalMetrics> {

    verify_and_jump!(record, tape, "horizontal metrics");
    Ok(try!(HorizontalMetrics::read(tape, header, profile)))
}

fn read_maximum_profile<T: Tape>(tape: &mut T, record: &OffsetTableRecord)
                                 -> Result<MaximumProfile> {

    verify_and_jump!(record, tape, "maximum profile");
    Ok(match try!(tape.peek::<Fixed>()) {
        Fixed(0x00005000) => MaximumProfile::Version05(try!(Value::read(tape))),
        Fixed(0x00010000) => MaximumProfile::Version10(try!(Value::read(tape))),
        _ => raise!("the format of the maximum profile is not supported"),
    })
}

fn read_naming_table<T: Tape>(tape: &mut T, record: &OffsetTableRecord) -> Result<NamingTable> {
    verify_and_jump!(record, tape, "naming table");
    Ok(match try!(tape.peek::<UShort>()) {
        0 => NamingTable::Format0(try!(Value::read(tape))),
        1 => NamingTable::Format1(try!(Value::read(tape))),
        _ => raise!("the format of the naming table is not supported"),
    })
}

fn read_postscript<T: Tape>(tape: &mut T, record: &OffsetTableRecord) -> Result<PostScript> {
    verify_and_jump!(record, tape, "PostScript information");
    Ok(match try!(tape.peek::<Fixed>()) {
        Fixed(0x00010000) => PostScript::Version10(try!(Value::read(tape))),
        Fixed(0x00030000) => PostScript::Version30(try!(Value::read(tape))),
        _ => raise!("the format of the PostScript information is not supported"),
    })
}

fn read_windows_metrics<T: Tape>(tape: &mut T, record: &OffsetTableRecord)
                                 -> Result<WindowsMetrics> {

    verify_and_jump!(record, tape, "OS/2 and Windows metrics");
    Ok(match try!(tape.peek::<UShort>()) {
        3 => WindowsMetrics::Version3(try!(Value::read(tape))),
        5 => WindowsMetrics::Version5(try!(Value::read(tape))),
        _ => raise!("the format of the OS/2 and Windows metrics is not supported"),
    })
}

fn priority(tag: &[u8; 4]) -> usize {
    use std::collections::HashMap;
    use std::sync::{Once, ONCE_INIT};

    unsafe {
        static mut PRIORITY: *const HashMap<[u8; 4], usize> = 0 as *const _;
        static ONCE: Once = ONCE_INIT;
        ONCE.call_once(|| {
            let mut map: HashMap<[u8; 4], usize> = HashMap::new();
            map.insert(*b"hmtx", 42);
            PRIORITY = mem::transmute(Box::new(map));
        });
        *(&*PRIORITY).get(tag).unwrap_or(&0)
    }
}
<commit_msg>Rename a macro<commit_after>use std::io::{Read, Seek};
use std::mem;

use Result;
use primitive::*;
use table::*;
use tape::{Tape, Value};
use truetype::compound::{FontHeader, CharMapping, OffsetTable, OffsetTableRecord};

/// A font.
#[derive(Default)]
pub struct Font {
    pub offset_table: OffsetTable,
    pub char_mapping: Option<CharMapping>,
    pub font_header: Option<FontHeader>,
    pub horizontal_header: Option<HorizontalHeader>,
    pub horizontal_metrics: Option<HorizontalMetrics>,
    pub maximum_profile: Option<MaximumProfile>,
    pub naming_table: Option<NamingTable>,
    pub postscript: Option<PostScript>,
    pub windows_metrics: Option<WindowsMetrics>,
}

macro_rules! tag(
    ($value:expr) => (unsafe {
        mem::transmute::<_, [u8; 4]>(u32::from_be(mem::transmute($value)))
    });
);

macro_rules! checksum_and_jump(
    ($record:ident, $tape:ident, $table:expr, $process:expr) => ({
        if !try!($record.checksum($tape, $process)) {
            raise!(concat!("the ", $table, " is corrupted"));
        }
        try!($tape.jump($record.offset as u64));
    });
    ($record:ident, $tape:ident, $table:expr) => (
        checksum_and_jump!($record, $tape, $table, |_, word| word);
    );
);

impl Font {
    #[inline]
    pub fn read<T: Read + Seek>(reader: &mut T) -> Result<Font> {
        Value::read(reader)
    }
}

impl Value for Font {
    fn read<T: Tape>(tape: &mut T) -> Result<Font> {
        macro_rules! sort(
            ($records:expr) => ({
                let mut records = $records.iter().collect::<Vec<_>>();
                records.sort_by(|one, two| {
                    priority(&tag!(one.tag)).cmp(&priority(&tag!(two.tag)))
                });
                records
            });
        );

        let mut font = Font { offset_table: try!(read_offset_table(tape)), .. Font::default() };

        for record in sort!(font.offset_table.records) {
            macro_rules! set(
                ($name:ident, $read:ident $($argument:tt)*) => (
                    font.$name = Some(try!($read(tape, record $($argument)*)))
                );
            );
            match &tag!(record.tag) {
                b"cmap" => set!(char_mapping, read_char_mapping),
                b"head" => set!(font_header, read_font_header),
                b"hhea" => set!(horizontal_header, read_horizontal_header),
                b"hmtx" => {
                    let header = match font.horizontal_header {
                        Some(ref table) => table,
                        _ => continue,
                    };
                    let profile = match font.maximum_profile {
                        Some(ref table) => table,
                        _ => continue,
                    };
                    set!(horizontal_metrics, read_horizontal_metrics, header, profile);
                },
                b"maxp" => set!(maximum_profile, read_maximum_profile),
                b"name" => set!(naming_table, read_naming_table),
                b"post" => set!(postscript, read_postscript),
                b"OS/2" => set!(windows_metrics, read_windows_metrics),
                _ => {},
            }
        }

        Ok(font)
    }
}

fn read_offset_table<T: Tape>(tape: &mut T) -> Result<OffsetTable> {
    use truetype::Value;

    match &tag!(try!(tape.peek::<Fixed>())) {
        b"OTTO" => OffsetTable::read(tape),
        _ => raise!("the format of a font is not supported"),
    }
}

fn read_char_mapping<T: Tape>(tape: &mut T, record: &OffsetTableRecord) -> Result<CharMapping> {
    use truetype::Value;

    checksum_and_jump!(record, tape, "character-to-glyph mapping");
    match try!(tape.peek::<UShort>()) {
        0 => CharMapping::read(tape),
        _ => raise!("the format of the character-to-glyph mapping header is not supported"),
    }
}

fn read_font_header<T: Tape>(tape: &mut T, record: &OffsetTableRecord) -> Result<FontHeader> {
    use truetype::Value;

    checksum_and_jump!(record, tape, "font header", |i, word| if i == 2 { 0 } else { word });
    let table = match try!(tape.peek::<Fixed>()) {
        Fixed(0x00010000) => try!(FontHeader::read(tape)),
        _ => raise!("the format of the font header is not supported"),
    };
    Ok(table)
}

fn read_horizontal_header<T: Tape>(tape: &mut T, record: &OffsetTableRecord)
                                   -> Result<HorizontalHeader> {

    checksum_and_jump!(record, tape, "horizontal header");
    Ok(match try!(tape.peek::<Fixed>()) {
        Fixed(0x00010000) => try!(HorizontalHeader::read(tape)),
        _ => raise!("the format of the horizontal header is not supported"),
    })
}

fn read_horizontal_metrics<T: Tape>(tape: &mut T, record: &OffsetTableRecord,
                                    header: &HorizontalHeader, profile: &MaximumProfile)
                                    -> Result<HorizontalMetrics> {

    checksum_and_jump!(record, tape, "horizontal metrics");
    Ok(try!(HorizontalMetrics::read(tape, header, profile)))
}

fn read_maximum_profile<T: Tape>(tape: &mut T, record: &OffsetTableRecord)
                                 -> Result<MaximumProfile> {

    checksum_and_jump!(record, tape, "maximum profile");
    Ok(match try!(tape.peek::<Fixed>()) {
        Fixed(0x00005000) => MaximumProfile::Version05(try!(Value::read(tape))),
        Fixed(0x00010000) => MaximumProfile::Version10(try!(Value::read(tape))),
        _ => raise!("the format of the maximum profile is not supported"),
    })
}

fn read_naming_table<T: Tape>(tape: &mut T, record: &OffsetTableRecord) -> Result<NamingTable> {
    checksum_and_jump!(record, tape, "naming table");
    Ok(match try!(tape.peek::<UShort>()) {
        0 => NamingTable::Format0(try!(Value::read(tape))),
        1 => NamingTable::Format1(try!(Value::read(tape))),
        _ => raise!("the format of the naming table is not supported"),
    })
}

fn read_postscript<T: Tape>(tape: &mut T, record: &OffsetTableRecord) -> Result<PostScript> {
    checksum_and_jump!(record, tape, "PostScript information");
    Ok(match try!(tape.peek::<Fixed>()) {
        Fixed(0x00010000) => PostScript::Version10(try!(Value::read(tape))),
        Fixed(0x00030000) => PostScript::Version30(try!(Value::read(tape))),
        _ => raise!("the format of the PostScript information is not supported"),
    })
}

fn read_windows_metrics<T: Tape>(tape: &mut T, record: &OffsetTableRecord)
                                 -> Result<WindowsMetrics> {

    checksum_and_jump!(record, tape, "OS/2 and Windows metrics");
    Ok(match try!(tape.peek::<UShort>()) {
        3 => WindowsMetrics::Version3(try!(Value::read(tape))),
        5 => WindowsMetrics::Version5(try!(Value::read(tape))),
        _ => raise!("the format of the OS/2 and Windows metrics is not supported"),
    })
}

fn priority(tag: &[u8; 4]) -> usize {
    use std::collections::HashMap;
    use std::sync::{Once, ONCE_INIT};

    unsafe {
        static mut PRIORITY: *const HashMap<[u8; 4], usize> = 0 as *const _;
        static ONCE: Once = ONCE_INIT;
        ONCE.call_once(|| {
            let mut map: HashMap<[u8; 4], usize> = HashMap::new();
            map.insert(*b"hmtx", 42);
            PRIORITY = mem::transmute(Box::new(map));
        });
        *(&*PRIORITY).get(tag).unwrap_or(&0)
    }
}
<|endoftext|>


<commit_before>use rand;
use std::iter::DoubleEndedIterator;
use std::fmt;

static SIZE  : uint = 4;

#[deriving(Eq, Show)]
pub enum Direction {
    Up,
    Right,
    Down,
    Left,
}

impl Direction {
    fn to_vector(self) -> (int, int) {
        match self {
            Up    => (0, -1),
            Right => (1, 0),
            Down  => (0, 1),
            Left  => (-1, 0)
        }
    }

    // Haskell succ/pred???
    fn all_directions() -> ~[Direction] {
        ~[Up, Right, Down, Left]
    }

}

pub struct Traversal {
    xs: ~[uint],
    ys: ~[uint],

    idx: uint,
    max_idx: uint,
    size: uint
}

impl Traversal {
    pub fn new(size: uint, dir: Direction) -> Traversal {
        let (x, y) = dir.to_vector();
        let xs = if x == 1 {
            range(0u, size).rev().collect::<~[uint]>()
        } else {
            range(0u, size).collect::<~[uint]>()
        };
        let ys = if y == 1 {
            range(0u, size).rev().collect::<~[uint]>()
        } else {
            range(0u, size).collect::<~[uint]>()
        };

        Traversal { xs: xs, ys: ys,
                    idx: 0, max_idx: size * size,
                    size: size,
        }
    }
}

impl Iterator<(uint, uint)> for Traversal {
    fn next(&mut self) -> Option<(uint, uint)> {
        if self.idx == self.max_idx {
            None
        } else {
            let ret = (self.xs[self.idx / self.size],
                       self.ys[self.idx % self.size]);
            self.idx += 1;
            Some(ret)
        }
    }
}

#[deriving(Eq, Clone)]
pub struct Tile {
    pub x: uint,
    pub y: uint,
    pub value: int,

    pub prev_pos: Option<(uint, uint)>,
    pub merged_from: Option<((uint,uint), (uint, uint))>
}

impl Tile {
    pub fn new((x, y): (uint, uint), value: int) -> Tile {
        Tile { x: x, y: y, value: value,
               prev_pos: None, merged_from: None }
    }

    pub fn save_position(&mut self) {
        self.prev_pos = Some((self.x, self.y));
    }

    pub fn update_position(&mut self, (x, y): (uint, uint)) {
        self.x = x;
        self.y = y
    }

    pub fn pos(&self) -> (uint, uint) {
        (self.x, self.y)
    }

}

impl fmt::Show for Tile {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.buf.write_str(format!("[{:4d}]", self.value))
    }
}

// Layout for console
//
// +-----+-----+-----+-----+
// |(0,0)|(0,1)|     |     |
// +-----+-----+-----+-----+
// |(1,0)|     |     |     |
// +-----+-----+-----+-----+
// |     |     |     |     |
// +-----+-----+-----+-----+
// |     |     |     |(3,3)|
// +-----+-----+-----+-----+
//
//

#[deriving(Eq, Show, Clone)]
pub struct Grid {
    pub size: uint,
    pub cells: Vec<Vec<Option<Tile>>>
}

impl Grid {
    pub fn new(size: uint) -> Grid {
        Grid {
            size: size,
            cells: Vec::from_elem(size,
                                  Vec::from_elem(size, None))
        }
    }

    pub fn random_available_cell(&self) -> Option<(uint, uint)> {
        let cells = self.available_cells();

        match cells.len() {
            0 => None,
            n => {
                Some(*cells.get(rand::random::<uint>() % n))
            }
        }
    }

    pub fn available_cells(&self) -> Vec<(uint, uint)> {
        let mut cells = Vec::new();

        self.each_cell(|x, y, tile| {
            if tile.is_none() {
                cells.push((x,y))
            }
        });
        cells
    }

    pub fn each_cell(&self, callback: |uint, uint, Option<&Tile>|) {
        for x in range(0u, self.size) {
            for y in range(0u, self.size) {
                callback(x, y, self.cells.get(x).get(y).as_ref())
            }
        }
    }

    pub fn each_mut_cell(&mut self, callback: |uint, uint, &mut Option<Tile>|) {
        for x in range(0u, self.size) {
            for y in range(0u, self.size) {
                callback(x, y, self.cells.get_mut(x).get_mut(y))
            }
        }
    }

    pub fn cells_available(&self) -> bool {
        self.available_cells().len() != 0
    }

    pub fn cell_available(&self, (x, y): (uint, uint)) -> bool {
        self.cells.get(x).get(y).is_none()
    }

    pub fn cell_occupied(&self, (x, y): (uint, uint)) -> bool {
        self.cells.get(x).get(y).is_some()
    }

    pub fn insert_tile(&mut self, tile: Tile) {
        *self.cells.get_mut(tile.x).get_mut(tile.y) = Some(tile);
    }

    pub fn remove_tile(&mut self, tile: Tile) {
        println!("remove ({}, {})", tile.x, tile.y);
        *self.cells.get_mut(tile.x).get_mut(tile.y) = None;
    }

    pub fn within_bounds(&self, (x, y): (uint, uint)) -> bool {
        x < self.size && y < self.size
    }

    pub fn cell_content(&self, (x, y): (uint, uint)) -> Option<Tile> {
        if self.within_bounds((x, y)) {
            *self.cells.get(x).get(y)
        } else {
            None
        }
    }

    fn debug_print(&self) {
        for col in self.cells.iter() {
            for cell in col.iter() {
                match *cell {
                    Some(t) => print!("{}\t", t),
                    None    => print!("[    ]\t"),
                }
            }
            println!("");
        }
    }

}

#[deriving(Show)]
pub struct GameManager {
    pub size: uint,
    pub start_tiles: uint,

    pub grid: Grid,
    pub score: uint,
    pub playing: bool
}

impl GameManager {
    pub fn new(size: uint) -> GameManager {
        GameManager { size: size,
                      start_tiles: 2,
                      grid: Grid::new(size),
                      score: 0,
                      playing: false }
    }

    pub fn setup(&mut self) {
        self.playing = true;

        self.add_start_tiles();
    }

    pub fn add_random_tile(&mut self) {
        if self.grid.cells_available() {
            let value = if rand::random::<uint>() % 10 < 9 { 2 } else { 4 };
            let tile = Tile::new(self.grid.random_available_cell().unwrap(), value);

            self.grid.insert_tile(tile);
        }
    }

    fn add_start_tiles(&mut self) {
        for _ in range(0, self.start_tiles) {
            self.add_random_tile();
        }
    }

    pub fn prepare_tiles(&mut self) {
        self.grid.each_mut_cell(|_x, _y, tile| {
            if tile.is_some() {
                let mut t = tile.unwrap().clone();
                t.merged_from = None;
                t.save_position();
                *tile = Some(t);
            }
        })
    }

    pub fn move_tile(&mut self, tile: Tile, (x, y): (uint, uint)) {
        println!("move {} to {}", tile.pos(), (x,y));
        let mut tile = tile;
        *self.grid.cells.get_mut(tile.x).get_mut(tile.y) = None;
        tile.update_position((x, y));
        //*self.grid.cells.get_mut(tile.x).get_mut(tile.y) = None;
        let pos = self.grid.cells.get_mut(x).get_mut(y);
        *pos = Some(tile);
        //(*pos).unwrap().update_position((x, y));
    }

    pub fn move(&mut self, dir: Direction) -> bool {
        let mut moved = false;

        self.prepare_tiles();
        for (x, y) in self.build_traversal(dir) {
            let tile_opt = self.grid.cell_content((x, y));

            match tile_opt {
                None => (),
                Some(mut tile) => {
                    let (farthest_pos, next_pos) = self.find_farthest_position((x,y), dir.to_vector());
                    //println!("debug 1 ({}, {}), {:}", x, y, (farthest_pos, next_pos));
                    //println!("debug 3 {}{} ",tile.pos(), tile);
                    let next_opt = self.grid.cell_content(next_pos);
                    // println!("next => {}", next_opt);
                    match next_opt {
                        Some(next) if next.value == tile.value && tile.merged_from.is_none() => {
                            let mut merged = Tile::new(next_pos, tile.value * 2);
                            merged.merged_from = Some((tile.pos(), next.pos()));

                            self.grid.insert_tile(merged);
                            //println!("debug 2: remove {:?}", tile);
                            self.grid.remove_tile(tile);

                            tile.update_position(next_pos);

                            self.score += merged.value as uint;

                            // The mighty 2048 tile
                            moved = true;
                        }
                        _ => {
                            if tile.pos() != farthest_pos {
                                self.move_tile(tile, farthest_pos);
                                moved = true;
                            }
                        }
                    }
                }
            }
        }

        if moved {
            // xxx moves_av

            println!("moved some cell!");
            self.add_random_tile();
        }

        moved
    }

    pub fn moves_available(&self) -> bool {
        self.grid.cells_available() || self.tile_matches_available()
    }

    // visit order
    fn build_traversal(&self, dir: Direction) -> Traversal {
        Traversal::new(self.size, dir)
    }

    fn find_farthest_position(&self, (x, y): (uint, uint), (dx, dy): (int, int)) -> ((uint, uint), (uint, uint)) {
        let (mut prev_x, mut prev_y) = (x as int, y as int);
        let (mut next_x, mut next_y) = (prev_x + dx, prev_y + dy);

        while self.grid.within_bounds((next_x as uint, next_y as uint)) &&
              self.grid.cell_available((next_x as uint, next_y as uint)) {
                  prev_x = next_x;
                  prev_y = next_y;
                  next_x = prev_x + dx;
                  next_y =  prev_y + dy;
        }
        // (farthest, next)
        ((prev_x as uint, prev_y as uint),
         (next_x as uint, next_y as uint))
    }

    fn tile_matches_available(&self) -> bool {
        for x in range(0u, self.size) {
            for y in range(0u, self.size) {
                match self.grid.cell_content((x,y)) {
                    Some(tile) => {
                        for dir in Direction::all_directions().iter() {
                            let (dx, dy) = dir.to_vector();
                            let nx = (x as int + dx) as uint;
                            let ny = (y as int + dy) as uint;
                            match self.grid.cell_content((nx, ny)) {
                                Some(other) if other.value == tile.value => {
                                    return true;
                                }
                                _ => ()
                            }
                        }

                    }
                    _ => ()
                }

            }
        }
        false
    }

}

fn main() {
    let mut gm = GameManager::new(SIZE);
    gm.setup();
    println!("DEBUG: gm-> {}", gm);
    // gm.add_start_tiles();
    println!("DEBUG: gm-> {}", gm);
    gm.add_random_tile();
    gm.grid.debug_print();
    //gm.move(Up);                // Left
    //gm.move(Down);              // Right
    //gm.move(Left);              // Up
    //gm.move(Right);             // Down
    gm.move(Up);
    println!("===========");
    gm.grid.debug_print();
/*    gm.add_random_tile();
    gm.grid.debug_print();
    gm.add_random_tile();
    gm.grid.debug_print();
    gm.add_random_tile();
    println!("===========");

    gm.move(Down);
    gm.grid.debug_print();
    println!("=========== Up");
    gm.move(Up);
    gm.grid.debug_print();
    let ret : ~[(uint, uint)]  =  gm.build_traversal(Up).collect::<~[(uint,uint)]>();
    println!("traversal => {}", ret);
*/
    //println!("build tranv = {}", gm.build_traversals(Down).collect::<Vec<(uint,uint)>>());
}
<commit_msg>add debug print<commit_after>use rand;
use std::iter::DoubleEndedIterator;
use std::fmt;

static SIZE  : uint = 4;

#[deriving(Eq, Show)]
pub enum Direction {
    Up,
    Right,
    Down,
    Left,
}

impl Direction {
    fn to_vector(self) -> (int, int) {
        match self {
            Up    => (0, -1),
            Right => (1, 0),
            Down  => (0, 1),
            Left  => (-1, 0)
        }
    }

    // Haskell succ/pred???
    fn all_directions() -> ~[Direction] {
        ~[Up, Right, Down, Left]
    }

}

pub struct Traversal {
    xs: ~[uint],
    ys: ~[uint],

    idx: uint,
    max_idx: uint,
    size: uint
}

impl Traversal {
    pub fn new(size: uint, dir: Direction) -> Traversal {
        let (x, y) = dir.to_vector();
        let xs = if x == 1 {
            range(0u, size).rev().collect::<~[uint]>()
        } else {
            range(0u, size).collect::<~[uint]>()
        };
        let ys = if y == 1 {
            range(0u, size).rev().collect::<~[uint]>()
        } else {
            range(0u, size).collect::<~[uint]>()
        };

        Traversal { xs: xs, ys: ys,
                    idx: 0, max_idx: size * size,
                    size: size,
        }
    }
}

impl Iterator<(uint, uint)> for Traversal {
    fn next(&mut self) -> Option<(uint, uint)> {
        if self.idx == self.max_idx {
            None
        } else {
            let ret = (self.xs[self.idx / self.size],
                       self.ys[self.idx % self.size]);
            self.idx += 1;
            Some(ret)
        }
    }
}

#[deriving(Eq, Clone)]
pub struct Tile {
    pub x: uint,
    pub y: uint,
    pub value: int,

    pub prev_pos: Option<(uint, uint)>,
    pub merged_from: Option<((uint,uint), (uint, uint))>
}

impl Tile {
    pub fn new((x, y): (uint, uint), value: int) -> Tile {
        Tile { x: x, y: y, value: value,
               prev_pos: None, merged_from: None }
    }

    pub fn save_position(&mut self) {
        self.prev_pos = Some((self.x, self.y));
    }

    pub fn update_position(&mut self, (x, y): (uint, uint)) {
        self.x = x;
        self.y = y
    }

    pub fn pos(&self) -> (uint, uint) {
        (self.x, self.y)
    }

}

impl fmt::Show for Tile {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.buf.write_str(format!("[{:4d}]", self.value))
    }
}

// Layout for console
//
// +-----+-----+-----+-----+
// |(0,0)|(0,1)|     |     |
// +-----+-----+-----+-----+
// |(1,0)|     |     |     |
// +-----+-----+-----+-----+
// |     |     |     |     |
// +-----+-----+-----+-----+
// |     |     |     |(3,3)|
// +-----+-----+-----+-----+
//
//

#[deriving(Eq, Show, Clone)]
pub struct Grid {
    pub size: uint,
    pub cells: Vec<Vec<Option<Tile>>>
}

impl Grid {
    pub fn new(size: uint) -> Grid {
        Grid {
            size: size,
            cells: Vec::from_elem(size,
                                  Vec::from_elem(size, None))
        }
    }

    pub fn random_available_cell(&self) -> Option<(uint, uint)> {
        let cells = self.available_cells();

        match cells.len() {
            0 => None,
            n => {
                Some(*cells.get(rand::random::<uint>() % n))
            }
        }
    }

    pub fn available_cells(&self) -> Vec<(uint, uint)> {
        let mut cells = Vec::new();

        self.each_cell(|x, y, tile| {
            if tile.is_none() {
                cells.push((x,y))
            }
        });
        cells
    }

    pub fn each_cell(&self, callback: |uint, uint, Option<&Tile>|) {
        for x in range(0u, self.size) {
            for y in range(0u, self.size) {
                callback(x, y, self.cells.get(x).get(y).as_ref())
            }
        }
    }

    pub fn each_mut_cell(&mut self, callback: |uint, uint, &mut Option<Tile>|) {
        for x in range(0u, self.size) {
            for y in range(0u, self.size) {
                callback(x, y, self.cells.get_mut(x).get_mut(y))
            }
        }
    }

    pub fn cells_available(&self) -> bool {
        self.available_cells().len() != 0
    }

    pub fn cell_available(&self, (x, y): (uint, uint)) -> bool {
        self.cells.get(x).get(y).is_none()
    }

    pub fn cell_occupied(&self, (x, y): (uint, uint)) -> bool {
        self.cells.get(x).get(y).is_some()
    }

    pub fn insert_tile(&mut self, tile: Tile) {
        *self.cells.get_mut(tile.x).get_mut(tile.y) = Some(tile);
    }

    pub fn remove_tile(&mut self, tile: Tile) {
        println!("remove ({}, {})", tile.x, tile.y);
        *self.cells.get_mut(tile.x).get_mut(tile.y) = None;
    }

    pub fn within_bounds(&self, (x, y): (uint, uint)) -> bool {
        x < self.size && y < self.size
    }

    pub fn cell_content(&self, (x, y): (uint, uint)) -> Option<Tile> {
        if self.within_bounds((x, y)) {
            *self.cells.get(x).get(y)
        } else {
            None
        }
    }

    fn debug_print(&self) {
        for col in self.cells.iter() {
            for cell in col.iter() {
                match *cell {
                    Some(t) => print!("{}\t", t),
                    None    => print!("[    ]\t"),
                }
            }
            println!("");
        }
    }

}

#[deriving(Show)]
pub struct GameManager {
    pub size: uint,
    pub start_tiles: uint,

    pub grid: Grid,
    pub score: uint,
    pub playing: bool
}

impl GameManager {
    pub fn new(size: uint) -> GameManager {
        GameManager { size: size,
                      start_tiles: 2,
                      grid: Grid::new(size),
                      score: 0,
                      playing: false }
    }

    pub fn setup(&mut self) {
        self.playing = true;

        self.add_start_tiles();
    }

    pub fn add_random_tile(&mut self) {
        if self.grid.cells_available() {
            let value = if rand::random::<uint>() % 10 < 9 { 2 } else { 4 };
            let tile = Tile::new(self.grid.random_available_cell().unwrap(), value);
            println!("add new {}", tile.pos());
            self.grid.insert_tile(tile);
        }
    }

    fn add_start_tiles(&mut self) {
        for _ in range(0, self.start_tiles) {
            self.add_random_tile();
        }
    }

    pub fn prepare_tiles(&mut self) {
        self.grid.each_mut_cell(|_x, _y, tile| {
            if tile.is_some() {
                let mut t = tile.unwrap().clone();
                t.merged_from = None;
                t.save_position();
                *tile = Some(t);
            }
        })
    }

    pub fn move_tile(&mut self, tile: Tile, (x, y): (uint, uint)) {
        println!("move {} to {}", tile.pos(), (x,y));
        let mut tile = tile;
        *self.grid.cells.get_mut(tile.x).get_mut(tile.y) = None;
        tile.update_position((x, y));
        //*self.grid.cells.get_mut(tile.x).get_mut(tile.y) = None;
        let pos = self.grid.cells.get_mut(x).get_mut(y);
        *pos = Some(tile);
        //(*pos).unwrap().update_position((x, y));
    }

    pub fn move(&mut self, dir: Direction) -> bool {
        let mut moved = false;

        self.prepare_tiles();
        for (x, y) in self.build_traversal(dir) {
            let tile_opt = self.grid.cell_content((x, y));

            match tile_opt {
                None => (),
                Some(mut tile) => {
                    let (farthest_pos, next_pos) = self.find_farthest_position((x,y), dir.to_vector());
                    //println!("debug 1 ({}, {}), {:}", x, y, (farthest_pos, next_pos));
                    //println!("debug 3 {}{} ",tile.pos(), tile);
                    let next_opt = self.grid.cell_content(next_pos);
                    // println!("next => {}", next_opt);
                    match next_opt {
                        Some(next) if next.value == tile.value && tile.merged_from.is_none() => {
                            let mut merged = Tile::new(next_pos, tile.value * 2);
                            println!("{}, {} merged to {}", tile.pos(), next_pos, next_pos);
                            merged.merged_from = Some((tile.pos(), next.pos()));

                            self.grid.insert_tile(merged);
                            //println!("debug 2: remove {:?}", tile);
                            self.grid.remove_tile(tile);

                            tile.update_position(next_pos);

                            self.score += merged.value as uint;

                            // The mighty 2048 tile
                            moved = true;
                        }
                        _ => {
                            if tile.pos() != farthest_pos {
                                self.move_tile(tile, farthest_pos);
                                moved = true;
                            }
                        }
                    }
                }
            }
        }

        if moved {
            // xxx moves_av

            println!("moved some cell!");
            self.add_random_tile();
            self.grid.debug_print();
        }

        moved
    }

    pub fn moves_available(&self) -> bool {
        self.grid.cells_available() || self.tile_matches_available()
    }

    // visit order
    fn build_traversal(&self, dir: Direction) -> Traversal {
        Traversal::new(self.size, dir)
    }

    fn find_farthest_position(&self, (x, y): (uint, uint), (dx, dy): (int, int)) -> ((uint, uint), (uint, uint)) {
        let (mut prev_x, mut prev_y) = (x as int, y as int);
        let (mut next_x, mut next_y) = (prev_x + dx, prev_y + dy);

        while self.grid.within_bounds((next_x as uint, next_y as uint)) &&
              self.grid.cell_available((next_x as uint, next_y as uint)) {
                  prev_x = next_x;
                  prev_y = next_y;
                  next_x = prev_x + dx;
                  next_y =  prev_y + dy;
        }
        // (farthest, next)
        ((prev_x as uint, prev_y as uint),
         (next_x as uint, next_y as uint))
    }

    fn tile_matches_available(&self) -> bool {
        for x in range(0u, self.size) {
            for y in range(0u, self.size) {
                match self.grid.cell_content((x,y)) {
                    Some(tile) => {
                        for dir in Direction::all_directions().iter() {
                            let (dx, dy) = dir.to_vector();
                            let nx = (x as int + dx) as uint;
                            let ny = (y as int + dy) as uint;
                            match self.grid.cell_content((nx, ny)) {
                                Some(other) if other.value == tile.value => {
                                    return true;
                                }
                                _ => ()
                            }
                        }

                    }
                    _ => ()
                }

            }
        }
        false
    }

}

fn main() {
    let mut gm = GameManager::new(SIZE);
    gm.setup();
    println!("DEBUG: gm-> {}", gm);
    // gm.add_start_tiles();
    println!("DEBUG: gm-> {}", gm);
    gm.add_random_tile();
    gm.grid.debug_print();
    //gm.move(Up);                // Left
    //gm.move(Down);              // Right
    //gm.move(Left);              // Up
    //gm.move(Right);             // Down
    gm.move(Up);
    println!("===========");
    gm.grid.debug_print();
/*    gm.add_random_tile();
    gm.grid.debug_print();
    gm.add_random_tile();
    gm.grid.debug_print();
    gm.add_random_tile();
    println!("===========");

    gm.move(Down);
    gm.grid.debug_print();
    println!("=========== Up");
    gm.move(Up);
    gm.grid.debug_print();
    let ret : ~[(uint, uint)]  =  gm.build_traversal(Up).collect::<~[(uint,uint)]>();
    println!("traversal => {}", ret);
*/
    //println!("build tranv = {}", gm.build_traversals(Down).collect::<Vec<(uint,uint)>>());
}
<|endoftext|>