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
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");
|
|
}
|
|
}
|
|
|