box_format/file/
mod.rs

1use std::collections::HashMap;
2use std::num::NonZeroU64;
3
4#[repr(transparent)]
5#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
6pub struct Inode(NonZeroU64);
7
8impl Inode {
9    pub fn new(value: u64) -> std::io::Result<Inode> {
10        match NonZeroU64::new(value) {
11            Some(v) => Ok(Inode(v)),
12            None => Err(std::io::Error::new(
13                std::io::ErrorKind::InvalidInput,
14                "inode must not be zero",
15            )),
16        }
17    }
18
19    pub fn get(self) -> u64 {
20        self.0.get()
21    }
22}
23
24pub(crate) mod meta;
25#[cfg(feature = "reader")]
26pub mod reader;
27#[cfg(feature = "writer")]
28pub mod writer;
29
30pub use self::meta::BoxMetadata;
31
32pub type AttrMap = HashMap<usize, Vec<u8>>;
33
34#[cfg(test)]
35mod tests {
36    use crate::{compression::Compression, *};
37    use std::collections::HashMap;
38    use std::io::prelude::*;
39    use std::io::Cursor;
40    use std::path::Path;
41
42    fn create_test_box<F: AsRef<Path>>(filename: F) {
43        let _ = std::fs::remove_file(filename.as_ref());
44
45        let mut cursor: Cursor<Vec<u8>> = Cursor::new(vec![]);
46        let data = b"hello\0\0\0";
47        cursor.write_all(data).unwrap();
48        cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
49
50        let mut writer = BoxFileWriter::create(filename).unwrap();
51        writer
52            .insert(
53                Compression::Stored,
54                BoxPath::new("hello.txt").unwrap(),
55                &mut cursor,
56                HashMap::new(),
57            )
58            .unwrap();
59    }
60
61    #[test]
62    fn create_box_file() {
63        create_test_box("./smoketest.box");
64    }
65
66    #[test]
67    fn read_garbage() {
68        let filename = "./read_garbage.box";
69        create_test_box(filename);
70
71        let bf = BoxFileReader::open(filename).unwrap();
72        let trailer = bf.metadata();
73        println!("{:?}", bf.header);
74        println!("{:?}", &trailer);
75        let file_data = unsafe { bf.memory_map(trailer.inodes[0].as_file().unwrap()).unwrap() };
76        println!("{:?}", &*file_data);
77        assert_eq!(&*file_data, b"hello\0\0\0")
78    }
79
80    #[test]
81    fn create_garbage() {
82        let filename = "./create_garbage.box";
83        let _ = std::fs::remove_file(filename);
84        let bf = BoxFileWriter::create(filename).expect("Mah box");
85        bf.finish().unwrap();
86    }
87
88    #[test]
89    fn read_bytes() {
90        let filename = "./read_bytes.box";
91        create_test_box(filename);
92        let bf = BoxFileReader::open(filename).unwrap();
93        let record = bf
94            .metadata()
95            .inodes
96            .first()
97            .map(|f| f.as_file().unwrap())
98            .unwrap();
99        let mut reader = bf.read_bytes(record).unwrap();
100        let mut vec = vec![];
101        reader.read_to_end(&mut vec).unwrap();
102        assert_eq!(vec, b"hello\0\0\0")
103    }
104
105    fn insert_impl<F>(filename: &str, f: F)
106    where
107        F: Fn(&str) -> BoxFileWriter,
108    {
109        let _ = std::fs::remove_file(filename);
110        let v =
111            "This, this, this, this, this is a compressable string string string string string.\n"
112                .to_string();
113
114        {
115            use std::time::SystemTime;
116            let now = SystemTime::now()
117                .duration_since(SystemTime::UNIX_EPOCH)
118                .unwrap()
119                .as_secs()
120                .to_le_bytes();
121
122            let mut bf = f(filename);
123
124            let mut dir_attrs = HashMap::new();
125            dir_attrs.insert("created".into(), now.to_vec());
126            dir_attrs.insert("unix.mode".into(), 0o755u16.to_le_bytes().to_vec());
127
128            let mut attrs = HashMap::new();
129            attrs.insert("created".into(), now.to_vec());
130            attrs.insert("unix.mode".into(), 0o644u16.to_le_bytes().to_vec());
131
132            bf.mkdir(BoxPath::new("test").unwrap(), dir_attrs).unwrap();
133
134            bf.insert(
135                Compression::Zstd,
136                BoxPath::new("test/string.txt").unwrap(),
137                &mut std::io::Cursor::new(v.clone()),
138                attrs.clone(),
139            )
140            .unwrap();
141            bf.insert(
142                Compression::Deflate,
143                BoxPath::new("test/string2.txt").unwrap(),
144                &mut std::io::Cursor::new(v.clone()),
145                attrs.clone(),
146            )
147            .unwrap();
148            // println!("{:?}", &bf);
149            bf.finish().unwrap();
150        }
151
152        let bf = BoxFileReader::open(filename).expect("Mah box");
153        println!("{:#?}", &bf);
154
155        assert_eq!(
156            v,
157            bf.decompress_value::<String>(bf.meta.inodes[1].as_file().unwrap())
158                .unwrap()
159        );
160        assert_eq!(
161            v,
162            bf.decompress_value::<String>(bf.meta.inodes[2].as_file().unwrap())
163                .unwrap()
164        );
165    }
166
167    #[test]
168    fn insert() {
169        insert_impl("./insert_garbage.box", |n| {
170            BoxFileWriter::create(n).unwrap()
171        });
172        insert_impl("./insert_garbage_align8.box", |n| {
173            BoxFileWriter::create_with_alignment(n, 8).unwrap()
174        });
175        insert_impl("./insert_garbage_align7.box", |n| {
176            BoxFileWriter::create_with_alignment(n, 7).unwrap()
177        });
178    }
179
180    // #[test]
181    // fn read_index() {
182    //     insert_impl("./read_index.box", |n| BoxFileWriter::create(n).unwrap());
183
184    //     let bf = BoxFileReader::open("./read_index.box").unwrap();
185    //     let fst = bf.meta.index.unwrap();
186
187    //     fst.get("nothing");
188    // }
189}