1
use self::index_path::IndexPath;
2
use crate::server_error::MyError;
3
use sophia::{
4
    api::{
5
        graph::MutableGraph,
6
        ns::{rdf::type_, Namespace},
7
        parser::TripleParser,
8
        serializer::{Stringifier, TripleSerializer},
9
        source::TripleSource,
10
        triple::Triple,
11
    },
12
    inmem::graph::LightGraph,
13
    iri::Iri,
14
    turtle::{parser::turtle::TurtleParser, serializer::turtle::TurtleSerializer},
15
};
16
use std::{error::Error, path::Path};
17

            
18
#[derive(PartialEq, Eq, PartialOrd, Ord)]
19
pub struct IndexEntry {
20
    pub name: String,
21
    pub base_uri: Option<String>,
22
}
23

            
24
pub(crate) mod index_path {
25
    use chrono::{DateTime, Utc};
26
    use std::{
27
        ffi::OsStr,
28
        fs::DirEntry,
29
        path::{Path, PathBuf},
30
    };
31

            
32
    pub(crate) struct IndexPath(PathBuf);
33

            
34
    impl IndexPath {
35
        pub(crate) fn to_path_buf(&self) -> PathBuf {
36
            self.0.clone()
37
        }
38
        fn entries(&self) -> std::io::Result<impl Iterator<Item = DirEntry>> {
39
            std::fs::read_dir(self.0.parent().unwrap()).map(|iter| {
40
                iter.filter_map(|e| e.ok()).filter(|e| {
41
                    if e.file_name() == INDEX_NAME {
42
                        return false;
43
                    }
44
                    // skip entries that are not files
45
                    !e.file_type().map(|ft| ft.is_file()).unwrap_or_default()
46
                })
47
            })
48
        }
49
        pub(crate) fn file_paths(&self) -> std::io::Result<impl Iterator<Item = PathBuf>> {
50
            self.entries().map(|f| f.map(|e| e.path()))
51
        }
52
        pub(crate) fn files(&self) -> std::io::Result<impl Iterator<Item = String>> {
53
            self.entries()
54
                .map(|f| f.filter_map(|path| path.file_name().to_str().map(|s| s.to_string())))
55
        }
56
        pub(crate) fn dir_name(&self) -> &OsStr {
57
            self.0.parent().unwrap().file_name().unwrap()
58
        }
59
        pub(crate) fn date(&self) -> DateTime<Utc> {
60
            DateTime::parse_from_rfc3339(self.dir_name().to_str().unwrap())
61
                .unwrap()
62
                .into()
63
        }
64
    }
65

            
66
    #[derive(Debug, thiserror::Error)]
67
    #[error("{0}")]
68
    pub(crate) struct InvalidIndexPath(PathBuf);
69

            
70
    const INDEX_NAME: &str = "index.ttl";
71

            
72
    impl TryFrom<PathBuf> for IndexPath {
73
        type Error = InvalidIndexPath;
74

            
75
        fn try_from(dir: PathBuf) -> Result<Self, Self::Error> {
76
            let file_name = if let Some(file_name) = dir.file_name() {
77
                file_name
78
            } else {
79
                return Err(InvalidIndexPath(dir));
80
            };
81
            let file_name = if let Some(file_name) = file_name.to_str() {
82
                file_name
83
            } else {
84
                return Err(InvalidIndexPath(dir));
85
            };
86
            if DateTime::parse_from_rfc3339(file_name).is_err() {
87
                return Err(InvalidIndexPath(dir));
88
            }
89
            let path = dir.join(INDEX_NAME);
90
            if !path.exists() {
91
                return Err(InvalidIndexPath(dir));
92
            }
93
            Ok(Self(path))
94
        }
95
    }
96
    impl TryFrom<&Path> for IndexPath {
97
        type Error = InvalidIndexPath;
98
        fn try_from(value: &Path) -> Result<Self, Self::Error> {
99
            IndexPath::try_from(value.to_path_buf())
100
        }
101
    }
102

            
103
    impl AsRef<Path> for IndexPath {
104
        fn as_ref(&self) -> &Path {
105
            &self.0
106
        }
107
    }
108
}
109

            
110
pub(crate) fn read_index(index_ttl: &index_path::IndexPath) -> Result<Vec<IndexEntry>, MyError> {
111
    println!("{}", index_ttl.as_ref().display());
112
    let rdf = std::fs::read_to_string(index_ttl)
113
        .map_err(|e| MyError::io_error(index_ttl.to_path_buf(), e))?;
114
    let base: Iri<String> = Iri::new("http://example.com/".to_string())?;
115
    let parser = TurtleParser { base: Some(base) };
116
    let mut source = parser.parse_str(&rdf);
117
    let mut entries = Vec::new();
118
    let ldp = Namespace::new("http://www.w3.org/ns/ldp#")?;
119
    let ldp_contains = ldp.get("contains")?;
120
    source
121
        .for_each_triple(|t| {
122
            let p = t.p();
123
            if ldp_contains.eq(&p) {
124
                entries.push(IndexEntry {
125
                    name: t.s().to_string(),
126
                    base_uri: None,
127
                });
128
            }
129
        })
130
        .map_err(MyError::format_error)?;
131
    Ok(entries)
132
}
133

            
134
pub fn write_index(index_path: &Path, entries: &[IndexEntry]) -> Result<(), Box<dyn Error>> {
135
    let mut graph = LightGraph::new();
136
    let local = Namespace::new("")?;
137
    let ldp = Namespace::new("http://www.w3.org/ns/ldp#")?;
138
    let ldp_container = ldp.get("Container")?;
139
    let ldpc = local.get("")?;
140
    let ldp_contains = ldp.get("contains")?;
141
    graph.insert(ldpc, type_, ldp_container)?;
142
    for entry in entries {
143
        graph.insert(ldpc, ldp_contains, local.get(&entry.name)?)?;
144
    }
145
    let mut stringifier = TurtleSerializer::new_stringifier();
146
    let rdf = stringifier.serialize_graph(&graph)?.as_str();
147
    std::fs::write(index_path, rdf)?;
148
    println!("wrote {}", index_path.display());
149
    Ok(())
150
}
151

            
152
pub fn read_entries(path: &Path) -> Result<Vec<IndexEntry>, Box<dyn Error>> {
153
    let index_path = IndexPath::try_from(path)?;
154
    let mut names = Vec::new();
155
    for name in index_path.files()? {
156
        names.push(IndexEntry {
157
            name,
158
            base_uri: None,
159
        });
160
    }
161
    Ok(names)
162
}
163

            
164
pub fn check_index(path: &Path) -> Result<(), Box<dyn Error>> {
165
    let index_path = IndexPath::try_from(path)?;
166
    read_index(&index_path)?;
167
    Ok(())
168
}