Backup, Synchronize, Versionize
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

255 lines
7.6 KiB

// This file is part of bsv.
//
// bsv is free software: you can redistribute it and/or modify it under the
// terms of the GNU Affero General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// bsv 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 Affero General Public License for
// more details.
//
// You should have received a copy of the Affero GNU General Public License
// along with bsv. If not, see <https://www.gnu.org/licenses/>.
use camino::{Utf8Path, Utf8PathBuf};
use toml::Value;
use cas_core::{err, Error, Result};
#[derive(Debug, Clone)]
pub struct PathPair {
pub logic: Utf8PathBuf,
pub physic: Utf8PathBuf,
}
impl PathPair {
pub fn new(logic: Utf8PathBuf, physic: Utf8PathBuf) -> Self {
Self { logic, physic }
}
}
#[derive(Debug)]
pub struct PathMap {
pairs: Vec<PathPair>,
logic_order: Vec<usize>,
physic_order: Vec<usize>,
}
impl PathMap {
pub fn new() -> PathMap {
PathMap {
pairs: vec![],
logic_order: vec![],
physic_order: vec![],
}
}
pub fn from_vec(vec: Vec<PathPair>) -> Result<Self> {
if let Some(pair) =
vec.iter()
.filter(|p| !p.logic.is_absolute())
.next() {
return err!("relative logic path: {}", pair.logic);
}
let mut logic_order: Vec<_> = (0..vec.len()).collect();
logic_order.sort_by(|&i0, &i1| vec[i0].logic.cmp(&vec[i1].logic).reverse());
if let Some((&index, _)) =
logic_order[..logic_order.len()-1].iter()
.zip(logic_order[1..].iter())
.filter(|(&i0, &i1)| vec[i0].logic == vec[i1].logic)
.next() {
return err!("duplicate logic path: {:?}", vec[index]);
}
let mut physic_order: Vec<_> = (0..vec.len()).collect();
physic_order.sort_by(|&i0, &i1| vec[i0].physic.cmp(&vec[i1].physic).reverse());
if let Some((&index, _)) =
physic_order[..physic_order.len()-1].iter()
.zip(physic_order[1..].iter())
.filter(|(&i0, &i1)| vec[i0].physic == vec[i1].physic)
.next() {
return err!("duplicate physic path: {:?}", vec[index]);
}
Ok(Self {
pairs: vec,
logic_order,
physic_order,
})
}
pub fn from_toml_value(config: &Value) -> Result<Self> {
let map = if let Some(mapping) = config.get("mapping") {
if let Some(ref table) = mapping.as_table() {
table.iter()
.map(|(k, v)| Ok(PathPair{
logic: k.into(),
physic: Utf8PathBuf::from_path_buf(
Utf8Path::new(
v.as_str()
.ok_or_else(|| Error::unknown("mapping values must be strings"))?
).canonicalize()?
).or_else(|e| err!("non-unicode character in path: {:?}", e))?
}))
.collect::<Result<_>>()?
}
else {
err!("mapping must be a table, got {}", mapping)?
}
}
else {
vec![]
};
Self::from_vec(map)
}
pub fn physic_from_logic<P: AsRef<Utf8Path>>(&self, logic_path: P) -> Option<Utf8PathBuf> {
let path = logic_path.as_ref();
let pair = self.logic_order.iter()
.map(|&i| &self.pairs[i])
.filter(|p| path.starts_with(&p.logic))
.next()?;
let mut physic = pair.physic.clone();
physic.push(
path.strip_prefix(&pair.logic)
.ok()?
);
return Some(physic)
}
pub fn logic_from_physic<P: AsRef<Utf8Path>>(&self, physic_path: P) -> Option<Utf8PathBuf> {
let path = physic_path.as_ref();
let pair = self.physic_order.iter()
.map(|&i| &self.pairs[i])
.filter(|p| path.starts_with(&p.physic))
.next()?;
let mut logic = pair.logic.clone();
logic.push(
path.strip_prefix(&pair.physic)
.ok()?
);
return Some(logic)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_path_map() {
let path_map = PathMap::from_vec(vec![
PathPair::new("/foo/bar".into(), "/home/user/bar".into()),
]).unwrap();
assert_eq!(
path_map.physic_from_logic("/bar"),
None,
);
assert_eq!(
path_map.physic_from_logic("/foo/bar"),
Some("/home/user/bar".into()),
);
assert_eq!(
path_map.physic_from_logic("/foo/bar/file.txt"),
Some("/home/user/bar/file.txt".into()),
);
assert_eq!(
path_map.logic_from_physic("/home/user"),
None,
);
assert_eq!(
path_map.logic_from_physic("/home/user/bar"),
Some("/foo/bar".into()),
);
assert_eq!(
path_map.logic_from_physic("/home/user/bar/file.txt"),
Some("/foo/bar/file.txt".into()),
);
}
#[test]
fn test_path_map_subdirs() {
let path_map = PathMap::from_vec(vec![
PathPair::new("/foo/bar/baz".into(), "/home/user/bar/test".into()),
PathPair::new("/foo/bar".into(), "/home/user/bar".into()),
]).unwrap();
assert_eq!(
path_map.physic_from_logic("/foo/bar/file.txt"),
Some("/home/user/bar/file.txt".into()),
);
assert_eq!(
path_map.physic_from_logic("/foo/bar/baz/file.txt"),
Some("/home/user/bar/test/file.txt".into()),
);
assert_eq!(
path_map.logic_from_physic("/home/user/bar/file.txt"),
Some("/foo/bar/file.txt".into()),
);
assert_eq!(
path_map.logic_from_physic("/home/user/bar/test/file.txt"),
Some("/foo/bar/baz/file.txt".into()),
);
}
#[test]
fn test_path_map_crossed_subdirs() {
let path_map = PathMap::from_vec(vec![
PathPair::new("/foo/bar/baz".into(), "/home/user/bar".into()),
PathPair::new("/foo/bar".into(), "/home/user/bar/test".into()),
]).unwrap();
assert_eq!(
path_map.physic_from_logic("/foo/bar/baz/file.txt"),
Some("/home/user/bar/file.txt".into()),
);
assert_eq!(
path_map.physic_from_logic("/foo/bar/file.txt"),
Some("/home/user/bar/test/file.txt".into()),
);
assert_eq!(
path_map.logic_from_physic("/home/user/bar/test/file.txt"),
Some("/foo/bar/file.txt".into()),
);
assert_eq!(
path_map.logic_from_physic("/home/user/bar/file.txt"),
Some("/foo/bar/baz/file.txt".into()),
);
}
#[test]
fn test_path_fail_on_duplicates() {
PathMap::from_vec(vec![
PathPair::new("/foo/bar".into(), "/home/user/bar".into()),
PathPair::new("/foo/bar".into(), "/home/user/bar/test".into()),
]).expect_err("should fail on duplicate logic path");
PathMap::from_vec(vec![
PathPair::new("/foo/bar/baz".into(), "/home/user/bar".into()),
PathPair::new("/foo/bar".into(), "/home/user/bar".into()),
]).expect_err("should fail on duplicate physic path");
}
}