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 {
76            bf.memory_map(&trailer.inodes[0].as_file().unwrap())
77                .unwrap()
78        };
79        println!("{:?}", &*file_data);
80        assert_eq!(&*file_data, b"hello\0\0\0")
81    }
82
83    #[test]
84    fn create_garbage() {
85        let filename = "./create_garbage.box";
86        let _ = std::fs::remove_file(&filename);
87        let bf = BoxFileWriter::create(&filename).expect("Mah box");
88        bf.finish().unwrap();
89    }
90
91    #[test]
92    fn read_bytes() {
93        let filename = "./read_bytes.box";
94        create_test_box(&filename);
95        let bf = BoxFileReader::open(&filename).unwrap();
96        let record = bf
97            .metadata()
98            .inodes
99            .first()
100            .map(|f| f.as_file().unwrap())
101            .unwrap();
102        let mut reader = bf.read_bytes(&record).unwrap();
103        let mut vec = vec![];
104        reader.read_to_end(&mut vec).unwrap();
105        assert_eq!(vec, b"hello\0\0\0")
106    }
107
108    fn insert_impl<F>(filename: &str, f: F)
109    where
110        F: Fn(&str) -> BoxFileWriter,
111    {
112        let _ = std::fs::remove_file(&filename);
113        let v =
114            "This, this, this, this, this is a compressable string string string string string.\n"
115                .to_string();
116
117        {
118            use std::time::SystemTime;
119            let now = SystemTime::now()
120                .duration_since(SystemTime::UNIX_EPOCH)
121                .unwrap()
122                .as_secs()
123                .to_le_bytes();
124
125            let mut bf = f(filename);
126
127            let mut dir_attrs = HashMap::new();
128            dir_attrs.insert("created".into(), now.to_vec());
129            dir_attrs.insert("unix.mode".into(), 0o755u16.to_le_bytes().to_vec());
130
131            let mut attrs = HashMap::new();
132            attrs.insert("created".into(), now.to_vec());
133            attrs.insert("unix.mode".into(), 0o644u16.to_le_bytes().to_vec());
134
135            bf.mkdir(BoxPath::new("test").unwrap(), dir_attrs).unwrap();
136
137            bf.insert(
138                Compression::Zstd,
139                BoxPath::new("test/string.txt").unwrap(),
140                &mut std::io::Cursor::new(v.clone()),
141                attrs.clone(),
142            )
143            .unwrap();
144            bf.insert(
145                Compression::Deflate,
146                BoxPath::new("test/string2.txt").unwrap(),
147                &mut std::io::Cursor::new(v.clone()),
148                attrs.clone(),
149            )
150            .unwrap();
151            // println!("{:?}", &bf);
152            bf.finish().unwrap();
153        }
154
155        let bf = BoxFileReader::open(&filename).expect("Mah box");
156        println!("{:#?}", &bf);
157
158        assert_eq!(
159            v,
160            bf.decompress_value::<String>(&bf.meta.inodes[1].as_file().unwrap())
161                .unwrap()
162        );
163        assert_eq!(
164            v,
165            bf.decompress_value::<String>(&bf.meta.inodes[2].as_file().unwrap())
166                .unwrap()
167        );
168    }
169
170    #[test]
171    fn insert() {
172        insert_impl("./insert_garbage.box", |n| {
173            BoxFileWriter::create(n).unwrap()
174        });
175        insert_impl("./insert_garbage_align8.box", |n| {
176            BoxFileWriter::create_with_alignment(n, 8).unwrap()
177        });
178        insert_impl("./insert_garbage_align7.box", |n| {
179            BoxFileWriter::create_with_alignment(n, 7).unwrap()
180        });
181    }
182
183    // #[test]
184    // fn read_index() {
185    //     insert_impl("./read_index.box", |n| BoxFileWriter::create(n).unwrap());
186
187    //     let bf = BoxFileReader::open("./read_index.box").unwrap();
188    //     let fst = bf.meta.index.unwrap();
189
190    //     fst.get("nothing");
191    // }
192}