webpage_quality_analyzer 1.0.2

High-performance webpage quality analyzer with 115 comprehensive metrics - Rust library with WASM, C++, and Python bindings
Documentation
//! Batch processing functions

use std::os::raw::c_char;
use std::ptr;

use super::conversions::c_str_to_rust;
use super::errors::set_last_error;
use super::types::{CReportArray, CStringArray};

/// Analyze multiple URLs in parallel
///
/// # Arguments
/// * `urls` - Array of URLs to analyze
/// * `max_concurrent` - Maximum number of concurrent analyses (0 = default of 10)
///
/// # Returns
/// Pointer to CReportArray. Caller must free with `wqa_free_report_array()`.
#[no_mangle]
pub extern "C" fn wqa_analyze_batch_parallel(
    urls: *const CStringArray,
    max_concurrent: usize,
) -> *mut CReportArray {
    if urls.is_null() {
        set_last_error("Null URLs array provided".to_string());
        return ptr::null_mut();
    }

    let url_array = unsafe { &*urls };

    // Convert C string array to Vec<String>
    let url_vec: Vec<String> = unsafe {
        if url_array.data.is_null() || url_array.length == 0 {
            set_last_error("Empty URLs array".to_string());
            return ptr::null_mut();
        }

        let url_ptrs = std::slice::from_raw_parts(url_array.data, url_array.length);
        url_ptrs
            .iter()
            .filter_map(|&ptr| c_str_to_rust(ptr))
            .map(|s| s.to_string())
            .collect()
    };

    let concurrent_limit = if max_concurrent == 0 {
        10
    } else {
        max_concurrent
    };

    // Create runtime
    let runtime = match tokio::runtime::Runtime::new() {
        Ok(rt) => rt,
        Err(e) => {
            set_last_error(format!("Failed to create runtime: {}", e));
            return ptr::null_mut();
        }
    };

    // Block on batch analysis using the existing high-performance function
    // Note: We need to collect individual reports, not JSON string
    // For now, we'll analyze them individually with concurrency control
    use futures::stream::{self, StreamExt};
    use std::sync::Arc;
    use tokio::sync::Semaphore;

    let result = runtime.block_on(async {
        let semaphore = Arc::new(Semaphore::new(concurrent_limit));
        let tasks: Vec<_> = url_vec
            .iter()
            .map(|url| {
                let url = url.clone();
                let semaphore = Arc::clone(&semaphore);
                async move {
                    let _permit = semaphore.acquire().await.ok()?;
                    crate::analyze(&url, None).await.ok()
                }
            })
            .collect();

        stream::iter(tasks)
            .buffer_unordered(concurrent_limit)
            .collect::<Vec<_>>()
            .await
    });

    // Filter out None values
    let reports: Vec<_> = result.into_iter().filter_map(|r| r).collect();

    if reports.is_empty() {
        set_last_error("All analyses failed".to_string());
        return ptr::null_mut();
    }

    CReportArray::from_vec(reports)
}

/// Free report array
#[no_mangle]
pub extern "C" fn wqa_free_report_array(arr: *mut CReportArray) {
    unsafe {
        CReportArray::free(arr);
    }
}

/// Free string array
#[no_mangle]
pub extern "C" fn wqa_free_string_array(arr: *mut CStringArray) {
    unsafe {
        CStringArray::free(arr);
    }
}