<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 {
    /// 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(filename).mode(mode).open()
    }

    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 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
        }
    }

    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)]<commit_msg>Move `filename` argument from FileBuilder::new() to FileBuilder::open().<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 {
    /// 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)
    }

    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 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
        }
    }

    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 {
    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 = 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: {:?}", 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 = 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: {:?}", 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)]
{'fraction_non_alphanumeric': 0.14685478213422715, 'fraction_numerical': 0.013563021279912697, 'mean_word_length': 1.864478678276401, 'pattern_counts': {'":': 0, '<': 35, '<?xml version=': 0, '>': 103, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '45826', 'n_tokens_mistral': 4354, 'n_tokens_neox': 4203, 'n_words': 870}