diff --git a/src/index.rs b/src/index.rs index 24cb6546..61445e98 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1,9 +1,8 @@ -use std::path::{Path, PathBuf}; +use std::path::Path; use sourmash::sketch::Sketch; use sourmash::index::revindex::RevIndex; - -use crate::utils::{read_signatures_from_zip, load_sketchlist_filenames}; +use crate::utils::load_sigpaths_from_zip_or_pathlist; pub fn index>( siglist: P, @@ -12,35 +11,20 @@ pub fn index>( save_paths: bool, colors: bool, ) -> Result<(), Box> { - let mut temp_dir = None; println!("Loading siglist"); - let index_sigs: Vec; - - if siglist.as_ref().extension().map(|ext| ext == "zip").unwrap_or(false) { - let (paths, tempdir) = read_signatures_from_zip(&siglist)?; - temp_dir = Some(tempdir); - index_sigs = paths; - } else { - index_sigs = load_sketchlist_filenames(&siglist)?; - } + let (index_sigs, _temp_dir) = load_sigpaths_from_zip_or_pathlist(&siglist)?; // if index_sigs pathlist is empty, bail if index_sigs.is_empty() { bail!("No signatures to index loaded, exiting."); } - eprintln!("Loaded {} sig paths in siglist", index_sigs.len()); - // Create or open the RevIndex database with the provided output path and colors flag let db = RevIndex::create(output.as_ref(), colors); // Index the signatures using the loaded template, threshold, and save_paths option db.index(index_sigs, &template, 0.0, save_paths); - if let Some(temp_dir) = temp_dir { - temp_dir.close()?; - } - Ok(()) } \ No newline at end of file diff --git a/src/manysketch.rs b/src/manysketch.rs index 5bef6982..944ceb50 100644 --- a/src/manysketch.rs +++ b/src/manysketch.rs @@ -5,7 +5,7 @@ use rayon::prelude::*; use std::io::Read; use std::path::Path; -use crate::utils::{Params, load_sketch_fromfile, ZipMessage, sigwriter}; +use crate::utils::{Params, load_fasta_fromfile, ZipMessage, sigwriter}; use sourmash::signature::Signature; use sourmash::cmd::ComputeParameters; use std::sync::atomic; @@ -130,7 +130,7 @@ pub fn manysketch + Sync>( output: String, ) -> Result<(), Box> { - let fileinfo = match load_sketch_fromfile(&filelist) { + let fileinfo = match load_fasta_fromfile(&filelist) { Ok(result) => result, Err(e) => bail!("Could not load fromfile csv. Underlying error: {}", e) }; diff --git a/src/multisearch.rs b/src/multisearch.rs index 58cda053..8a88261b 100644 --- a/src/multisearch.rs +++ b/src/multisearch.rs @@ -15,7 +15,7 @@ use sourmash::signature::SigsTrait; use sourmash::sketch::minhash::{max_hash_for_scaled, KmerMinHash}; use sourmash::sketch::Sketch; -use crate::utils::{load_sketchlist_filenames, load_sketches}; +use crate::utils::{load_sketches_from_zip_or_pathlist, ReportType}; /// Search many queries against a list of signatures. /// @@ -29,7 +29,7 @@ pub fn multisearch>( ksize: u8, scaled: usize, output: Option

, -) -> Result<()> { +) -> Result<(), Box> { // construct a MinHash template for loading. let max_hash = max_hash_for_scaled(scaled as u64); let template_mh = KmerMinHash::builder() @@ -39,51 +39,11 @@ let template_mh = KmerMinHash::builder() .build(); let template = Sketch::MinHash(template_mh); -// Read in list of query paths. -eprintln!("Reading list of queries from: '{}'", querylist.as_ref().display()); - // Load all queries into memory at once. -let querylist_paths = load_sketchlist_filenames(&querylist)?; - -let result = load_sketches(querylist_paths, &template)?; -let (queries, skipped_paths, failed_paths) = result; - -eprintln!("Loaded {} query signatures", queries.len()); -if failed_paths > 0 { - eprintln!("WARNING: {} signature paths failed to load. See error messages above.", - failed_paths); -} -if skipped_paths > 0 { - eprintln!("WARNING: skipped {} paths - no compatible signatures.", - skipped_paths); -} - -if queries.is_empty() { - bail!("No query signatures loaded, exiting."); -} - -// Read in list of against paths. -eprintln!("Reading list of against paths from: '{}'", againstlist.as_ref().display()); +let queries = load_sketches_from_zip_or_pathlist(&querylist, &template, ReportType::Query)?; // Load all against sketches into memory at once. -let againstlist_paths = load_sketchlist_filenames(&againstlist)?; - -let result = load_sketches(againstlist_paths, &template)?; -let (against, skipped_paths, failed_paths) = result; - -eprintln!("Loaded {} against signatures", against.len()); -if failed_paths > 0 { - eprintln!("WARNING: {} signature paths failed to load. See error messages above.", - failed_paths); -} -if skipped_paths > 0 { - eprintln!("WARNING: skipped {} paths - no compatible signatures.", - skipped_paths); -} - -if against.is_empty() { - bail!("No query signatures loaded, exiting."); -} +let against = load_sketches_from_zip_or_pathlist(&againstlist, &template, ReportType::Against)?; // set up a multi-producer, single-consumer channel. let (send, recv) = std::sync::mpsc::sync_channel(rayon::current_num_threads()); diff --git a/src/python/tests/test_index.py b/src/python/tests/test_index.py index b90996c8..66e08e9f 100644 --- a/src/python/tests/test_index.py +++ b/src/python/tests/test_index.py @@ -151,7 +151,7 @@ def test_index_zipfile(runtmp, capfd): assert 'index is done' in runtmp.last_result.err captured = capfd.readouterr() print(captured.err) - assert 'Loaded 3 sig paths in siglist' in captured.err + assert 'Found 3 filepaths' in captured.err def test_index_check(runtmp): diff --git a/src/python/tests/test_multisearch.py b/src/python/tests/test_multisearch.py index b32fd396..b1ad3011 100644 --- a/src/python/tests/test_multisearch.py +++ b/src/python/tests/test_multisearch.py @@ -23,7 +23,14 @@ def test_installed(runtmp): assert 'usage: multisearch' in runtmp.last_result.err -def test_simple(runtmp): +def zip_siglist(runtmp, siglist, db): + runtmp.sourmash('sig', 'cat', siglist, + '-o', db) + return db + +@pytest.mark.parametrize("zip_query", [False, True]) +@pytest.mark.parametrize("zip_db", [False, True]) +def test_simple(runtmp, zip_query, zip_db): # test basic execution! query_list = runtmp.output('query.txt') against_list = runtmp.output('against.txt') @@ -37,6 +44,11 @@ def test_simple(runtmp): output = runtmp.output('out.csv') + if zip_db: + against_list = zip_siglist(runtmp, against_list, runtmp.output('db.zip')) + if zip_query: + query_list = zip_siglist(runtmp, query_list, runtmp.output('query.zip')) + runtmp.sourmash('scripts', 'multisearch', query_list, against_list, '-o', output) assert os.path.exists(output) @@ -82,7 +94,9 @@ def test_simple(runtmp): assert intersect_hashes == 2529 -def test_simple_threshold(runtmp): +@pytest.mark.parametrize("zip_query", [False, True]) +@pytest.mark.parametrize("zip_db", [False, True]) +def test_simple_threshold(runtmp, zip_query, zip_db): # test with a simple threshold => only 3 results query_list = runtmp.output('query.txt') against_list = runtmp.output('against.txt') @@ -96,6 +110,11 @@ def test_simple_threshold(runtmp): output = runtmp.output('out.csv') + if zip_db: + against_list = zip_siglist(runtmp, against_list, runtmp.output('db.zip')) + if zip_query: + query_list = zip_siglist(runtmp, query_list, runtmp.output('query.zip')) + runtmp.sourmash('scripts', 'multisearch', query_list, against_list, '-o', output, '-t', '0.5') assert os.path.exists(output) @@ -104,7 +123,8 @@ def test_simple_threshold(runtmp): assert len(df) == 3 -def test_missing_query(runtmp, capfd): +@pytest.mark.parametrize("zip_query", [False, True]) +def test_missing_query(runtmp, capfd, zip_query): # test with a missing query list query_list = runtmp.output('query.txt') against_list = runtmp.output('against.txt') @@ -118,6 +138,9 @@ def test_missing_query(runtmp, capfd): output = runtmp.output('out.csv') + if zip_query: + query_list = runtmp.output('query.zip') + with pytest.raises(utils.SourmashCommandFailed): runtmp.sourmash('scripts', 'multisearch', query_list, against_list, '-o', output) @@ -170,10 +193,39 @@ def test_bad_query_2(runtmp, capfd): print(captured.err) assert "WARNING: could not load sketches from path 'no-exist'" in captured.err - assert "WARNING: 1 signature paths failed to load. See error messages above." in captured.err + assert "WARNING: 1 query paths failed to load. See error messages above." in captured.err + + +def test_bad_query_3(runtmp, capfd): + # test with a bad query (a .sig.gz file renamed as zip file) + against_list = runtmp.output('against.txt') + + sig2 = get_test_data('2.fa.sig.gz') + sig47 = get_test_data('47.fa.sig.gz') + sig63 = get_test_data('63.fa.sig.gz') + + query_zip = runtmp.output('query.zip') + # cp sig2 into query_zip + with open(query_zip, 'wb') as fp: + with open(sig2, 'rb') as fp2: + fp.write(fp2.read()) + make_file_list(against_list, [sig2, sig47, sig63]) + + output = runtmp.output('out.csv') -def test_missing_against(runtmp, capfd): + with pytest.raises(utils.SourmashCommandFailed): + runtmp.sourmash('scripts', 'multisearch', query_zip, against_list, + '-o', output) + + captured = capfd.readouterr() + print(captured.err) + + assert 'Error: invalid Zip archive: Could not find central directory end' in captured.err + + +@pytest.mark.parametrize("zip_db", [False, True]) +def test_missing_against(runtmp, capfd, zip_db): # test with a missing against list query_list = runtmp.output('query.txt') against_list = runtmp.output('against.txt') @@ -185,6 +237,10 @@ def test_missing_against(runtmp, capfd): make_file_list(query_list, [sig2, sig47, sig63]) # do not create against_list + if zip_db: + #.zip but don't create the file + against_list = runtmp.output('db.zip') + output = runtmp.output('out.csv') with pytest.raises(utils.SourmashCommandFailed): @@ -241,7 +297,7 @@ def test_bad_against_2(runtmp, capfd): print(captured.err) assert "WARNING: could not load sketches from path 'no-exist'" in captured.err - assert "WARNING: 1 signature paths failed to load. See error messages above." in captured.err + assert "WARNING: 1 search paths failed to load. See error messages above." in captured.err def test_empty_query(runtmp): @@ -266,7 +322,8 @@ def test_empty_query(runtmp): # @CTB -def test_nomatch_query(runtmp, capfd): +@pytest.mark.parametrize("zip_query", [False, True]) +def test_nomatch_query(runtmp, capfd, zip_query): # test a non-matching (diff ksize) in query; do we get warning message? query_list = runtmp.output('query.txt') against_list = runtmp.output('against.txt') @@ -281,6 +338,9 @@ def test_nomatch_query(runtmp, capfd): output = runtmp.output('out.csv') + if zip_query: + query_list = zip_siglist(runtmp, query_list, runtmp.output('query.zip')) + runtmp.sourmash('scripts', 'multisearch', query_list, against_list, '-o', output) assert os.path.exists(output) @@ -288,10 +348,11 @@ def test_nomatch_query(runtmp, capfd): captured = capfd.readouterr() print(captured.err) - assert 'WARNING: skipped 1 paths - no compatible signatures.' in captured.err + assert 'WARNING: skipped 1 query paths - no compatible signatures' in captured.err -def test_load_only_one_bug(runtmp, capfd): +@pytest.mark.parametrize("zip_db", [False, True]) +def test_load_only_one_bug(runtmp, capfd, zip_db): # check that we behave properly when presented with multiple against # sketches query_list = runtmp.output('query.txt') @@ -306,6 +367,9 @@ def test_load_only_one_bug(runtmp, capfd): make_file_list(query_list, [sig1_k31]) make_file_list(against_list, [sig1_all]) + if zip_db: + against_list = zip_siglist(runtmp, against_list, runtmp.output('db.zip')) + output = runtmp.output('out.csv') runtmp.sourmash('scripts', 'multisearch', query_list, against_list, @@ -319,7 +383,8 @@ def test_load_only_one_bug(runtmp, capfd): assert not 'WARNING: no compatible sketches in path ' in captured.err -def test_load_only_one_bug_as_query(runtmp, capfd): +@pytest.mark.parametrize("zip_query", [False, True]) +def test_load_only_one_bug_as_query(runtmp, capfd, zip_query): # check that we behave properly when presented with multiple query # sketches in one file, with only one matching. query_list = runtmp.output('query.txt') @@ -334,6 +399,9 @@ def test_load_only_one_bug_as_query(runtmp, capfd): make_file_list(query_list, [sig1_all]) make_file_list(against_list, [sig1_k31]) + if zip_query: + query_list = zip_siglist(runtmp, query_list, runtmp.output('query.zip')) + output = runtmp.output('out.csv') runtmp.sourmash('scripts', 'multisearch', query_list, against_list, @@ -347,7 +415,9 @@ def test_load_only_one_bug_as_query(runtmp, capfd): assert not 'WARNING: no compatible sketches in path ' in captured.err -def test_md5(runtmp): +@pytest.mark.parametrize("zip_query", [False, True]) +@pytest.mark.parametrize("zip_db", [False, True]) +def test_md5(runtmp, zip_query, zip_db): # test that md5s match what was in the original files, not downsampled etc. query_list = runtmp.output('query.txt') against_list = runtmp.output('against.txt') @@ -361,6 +431,11 @@ def test_md5(runtmp): output = runtmp.output('out.csv') + if zip_query: + query_list = zip_siglist(runtmp, query_list, runtmp.output('query.zip')) + if zip_db: + against_list = zip_siglist(runtmp, against_list, runtmp.output('db.zip')) + runtmp.sourmash('scripts', 'multisearch', query_list, against_list, '-o', output) assert os.path.exists(output) diff --git a/src/utils.rs b/src/utils.rs index d3f1e404..f155baca 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -25,6 +25,7 @@ use sourmash::sketch::Sketch; use sourmash::prelude::MinHashOps; use sourmash::prelude::FracMinHashOps; +// use tempfile::tempdir; /// Track a name/minhash. pub struct SmallSignature { @@ -171,7 +172,6 @@ pub fn prefetch( } /// Write list of prefetch matches. - pub fn write_prefetch + std::fmt::Debug + std::fmt::Display + Clone>( query: &SmallSignature, prefetch_output: Option

, @@ -195,7 +195,6 @@ pub fn write_prefetch + std::fmt::Debug + std::fmt::Display + Clo } /// Load a list of filenames from a file. Exits on bad lines. - pub fn load_sketchlist_filenames>(sketchlist_filename: &P) -> Result> { @@ -221,7 +220,60 @@ pub fn load_sketchlist_filenames>(sketchlist_filename: &P) -> Ok(sketchlist_filenames) } -pub fn load_sketch_fromfile>(sketchlist_filename: &P) -> Result> { + +/// Loads signature file paths from a ZIP archive. +/// +/// This function extracts the contents of a ZIP archive containing +/// signature files (with extensions ".sig" or ".sig.gz") to a temporary directory. +/// It returns the paths of these extracted signature files. +/// +/// # Arguments +/// +/// * `zip_path` - The path to the ZIP archive. +/// +/// # Returns +/// +/// Returns a tuple containing: +/// * A vector of `PathBuf` representing the paths to the extracted signature files. +/// * The `TempDir` representing the temporary directory where the files were extracted. +/// Since tempfile::TempDir creates a temporary directory that is automatically +/// deleted once the TempDir value goes out of scope, we return it here to move it +/// to the main function scope. +/// +/// # Errors +/// +/// Returns an error if: +/// * Unable to create a temporary directory. +/// * Unable to open or read the ZIP archive. +/// * Any other IO or file related error. +pub fn load_sigpaths_from_zip>( + zip_path: P, +) -> Result<(Vec, tempfile::TempDir)> { + let mut signature_paths = Vec::new(); + let temp_dir = tempdir()?; + let zip_file = File::open(&zip_path)?; + let mut zip_archive = ZipArchive::new(zip_file)?; + + for i in 0..zip_archive.len() { + let mut file = zip_archive.by_index(i)?; + let mut sig = Vec::new(); + file.read_to_end(&mut sig)?; + + let file_name = Path::new(file.name()).file_name().unwrap().to_str().unwrap(); + if file_name.ends_with(".sig") || file_name.ends_with(".sig.gz") { + let mut new_file = File::create(temp_dir.path().join(file_name))?; + new_file.write_all(&sig)?; + + // add path to signature_paths + signature_paths.push(temp_dir.path().join(file_name)); + } + } + println!("loaded paths for {} signature files from zipfile {}", signature_paths.len(), zip_path.as_ref().display()); + Ok((signature_paths, temp_dir)) +} + + +pub fn load_fasta_fromfile>(sketchlist_filename: &P) -> Result> { let mut rdr = csv::Reader::from_path(sketchlist_filename)?; // Check for right header @@ -375,6 +427,200 @@ pub fn load_sketches_above_threshold( Ok((matchlist, skipped_paths, failed_paths)) } + +/// Loads all compatible sketches from a ZIP archive at the given path into memory. +/// Currently not parallelized; use a different zip crate to enable parallelization. +/// +/// # Arguments +/// +/// * `zip_path` - Path to the ZIP archive. +/// * `template` - Reference to the Sketch template. +/// +/// # Returns +/// +/// Returns a tuple containing: +/// * A vector of `SmallSignature`s. +/// * Number of paths that were skipped because they did not match the sketch parameters. +/// * Number of paths that failed to load. +/// +/// # Errors +/// +/// Returns an error if: +/// * Unable to open the ZIP file. +/// * ZIP archive is malformed. +pub fn load_sketches_from_zip>( + zip_path: P, + template: &Sketch, +) -> Result<(Vec, usize, usize)> { + let mut sketchlist = Vec::new(); + let zip_file = File::open(&zip_path)?; + let mut zip_archive = ZipArchive::new(zip_file)?; + let mut skipped_paths = 0; + let mut failed_paths = 0; + + // loop through, loading signatures + for i in 0..zip_archive.len() { + let mut file = zip_archive.by_index(i)?; + let file_name = Path::new(file.name()).file_name().unwrap().to_str().unwrap().to_owned(); + + if !file_name.ends_with(".sig") && !file_name.ends_with(".sig.gz") { + continue; + } + if let Ok(sigs) = Signature::from_reader(&mut file) { + if let Some(sm) = prepare_query(&sigs, template, &zip_path.as_ref().display().to_string()) { + sketchlist.push(sm); + } else { + // track number of paths that have no matching sigs + skipped_paths += 1; + } + } else { + // failed to load from this path - print error & track. + eprintln!("WARNING: could not load sketches from path '{}'", file_name); + failed_paths += 1; + } + } + drop(zip_archive); + println!("loaded {} signatures", sketchlist.len()); + Ok((sketchlist, skipped_paths, failed_paths)) +} + + +/// Control function to read signature FILE PATHS from an input file. +/// If a ZIP archive is provided (detected via extension), +/// use `load_sigpaths_from_zip`. Otherwise, assume the +/// user provided a `fromfile` sketchlist and use +/// `load_sketchlist_filenames`. +/// +/// # Arguments +/// +/// * `sketchlist_path` - Path to either a ZIP archive or a list of signature file paths. +/// +/// # Returns +/// +/// Returns a tuple containing: +/// * A vector of `PathBuf` representing the signature file paths. +/// * If extracting from a zipfile, signature files will be extracted to a +/// `TempDir` temporary directory where they can be used individually. +pub fn load_sigpaths_from_zip_or_pathlist>( + sketchlist_path: P, +) -> Result<(Vec, Option)> { + eprintln!("Reading list of filepaths from: '{}'", sketchlist_path.as_ref().display()); + + let result = if sketchlist_path.as_ref().extension().map(|ext| ext == "zip").unwrap_or(false) { + let (paths, tempdir) = load_sigpaths_from_zip(&sketchlist_path)?; + (paths, Some(tempdir)) + } else { + let paths = load_sketchlist_filenames(&sketchlist_path)?; + (paths, None) + }; + + eprintln!("Found {} filepaths", result.0.len()); + // should we bail here if empty? + Ok(result) +} + +pub enum ReportType { + Query, + Against, +} + +impl std::fmt::Display for ReportType { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let description = match self { + ReportType::Query => "query", + ReportType::Against => "search", + }; + write!(f, "{}", description) + } +} + +/// Control function to load compatible signatures from an input file. +/// If a ZIP archive is provided (detected via extension), +/// calls `load_sketches_from_zip`. Otherwise, assumes the +/// user provided a `fromfile` sketchlist and calls +/// `load_sketchlist_filenames`. +/// +/// # Arguments +/// +/// * `sketchlist_path` - Path to either a ZIP archive or a list of signature file paths. +/// * `template` - Reference to the Sketch template (used to load only compatible signatures). +/// * `report_type` - ReportType Enum. Are these 'query' or 'search' signatures? +/// +/// # Returns +/// +/// Returns a vector of `SmallSignature`s. +pub fn load_sketches_from_zip_or_pathlist>( + sketchlist_path: P, + template: &Sketch, + report_type: ReportType, +) -> Result> { + eprintln!("Reading list of {} paths from: '{}'", report_type, sketchlist_path.as_ref().display()); + + let (sketchlist, skipped_paths, failed_paths) = + if sketchlist_path.as_ref().extension().map(|ext| ext == "zip").unwrap_or(false) { + load_sketches_from_zip(sketchlist_path, template)? + } else { + let sketch_paths = load_sketchlist_filenames(&sketchlist_path)?; + load_sketches(sketch_paths, template)? + }; + + report_on_sketch_loading(&sketchlist, skipped_paths, failed_paths, report_type)?; + + Ok(sketchlist) +} + +/// Uses the output of sketch loading functions to report the +/// total number of sketches loaded, as well as the number of files, +/// if any, that failed to load or contained no compatible sketches. +/// If no sketches were loaded, bail. +/// +/// # Arguments +/// +/// * `sketchlist` - A slice of loaded `SmallSignature` sketches. +/// * `skipped_paths` - # paths that contained no compatible sketches. +/// * `failed_paths` - # paths that failed to load. +/// * `report_type` - ReportType Enum (Query or Against). Used to specify +/// which sketch input this information pertains to. +/// +/// # Returns +/// +/// Returns `Ok(())` if at least one signature was successfully loaded. +/// Returns an error if no signatures were loaded. +/// +/// # Errors +/// +/// Returns an error if: +/// * No signatures were successfully loaded. +pub fn report_on_sketch_loading( + sketchlist: &[SmallSignature], + skipped_paths: usize, + failed_paths: usize, + report_type: ReportType, +) -> Result<()> { + + if failed_paths > 0 { + eprintln!( + "WARNING: {} {} paths failed to load. See error messages above.", + failed_paths, + report_type + ); + } + if skipped_paths > 0 { + eprintln!( + "WARNING: skipped {} {} paths - no compatible signatures.", + skipped_paths, + report_type + ); + } + + // Validate sketches + eprintln!("Loaded {} {} signature(s)", sketchlist.len(), report_type); + if sketchlist.is_empty() { + bail!("No {} signatures loaded, exiting.", report_type); + } + Ok(()) +} + /// Execute the gather algorithm, greedy min-set-cov, by iteratively /// removing matches in 'matchlist' from 'query'. @@ -434,8 +680,6 @@ pub fn consume_query_by_gather + std::fmt::Debug + std::fmt::Disp } - // mastiff rocksdb functions - pub fn build_template(ksize: u8, scaled: usize) -> Sketch { let max_hash = max_hash_for_scaled(scaled as u64); let template_mh = KmerMinHash::builder() @@ -446,32 +690,6 @@ pub fn build_template(ksize: u8, scaled: usize) -> Sketch { Sketch::MinHash(template_mh) } -pub fn read_signatures_from_zip>( - zip_path: P, -) -> Result<(Vec, tempfile::TempDir), Box> { - let mut signature_paths = Vec::new(); - let temp_dir = tempdir()?; - let zip_file = File::open(&zip_path)?; - let mut zip_archive = ZipArchive::new(zip_file)?; - - for i in 0..zip_archive.len() { - let mut file = zip_archive.by_index(i)?; - let mut sig = Vec::new(); - file.read_to_end(&mut sig)?; - - let file_name = Path::new(file.name()).file_name().unwrap().to_str().unwrap(); - if file_name.ends_with(".sig") || file_name.ends_with(".sig.gz") { - println!("Found signature file: {}", file_name); - let mut new_file = File::create(temp_dir.path().join(file_name))?; - new_file.write_all(&sig)?; - - // Push the created path directly to the vector - signature_paths.push(temp_dir.path().join(file_name)); - } - } - println!("wrote {} signatures to temp dir", signature_paths.len()); - Ok((signature_paths, temp_dir)) -} pub fn is_revindex_database(path: &Path) -> bool { // quick file check for Revindex database: