<commit_before>use crate::error::Error;
use crate::state::{State, Step, StepStatus};
use async_trait::async_trait;
use common::document::ContainerDocument;
use cucumber::{t, StepContext, Steps};
use mimir2::adapters::primary::{
    common::coord::Coord, common::dsl::build_query, common::filters::Filters,
    common::settings::QuerySettings,
};
use mimir2::adapters::secondary::elasticsearch::ElasticsearchStorage;
use mimir2::domain::{model::query::Query, ports::primary::search_documents::SearchDocuments};
use places::{addr::Addr, admin::Admin, poi::Poi, stop::Stop, street::Street};

pub fn steps() -> Steps<State> {
    let mut steps: Steps<State> = Steps::new();

    steps.when_regex_async(
        r#"the user searches (.*) datatype(?: in (.*))? for "(.*)"(?: at \((.*),(.*)\))?"#,
        t!(|mut state, ctx| {
            let places = ctx.matches[1].clone();
            let places = if places == "all" {
                vec![
                    Addr::static_doc_type().to_string(),
                    Admin::static_doc_type().to_string(),
                    Street::static_doc_type().to_string(),
                    Stop::static_doc_type().to_string(),
                    Poi::static_doc_type().to_string(),
                ]
            } else {
                places.split(',').map(|s| s.trim().to_string()).collect()
            };
            let lat = ctx.matches[4].trim();
            let lon = ctx.matches[5].trim();
            let coord = if lat.is_empty() {
                None
            } else {
                // round to 4 digits
                let lat = (lat.parse::<f32>().expect("lat is f32") * 10000.0).round() / 10000.0;
                let lon = (lon.parse::<f32>().expect("lon is f32") * 10000.0).round() / 10000.0;
                Some(Coord::new(lat, lon))
            };
            let filters = Filters {
                coord,
                ..Default::default()
            };

            let query = ctx.matches[3].clone();
            let search = Search {
                filters,
                places,
                query,
                results: Vec::new(),
            };

            state.execute(search, &ctx).await.expect("failed to search");

            state
        }),
    );

    steps.then_regex_async(
        r#"he finds "(.*)" as the first result"#,
        t!(|mut state, ctx| {
            let id = ctx.matches[1].clone();

            state
                .execute(HasDocument { id, max_rank: 1 }, &ctx)
                .await
                .expect("failed to find document");

            state
        }),
    );

    steps.then_regex_async(
        r#"he finds "(.*)", "(.*)", "(.*)", and "(.*)" in the first "(.*)" results"#,
        t!(|mut state, ctx| {
            let house = Some(ctx.matches[1].clone()).and_then(|house| {
                if house.is_empty() {
                    None
                } else {
                    Some(house)
                }
            });
            let street = Some(ctx.matches[2].clone()).and_then(|street| {
                if street.is_empty() {
                    None
                } else {
                    Some(street)
                }
            });
            let city = Some(ctx.matches[3].clone()).and_then(|city| {
                if city.is_empty() {
                    None
                } else {
                    Some(city)
                }
            });
            let postcode = Some(ctx.matches[4].clone()).and_then(|postcode| {
                if postcode.is_empty() {
                    None
                } else {
                    Some(postcode)
                }
            });
            let limit = ctx.matches[5].clone();
            let limit = if limit.is_empty() {
                1
            } else {
                limit.parse().expect("limit as usize")
            };

            state
                .execute(
                    HasAddress {
                        house,
                        street,
                        city,
                        postcode,
                        limit,
                    },
                    &ctx,
                )
                .await
                .expect("failed to find document");

            state
        }),
    );

    steps
}

/// Perform a search in current Elasticsearch DB.
#[derive(Debug)]
pub struct Search {
    places: Vec<String>, // What kind of places we're searching
    query: String,       // The search string
    filters: Filters,    // Search filters
    results: Vec<serde_json::Value>,
}

#[async_trait(?Send)]
impl Step for Search {
    async fn execute(&mut self, _state: &State, ctx: &StepContext) -> Result<StepStatus, Error> {
        let client: &ElasticsearchStorage = ctx.get().expect("could not get ES client");
        // Build ES query
        let dsl = build_query(
            &self.query,
            self.filters.clone(),
            &["fr"],
            &QuerySettings::default(),
        );

        // Fetch documents
        self.results = {
            client
                .search_documents(self.places.clone(), Query::QueryDSL(dsl), 10)
                .await
                .unwrap()
        };

        Ok(StepStatus::Done)
    }
}

/// Check if given document is in the output.
///
/// It assumes that a Search has already been performed before.
pub struct HasDocument {
    id: String,
    max_rank: usize,
}

#[async_trait(?Send)]
impl Step for HasDocument {
    async fn execute(&mut self, state: &State, _ctx: &StepContext) -> Result<StepStatus, Error> {
        let (search, _) = state
            .steps_for::<Search>()
            .next_back()
            .expect("the user must perform a search before checking results");

        let (rank, _) = (search.results.iter().enumerate())
            .find(|(_, doc)| {
                println!("{} vs {}", self.id, doc["id"].as_str().unwrap());
                self.id == doc["id"].as_str().unwrap()
            })
            .expect("document was not found in search results");

        assert!(rank < self.max_rank);
        Ok(StepStatus::Done)
    }
}

/// Check if given document is in the output.
///
/// It assumes that a Search has already been performed before.
pub struct HasAddress {
    house: Option<String>,
    street: Option<String>,
    city: Option<String>,
    postcode: Option<String>,
    limit: usize,
}

#[async_trait(?Send)]
impl Step for HasAddress {
    async fn execute(&mut self, state: &State, _ctx: &StepContext) -> Result<StepStatus, Error> {
        let (search, _) = state
            .steps_for::<Search>()
            .next_back()
            .expect("the user must perform a search before checking results");

        let rank = search
            .results
            .clone()
            .into_iter()
            .enumerate()
            .find_map(|(num, doc)| {
                let addr: Addr = serde_json::from_value(doc).expect("address");
                if let Some(house) = &self.house {
                    if house != addr.house_number.as_str() {
                        return None;
                    }
                }
                if let Some(street) = &self.street {
                    if street != addr.street.name.as_str() {
                        return None;
                    }
                }
                if let Some(postcode) = &self.postcode {
                    if postcode != addr.zip_codes[0].as_str() {
                        return None;
                    }
                }
                if let Some(city) = &self.city {
                    if city != addr.city().expect("city").as_str() {
                        return None;
                    }
                }
                Some(num)
            })
            .expect("document was not found in search results");

        assert!(rank < self.limit);
        Ok(StepStatus::Done)
    }
}
<commit_msg>update to default constant<commit_after>use crate::error::Error;
use crate::state::{State, Step, StepStatus};
use async_trait::async_trait;
use common::document::ContainerDocument;
use cucumber::{t, StepContext, Steps};
use mimir2::adapters::primary::bragi::api::DEFAULT_LIMIT_RESULT_ES;
use mimir2::adapters::primary::{
    common::coord::Coord, common::dsl::build_query, common::filters::Filters,
    common::settings::QuerySettings,
};
use mimir2::adapters::secondary::elasticsearch::ElasticsearchStorage;
use mimir2::domain::{model::query::Query, ports::primary::search_documents::SearchDocuments};
use places::{addr::Addr, admin::Admin, poi::Poi, stop::Stop, street::Street};

pub fn steps() -> Steps<State> {
    let mut steps: Steps<State> = Steps::new();

    steps.when_regex_async(
        r#"the user searches (.*) datatype(?: in (.*))? for "(.*)"(?: at \((.*),(.*)\))?"#,
        t!(|mut state, ctx| {
            let places = ctx.matches[1].clone();
            let places = if places == "all" {
                vec![
                    Addr::static_doc_type().to_string(),
                    Admin::static_doc_type().to_string(),
                    Street::static_doc_type().to_string(),
                    Stop::static_doc_type().to_string(),
                    Poi::static_doc_type().to_string(),
                ]
            } else {
                places.split(',').map(|s| s.trim().to_string()).collect()
            };
            let lat = ctx.matches[4].trim();
            let lon = ctx.matches[5].trim();
            let coord = if lat.is_empty() {
                None
            } else {
                // round to 4 digits
                let lat = (lat.parse::<f32>().expect("lat is f32") * 10000.0).round() / 10000.0;
                let lon = (lon.parse::<f32>().expect("lon is f32") * 10000.0).round() / 10000.0;
                Some(Coord::new(lat, lon))
            };
            let filters = Filters {
                coord,
                ..Default::default()
            };

            let query = ctx.matches[3].clone();
            let search = Search {
                filters,
                places,
                query,
                results: Vec::new(),
            };

            state.execute(search, &ctx).await.expect("failed to search");

            state
        }),
    );

    steps.then_regex_async(
        r#"he finds "(.*)" as the first result"#,
        t!(|mut state, ctx| {
            let id = ctx.matches[1].clone();

            state
                .execute(HasDocument { id, max_rank: 1 }, &ctx)
                .await
                .expect("failed to find document");

            state
        }),
    );

    steps.then_regex_async(
        r#"he finds "(.*)", "(.*)", "(.*)", and "(.*)" in the first "(.*)" results"#,
        t!(|mut state, ctx| {
            let house = Some(ctx.matches[1].clone()).and_then(|house| {
                if house.is_empty() {
                    None
                } else {
                    Some(house)
                }
            });
            let street = Some(ctx.matches[2].clone()).and_then(|street| {
                if street.is_empty() {
                    None
                } else {
                    Some(street)
                }
            });
            let city = Some(ctx.matches[3].clone()).and_then(|city| {
                if city.is_empty() {
                    None
                } else {
                    Some(city)
                }
            });
            let postcode = Some(ctx.matches[4].clone()).and_then(|postcode| {
                if postcode.is_empty() {
                    None
                } else {
                    Some(postcode)
                }
            });
            let limit = ctx.matches[5].clone();
            let limit = if limit.is_empty() {
                1
            } else {
                limit.parse().expect("limit as usize")
            };

            state
                .execute(
                    HasAddress {
                        house,
                        street,
                        city,
                        postcode,
                        limit,
                    },
                    &ctx,
                )
                .await
                .expect("failed to find document");

            state
        }),
    );

    steps
}

/// Perform a search in current Elasticsearch DB.
#[derive(Debug)]
pub struct Search {
    places: Vec<String>, // What kind of places we're searching
    query: String,       // The search string
    filters: Filters,    // Search filters
    results: Vec<serde_json::Value>,
}

#[async_trait(?Send)]
impl Step for Search {
    async fn execute(&mut self, _state: &State, ctx: &StepContext) -> Result<StepStatus, Error> {
        let client: &ElasticsearchStorage = ctx.get().expect("could not get ES client");
        // Build ES query
        let dsl = build_query(
            &self.query,
            self.filters.clone(),
            &["fr"],
            &QuerySettings::default(),
        );

        // Fetch documents
        self.results = {
            client
                .search_documents(
                    self.places.clone(),
                    Query::QueryDSL(dsl),
                    DEFAULT_LIMIT_RESULT_ES,
                )
                .await
                .unwrap()
        };

        Ok(StepStatus::Done)
    }
}

/// Check if given document is in the output.
///
/// It assumes that a Search has already been performed before.
pub struct HasDocument {
    id: String,
    max_rank: usize,
}

#[async_trait(?Send)]
impl Step for HasDocument {
    async fn execute(&mut self, state: &State, _ctx: &StepContext) -> Result<StepStatus, Error> {
        let (search, _) = state
            .steps_for::<Search>()
            .next_back()
            .expect("the user must perform a search before checking results");

        let (rank, _) = (search.results.iter().enumerate())
            .find(|(_, doc)| {
                println!("{} vs {}", self.id, doc["id"].as_str().unwrap());
                self.id == doc["id"].as_str().unwrap()
            })
            .expect("document was not found in search results");

        assert!(rank < self.max_rank);
        Ok(StepStatus::Done)
    }
}

/// Check if given document is in the output.
///
/// It assumes that a Search has already been performed before.
pub struct HasAddress {
    house: Option<String>,
    street: Option<String>,
    city: Option<String>,
    postcode: Option<String>,
    limit: usize,
}

#[async_trait(?Send)]
impl Step for HasAddress {
    async fn execute(&mut self, state: &State, _ctx: &StepContext) -> Result<StepStatus, Error> {
        let (search, _) = state
            .steps_for::<Search>()
            .next_back()
            .expect("the user must perform a search before checking results");

        let rank = search
            .results
            .clone()
            .into_iter()
            .enumerate()
            .find_map(|(num, doc)| {
                let addr: Addr = serde_json::from_value(doc).expect("address");
                if let Some(house) = &self.house {
                    if house != addr.house_number.as_str() {
                        return None;
                    }
                }
                if let Some(street) = &self.street {
                    if street != addr.street.name.as_str() {
                        return None;
                    }
                }
                if let Some(postcode) = &self.postcode {
                    if postcode != addr.zip_codes[0].as_str() {
                        return None;
                    }
                }
                if let Some(city) = &self.city {
                    if city != addr.city().expect("city").as_str() {
                        return None;
                    }
                }
                Some(num)
            })
            .expect("document was not found in search results");

        assert!(rank < self.limit);
        Ok(StepStatus::Done)
    }
}
<|endoftext|>


<commit_before>//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//

use std::collections::HashMap;
use std::io::Cursor;
use std::io::{Read, Write};
use std::path::PathBuf;

use serde_json;
use toml;

use error::StoreErrorKind as SEK;
use error::MapErrInto;
use super::Mapper;
use store::Result;

use libimagerror::into::IntoError;

#[derive(Deserialize, Serialize)]
struct Entry {
    header: serde_json::Value,
    content: String,
}

impl Entry {

    fn to_string(self) -> Result<String> {
        toml::to_string(&self.header)
            .map_err_into(SEK::IoError)
            .map(|hdr| {
                format!("---\n{header}---\n{content}",
                        header  = hdr,
                        content = self.content)
            })
    }

}

#[derive(Deserialize, Serialize)]
struct Document {
    version: String,
    store: HashMap<PathBuf, Entry>,
}

pub struct JsonMapper;

impl JsonMapper {

    pub fn new() -> JsonMapper {
        JsonMapper
    }

}

impl Mapper for JsonMapper {
    fn read_to_fs<R: Read>(&self, r: &mut R, hm: &mut HashMap<PathBuf, Cursor<Vec<u8>>>)   -> Result<()> {
        let mut document = {
            let mut s = String::new();
            try!(r.read_to_string(&mut s).map_err_into(SEK::IoError));
            let doc : Document = try!(serde_json::from_str(&s).map_err_into(SEK::IoError));
            doc
        };

        let _ = try!(::semver::Version::parse(&document.version)
            .map_err_into(SEK::VersionError)
            .and_then(|doc_vers| {
                // safe because cargo does not compile if crate version is not valid
                let crate_version = ::semver::Version::parse(version!()).unwrap();

                if doc_vers > crate_version {
                    Err(SEK::VersionError.into_error())
                } else {
                    Ok(())
                }
            }));

        for (key, val) in document.store.drain() {
            let res = val
                .to_string()
                .map(|vals| hm.insert(key, Cursor::new(vals.into_bytes())))
                .map(|_| ());

            let _ = try!(res);
        }

        Ok(())
    }

    fn fs_to_write<W: Write>(&self, hm: &mut HashMap<PathBuf, Cursor<Vec<u8>>>, out: &mut W) -> Result<()> {
        #[derive(Serialize)]
        struct OutDocument {
            version: String,
            store: HashMap<PathBuf, String>,
        }

        let mut doc = OutDocument {
            version: String::from(version!()),
            store:   HashMap::new(),
        };

        for (key, value) in hm.drain() {
            let res = String::from_utf8(value.into_inner())
                .map_err_into(SEK::IoError)
                .map(|entrystr| {
                    doc.store.insert(key, entrystr);
                })
                .map(|_| ());

            let _ = try!(res);
        }

        serde_json::to_string(&doc)
            .map_err_into(SEK::IoError)
            .and_then(|json| out.write(&json.into_bytes()).map_err_into(SEK::IoError))
            .and_then(|_| out.flush().map_err_into(SEK::IoError))
            .map(|_| ())
    }
}

#[cfg(test)]
mod test {
    use std::io::Cursor;

    use super::*;

    #[test]
    fn test_json_to_fs() {
        let json = r#"
        { "version": "0.3.0",
          "store": {
            "/example": {
                "header": {
                    "imag": {
                        "version": "0.3.0"
                    }
                },
                "content": "test"
            }
          }
        }
        "#;
        let mut json = Cursor::new(String::from(json).into_bytes());
        let mapper   = JsonMapper::new();
        let mut hm   = HashMap::new();

        let io_res  = mapper.read_to_fs(&mut json, &mut hm);
        assert!(io_res.is_ok());

        assert_eq!(1, hm.len()); // we should have exactly one entry
    }

    #[test]
    fn test_fs_to_json() {
        let mapper                    = JsonMapper::new();
        let mut out : Cursor<Vec<u8>> = Cursor::new(vec![]);

        let mut hm = {
            let mut hm = HashMap::new();
            let content = r#"---
[imag]
version = "0.3.0"
---
hi there!"#;
            hm.insert(PathBuf::from("/example"), Cursor::new(String::from(content).into_bytes()));
            hm
        };

        let io_res = mapper.fs_to_write(&mut hm, &mut out);
        assert!(io_res.is_ok());

        let example = r#"
        {
            "version": "0.3.0",
            "store": {
                "/example": {
                    "header": {
                        "imag": {
                            "version": "0.3.0"
                        }
                    },
                    "content": "hi there!"
                }
            }
        }
        "#;

        let example_json : ::serde_json::Value = ::serde_json::from_str(example).unwrap();

        let output_json = String::from_utf8(out.into_inner()).unwrap();
        let output_json : ::serde_json::Value = ::serde_json::from_str(&output_json).unwrap();

        assert_eq!(example_json, output_json);
    }
}

<commit_msg>Fix fs_to_write() to serialize headers correctly<commit_after>//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//

use std::collections::HashMap;
use std::io::Cursor;
use std::io::{Read, Write};
use std::path::PathBuf;

use serde_json;
use toml;

use error::StoreErrorKind as SEK;
use error::MapErrInto;
use super::Mapper;
use store::Result;

use libimagerror::into::IntoError;

#[derive(Deserialize, Serialize)]
struct Entry {
    header: serde_json::Value,
    content: String,
}

impl Entry {

    fn to_string(self) -> Result<String> {
        toml::to_string(&self.header)
            .map_err_into(SEK::IoError)
            .map(|hdr| {
                format!("---\n{header}---\n{content}",
                        header  = hdr,
                        content = self.content)
            })
    }

}

#[derive(Deserialize, Serialize)]
struct Document {
    version: String,
    store: HashMap<PathBuf, Entry>,
}

pub struct JsonMapper;

impl JsonMapper {

    pub fn new() -> JsonMapper {
        JsonMapper
    }

}

impl Mapper for JsonMapper {
    fn read_to_fs<R: Read>(&self, r: &mut R, hm: &mut HashMap<PathBuf, Cursor<Vec<u8>>>)   -> Result<()> {
        let mut document = {
            let mut s = String::new();
            try!(r.read_to_string(&mut s).map_err_into(SEK::IoError));
            let doc : Document = try!(serde_json::from_str(&s).map_err_into(SEK::IoError));
            doc
        };

        let _ = try!(::semver::Version::parse(&document.version)
            .map_err_into(SEK::VersionError)
            .and_then(|doc_vers| {
                // safe because cargo does not compile if crate version is not valid
                let crate_version = ::semver::Version::parse(version!()).unwrap();

                if doc_vers > crate_version {
                    Err(SEK::VersionError.into_error())
                } else {
                    Ok(())
                }
            }));

        for (key, val) in document.store.drain() {
            let res = val
                .to_string()
                .map(|vals| hm.insert(key, Cursor::new(vals.into_bytes())))
                .map(|_| ());

            let _ = try!(res);
        }

        Ok(())
    }

    fn fs_to_write<W: Write>(&self, hm: &mut HashMap<PathBuf, Cursor<Vec<u8>>>, out: &mut W) -> Result<()> {
        use util::entry_buffer_to_header_content;

        #[derive(Serialize, Deserialize)]
        struct Entry {
            header: ::toml::Value,
            content: String,
        }

        #[derive(Serialize)]
        struct OutDocument {
            version: String,
            store: HashMap<PathBuf, Entry>,
        }

        let mut doc = OutDocument {
            version: String::from(version!()),
            store:   HashMap::new(),
        };

        for (key, value) in hm.drain() {
            let res = String::from_utf8(value.into_inner())
                .map_err_into(SEK::IoError)
                .and_then(|buf| entry_buffer_to_header_content(&buf))
                .map(|(header, content)| {
                    let entry = Entry {
                        header: header,
                        content: content
                    };

                    doc.store.insert(key, entry);
                })
                .map(|_| ());

            let _ = try!(res);
        }

        serde_json::to_string(&doc)
            .map_err_into(SEK::IoError)
            .and_then(|json| out.write(&json.into_bytes()).map_err_into(SEK::IoError))
            .and_then(|_| out.flush().map_err_into(SEK::IoError))
            .map(|_| ())
    }
}

#[cfg(test)]
mod test {
    use std::io::Cursor;

    use super::*;

    #[test]
    fn test_json_to_fs() {
        let json = r#"
        { "version": "0.3.0",
          "store": {
            "/example": {
                "header": {
                    "imag": {
                        "version": "0.3.0"
                    }
                },
                "content": "test"
            }
          }
        }
        "#;
        let mut json = Cursor::new(String::from(json).into_bytes());
        let mapper   = JsonMapper::new();
        let mut hm   = HashMap::new();

        let io_res  = mapper.read_to_fs(&mut json, &mut hm);
        assert!(io_res.is_ok());

        assert_eq!(1, hm.len()); // we should have exactly one entry
    }

    #[test]
    fn test_fs_to_json() {
        let mapper                    = JsonMapper::new();
        let mut out : Cursor<Vec<u8>> = Cursor::new(vec![]);

        let mut hm = {
            let mut hm = HashMap::new();
            let content = r#"---
[imag]
version = "0.3.0"
---
hi there!"#;
            hm.insert(PathBuf::from("/example"), Cursor::new(String::from(content).into_bytes()));
            hm
        };

        let io_res = mapper.fs_to_write(&mut hm, &mut out);
        assert!(io_res.is_ok());

        let example = r#"
        {
            "version": "0.3.0",
            "store": {
                "/example": {
                    "header": {
                        "imag": {
                            "version": "0.3.0"
                        }
                    },
                    "content": "hi there!"
                }
            }
        }
        "#;

        let example_json : ::serde_json::Value = ::serde_json::from_str(example).unwrap();

        let output_json = String::from_utf8(out.into_inner()).unwrap();
        let output_json : ::serde_json::Value = ::serde_json::from_str(&output_json).unwrap();

        assert_eq!(example_json, output_json);
    }
}

<|endoftext|>


<commit_before>extern crate generic_array;
extern crate libc;
extern crate sha2;
extern crate xattr;

use std::ffi::{OsStr, OsString};
use std::io;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::io::RawFd;
use std::path::{Component, Path, PathBuf};

use self::generic_array::GenericArray;
use self::generic_array::typenum::U64;
use self::sha2::{Sha512, Digest};
use self::xattr::FileExt;

use catfs::error;
use catfs::error::RError;
use catfs::rlibc;
use catfs::rlibc::File;

pub struct Handle {
    src_file: File,
    cache_file: File,
    dirty: bool,
}

// no-op to workaround the fact that we send the entire CatFS at start
// time, but we never send anything. Could have used Unique but that
// bounds us to rust nightly
unsafe impl Send for Handle {}

fn make_rdwr(f: &mut u32) {
    *f = (*f & !rlibc::O_ACCMODE) | rlibc::O_RDWR;
}

fn maybe_unlinkat(dir: RawFd, path: &AsRef<Path>) -> io::Result<()> {
    if let Err(e) = rlibc::unlinkat(dir, path, 0) {
        if !error::is_enoent(&e) {
            return Err(e);
        }
    }
    return Ok(());
}

fn mkdirat_all(dir: RawFd, path: &AsRef<Path>, mode: u32) -> io::Result<()> {
    let mut p = PathBuf::new();

    for c in path.as_ref().components() {
        if let Component::Normal(n) = c {
            p.push(n);

            if let Err(e) = rlibc::mkdirat(dir, &p, mode) {
                if e.raw_os_error().unwrap() != libc::EEXIST {
                    return Err(e);
                }
            }
        }
    }

    return Ok(());
}

impl Handle {
    pub fn create(
        src_dir: RawFd,
        cache_dir: RawFd,
        path: &AsRef<Path>,
        flags: u32,
        mode: u32,
    ) -> error::Result<Handle> {
        // need to read the cache file for writeback
        let mut cache_flags = flags.clone();
        if (cache_flags & rlibc::O_ACCMODE) == rlibc::O_WRONLY {
            make_rdwr(&mut cache_flags);
        }
        //debug!("create {:b} {:b} {:#o}", flags, cache_flags, mode);

        if let Some(parent) = path.as_ref().parent() {
            mkdirat_all(cache_dir, &parent, 0o777)?;
        }

        let src_file = File::openat(src_dir, path, flags, mode)?;
        // we are able to create the src file, then the cache file
        // shouldn't be here, but it could be because of bug/crash,
        // so unlink it first
        maybe_unlinkat(cache_dir, path)?;

        return Ok(Handle {
            src_file: src_file,
            cache_file: File::openat(cache_dir, path, cache_flags, mode)?,
            dirty: true,
        });
    }

    pub fn open(
        src_dir: RawFd,
        cache_dir: RawFd,
        path: &AsRef<Path>,
        flags: u32,
        cache_valid_if_present: bool,
    ) -> error::Result<Handle> {
        // even if file is open for write only, I still need to be
        // able to read the src for read-modify-write
        let mut flags = flags;
        if (flags & rlibc::O_ACCMODE) == rlibc::O_WRONLY {
            make_rdwr(&mut flags);
        }

        let valid = Handle::validate_cache(src_dir, cache_dir, &path, cache_valid_if_present)?;
        debug!(
            "{:?} {} a valid cache file",
            path.as_ref(),
            if valid { "is" } else { "is not" },
        );
        let mut cache_flags = flags.clone();

        if !valid {
            // mkdir the parents
            if let Some(parent) = path.as_ref().parent() {
                mkdirat_all(cache_dir, &parent, 0o777)?;
            }
            // need to cache this file so need to open it for write
            cache_flags |= rlibc::O_CREAT;
            if (cache_flags & rlibc::O_ACCMODE) == rlibc::O_RDONLY {
                make_rdwr(&mut cache_flags);
            }
        }

        let src_file: File;
        if valid && (flags & rlibc::O_ACCMODE) == rlibc::O_RDONLY {
            src_file = Default::default();
        } else {
            src_file = File::openat(src_dir, path, flags, 0o666)?;
        }

        let handle = Handle {
            src_file: src_file,
            cache_file: File::openat(cache_dir, path, cache_flags, 0o666)?,
            dirty: false,
        };

        if !valid && (flags & rlibc::O_TRUNC) == 0 {
            debug!("read ahead {:?}", path.as_ref());
            handle.copy(true)?;
        }

        return Ok(handle);
    }

    // the equivalent of:
    // getfattr -e hex --match=.* -d $f 2>/dev/null | grep =;
    // /usr/bin/stat -t --printf "%Y\n%s\n" $f
    // note that mtime is printed first and then size
    pub fn src_str_to_checksum(f: &File) -> error::Result<OsString> {
        let mut s = OsString::new();
        match f.list_xattr() {
            Ok(attrs) => {
                for x in attrs {
                    if let Some(v) = f.get_xattr(&x)? {
                        s.push(x);
                        s.push(OsStr::new("="));
                        s.push("0x");
                        for b in v {
                            s.push(format!("{:x}", b));
                        }
                        s.push("\n");
                    }
                }
            }
            Err(e) => {
                if e.raw_os_error().unwrap() != libc::ENOTSUP {
                    return Err(RError::from(e));
                }
            }
        }

        let st = f.stat()?;
        s.push(format!("{}\n", st.st_mtime));
        s.push(format!("{}\n", st.st_size));
        return Ok(s);
    }

    fn src_chksum(f: &File) -> error::Result<GenericArray<u8, U64>> {
        let s = Handle::src_str_to_checksum(f)?;
        let mut h = Sha512::default();
        h.input(s.as_bytes());
        return Ok(h.result());
    }

    fn set_pristine(&self, pristine: bool) -> error::Result<()> {
        if pristine {
            self.cache_file.set_xattr(
                "user.catfs.src_chksum",
                Handle::src_chksum(&self.src_file)?
                    .as_slice(),
            )?;
        } else {
            self.cache_file.remove_xattr("user.catfs.src_chksum")?;
        }
        return Ok(());
    }

    fn is_pristine(src_file: &File, cache_file: &File) -> error::Result<bool> {
        if let Some(v) = cache_file.get_xattr("user.catfs.src_chksum")? {
            return Ok(v == Handle::src_chksum(src_file)?.as_slice());
        }

        return Ok(false);
    }

    pub fn unlink(src_dir: RawFd, cache_dir: RawFd, path: &AsRef<Path>) -> io::Result<()> {
        maybe_unlinkat(cache_dir, path)?;
        return rlibc::unlinkat(src_dir, path, 0);
    }

    fn validate_cache(
        src_dir: RawFd,
        cache_dir: RawFd,
        path: &AsRef<Path>,
        cache_valid_if_present: bool,
    ) -> error::Result<bool> {
        match File::openat(src_dir, path, rlibc::O_RDONLY, 0) {
            Ok(mut src_file) => {
                match File::openat(cache_dir, path, rlibc::O_RDONLY, 0) {
                    Ok(mut cache_file) => {
                        let valid: bool;
                        if cache_valid_if_present || Handle::is_pristine(&src_file, &cache_file)? {
                            valid = true;
                        } else {
                            valid = false;
                            rlibc::unlinkat(cache_dir, path, 0)?;
                        }
                        src_file.close()?;
                        cache_file.close()?;
                        return Ok(valid);
                    }
                    Err(e) => {
                        src_file.close()?;
                        if error::try_enoent(e)? {
                            return Ok(false);
                        }
                    }
                }
            }
            Err(e) => {
                if error::try_enoent(e)? {
                    // the source file doesn't exist, the cache file shouldn't either
                    maybe_unlinkat(cache_dir, path)?;
                }
            }
        }

        return Ok(false);
    }

    pub fn read(&self, offset: u64, buf: &mut [u8]) -> error::Result<usize> {
        let nwant = buf.len();
        let mut bytes_read: usize = 0;

        while bytes_read < nwant {
            match self.cache_file.read_at(
                &mut buf[bytes_read..],
                offset + (bytes_read as u64),
            ) {
                Ok(nread) => {
                    if nread == 0 {
                        return Ok(bytes_read);
                    }
                    bytes_read += nread;
                }
                Err(e) => {
                    if bytes_read > 0 {
                        return Ok(bytes_read);
                    } else {
                        return Err(RError::from(e));
                    }
                }
            }
        }

        return Ok(bytes_read);
    }

    pub fn write(&mut self, offset: u64, buf: &[u8]) -> error::Result<usize> {
        let nwant = buf.len();
        let mut bytes_written: usize = 0;

        if !self.dirty {
            // assumes that the metadata will hit the disk before the
            // incoming data will, and not flushing
            self.set_pristine(false)?;
        }

        while bytes_written < nwant {
            match self.cache_file.write_at(
                &buf[bytes_written..],
                offset + (bytes_written as u64),
            ) {
                Ok(nwritten) => {
                    if nwritten == 0 {
                        return Ok(bytes_written);
                    }
                    bytes_written += nwritten;
                }
                Err(e) => {
                    if bytes_written > 0 {
                        return Ok(bytes_written);
                    } else {
                        return Err(RError::from(e));
                    }
                }
            }
        }

        if bytes_written != 0 {
            self.dirty = true;
        }

        return Ok(bytes_written);
    }

    pub fn flush(&mut self) -> error::Result<(bool)> {
        let mut flushed_to_src = false;
        if self.dirty {
            self.copy(false)?;
            self.dirty = false;
            self.cache_file.flush()?;
            // if file was opened for read only and cache is valid we
            // might not have opened src_file
            if self.src_file.valid() {
                self.src_file.flush()?;
                flushed_to_src = true;
            }
        }
        return Ok(flushed_to_src);
    }

    fn copy_user(rh: &File, wh: &File) -> error::Result<()> {
        let mut buf = [0u8; 32 * 1024];
        let mut offset = 0;
        loop {
            let nread = rh.read_at(&mut buf, offset)?;
            if nread == 0 {
                break;
            }
            wh.write_at(&buf[..nread], offset)?;
            offset += nread as u64;
        }
        return Ok(());
    }

    fn copy_splice(rh: &File, wh: &File) -> error::Result<()> {
        let (pin, pout) = rlibc::pipe()?;

        let mut offset = 0;
        loop {
            let nread = rlibc::splice(rh.as_raw_fd(), offset, pout, -1, 128 * 1024)?;
            if nread == 0 {
                break;
            }

            let mut written = 0;
            while written < nread {
                let nxfer = rlibc::splice(pin, -1, wh.as_raw_fd(), offset, 128 * 1024)?;

                written += nxfer;
                offset += nxfer as i64;
            }
        }

        if let Err(e) = rlibc::close(pin) {
            rlibc::close(pout)?;
            return Err(RError::from(e));
        } else {
            rlibc::close(pout)?;
        }

        return Ok(());
    }

    fn copy(&self, to_cache: bool) -> error::Result<()> {
        let rh: &File;
        let wh: &File;
        if to_cache {
            rh = &self.src_file;
            wh = &self.cache_file;
        } else {
            rh = &self.cache_file;
            wh = &self.src_file;
        }

        wh.set_size(rh.filesize()?)?;

        if let Err(e) = Handle::copy_splice(rh, wh) {
            if e.raw_os_error().unwrap() == libc::EINVAL {
                Handle::copy_user(rh, wh)?;
            }
        }

        self.set_pristine(true)?;
        return Ok(());
    }
}

impl Drop for Handle {
    fn drop(&mut self) {
        if let Err(e) = self.cache_file.close() {
            error!("!close(cache) = {}", RError::from(e));
        }
        if self.src_file.valid() {
            if let Err(e) = self.src_file.close() {
                error!("!close(src) = {}", RError::from(e));
            }
        }
    }
}
<commit_msg>do not extend the file for now<commit_after>extern crate generic_array;
extern crate libc;
extern crate sha2;
extern crate xattr;

use std::ffi::{OsStr, OsString};
use std::io;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::io::RawFd;
use std::path::{Component, Path, PathBuf};

use self::generic_array::GenericArray;
use self::generic_array::typenum::U64;
use self::sha2::{Sha512, Digest};
use self::xattr::FileExt;

use catfs::error;
use catfs::error::RError;
use catfs::rlibc;
use catfs::rlibc::File;

pub struct Handle {
    src_file: File,
    cache_file: File,
    dirty: bool,
}

// no-op to workaround the fact that we send the entire CatFS at start
// time, but we never send anything. Could have used Unique but that
// bounds us to rust nightly
unsafe impl Send for Handle {}

fn make_rdwr(f: &mut u32) {
    *f = (*f & !rlibc::O_ACCMODE) | rlibc::O_RDWR;
}

fn maybe_unlinkat(dir: RawFd, path: &AsRef<Path>) -> io::Result<()> {
    if let Err(e) = rlibc::unlinkat(dir, path, 0) {
        if !error::is_enoent(&e) {
            return Err(e);
        }
    }
    return Ok(());
}

pub fn mkdirat_all(dir: RawFd, path: &AsRef<Path>, mode: u32) -> io::Result<()> {
    let mut p = PathBuf::new();

    for c in path.as_ref().components() {
        if let Component::Normal(n) = c {
            p.push(n);

            if let Err(e) = rlibc::mkdirat(dir, &p, mode) {
                if e.raw_os_error().unwrap() != libc::EEXIST {
                    return Err(e);
                }
            }
        }
    }

    return Ok(());
}

impl Handle {
    pub fn create(
        src_dir: RawFd,
        cache_dir: RawFd,
        path: &AsRef<Path>,
        flags: u32,
        mode: u32,
    ) -> error::Result<Handle> {
        // need to read the cache file for writeback
        let mut cache_flags = flags.clone();
        if (cache_flags & rlibc::O_ACCMODE) == rlibc::O_WRONLY {
            make_rdwr(&mut cache_flags);
        }
        //debug!("create {:b} {:b} {:#o}", flags, cache_flags, mode);

        if let Some(parent) = path.as_ref().parent() {
            mkdirat_all(cache_dir, &parent, 0o777)?;
        }

        let src_file = File::openat(src_dir, path, flags, mode)?;
        // we are able to create the src file, then the cache file
        // shouldn't be here, but it could be because of bug/crash,
        // so unlink it first
        maybe_unlinkat(cache_dir, path)?;

        return Ok(Handle {
            src_file: src_file,
            cache_file: File::openat(cache_dir, path, cache_flags, mode)?,
            dirty: true,
        });
    }

    pub fn open(
        src_dir: RawFd,
        cache_dir: RawFd,
        path: &AsRef<Path>,
        flags: u32,
        cache_valid_if_present: bool,
    ) -> error::Result<Handle> {
        // even if file is open for write only, I still need to be
        // able to read the src for read-modify-write
        let mut flags = flags;
        if (flags & rlibc::O_ACCMODE) == rlibc::O_WRONLY {
            make_rdwr(&mut flags);
        }

        let valid = Handle::validate_cache(src_dir, cache_dir, &path, cache_valid_if_present)?;
        debug!(
            "{:?} {} a valid cache file",
            path.as_ref(),
            if valid { "is" } else { "is not" },
        );
        let mut cache_flags = flags.clone();

        if !valid {
            // mkdir the parents
            if let Some(parent) = path.as_ref().parent() {
                mkdirat_all(cache_dir, &parent, 0o777)?;
            }
            // need to cache this file so need to open it for write
            cache_flags |= rlibc::O_CREAT;
            if (cache_flags & rlibc::O_ACCMODE) == rlibc::O_RDONLY {
                make_rdwr(&mut cache_flags);
            }
        }

        let src_file: File;
        if valid && (flags & rlibc::O_ACCMODE) == rlibc::O_RDONLY {
            src_file = Default::default();
        } else {
            src_file = File::openat(src_dir, path, flags, 0o666)?;
        }

        let handle = Handle {
            src_file: src_file,
            cache_file: File::openat(cache_dir, path, cache_flags, 0o666)?,
            dirty: false,
        };

        if !valid && (flags & rlibc::O_TRUNC) == 0 {
            debug!("read ahead {:?}", path.as_ref());
            handle.copy(true)?;
        }

        return Ok(handle);
    }

    // the equivalent of:
    // getfattr -e hex --match=.* -d $f 2>/dev/null | grep =;
    // /usr/bin/stat -t --printf "%Y\n%s\n" $f
    // note that mtime is printed first and then size
    pub fn src_str_to_checksum(f: &File) -> error::Result<OsString> {
        let mut s = OsString::new();
        match f.list_xattr() {
            Ok(attrs) => {
                for x in attrs {
                    if let Some(v) = f.get_xattr(&x)? {
                        s.push(x);
                        s.push(OsStr::new("="));
                        s.push("0x");
                        for b in v {
                            s.push(format!("{:x}", b));
                        }
                        s.push("\n");
                    }
                }
            }
            Err(e) => {
                if e.raw_os_error().unwrap() != libc::ENOTSUP {
                    return Err(RError::from(e));
                }
            }
        }

        let st = f.stat()?;
        s.push(format!("{}\n", st.st_mtime));
        s.push(format!("{}\n", st.st_size));
        return Ok(s);
    }

    fn src_chksum(f: &File) -> error::Result<GenericArray<u8, U64>> {
        let s = Handle::src_str_to_checksum(f)?;
        let mut h = Sha512::default();
        h.input(s.as_bytes());
        return Ok(h.result());
    }

    fn set_pristine(&self, pristine: bool) -> error::Result<()> {
        if pristine {
            self.cache_file.set_xattr(
                "user.catfs.src_chksum",
                Handle::src_chksum(&self.src_file)?
                    .as_slice(),
            )?;
        } else {
            self.cache_file.remove_xattr("user.catfs.src_chksum")?;
        }
        return Ok(());
    }

    fn is_pristine(src_file: &File, cache_file: &File) -> error::Result<bool> {
        if let Some(v) = cache_file.get_xattr("user.catfs.src_chksum")? {
            return Ok(v == Handle::src_chksum(src_file)?.as_slice());
        }

        return Ok(false);
    }

    pub fn unlink(src_dir: RawFd, cache_dir: RawFd, path: &AsRef<Path>) -> io::Result<()> {
        maybe_unlinkat(cache_dir, path)?;
        return rlibc::unlinkat(src_dir, path, 0);
    }

    fn validate_cache(
        src_dir: RawFd,
        cache_dir: RawFd,
        path: &AsRef<Path>,
        cache_valid_if_present: bool,
    ) -> error::Result<bool> {
        match File::openat(src_dir, path, rlibc::O_RDONLY, 0) {
            Ok(mut src_file) => {
                match File::openat(cache_dir, path, rlibc::O_RDONLY, 0) {
                    Ok(mut cache_file) => {
                        let valid: bool;
                        if cache_valid_if_present || Handle::is_pristine(&src_file, &cache_file)? {
                            valid = true;
                        } else {
                            valid = false;
                            rlibc::unlinkat(cache_dir, path, 0)?;
                        }
                        src_file.close()?;
                        cache_file.close()?;
                        return Ok(valid);
                    }
                    Err(e) => {
                        src_file.close()?;
                        if error::try_enoent(e)? {
                            return Ok(false);
                        }
                    }
                }
            }
            Err(e) => {
                if error::try_enoent(e)? {
                    // the source file doesn't exist, the cache file shouldn't either
                    maybe_unlinkat(cache_dir, path)?;
                }
            }
        }

        return Ok(false);
    }

    pub fn read(&self, offset: u64, buf: &mut [u8]) -> error::Result<usize> {
        let nwant = buf.len();
        let mut bytes_read: usize = 0;

        while bytes_read < nwant {
            match self.cache_file.read_at(
                &mut buf[bytes_read..],
                offset + (bytes_read as u64),
            ) {
                Ok(nread) => {
                    if nread == 0 {
                        return Ok(bytes_read);
                    }
                    bytes_read += nread;
                }
                Err(e) => {
                    if bytes_read > 0 {
                        return Ok(bytes_read);
                    } else {
                        return Err(RError::from(e));
                    }
                }
            }
        }

        return Ok(bytes_read);
    }

    pub fn write(&mut self, offset: u64, buf: &[u8]) -> error::Result<usize> {
        let nwant = buf.len();
        let mut bytes_written: usize = 0;

        if !self.dirty {
            // assumes that the metadata will hit the disk before the
            // incoming data will, and not flushing
            self.set_pristine(false)?;
        }

        while bytes_written < nwant {
            match self.cache_file.write_at(
                &buf[bytes_written..],
                offset + (bytes_written as u64),
            ) {
                Ok(nwritten) => {
                    if nwritten == 0 {
                        return Ok(bytes_written);
                    }
                    bytes_written += nwritten;
                }
                Err(e) => {
                    if bytes_written > 0 {
                        return Ok(bytes_written);
                    } else {
                        return Err(RError::from(e));
                    }
                }
            }
        }

        if bytes_written != 0 {
            self.dirty = true;
        }

        return Ok(bytes_written);
    }

    pub fn flush(&mut self) -> error::Result<(bool)> {
        let mut flushed_to_src = false;
        if self.dirty {
            self.copy(false)?;
            self.dirty = false;
            self.cache_file.flush()?;
            // if file was opened for read only and cache is valid we
            // might not have opened src_file
            if self.src_file.valid() {
                self.src_file.flush()?;
                flushed_to_src = true;
            }
        }
        return Ok(flushed_to_src);
    }

    fn copy_user(rh: &File, wh: &File) -> error::Result<()> {
        let mut buf = [0u8; 32 * 1024];
        let mut offset = 0;
        loop {
            let nread = rh.read_at(&mut buf, offset)?;
            if nread == 0 {
                break;
            }
            wh.write_at(&buf[..nread], offset)?;
            offset += nread as u64;
        }
        return Ok(());
    }

    fn copy_splice(rh: &File, wh: &File) -> error::Result<()> {
        let (pin, pout) = rlibc::pipe()?;

        let mut offset = 0;
        loop {
            let nread = rlibc::splice(rh.as_raw_fd(), offset, pout, -1, 128 * 1024)?;
            if nread == 0 {
                break;
            }

            let mut written = 0;
            while written < nread {
                let nxfer = rlibc::splice(pin, -1, wh.as_raw_fd(), offset, 128 * 1024)?;

                written += nxfer;
                offset += nxfer as i64;
            }
        }

        if let Err(e) = rlibc::close(pin) {
            rlibc::close(pout)?;
            return Err(RError::from(e));
        } else {
            rlibc::close(pout)?;
        }

        return Ok(());
    }

    fn copy(&self, to_cache: bool) -> error::Result<()> {
        let rh: &File;
        let wh: &File;
        if to_cache {
            rh = &self.src_file;
            wh = &self.cache_file;
        } else {
            rh = &self.cache_file;
            wh = &self.src_file;
        }

        let size = rh.filesize()?;
        if size < wh.filesize()? {
            wh.truncate(size)?;
        }

        if let Err(e) = Handle::copy_splice(rh, wh) {
            if e.raw_os_error().unwrap() == libc::EINVAL {
                Handle::copy_user(rh, wh)?;
            }
        }

        self.set_pristine(true)?;
        return Ok(());
    }
}

impl Drop for Handle {
    fn drop(&mut self) {
        if let Err(e) = self.cache_file.close() {
            error!("!close(cache) = {}", RError::from(e));
        }
        if self.src_file.valid() {
            if let Err(e) = self.src_file.close() {
                error!("!close(src) = {}", RError::from(e));
            }
        }
    }
}
<|endoftext|>


<commit_before>use std::fs::File;
use std::io::Read;
use std::io::Write;
use std::io::{Error, ErrorKind};
use std::path::{Path, PathBuf};
use std::string::String;

const MAX_BRIGHTNESS_BASENAME: &str = "max_brightness";
const BRIGHTNESS_BASENAME: &str = "brightness";

// Defines the apparent path to the directory containing both
// the brightness and the max_brightness files.
const BRIGHTNESS_CONTAINING_DIR: &str = "/sys/class/backlight/intel_backlight/";

// Reads the file named by |path| and returns the integral contents.
fn sysfs_file_to_int(path: &PathBuf) -> std::result::Result<i32, Error> {
    let mut file = File::open(path)?;
    let mut raw_contents = String::new();
    file.read_to_string(&mut raw_contents)?;

    let contents = raw_contents.trim();

    match contents.parse::<i32>() {
        Ok(value) => Ok(value),
        Err(why) => Err(Error::new(
            ErrorKind::InvalidInput,
            format!("parse failure ({:#?}): {}", path, why),
        )),
    }
}

// Stringifies and writes |value| into file named by |path|.
fn write_int(path: &PathBuf, value: i32) -> std::io::Result<()> {
    let mut file = File::create(&path)?;
    file.write_all(&value.to_string().as_bytes())?;
    file.flush()
}

// Reads the current and max brightness; returns the new target
// brightness.
fn target_brightness(current: i32, max: i32) -> Result<i32, std::io::Error> {
    let args: Vec<String> = std::env::args().collect();
    let delta_as_string = &args[1];
    let delta = delta_as_string.parse::<i32>();

    if !delta.is_ok() {
        return Err(Error::new(
            ErrorKind::InvalidInput,
            format!("bad arg: {}", delta_as_string),
        ));
    }

    let tentative = current + delta.unwrap();
    if tentative >= max {
        return Ok(max);
    } else if tentative <= 0 {
        return Ok(0);
    }
    Ok(tentative)
}

fn main() -> Result<(), std::io::Error> {
    let base_dir = Path::new(BRIGHTNESS_CONTAINING_DIR);
    let brightness_path: PathBuf = base_dir.join(BRIGHTNESS_BASENAME);

    let current = sysfs_file_to_int(&brightness_path)?;
    let max = sysfs_file_to_int(&base_dir.join(MAX_BRIGHTNESS_BASENAME))?;

    let new = target_brightness(current, max)?;
    write_int(&brightness_path, new)
}
<commit_msg>avocado-attenborough: cheese min brightness<commit_after>use std::fs::File;
use std::io::Read;
use std::io::Write;
use std::io::{Error, ErrorKind};
use std::path::{Path, PathBuf};
use std::string::String;

const MAX_BRIGHTNESS_BASENAME: &str = "max_brightness";
const BRIGHTNESS_BASENAME: &str = "brightness";

// Defines the apparent path to the directory containing both
// the brightness and the max_brightness files.
const BRIGHTNESS_CONTAINING_DIR: &str = "/sys/class/backlight/intel_backlight/";

// Reads the file named by |path| and returns the integral contents.
fn sysfs_file_to_int(path: &PathBuf) -> std::result::Result<i32, Error> {
    let mut file = File::open(path)?;
    let mut raw_contents = String::new();
    file.read_to_string(&mut raw_contents)?;

    let contents = raw_contents.trim();

    match contents.parse::<i32>() {
        Ok(value) => Ok(value),
        Err(why) => Err(Error::new(
            ErrorKind::InvalidInput,
            format!("parse failure ({:#?}): {}", path, why),
        )),
    }
}

// Stringifies and writes |value| into file named by |path|.
fn write_int(path: &PathBuf, value: i32) -> std::io::Result<()> {
    let mut file = File::create(&path)?;
    file.write_all(&value.to_string().as_bytes())?;
    file.flush()
}

// Reads the current and max brightness; returns the new target
// brightness.
fn target_brightness(current: i32, max: i32) -> Result<i32, std::io::Error> {
    let args: Vec<String> = std::env::args().collect();
    let delta_as_string = &args[1];
    let delta = delta_as_string.parse::<i32>();

    if !delta.is_ok() {
        return Err(Error::new(
            ErrorKind::InvalidInput,
            format!("bad arg: {}", delta_as_string),
        ));
    }

    let tentative = current + delta.unwrap();
    if tentative >= max {
        return Ok(max);
    } else if tentative < 0 {
        // For kicks, set the brightness all the way up if caller is
        // trying to take it below 0.
        if current == 0 {
            return Ok(max);
        }
        return Ok(0);
    }
    Ok(tentative)
}

fn main() -> Result<(), std::io::Error> {
    let base_dir = Path::new(BRIGHTNESS_CONTAINING_DIR);
    let brightness_path: PathBuf = base_dir.join(BRIGHTNESS_BASENAME);

    let current = sysfs_file_to_int(&brightness_path)?;
    let max = sysfs_file_to_int(&base_dir.join(MAX_BRIGHTNESS_BASENAME))?;

    let new = target_brightness(current, max)?;
    write_int(&brightness_path, new)
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! The [Response](https://fetch.spec.whatwg.org/#responses) object
//! resulting from a [fetch operation](https://fetch.spec.whatwg.org/#concept-fetch)
use {FetchMetadata, FilteredMetadata, Metadata, NetworkError};
use hyper::header::{AccessControlExposeHeaders, ContentType, Headers};
use hyper::status::StatusCode;
use hyper_serde::Serde;
use servo_url::ServoUrl;
use std::ascii::AsciiExt;
use std::cell::{Cell, RefCell};
use std::sync::{Arc, Mutex};

/// [Response type](https://fetch.spec.whatwg.org/#concept-response-type)
#[derive(Clone, PartialEq, Debug, Deserialize, Serialize, HeapSizeOf)]
pub enum ResponseType {
    Basic,
    Cors,
    Default,
    Error(NetworkError),
    Opaque,
    OpaqueRedirect
}

/// [Response termination reason](https://fetch.spec.whatwg.org/#concept-response-termination-reason)
#[derive(Debug, Clone, Copy, Deserialize, Serialize, HeapSizeOf)]
pub enum TerminationReason {
    EndUserAbort,
    Fatal,
    Timeout
}

/// The response body can still be pushed to after fetch
/// This provides a way to store unfinished response bodies
#[derive(Clone, Debug, PartialEq, HeapSizeOf)]
pub enum ResponseBody {
    Empty, // XXXManishearth is this necessary, or is Done(vec![]) enough?
    Receiving(Vec<u8>),
    Done(Vec<u8>),
}

impl ResponseBody {
    pub fn is_done(&self) -> bool {
        match *self {
            ResponseBody::Done(..) => true,
            ResponseBody::Empty | ResponseBody::Receiving(..) => false
        }
    }
}

/// [Cache state](https://fetch.spec.whatwg.org/#concept-response-cache-state)
#[derive(Clone, Debug, Deserialize, Serialize, HeapSizeOf)]
pub enum CacheState {
    None,
    Local,
    Validated,
    Partial
}

/// [Https state](https://fetch.spec.whatwg.org/#concept-response-https-state)
#[derive(Debug, Clone, Copy, HeapSizeOf, Deserialize, Serialize)]
pub enum HttpsState {
    None,
    Deprecated,
    Modern
}

pub enum ResponseMsg {
    Chunk(Vec<u8>),
    Finished,
    Errored
}

/// A [Response](https://fetch.spec.whatwg.org/#concept-response) as defined by the Fetch spec
#[derive(Debug, Clone, HeapSizeOf)]
pub struct Response {
    pub response_type: ResponseType,
    pub termination_reason: Option<TerminationReason>,
    url: Option<ServoUrl>,
    pub url_list: RefCell<Vec<ServoUrl>>,
    /// `None` can be considered a StatusCode of `0`.
    #[ignore_heap_size_of = "Defined in hyper"]
    pub status: Option<StatusCode>,
    pub raw_status: Option<(u16, Vec<u8>)>,
    #[ignore_heap_size_of = "Defined in hyper"]
    pub headers: Headers,
    #[ignore_heap_size_of = "Mutex heap size undefined"]
    pub body: Arc<Mutex<ResponseBody>>,
    pub cache_state: CacheState,
    pub https_state: HttpsState,
    pub referrer: Option<ServoUrl>,
    /// [Internal response](https://fetch.spec.whatwg.org/#concept-internal-response), only used if the Response
    /// is a filtered response
    pub internal_response: Option<Box<Response>>,
    /// whether or not to try to return the internal_response when asked for actual_response
    pub return_internal: Cell<bool>,
}

impl Response {
    pub fn new(url: ServoUrl) -> Response {
        Response {
            response_type: ResponseType::Default,
            termination_reason: None,
            url: Some(url),
            url_list: RefCell::new(Vec::new()),
            status: Some(StatusCode::Ok),
            raw_status: Some((200, b"OK".to_vec())),
            headers: Headers::new(),
            body: Arc::new(Mutex::new(ResponseBody::Empty)),
            cache_state: CacheState::None,
            https_state: HttpsState::None,
            referrer: None,
            internal_response: None,
            return_internal: Cell::new(true)
        }
    }

    pub fn network_error(e: NetworkError) -> Response {
        Response {
            response_type: ResponseType::Error(e),
            termination_reason: None,
            url: None,
            url_list: RefCell::new(vec![]),
            status: None,
            raw_status: None,
            headers: Headers::new(),
            body: Arc::new(Mutex::new(ResponseBody::Empty)),
            cache_state: CacheState::None,
            https_state: HttpsState::None,
            referrer: None,
            internal_response: None,
            return_internal: Cell::new(true)
        }
    }

    pub fn url(&self) -> Option<&ServoUrl> {
        self.url.as_ref()
    }

    pub fn is_network_error(&self) -> bool {
        match self.response_type {
            ResponseType::Error(..) => true,
            _ => false
        }
    }

    pub fn get_network_error(&self) -> Option<&NetworkError> {
        match self.response_type {
            ResponseType::Error(ref e) => Some(e),
            _ => None,
        }
    }

    pub fn actual_response(&self) -> &Response {
        if self.return_internal.get() && self.internal_response.is_some() {
            &**self.internal_response.as_ref().unwrap()
        } else {
            self
        }
    }

    pub fn to_actual(self) -> Response {
        if self.return_internal.get() && self.internal_response.is_some() {
            *self.internal_response.unwrap()
        } else {
            self
        }
    }

    /// Convert to a filtered response, of type `filter_type`.
    /// Do not use with type Error or Default
    pub fn to_filtered(self, filter_type: ResponseType) -> Response {
        match filter_type {
            ResponseType::Default | ResponseType::Error(..) => panic!(),
            _ => (),
        }

        let old_response = self.to_actual();

        if let ResponseType::Error(e) = old_response.response_type {
            return Response::network_error(e);
        }

        let old_headers = old_response.headers.clone();
        let mut response = old_response.clone();
        response.internal_response = Some(Box::new(old_response));
        response.response_type = filter_type;

        match response.response_type {
            ResponseType::Default | ResponseType::Error(..) => unreachable!(),

            ResponseType::Basic => {
                let headers = old_headers.iter().filter(|header| {
                    match &*header.name().to_ascii_lowercase() {
                        "set-cookie" | "set-cookie2" => false,
                        _ => true
                    }
                }).collect();
                response.headers = headers;
            },

            ResponseType::Cors => {
                let access = old_headers.get::<AccessControlExposeHeaders>();
                let allowed_headers = access.as_ref().map(|v| &v[..]).unwrap_or(&[]);

                let headers = old_headers.iter().filter(|header| {
                    match &*header.name().to_ascii_lowercase() {
                        "cache-control" | "content-language" | "content-type" |
                        "expires" | "last-modified" | "pragma" => true,
                        "set-cookie" | "set-cookie2" => false,
                        header => {
                            let result =
                                allowed_headers.iter().find(|h| *header == *h.to_ascii_lowercase());
                            result.is_some()
                        }
                    }
                }).collect();
                response.headers = headers;
            },

            ResponseType::Opaque => {
                response.url_list = RefCell::new(vec![]);
                response.url = None;
                response.headers = Headers::new();
                response.status = None;
                response.body = Arc::new(Mutex::new(ResponseBody::Empty));
                response.cache_state = CacheState::None;
            },

            ResponseType::OpaqueRedirect => {
                response.headers = Headers::new();
                response.status = None;
                response.body = Arc::new(Mutex::new(ResponseBody::Empty));
                response.cache_state = CacheState::None;
            }
        }

        response
    }

    pub fn metadata(&self) -> Result<FetchMetadata, NetworkError> {
        fn init_metadata(response: &Response, url: &ServoUrl) -> Metadata {
            let mut metadata = Metadata::default(url.clone());
            metadata.set_content_type(match response.headers.get() {
                Some(&ContentType(ref mime)) => Some(mime),
                None => None
            });
            metadata.headers = Some(Serde(response.headers.clone()));
            metadata.status = response.raw_status.clone();
            metadata.https_state = response.https_state;
            metadata.referrer = response.referrer.clone();
            metadata
        };

        if let Some(error) = self.get_network_error() {
            return Err(error.clone());
        }

        let metadata = self.url.as_ref().map(|url| init_metadata(self, url));

        if let Some(ref response) = self.internal_response {
            match response.url {
                Some(ref url) => {
                    let unsafe_metadata = init_metadata(response, url);

                    Ok(FetchMetadata::Filtered {
                        filtered: match metadata {
                            Some(m) => FilteredMetadata::Transparent(m),
                            None => FilteredMetadata::Opaque
                        },
                        unsafe_: unsafe_metadata
                    })
                }
                None => Err(NetworkError::Internal("No url found in unsafe response".to_owned()))
            }
        } else {
            Ok(FetchMetadata::Unfiltered(metadata.unwrap()))
        }
    }
}
<commit_msg>Skip rustfmt on to_filtered.<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! The [Response](https://fetch.spec.whatwg.org/#responses) object
//! resulting from a [fetch operation](https://fetch.spec.whatwg.org/#concept-fetch)
use {FetchMetadata, FilteredMetadata, Metadata, NetworkError};
use hyper::header::{AccessControlExposeHeaders, ContentType, Headers};
use hyper::status::StatusCode;
use hyper_serde::Serde;
use servo_url::ServoUrl;
use std::ascii::AsciiExt;
use std::cell::{Cell, RefCell};
use std::sync::{Arc, Mutex};

/// [Response type](https://fetch.spec.whatwg.org/#concept-response-type)
#[derive(Clone, PartialEq, Debug, Deserialize, Serialize, HeapSizeOf)]
pub enum ResponseType {
    Basic,
    Cors,
    Default,
    Error(NetworkError),
    Opaque,
    OpaqueRedirect
}

/// [Response termination reason](https://fetch.spec.whatwg.org/#concept-response-termination-reason)
#[derive(Debug, Clone, Copy, Deserialize, Serialize, HeapSizeOf)]
pub enum TerminationReason {
    EndUserAbort,
    Fatal,
    Timeout
}

/// The response body can still be pushed to after fetch
/// This provides a way to store unfinished response bodies
#[derive(Clone, Debug, PartialEq, HeapSizeOf)]
pub enum ResponseBody {
    Empty, // XXXManishearth is this necessary, or is Done(vec![]) enough?
    Receiving(Vec<u8>),
    Done(Vec<u8>),
}

impl ResponseBody {
    pub fn is_done(&self) -> bool {
        match *self {
            ResponseBody::Done(..) => true,
            ResponseBody::Empty | ResponseBody::Receiving(..) => false
        }
    }
}

/// [Cache state](https://fetch.spec.whatwg.org/#concept-response-cache-state)
#[derive(Clone, Debug, Deserialize, Serialize, HeapSizeOf)]
pub enum CacheState {
    None,
    Local,
    Validated,
    Partial
}

/// [Https state](https://fetch.spec.whatwg.org/#concept-response-https-state)
#[derive(Debug, Clone, Copy, HeapSizeOf, Deserialize, Serialize)]
pub enum HttpsState {
    None,
    Deprecated,
    Modern
}

pub enum ResponseMsg {
    Chunk(Vec<u8>),
    Finished,
    Errored
}

/// A [Response](https://fetch.spec.whatwg.org/#concept-response) as defined by the Fetch spec
#[derive(Debug, Clone, HeapSizeOf)]
pub struct Response {
    pub response_type: ResponseType,
    pub termination_reason: Option<TerminationReason>,
    url: Option<ServoUrl>,
    pub url_list: RefCell<Vec<ServoUrl>>,
    /// `None` can be considered a StatusCode of `0`.
    #[ignore_heap_size_of = "Defined in hyper"]
    pub status: Option<StatusCode>,
    pub raw_status: Option<(u16, Vec<u8>)>,
    #[ignore_heap_size_of = "Defined in hyper"]
    pub headers: Headers,
    #[ignore_heap_size_of = "Mutex heap size undefined"]
    pub body: Arc<Mutex<ResponseBody>>,
    pub cache_state: CacheState,
    pub https_state: HttpsState,
    pub referrer: Option<ServoUrl>,
    /// [Internal response](https://fetch.spec.whatwg.org/#concept-internal-response), only used if the Response
    /// is a filtered response
    pub internal_response: Option<Box<Response>>,
    /// whether or not to try to return the internal_response when asked for actual_response
    pub return_internal: Cell<bool>,
}

impl Response {
    pub fn new(url: ServoUrl) -> Response {
        Response {
            response_type: ResponseType::Default,
            termination_reason: None,
            url: Some(url),
            url_list: RefCell::new(Vec::new()),
            status: Some(StatusCode::Ok),
            raw_status: Some((200, b"OK".to_vec())),
            headers: Headers::new(),
            body: Arc::new(Mutex::new(ResponseBody::Empty)),
            cache_state: CacheState::None,
            https_state: HttpsState::None,
            referrer: None,
            internal_response: None,
            return_internal: Cell::new(true)
        }
    }

    pub fn network_error(e: NetworkError) -> Response {
        Response {
            response_type: ResponseType::Error(e),
            termination_reason: None,
            url: None,
            url_list: RefCell::new(vec![]),
            status: None,
            raw_status: None,
            headers: Headers::new(),
            body: Arc::new(Mutex::new(ResponseBody::Empty)),
            cache_state: CacheState::None,
            https_state: HttpsState::None,
            referrer: None,
            internal_response: None,
            return_internal: Cell::new(true)
        }
    }

    pub fn url(&self) -> Option<&ServoUrl> {
        self.url.as_ref()
    }

    pub fn is_network_error(&self) -> bool {
        match self.response_type {
            ResponseType::Error(..) => true,
            _ => false
        }
    }

    pub fn get_network_error(&self) -> Option<&NetworkError> {
        match self.response_type {
            ResponseType::Error(ref e) => Some(e),
            _ => None,
        }
    }

    pub fn actual_response(&self) -> &Response {
        if self.return_internal.get() && self.internal_response.is_some() {
            &**self.internal_response.as_ref().unwrap()
        } else {
            self
        }
    }

    pub fn to_actual(self) -> Response {
        if self.return_internal.get() && self.internal_response.is_some() {
            *self.internal_response.unwrap()
        } else {
            self
        }
    }

    /// Convert to a filtered response, of type `filter_type`.
    /// Do not use with type Error or Default
    #[cfg_attr(rustfmt, rustfmt_skip)]
    pub fn to_filtered(self, filter_type: ResponseType) -> Response {
        match filter_type {
            ResponseType::Default | ResponseType::Error(..) => panic!(),
            _ => (),
        }

        let old_response = self.to_actual();

        if let ResponseType::Error(e) = old_response.response_type {
            return Response::network_error(e);
        }

        let old_headers = old_response.headers.clone();
        let mut response = old_response.clone();
        response.internal_response = Some(Box::new(old_response));
        response.response_type = filter_type;

        match response.response_type {
            ResponseType::Default | ResponseType::Error(..) => unreachable!(),

            ResponseType::Basic => {
                let headers = old_headers.iter().filter(|header| {
                    match &*header.name().to_ascii_lowercase() {
                        "set-cookie" | "set-cookie2" => false,
                        _ => true
                    }
                }).collect();
                response.headers = headers;
            },

            ResponseType::Cors => {
                let access = old_headers.get::<AccessControlExposeHeaders>();
                let allowed_headers = access.as_ref().map(|v| &v[..]).unwrap_or(&[]);

                let headers = old_headers.iter().filter(|header| {
                    match &*header.name().to_ascii_lowercase() {
                        "cache-control" | "content-language" | "content-type" |
                        "expires" | "last-modified" | "pragma" => true,
                        "set-cookie" | "set-cookie2" => false,
                        header => {
                            let result =
                                allowed_headers.iter().find(|h| *header == *h.to_ascii_lowercase());
                            result.is_some()
                        }
                    }
                }).collect();
                response.headers = headers;
            },

            ResponseType::Opaque => {
                response.url_list = RefCell::new(vec![]);
                response.url = None;
                response.headers = Headers::new();
                response.status = None;
                response.body = Arc::new(Mutex::new(ResponseBody::Empty));
                response.cache_state = CacheState::None;
            },

            ResponseType::OpaqueRedirect => {
                response.headers = Headers::new();
                response.status = None;
                response.body = Arc::new(Mutex::new(ResponseBody::Empty));
                response.cache_state = CacheState::None;
            }
        }

        response
    }

    pub fn metadata(&self) -> Result<FetchMetadata, NetworkError> {
        fn init_metadata(response: &Response, url: &ServoUrl) -> Metadata {
            let mut metadata = Metadata::default(url.clone());
            metadata.set_content_type(match response.headers.get() {
                Some(&ContentType(ref mime)) => Some(mime),
                None => None
            });
            metadata.headers = Some(Serde(response.headers.clone()));
            metadata.status = response.raw_status.clone();
            metadata.https_state = response.https_state;
            metadata.referrer = response.referrer.clone();
            metadata
        };

        if let Some(error) = self.get_network_error() {
            return Err(error.clone());
        }

        let metadata = self.url.as_ref().map(|url| init_metadata(self, url));

        if let Some(ref response) = self.internal_response {
            match response.url {
                Some(ref url) => {
                    let unsafe_metadata = init_metadata(response, url);

                    Ok(FetchMetadata::Filtered {
                        filtered: match metadata {
                            Some(m) => FilteredMetadata::Transparent(m),
                            None => FilteredMetadata::Opaque
                        },
                        unsafe_: unsafe_metadata
                    })
                }
                None => Err(NetworkError::Internal("No url found in unsafe response".to_owned()))
            }
        } else {
            Ok(FetchMetadata::Unfiltered(metadata.unwrap()))
        }
    }
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use syntax::ast::MetaItem;
use syntax::codemap::Span;
use syntax::ext::base::{Annotatable, ExtCtxt};
use syntax::ptr::P;

pub fn expand_dom_struct(cx: &mut ExtCtxt, sp: Span, _: &MetaItem, anno: Annotatable) -> Annotatable {
    if let Annotatable::Item(item) = anno {
        let mut item2 = (*item).clone();
        item2.attrs.push(quote_attr!(cx, #[must_root]));
        item2.attrs.push(quote_attr!(cx, #[privatize]));
        item2.attrs.push(quote_attr!(cx, #[derive(JSTraceable)]));
        item2.attrs.push(quote_attr!(cx, #[derive(HeapSizeOf)]));

        // The following attributes are only for internal usage
        item2.attrs.push(quote_attr!(cx, #[_generate_reflector]));
        // #[dom_struct] gets consumed, so this lets us keep around a residue
        // Do NOT register a modifier/decorator on this attribute
        item2.attrs.push(quote_attr!(cx, #[_dom_struct_marker]));
        Annotatable::Item(P(item2))
    } else {
        cx.span_err(sp, "#[dom_struct] applied to something other than a struct");
        anno
    }
}
<commit_msg>Mark DOM interfaces as repr(C)<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use syntax::ast::MetaItem;
use syntax::codemap::Span;
use syntax::ext::base::{Annotatable, ExtCtxt};
use syntax::ptr::P;

pub fn expand_dom_struct(cx: &mut ExtCtxt, sp: Span, _: &MetaItem, anno: Annotatable) -> Annotatable {
    if let Annotatable::Item(item) = anno {
        let mut item2 = (*item).clone();
        item2.attrs.push(quote_attr!(cx, #[must_root]));
        item2.attrs.push(quote_attr!(cx, #[privatize]));
        item2.attrs.push(quote_attr!(cx, #[repr(C)]));
        item2.attrs.push(quote_attr!(cx, #[derive(JSTraceable)]));
        item2.attrs.push(quote_attr!(cx, #[derive(HeapSizeOf)]));

        // The following attributes are only for internal usage
        item2.attrs.push(quote_attr!(cx, #[_generate_reflector]));
        // #[dom_struct] gets consumed, so this lets us keep around a residue
        // Do NOT register a modifier/decorator on this attribute
        item2.attrs.push(quote_attr!(cx, #[_dom_struct_marker]));
        Annotatable::Item(P(item2))
    } else {
        cx.span_err(sp, "#[dom_struct] applied to something other than a struct");
        anno
    }
}
<|endoftext|>