use yo_kv::Db;
use yo_kv::hash::Text;
use yo_search::Source;
use super::Server;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Change {
Nothing,
Fields,
Taken,
Key,
Twice,
}
impl Change {
pub(super) fn when(wrote: bool) -> Self {
if wrote { Self::Fields } else { Self::Nothing }
}
pub(super) fn taken(went: bool) -> Self {
if went { Self::Taken } else { Self::Nothing }
}
fn rounds(self) -> usize {
match self {
Self::Nothing => 0,
Self::Twice => 2,
_ => 1,
}
}
}
#[derive(Debug)]
pub(super) struct Touched<'a> {
watching: bool,
news: Vec<News<'a>>,
}
#[derive(Debug, Clone, Copy)]
enum News<'a> {
Gone(&'a [u8]),
Wrote(&'a [u8]),
Renamed(&'a [u8], &'a [u8]),
}
impl<'a> Touched<'a> {
pub(super) fn new(server: &Server) -> Touched<'a> {
Touched {
watching: server.search.lock().watching(),
news: Vec::new(),
}
}
pub(super) fn gone(&mut self, key: &'a [u8]) {
self.note(News::Gone(key));
}
pub(super) fn wrote(&mut self, key: &'a [u8]) {
self.note(News::Wrote(key));
}
pub(super) fn renamed(&mut self, from: &'a [u8], to: &'a [u8]) {
self.note(News::Renamed(from, to));
}
fn note(&mut self, news: News<'a>) {
if self.watching {
self.news.push(news);
}
}
}
const INDEXED: usize = 0;
pub(super) fn touched(server: &Server, touched: &Touched<'_>) {
for news in &touched.news {
match *news {
News::Gone(key) => server.search.lock().went(key),
News::Wrote(key) => round(server, INDEXED, key, Change::Key),
News::Renamed(from, to) => renamed(server, from, to),
}
}
}
fn renamed(server: &Server, from: &[u8], to: &[u8]) {
let reread = {
let search = server.search.lock();
if !search.watching() {
return;
}
search.rereads(Source::Hash, from, to)
};
let doc = if reread {
read(&server.dbs[INDEXED], to)
} else {
None
};
let pairs = doc.as_ref().map(Document::pairs);
server
.search
.lock()
.renamed(Source::Hash, from, to, pairs.as_deref());
}
#[derive(Debug, Default)]
pub(super) struct Document {
bytes: Vec<u8>,
ends: Vec<usize>,
}
impl Document {
fn push(&mut self, text: Text<'_>) {
match text {
Text::Str(s) => self.bytes.extend_from_slice(s),
Text::Int(n) => yo_common::num::push_i64(&mut self.bytes, n),
}
self.ends.push(self.bytes.len());
}
pub(super) fn pairs(&self) -> Vec<(&[u8], &[u8])> {
let mut at = 0;
let mut out = Vec::with_capacity(self.ends.len() / 2);
let mut parts = self.ends.iter().map(|&end| {
let part = &self.bytes[at..end];
at = end;
part
});
while let (Some(field), Some(value)) = (parts.next(), parts.next()) {
out.push((field, value));
}
out
}
}
pub(super) fn read(db: &Db, key: &[u8]) -> Option<Document> {
let mut doc = Document::default();
let mut held = db.hold(key);
let found = held.hgetall(key, |field, value| {
doc.push(field);
doc.push(value);
});
drop(held);
match found {
Ok(true) => Some(doc),
_ => None,
}
}
pub(super) fn changed(server: &Server, db: usize, key: &[u8], change: Change) {
for _ in 0..change.rounds() {
round(server, db, key, change);
}
}
fn round(server: &Server, db: usize, key: &[u8], change: Change) {
let follows = {
let search = server.search.lock();
if !search.watching() {
return;
}
search.follows(Source::Hash, key)
};
if !follows {
server.search.lock().went(key);
return;
}
let doc = read(&server.dbs[db], key);
let mut search = server.search.lock();
match doc {
Some(doc) => search.wrote(Source::Hash, key, &doc.pairs()),
None if change == Change::Taken => search.vanished(Source::Hash, key),
None if change == Change::Key => search.went(key),
None => {
search.wrote(Source::Hash, key, &[]);
search.went(key);
}
}
}
pub(super) fn scan(server: &Server, db: usize, name: &[u8]) {
if !server.search.lock().scanning(name) {
return;
}
let mut keys = Vec::new();
server.dbs[db].keys(|key| keys.push(key.to_vec()));
{
let search = server.search.lock();
keys.retain(|key| search.wants(name, Source::Hash, key));
}
for key in keys {
if let Some(doc) = read(&server.dbs[db], &key) {
server
.search
.lock()
.filled(name, Source::Hash, &key, &doc.pairs());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_document_hands_back_the_pairs_it_was_given() {
let mut doc = Document::default();
doc.push(Text::Str(b"t"));
doc.push(Text::Str(b"alpha"));
doc.push(Text::Str(b"n"));
doc.push(Text::Int(-42));
assert_eq!(
doc.pairs(),
vec![(&b"t"[..], &b"alpha"[..]), (&b"n"[..], &b"-42"[..])]
);
}
#[test]
fn a_document_with_nothing_in_it_is_no_pairs() {
assert!(Document::default().pairs().is_empty());
let mut odd = Document::default();
odd.push(Text::Str(b"t"));
assert!(odd.pairs().is_empty());
}
}