use dashmap::DashSet;
use tokio::sync::Mutex;
use tracing::debug;
use crate::domain::DiscoveredUrl;
pub struct UrlQueue {
queue: Mutex<Vec<DiscoveredUrl>>,
seen: DashSet<String>,
}
impl UrlQueue {
#[must_use]
pub fn new() -> Self {
Self {
queue: Mutex::new(Vec::with_capacity(100)),
seen: DashSet::with_capacity(100),
}
}
pub fn push(&self, url: DiscoveredUrl) -> bool {
let url_str = url.url.as_str().to_string();
if self.seen.contains(&url_str) {
debug!("Duplicate URL in queue: {}", url_str);
return false;
}
self.seen.insert(url_str);
let mut queue = self.queue.blocking_lock();
queue.push(url);
true
}
pub fn pop(&self) -> Option<DiscoveredUrl> {
let mut queue = self.queue.blocking_lock();
queue.pop()
}
pub fn len(&self) -> usize {
self.queue.blocking_lock().len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.queue.blocking_lock().is_empty()
}
pub fn seen_count(&self) -> usize {
self.seen.len()
}
pub fn clear(&self) {
self.queue.blocking_lock().clear();
}
#[cfg(test)]
pub fn get_all(&self) -> Vec<DiscoveredUrl> {
self.queue.blocking_lock().clone()
}
}
impl Default for UrlQueue {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use url::Url;
fn create_test_url(path: &str) -> DiscoveredUrl {
let url = Url::parse(&format!("https://example.com{}", path)).unwrap();
let parent = Url::parse("https://example.com/").unwrap();
DiscoveredUrl::html(url, 0, parent)
}
#[test]
fn test_url_queue_new() {
let queue = UrlQueue::new();
assert!(queue.is_empty());
assert_eq!(queue.len(), 0);
assert_eq!(queue.seen_count(), 0);
}
#[test]
fn test_url_queue_push_pop() {
let queue = UrlQueue::new();
let url1 = create_test_url("/page1");
let url2 = create_test_url("/page2");
assert!(queue.push(url1));
assert!(queue.push(url2));
assert_eq!(queue.len(), 2);
assert_eq!(queue.seen_count(), 2);
let popped = queue.pop();
assert!(popped.is_some());
assert_eq!(popped.unwrap().url.path(), "/page2");
assert_eq!(queue.len(), 1);
}
#[test]
fn test_url_queue_duplicate_detection() {
let queue = UrlQueue::new();
let url1 = create_test_url("/page1");
let url2 = create_test_url("/page1");
assert!(queue.push(url1));
assert!(!queue.push(url2));
assert_eq!(queue.len(), 1);
assert_eq!(queue.seen_count(), 1);
}
#[test]
fn test_url_queue_empty_pop() {
let queue = UrlQueue::new();
assert!(queue.pop().is_none());
}
#[test]
fn test_url_queue_clear() {
let queue = UrlQueue::new();
queue.push(create_test_url("/page1"));
queue.push(create_test_url("/page2"));
assert_eq!(queue.len(), 2);
queue.clear();
assert_eq!(queue.len(), 0);
assert_eq!(queue.seen_count(), 2); }
#[test]
fn test_url_queue_multiple_urls() {
let queue = UrlQueue::new();
for i in 0..10 {
let url = create_test_url(&format!("/page{}", i));
assert!(queue.push(url));
}
assert_eq!(queue.len(), 10);
assert_eq!(queue.seen_count(), 10);
for _ in 0..10 {
assert!(queue.pop().is_some());
}
assert!(queue.is_empty());
}
}