use tokio::sync::{broadcast, mpsc};
use tonic::Status;
use crate::server::ProtocolServer;
use crate::{WatchEvent, WatchRequest};
pub async fn handle_watch(
server: &ProtocolServer,
req: WatchRequest,
tx: mpsc::Sender<Result<WatchEvent, Status>>,
) {
let session = match server.validate_session(&req.session_id) {
Ok(s) => s,
Err(e) => {
let _ = tx.send(Err(e)).await;
return;
}
};
let repo_id = if !req.repo_id.is_empty() {
req.repo_id.clone()
} else {
match server.engine().get_repo(&session.codebase).await {
Ok((rid, _git_repo)) => rid.to_string(),
Err(_) => {
String::new()
}
}
};
let mut rx = if repo_id.is_empty() {
server.event_bus().subscribe_all()
} else {
server.event_bus().subscribe(&repo_id)
};
let filter = &req.filter;
let self_session_id = req.session_id.clone();
loop {
match rx.recv().await {
Ok(event) => {
if event.session_id == self_session_id {
continue;
}
if matches_filter(&event.event_type, filter)
&& tx.send(Ok(event)).await.is_err()
{
break;
}
}
Err(broadcast::error::RecvError::Lagged(n)) => {
tracing::warn!("watch stream lagged by {} events", n);
}
Err(broadcast::error::RecvError::Closed) => break,
}
}
}
fn matches_filter(event_type: &str, filter: &str) -> bool {
if filter.is_empty() || filter == "*" {
return true;
}
if let Some(prefix) = filter.strip_suffix(".*") {
event_type.starts_with(prefix)
&& event_type.as_bytes().get(prefix.len()) == Some(&b'.')
} else if let Some(suffix) = filter.strip_prefix("*.") {
event_type.ends_with(suffix)
&& event_type.len() > suffix.len()
&& event_type.as_bytes()[event_type.len() - suffix.len() - 1] == b'.'
} else {
event_type == filter
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_filter_matches_all() {
assert!(matches_filter("changeset.submitted", ""));
assert!(matches_filter("anything", ""));
}
#[test]
fn star_matches_all() {
assert!(matches_filter("changeset.submitted", "*"));
assert!(matches_filter("anything", "*"));
}
#[test]
fn prefix_glob() {
assert!(matches_filter("changeset.submitted", "changeset.*"));
assert!(matches_filter("changeset.merged", "changeset.*"));
assert!(matches_filter("changeset.verified", "changeset.*"));
assert!(!matches_filter("branch.created", "changeset.*"));
assert!(!matches_filter("changesetx.foo", "changeset.*"));
}
#[test]
fn suffix_glob() {
assert!(matches_filter("changeset.merged", "*.merged"));
assert!(matches_filter("branch.merged", "*.merged"));
assert!(!matches_filter("changeset.submitted", "*.merged"));
assert!(!matches_filter("xmerged", "*.merged"));
}
#[test]
fn exact_match() {
assert!(matches_filter("changeset.submitted", "changeset.submitted"));
assert!(!matches_filter("changeset.merged", "changeset.submitted"));
}
}