use crate::mft::mft_convert_to_path_collection::convert_mft_file_to_path_collection;
use crate::mft::mft_file::MftFile;
use crate::search_index::format::SearchIndexHeader;
use crate::search_index::format::SearchIndexPathRow;
use crate::search_index::search_index_bytes::SearchIndexBytesMut;
use crate::sync::DriveSyncInfo;
use crate::sync::IfExistsOutputBehaviour;
use eyre::Context;
use eyre::bail;
use itertools::Itertools;
use tracing::debug;
use tracing::info;
use tracing::info_span;
use uom::si::information::byte;
#[derive(Debug)]
pub struct SyncIndex;
impl SyncIndex {
pub fn invoke_preflight(
drive_infos: Vec<DriveSyncInfo>,
if_exists: &IfExistsOutputBehaviour,
) -> eyre::Result<Vec<DriveSyncInfo>> {
let mut rtn = Vec::with_capacity(drive_infos.len());
for info in drive_infos {
let index_exists = info.index_output_path.exists();
match (index_exists, if_exists) {
(false, _) | (true, IfExistsOutputBehaviour::Overwrite) => {
rtn.push(info);
}
(true, IfExistsOutputBehaviour::Skip) => {
debug!(
drive = %info.drive_letter,
path = %info.index_output_path.display(),
"Skipping existing index output"
);
}
(true, IfExistsOutputBehaviour::Abort) => {
bail!(
"Aborting sync: {} already exists",
info.index_output_path.display()
);
}
}
}
Ok(rtn)
}
pub fn invoke(drive_infos: Vec<DriveSyncInfo>) -> eyre::Result<()> {
info!(
"Building search indexes for drives: {}",
drive_infos.iter().map(|info| info.drive_letter).join(", ")
);
for info in drive_infos {
let _span = info_span!(
"sync_index_for_drive",
drive = %info.drive_letter,
mft_path = %info.mft_output_path.display(),
index_path = %info.index_output_path.display(),
)
.entered();
Self::invoke_for_mft_path(&info)?;
}
info!("Index sync stage completed");
Ok(())
}
pub fn invoke_for_mft_file(info: &DriveSyncInfo, mft_file: &MftFile) -> eyre::Result<()> {
info!(
drive_letter = %info.drive_letter,
mft_path = %info.mft_output_path.display(),
"Building search index",
);
let rows = {
let _span = info_span!(
"build_search_index_rows",
drive = %info.drive_letter,
mft_size = %mft_file.size().get::<byte>(),
)
.entered();
Self::build_rows_for_mft_file(info, mft_file)?
};
{
let _span = info_span!(
"write_search_index_output",
drive = %info.drive_letter,
row_count = rows.len(),
output_path = %info.index_output_path.display(),
)
.entered();
Self::write_index_output(info, mft_file, &rows)
}
}
fn invoke_for_mft_path(info: &DriveSyncInfo) -> eyre::Result<()> {
if !info.mft_output_path.is_file() {
bail!(
"Cannot build index for drive {}: missing {}",
info.drive_letter,
info.mft_output_path.display()
);
}
let mft_file = {
let _span = info_span!(
"load_cached_mft_for_index",
drive = %info.drive_letter,
mft_path = %info.mft_output_path.display(),
)
.entered();
MftFile::from_path(&info.mft_output_path).wrap_err_with(|| {
format!(
"Failed parsing MFT snapshot for drive {} from {}",
info.drive_letter,
info.mft_output_path.display()
)
})?
};
Self::invoke_for_mft_file(info, &mft_file)
}
fn build_rows_for_mft_file(
info: &DriveSyncInfo,
mft_file: &MftFile,
) -> eyre::Result<Vec<SearchIndexPathRow>> {
let drive_name = info.drive_letter.to_string();
let files =
convert_mft_file_to_path_collection(&drive_name, mft_file).wrap_err_with(|| {
format!(
"Failed processing MFT data for drive {} from {}",
info.drive_letter,
info.mft_output_path.display()
)
})?;
Ok(files
.0
.into_iter()
.flatten()
.map(|path| SearchIndexPathRow {
path: path.path.to_string_lossy().into_owned(),
has_deleted_entries: path.has_deleted_entries(),
})
.collect())
}
fn write_index_output(
info: &DriveSyncInfo,
mft_file: &MftFile,
rows: &[SearchIndexPathRow],
) -> eyre::Result<()> {
SearchIndexBytesMut::from_rows(
SearchIndexHeader::new(
info.drive_letter,
mft_file.size().get::<byte>() as u64,
rows.len() as u64,
),
rows,
)?
.write_to_path(&info.index_output_path)
.wrap_err_with(|| {
format!(
"Failed writing index output for drive {} to {}",
info.drive_letter,
info.index_output_path.display()
)
})
}
}