use crate::scanner::{decode_str, skip_ws, value_kind, Cursor, Kind};
use memmap2::Mmap;
use std::sync::{
atomic::{AtomicBool, Ordering},
mpsc::{self, Receiver, Sender},
Arc,
};
use std::thread::{self, JoinHandle};
const MAX_MATCHES: usize = 5000;
pub struct Search {
cancel: Arc<AtomicBool>,
rx: Receiver<Vec<usize>>,
done: Arc<AtomicBool>,
_handle: JoinHandle<()>,
pub matches: Vec<Vec<usize>>,
pub finished: bool,
}
impl Search {
pub fn spawn(mmap: Arc<Mmap>, term: String) -> Search {
let cancel = Arc::new(AtomicBool::new(false));
let done = Arc::new(AtomicBool::new(false));
let (tx, rx) = mpsc::channel();
let cancel_w = cancel.clone();
let done_w = done.clone();
let needle = term.to_lowercase();
let handle = thread::spawn(move || {
let b: &[u8] = &mmap;
let rstart = skip_ws(b, 0, b.len());
if rstart < b.len() {
let kind = value_kind(b, rstart);
let mut path = Vec::new();
let mut counter = 0u64;
let mut found = 0usize;
scan(
b, rstart, b.len(), kind, "", &needle, &mut path, &cancel_w, &tx, &mut counter,
&mut found,
);
}
done_w.store(true, Ordering::Relaxed);
});
Search {
cancel,
rx,
done,
_handle: handle,
matches: Vec::new(),
finished: false,
}
}
pub fn drain(&mut self) -> usize {
let before = self.matches.len();
while let Ok(p) = self.rx.try_recv() {
self.matches.push(p);
}
if self.done.load(Ordering::Relaxed) {
while let Ok(p) = self.rx.try_recv() {
self.matches.push(p);
}
self.finished = true;
}
self.matches.len() - before
}
pub fn cancel(&self) {
self.cancel.store(true, Ordering::Relaxed);
}
}
impl Drop for Search {
fn drop(&mut self) {
self.cancel.store(true, Ordering::Relaxed);
}
}
#[allow(clippy::too_many_arguments)]
fn scan(
b: &[u8],
start: usize,
end: usize,
kind: Kind,
label: &str,
needle: &str,
path: &mut Vec<usize>,
cancel: &AtomicBool,
tx: &Sender<Vec<usize>>,
counter: &mut u64,
found: &mut usize,
) -> bool {
*counter += 1;
if *counter & 0xFFF == 0 && cancel.load(Ordering::Relaxed) {
return true;
}
if *found >= MAX_MATCHES {
return true;
}
let mut hit = !label.is_empty() && label.to_lowercase().contains(needle);
if !hit {
match kind {
Kind::Object | Kind::Array => {}
Kind::Str => hit = decode_str(b, start, end).to_lowercase().contains(needle),
_ => {
if let Ok(s) = std::str::from_utf8(&b[start..end]) {
hit = s.to_lowercase().contains(needle);
}
}
}
}
if hit {
if tx.send(path.clone()).is_err() {
return true; }
*found += 1;
}
if matches!(kind, Kind::Object | Kind::Array) {
let mut cur = Cursor::new(start, end, matches!(kind, Kind::Array));
let mut i = 0;
while let Some(rc) = cur.next(b) {
path.push(i);
let bailed = scan(
b, rc.start, rc.end, rc.kind, &rc.label, needle, path, cancel, tx, counter, found,
);
path.pop();
if bailed {
return true;
}
i += 1;
}
}
false
}