weavatrix-scan 0.4.6

Deterministic, safe repository scanner for code intelligence
Documentation
use super::{
    CacheValidationPolicy, HashMap, InspectedFiles, Inspection, InspectionStop, Instant, Result,
    ScanCache, ScanCacheEntry, ScanCacheStats, ScanOptions, ScannedFile, apply_cached, inspect,
    record_binary_skip, record_concurrent_modification, record_io_error, record_limit_skip,
    reusable_candidate,
};

pub(crate) fn inspect_files(
    files: Vec<ScannedFile>,
    options: &ScanOptions,
    started: Instant,
    previous: Option<&ScanCache>,
) -> Result<InspectedFiles> {
    if !options.hash_file_contents && !options.detect_binary_files {
        return Ok(InspectedFiles {
            files,
            skipped: Vec::new(),
            warnings: Vec::new(),
            termination: None,
            cache: ScanCacheStats::default(),
        });
    }
    let cache = previous.map_or_else(HashMap::new, |cache| {
        cache
            .entries
            .iter()
            .map(|entry| (entry.relative.as_str(), entry))
            .collect()
    });
    let stop = InspectionStop::default();
    let workers = options.worker_count(files.len());
    if workers <= 1 {
        return inspect_chunk(files, options, started, &stop, &cache);
    }
    let chunk_size = files.len().div_ceil(workers);
    let mut iterator = files.into_iter();
    let mut chunks = Vec::with_capacity(workers);
    loop {
        let chunk = iterator.by_ref().take(chunk_size).collect::<Vec<_>>();
        if chunk.is_empty() {
            break;
        }
        chunks.push(chunk);
    }

    std::thread::scope(|scope| {
        let handles = chunks
            .into_iter()
            .map(|chunk| scope.spawn(|| inspect_chunk(chunk, options, started, &stop, &cache)))
            .collect::<Vec<_>>();
        let mut inspected = InspectedFiles {
            files: Vec::new(),
            skipped: Vec::new(),
            warnings: Vec::new(),
            termination: None,
            cache: ScanCacheStats::default(),
        };
        for handle in handles {
            let chunk = handle.join().expect("content inspection worker panicked")?;
            inspected.files.extend(chunk.files);
            inspected.skipped.extend(chunk.skipped);
            inspected.warnings.extend(chunk.warnings);
            inspected.termination = inspected.termination.or(chunk.termination);
            inspected.cache.reused_hashes = inspected
                .cache
                .reused_hashes
                .saturating_add(chunk.cache.reused_hashes);
            inspected.cache.content_reads = inspected
                .cache
                .content_reads
                .saturating_add(chunk.cache.content_reads);
            inspected.cache.fingerprint_reads = inspected
                .cache
                .fingerprint_reads
                .saturating_add(chunk.cache.fingerprint_reads);
        }
        Ok(inspected)
    })
}

fn inspect_chunk(
    files: Vec<ScannedFile>,
    options: &ScanOptions,
    started: Instant,
    stop: &InspectionStop,
    cache: &HashMap<&str, &ScanCacheEntry>,
) -> Result<InspectedFiles> {
    let mut inspected = InspectedFiles {
        files: Vec::with_capacity(files.len()),
        skipped: Vec::new(),
        warnings: Vec::new(),
        termination: None,
        cache: ScanCacheStats::default(),
    };
    for mut file in files {
        if let Some(reason) = stop.reason(options, started) {
            record_limit_skip(&mut inspected, file.relative, reason, options);
            inspected.termination = Some(reason);
            continue;
        }
        let cached = cache.get(file.relative.as_str()).copied();
        if reusable_candidate(&file, options, cached) {
            let cached = cached.expect("reusable candidate has cache evidence");
            if options.cache_validation == CacheValidationPolicy::Fast {
                apply_cached(&mut file, cached);
                inspected.cache.reused_hashes = inspected.cache.reused_hashes.saturating_add(1);
                inspected.files.push(file);
                continue;
            }
            inspected.cache.content_reads = inspected.cache.content_reads.saturating_add(1);
            inspected.cache.fingerprint_reads = inspected.cache.fingerprint_reads.saturating_add(1);
            match inspect::validate_cached(&mut file, &cached.content_fingerprint) {
                Ok(inspect::CachedValidation::Match) => {
                    apply_cached(&mut file, cached);
                    inspected.cache.reused_hashes = inspected.cache.reused_hashes.saturating_add(1);
                    inspected.files.push(file);
                    continue;
                }
                Ok(inspect::CachedValidation::Changed) => {}
                Ok(inspect::CachedValidation::Concurrent) => {
                    record_concurrent_modification(&mut inspected, file.relative, options)?;
                    continue;
                }
                Err(source) => {
                    record_io_error(
                        &mut inspected,
                        &file,
                        "validate cached content",
                        source,
                        options,
                    )?;
                    continue;
                }
            }
        }
        inspected.cache.content_reads = inspected.cache.content_reads.saturating_add(1);
        let error_file = file.clone();
        match inspect::inspect(file, options) {
            Ok(Inspection::Selected(file)) => inspected.files.push(file),
            Ok(Inspection::Binary(relative)) => {
                record_binary_skip(&mut inspected, relative, options);
            }
            Ok(Inspection::Concurrent(relative)) => {
                record_concurrent_modification(&mut inspected, relative, options)?;
            }
            Err(source) => {
                record_io_error(
                    &mut inspected,
                    &error_file,
                    "inspect file content",
                    source,
                    options,
                )?;
            }
        }
    }
    Ok(inspected)
}