use crate::backends::MosaicBackend;
use crate::creator::BufferedMasterElement;
use crate::ebml::{MosaicTag, CRC32_SIZE};
use crate::reader::MosaicReader;
use crate::writer::MosaicWriter;
use crate::IdxDescription;
use crate::{Position, Size};
use anyhow::Result;
use std::collections::HashSet;
use std::fs::File;
use std::io::Write;
use std::io::{Seek, SeekFrom};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use thiserror::Error;
pub struct MosaicUpdater<B: MosaicBackend> {
path: PathBuf,
reader: MosaicReader<B>,
writer: MosaicWriter,
}
#[derive(Error, Debug)]
pub enum MosaicUpdaterError {
#[error("Key {key:?} not found")]
KeyNotFound { key: Vec<u8> },
#[error("Missing key of index {idx_description}")]
MissingKey { idx_description: IdxDescription },
#[error("{key_1:?} and {key_2:?} should refer to the same object but don't")]
MismatchingKeys { key_1: Vec<u8>, key_2: Vec<u8> },
#[error("Object with key {key:?} for index {idx_description} not found")]
ObjectNotFound {
key: Vec<u8>,
idx_description: IdxDescription,
},
}
impl<B: MosaicBackend> MosaicUpdater<B> {
pub fn new(path: &Path, backend: B) -> Result<Self> {
let reader = MosaicReader::with_backend(path, Arc::new(backend))?;
let writer = MosaicWriter::new(File::options().read(true).write(true).open(path)?)?;
let updater = MosaicUpdater {
path: path.to_path_buf(),
reader,
writer,
};
Ok(updater)
}
fn update_object_counter(&mut self, val: u64) -> Result<()> {
let (_, mosaic_offset) = self
.reader
.backend
.find_element(0.into(), MosaicTag::Mosaic)?;
let (_, md_offset) = self
.reader
.backend
.find_element(mosaic_offset, MosaicTag::ContainerMetaData)?;
self.writer
.file
.seek(SeekFrom::Start((md_offset + CRC32_SIZE).0.try_into()?))?;
let mut buffered_master = BufferedMasterElement::new();
buffered_master.append_full_uint(MosaicTag::ObjectsCounter, val)?;
self.writer.file.write_all(&buffered_master.buffer)?;
self.writer.rewrite_crc32(mosaic_offset)?;
Ok(())
}
fn update_object_total_size(&mut self, val: Size) -> Result<()> {
let (_, mosaic_offset) = self
.reader
.backend
.find_element(0.into(), MosaicTag::Mosaic)?;
let (_, md_offset) = self
.reader
.backend
.find_element(mosaic_offset, MosaicTag::ContainerMetaData)?;
let (oc_size, oc_offset) = self
.reader
.backend
.find_element(md_offset, MosaicTag::ObjectsCounter)?;
let (_, ots_offset) = self.reader.backend.read_uint(oc_offset, oc_size)?;
self.writer
.file
.seek(SeekFrom::Start((ots_offset).0.try_into()?))?;
let mut buffered_master = BufferedMasterElement::new();
buffered_master.append_full_uint(MosaicTag::ObjectsTotalSize, val.0)?;
self.writer.file.write_all(&buffered_master.buffer)?;
self.writer.rewrite_crc32(mosaic_offset)?;
Ok(())
}
fn write_void(&mut self, offset: Position) -> Result<Size> {
let (_, item_size, _) = self.reader.backend.find_tag_and_size(offset)?;
self.writer
.file
.seek(SeekFrom::Start(offset.0.try_into()?))?;
self.writer
.write_master(MosaicTag::Void, &vec![0; item_size.0 as usize], false, None)?;
Ok(item_size)
}
pub fn delete(&mut self, object_indexes: Vec<(IdxDescription, &[u8])>) -> Result<()> {
let indexes_list = self.reader.list_indexes()?;
let file_indexes: Vec<_> = indexes_list.iter().map(|x| x.0).collect();
let mut object_position = None;
let mut object_key: Option<Vec<u8>> = None;
let mut items_to_void = HashSet::new();
let mut object_real_size = None;
let mut crc32_to_update: HashSet<_> = indexes_list.iter().map(|x| x.1).collect();
for idx in &file_indexes {
if !object_indexes.iter().any(|x| x.0 == *idx) {
return Err(MosaicUpdaterError::MissingKey {
idx_description: *idx,
}
.into());
}
}
for (idx_description, key) in object_indexes.into_iter() {
if !file_indexes.contains(&idx_description) {
println!(
"Index {} not found in file {}, skipping it",
idx_description,
self.path.display()
);
continue;
};
self.reader.load_index(idx_description)?;
let Some((key_offset, key_start_offset)) = self.reader.find_key(key)? else {
return Err(MosaicUpdaterError::KeyNotFound { key: key.to_vec() }.into());
};
items_to_void.insert(key_start_offset);
let key_size = Size(key.len().try_into()?);
let (_, offset_start_offset) = self.reader.backend.read_binary(key_offset, key_size)?;
items_to_void.insert(offset_start_offset);
let Some((object_size, object_offset, object_start_offset)) =
self.reader.find_object_offset(key_size, key_offset)?
else {
return Err(MosaicUpdaterError::ObjectNotFound {
key: key.to_vec(),
idx_description,
}
.into());
};
items_to_void.insert(object_start_offset);
crc32_to_update.insert(self.reader.find_object_tile(object_offset)?);
if object_real_size.is_none() {
let obj = self.reader.read_object(object_offset, object_size)?;
object_real_size = Some(Size(obj.len().try_into()?));
};
if let (Some(position), Some(obj_key)) = (object_position, &object_key) {
if position != object_offset {
return Err(MosaicUpdaterError::MismatchingKeys {
key_1: obj_key.to_vec(),
key_2: key.to_vec(),
}
.into());
}
} else {
object_position = Some(object_offset);
object_key = Some(key.to_vec());
}
}
for pos in items_to_void {
self.write_void(pos)?;
}
for pos in crc32_to_update {
self.writer.rewrite_crc32(pos)?;
}
self.update_object_counter(self.reader.objects_counter - 1)?;
self.update_object_total_size(self.reader.objects_total_size - object_real_size.unwrap())?;
Ok(())
}
}