1use std::collections::HashMap;
2use std::default::Default;
3use std::fs::File;
4use std::fs::OpenOptions;
5use std::io::{prelude::*, BufReader, BufWriter, Result, SeekFrom};
6use std::num::NonZeroU64;
7use std::path::{Path, PathBuf};
8
9use memmap2::{Mmap, MmapOptions};
10
11use crate::{
12 compression::Compression,
13 header::BoxHeader,
14 path::BoxPath,
15 record::{DirectoryRecord, FileRecord, LinkRecord, Record},
16 ser::Serialize,
17};
18
19use super::{
20 reader::{read_header, read_trailer},
21 BoxMetadata,
22};
23
24pub struct BoxFileWriter {
25 pub(crate) file: BufWriter<File>,
26 pub(crate) path: PathBuf,
27 pub(crate) header: BoxHeader,
28 pub(crate) meta: BoxMetadata,
29}
30
31impl Drop for BoxFileWriter {
32 fn drop(&mut self) {
33 let _ = self.finish_inner();
34 }
35}
36
37impl BoxFileWriter {
38 #[inline(always)]
39 fn write_header(&mut self) -> std::io::Result<()> {
40 self.file.seek(SeekFrom::Start(0))?;
41 self.header.write(&mut self.file)
42 }
43
44 #[inline(always)]
45 fn finish_inner(&mut self) -> std::io::Result<u64> {
46 let pos = self.next_write_addr().get();
47 self.header.trailer = NonZeroU64::new(pos);
48 self.write_header()?;
49 self.file.seek(SeekFrom::Start(pos))?;
50 self.meta.write(&mut self.file)?;
51
52 let new_pos = self.file.seek(SeekFrom::Current(0))?;
53 let file = self.file.get_mut();
54 file.set_len(new_pos)?;
55 Ok(new_pos)
56 }
57
58 pub fn finish(mut self) -> std::io::Result<u64> {
59 self.finish_inner()
60 }
61
62 #[inline(always)]
63 fn next_write_addr(&self) -> NonZeroU64 {
64 let offset = self
66 .meta
67 .inodes
68 .iter()
69 .rev()
70 .find_map(|r| r.as_file())
71 .map(|r| r.data.get() + r.length)
72 .unwrap_or(std::mem::size_of::<BoxHeader>() as u64);
73
74 let v = match self.header.alignment {
75 0 => offset,
76 alignment => {
77 let diff = offset % alignment;
78 if diff == 0 {
79 offset
80 } else {
81 offset + (alignment - diff)
82 }
83 }
84 };
85
86 NonZeroU64::new(v).unwrap()
87 }
88
89 pub fn open<P: AsRef<Path>>(path: P) -> std::io::Result<BoxFileWriter> {
91 OpenOptions::new()
92 .read(true)
93 .write(true)
94 .open(path.as_ref())
95 .map(|mut file| {
96 let (header, meta) = {
99 let mut reader = BufReader::new(&mut file);
100 let header = read_header(&mut reader, 0)?;
101 let ptr = header.trailer.ok_or_else(|| {
102 std::io::Error::new(std::io::ErrorKind::Other, "no trailer found")
103 })?;
104 let meta = read_trailer(&mut reader, ptr, path.as_ref(), 0)?;
105 (header, meta)
106 };
107
108 let f = BoxFileWriter {
109 file: BufWriter::new(file),
110 path: path.as_ref().to_path_buf().canonicalize()?,
111 header,
112 meta,
113 };
114
115 Ok(f)
116 })?
117 }
118
119 pub fn create<P: AsRef<Path>>(path: P) -> std::io::Result<BoxFileWriter> {
121 let mut boxfile = OpenOptions::new()
122 .write(true)
123 .read(true)
124 .create_new(true)
125 .open(path.as_ref())
126 .and_then(|file| {
127 Ok(BoxFileWriter {
128 file: BufWriter::new(file),
129 path: path.as_ref().to_path_buf().canonicalize()?,
130 header: BoxHeader::default(),
131 meta: BoxMetadata::default(),
132 })
133 })?;
134
135 boxfile.write_header()?;
136
137 Ok(boxfile)
138 }
139
140 pub fn create_with_alignment<P: AsRef<Path>>(
143 path: P,
144 alignment: u64,
145 ) -> std::io::Result<BoxFileWriter> {
146 let mut boxfile = OpenOptions::new()
147 .write(true)
148 .read(true)
149 .create_new(true)
150 .open(path.as_ref())
151 .and_then(|file| {
152 Ok(BoxFileWriter {
153 file: BufWriter::new(file),
154 path: path.as_ref().to_path_buf().canonicalize()?,
155 header: BoxHeader::with_alignment(alignment),
156 meta: BoxMetadata::default(),
157 })
158 })?;
159
160 boxfile.write_header()?;
161
162 Ok(boxfile)
163 }
164
165 pub fn path(&self) -> &Path {
166 &self.path
167 }
168
169 pub fn alignment(&self) -> u64 {
170 self.header.alignment
171 }
172
173 pub fn version(&self) -> u32 {
174 self.header.version
175 }
176
177 pub fn metadata(&self) -> &BoxMetadata {
179 &self.meta
180 }
181
182 #[inline(always)]
183 fn iter(&self) -> super::meta::Records {
184 super::meta::Records::new(self.metadata(), &*self.metadata().root, None)
185 }
186
187 #[inline(always)]
188 fn insert_inner<F>(&mut self, path: BoxPath, create_record: F) -> std::io::Result<()>
189 where
190 F: FnOnce(&mut Self, &BoxPath) -> std::io::Result<Record>,
191 {
192 log::debug!("insert_inner path: {:?}", path);
193 match path.parent() {
194 Some(parent) => {
195 log::debug!("insert_inner parent: {:?}", parent);
196
197 match self.meta.inode(&parent) {
198 None => {
199 let err = std::io::Error::new(
200 std::io::ErrorKind::Other,
201 format!("No inode found for path: {:?}", parent),
202 );
203 Err(err)
204 }
205 Some(parent) => {
206 let record = create_record(self, &path)?;
207 log::debug!("Inserting record into parent {:?}: {:?}", &parent, &record);
208 let new_inode = self.meta.insert_record(record);
209 log::debug!("Inserted with inode: {:?}", &new_inode);
210 let parent = self
211 .meta
212 .record_mut(parent)
213 .unwrap()
214 .as_directory_mut()
215 .unwrap();
216 parent.inodes.push(new_inode);
217 Ok(())
218 }
219 }
220 }
221 None => {
222 let record = create_record(self, &path)?;
223 log::debug!("Inserting record into root: {:?}", &record);
224 let new_inode = self.meta.insert_record(record);
225 self.meta.root.push(new_inode);
226 Ok(())
227 }
228 }
229 }
230
231 pub fn mkdir(&mut self, path: BoxPath, attrs: HashMap<String, Vec<u8>>) -> std::io::Result<()> {
232 log::debug!("mkdir: {}", path);
233
234 self.insert_inner(path, move |this, path| {
235 let attrs = attrs
236 .into_iter()
237 .map(|(k, v)| {
238 let k = this.meta.attr_key_or_create(&k);
239 (k, v)
240 })
241 .collect::<HashMap<_, _>>();
242
243 let dir_record = DirectoryRecord {
244 name: path.filename(),
245 inodes: vec![],
246 attrs,
247 };
248
249 Ok(dir_record.upcast())
250 })
251 }
252
253 pub fn link(
254 &mut self,
255 path: BoxPath,
256 target: BoxPath,
257 attrs: HashMap<String, Vec<u8>>,
258 ) -> std::io::Result<()> {
259 self.insert_inner(path, move |this, path| {
260 let attrs = attrs
261 .into_iter()
262 .map(|(k, v)| {
263 let k = this.meta.attr_key_or_create(&k);
264 (k, v)
265 })
266 .collect::<HashMap<_, _>>();
267
268 let link_record = LinkRecord {
269 name: path.filename(),
270 target,
271 attrs,
272 };
273
274 Ok(link_record.upcast())
275 })
276 }
277
278 pub fn insert<R: Read>(
279 &mut self,
280 compression: Compression,
281 path: BoxPath,
282 value: &mut R,
283 attrs: HashMap<String, Vec<u8>>,
284 ) -> std::io::Result<&FileRecord> {
285 self.insert_inner(path, move |this, path| {
286 let next_addr = this.next_write_addr();
287 let byte_count = this.write_data::<R>(compression, next_addr.get(), value)?;
288 let attrs = attrs
289 .into_iter()
290 .map(|(k, v)| {
291 let k = this.meta.attr_key_or_create(&k);
292 (k, v)
293 })
294 .collect::<HashMap<_, _>>();
295
296 let record = FileRecord {
297 compression,
298 length: byte_count.write,
299 decompressed_length: byte_count.read,
300 name: path.filename(),
301 data: next_addr,
302 attrs,
303 };
304
305 Ok(record.upcast())
306 })?;
307
308 Ok(self.meta.inodes.last().unwrap().as_file().unwrap())
309 }
310
311 pub unsafe fn data(&self, record: &FileRecord) -> std::io::Result<Mmap> {
317 self.read_data(record)
318 }
319
320 #[inline(always)]
321 fn write_data<R: Read>(
322 &mut self,
323 compression: Compression,
324 pos: u64,
325 reader: &mut R,
326 ) -> std::io::Result<comde::com::ByteCount> {
327 self.file.seek(SeekFrom::Start(pos))?;
328 compression.compress(&mut self.file, reader)
329 }
330
331 pub fn set_attr<S: AsRef<str>>(
332 &mut self,
333 path: &BoxPath,
334 key: S,
335 value: Vec<u8>,
336 ) -> Result<()> {
337 let inode = match self.iter().find(|r| &r.path == path) {
338 Some(v) => v.inode,
339 None => todo!(),
340 };
341
342 let key = self.meta.attr_key_or_create(key.as_ref());
343 let record = self.meta.record_mut(inode).unwrap();
344 record.attrs_mut().insert(key, value);
345
346 Ok(())
347 }
348
349 pub fn set_file_attr<S: AsRef<str>>(&mut self, key: S, value: Vec<u8>) -> Result<()> {
350 let key = self.meta.attr_key_or_create(key.as_ref());
351
352 self.meta.attrs.insert(key, value);
353
354 Ok(())
355 }
356
357 #[inline(always)]
358 unsafe fn read_data(&self, header: &FileRecord) -> std::io::Result<Mmap> {
359 MmapOptions::new()
360 .offset(header.data.get())
361 .len(header.length as usize)
362 .map(self.file.get_ref())
363 }
364}