2020-06-01 14:56:24 +00:00
|
|
|
use std::path::{PathBuf,Path};
|
2020-04-30 20:11:06 +00:00
|
|
|
use std::fs;
|
|
|
|
use std::os::unix::fs::MetadataExt;
|
|
|
|
use std::ffi::OsString;
|
|
|
|
|
2020-05-01 11:19:33 +00:00
|
|
|
#[derive(Debug)]
|
2020-06-10 11:04:35 +00:00
|
|
|
pub enum Error {
|
2020-06-01 14:56:24 +00:00
|
|
|
EntryNotFound(String)
|
2020-05-01 11:19:33 +00:00
|
|
|
}
|
|
|
|
|
2020-06-01 14:56:24 +00:00
|
|
|
#[derive(Debug, Clone)]
|
2020-04-30 20:11:06 +00:00
|
|
|
pub struct Entry {
|
2020-04-30 21:28:25 +00:00
|
|
|
pub name: OsString,
|
2020-06-01 14:56:24 +00:00
|
|
|
pub path: PathBuf,
|
|
|
|
pub base_dir: PathBuf,
|
2020-04-30 20:11:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Entry {
|
2020-06-01 14:56:24 +00:00
|
|
|
pub fn new(path: PathBuf, name: OsString, base_dir: PathBuf) -> Entry {
|
2020-04-30 20:11:06 +00:00
|
|
|
Entry {
|
2020-06-01 14:56:24 +00:00
|
|
|
path,
|
|
|
|
name,
|
2020-04-30 20:11:06 +00:00
|
|
|
base_dir,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-01 14:56:24 +00:00
|
|
|
pub fn read_setting(&self, setting: &str) -> Option<String> {
|
|
|
|
let mut path = self.path.clone();
|
2020-04-30 20:11:06 +00:00
|
|
|
path.set_extension(setting);
|
|
|
|
read_or_none(&path)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-10 11:04:35 +00:00
|
|
|
/// Load single entry from folder
|
2020-07-11 13:12:16 +00:00
|
|
|
pub fn entry(basedir: &Path, filter: impl Fn(&Path) -> bool, name: &str) -> Option<Entry> {
|
2020-06-10 11:04:35 +00:00
|
|
|
let basepath = PathBuf::from(&basedir);
|
|
|
|
let path = basepath.clone().join(name);
|
|
|
|
|
|
|
|
if !path.exists() || !filter(&path) {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
|
|
|
Some(Entry::new(
|
2020-07-01 15:17:54 +00:00
|
|
|
path.clone(),
|
|
|
|
path.file_name().expect("Failed to read file name").to_os_string(),
|
|
|
|
basepath
|
|
|
|
))
|
2020-06-10 11:04:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Loads entire database from folder
|
2020-07-11 13:12:16 +00:00
|
|
|
pub fn from(path_name: &Path, filter: impl Fn(&Path) -> bool) -> Result<Vec<Entry>, std::io::Error> {
|
2020-06-01 14:56:24 +00:00
|
|
|
let path = PathBuf::from(&path_name);
|
2020-04-30 20:11:06 +00:00
|
|
|
|
2020-06-01 14:56:24 +00:00
|
|
|
let mut entries = Vec::new();
|
2020-04-30 20:11:06 +00:00
|
|
|
|
2020-06-01 14:56:24 +00:00
|
|
|
for file in path.read_dir()? {
|
|
|
|
if file.is_err() {
|
|
|
|
// Dismiss individual errors (in case there's a permission problem)
|
|
|
|
// TODO: maybe print a warning? Actually a configurable error level
|
|
|
|
// (using enum variants) should be passed to the function to configure
|
|
|
|
// whether to continue silently or error out for individual files
|
|
|
|
continue;
|
2020-04-30 20:11:06 +00:00
|
|
|
}
|
2020-06-01 14:56:24 +00:00
|
|
|
let entry = file?.path();
|
|
|
|
if filter(&entry) {
|
|
|
|
entries.push(
|
|
|
|
Entry::new(
|
|
|
|
entry.clone(),
|
|
|
|
entry.file_name().expect("Failed to read file name").to_os_string(),
|
|
|
|
path.clone()
|
2020-07-01 15:17:54 +00:00
|
|
|
)
|
|
|
|
);
|
2020-05-01 11:19:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-01 14:56:24 +00:00
|
|
|
return Ok(entries);
|
2020-04-30 20:11:06 +00:00
|
|
|
}
|
|
|
|
|
2020-06-01 14:56:24 +00:00
|
|
|
/// Reads the file and strips whitespace (including newlines)
|
2020-04-30 20:11:06 +00:00
|
|
|
/// Useful for file-based key-value store
|
2020-06-01 14:56:24 +00:00
|
|
|
pub fn read_or_none(path: &Path) -> Option<String> {
|
2020-04-30 20:11:06 +00:00
|
|
|
if !path.is_file() {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
match fs::read_to_string(path) {
|
|
|
|
Ok(content) => {
|
|
|
|
// Remove trailing space/newlines
|
|
|
|
Some(content.trim().to_string())
|
|
|
|
},
|
|
|
|
Err(e) => {
|
|
|
|
eprintln!("IO ERROR: {}", e);
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn list_files(path: &Path) -> Vec<PathBuf> {
|
|
|
|
let mut res = Vec::new();
|
|
|
|
match fs::read_dir(path) {
|
|
|
|
Ok(files) => {
|
|
|
|
for r in files {
|
|
|
|
match r {
|
|
|
|
Ok(entry) => {
|
|
|
|
let file = entry.path();
|
|
|
|
if file.is_file() {
|
|
|
|
res.push(file);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Err(e) => {
|
|
|
|
eprintln!("IOERROR: {}", e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Err(e) => {
|
|
|
|
eprintln!("IOERROR: {}", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// If the file doesn't exist or fails, return false
|
2020-06-01 14:56:24 +00:00
|
|
|
pub fn is_executable(path: &Path) -> bool {
|
|
|
|
// Do not match directories
|
|
|
|
if !path.is_file() {
|
|
|
|
return false;
|
|
|
|
}
|
2020-04-30 20:11:06 +00:00
|
|
|
match fs::metadata(path) {
|
|
|
|
Ok(stat) => {
|
|
|
|
let mode = stat.mode();
|
|
|
|
// Check user exec permission)
|
|
|
|
if (mode & 0o100) == 0o100 {
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Err(e) => {
|
|
|
|
eprintln!("IO Error: {}", e);
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-07-01 15:17:54 +00:00
|
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn can_load_db() {
|
|
|
|
let base_dir = "tests/success";
|
|
|
|
let entries = from(base_dir, is_executable).expect("Could not load db");
|
|
|
|
let entries_names: Vec<String> = entries.iter().map(|x| x.name.clone().into_string().unwrap()).collect();
|
|
|
|
|
|
|
|
let expected: Vec<String> = vec!("task", "symlink", "no_source").iter().map(|x| x.to_string()).collect();
|
|
|
|
|
|
|
|
assert_eq!(expected.len(), entries_names.len());
|
|
|
|
|
|
|
|
for entry in expected {
|
|
|
|
if !entries_names.contains(&entry.to_string()) {
|
|
|
|
panic!("Could not find {}", &entry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|