use super::terminal::CiFetchMsg;
use super::terminal::CleanMsg;
use super::terminal::ExampleMsg;
use crate::channel::Receiver;
use crate::channel::SendError;
use crate::channel::Sender;
use crate::project;
use crate::project::AbsolutePath;
use crate::project::RootItem;
use crate::scan::BackgroundMsg;
use crate::watcher::WatchRequest;
use crate::watcher::WatcherMsg;
pub struct BackgroundChannels {
pub background: (Sender<BackgroundMsg>, Receiver<BackgroundMsg>),
pub ci_fetch: (Sender<CiFetchMsg>, Receiver<CiFetchMsg>),
pub clean: (Sender<CleanMsg>, Receiver<CleanMsg>),
pub example: (Sender<ExampleMsg>, Receiver<ExampleMsg>),
pub watch_tx: Sender<WatcherMsg>,
}
pub(super) struct Background {
tx: Sender<BackgroundMsg>,
rx: Receiver<BackgroundMsg>,
ci_fetch_tx: Sender<CiFetchMsg>,
ci_fetch_rx: Receiver<CiFetchMsg>,
clean_tx: Sender<CleanMsg>,
clean_rx: Receiver<CleanMsg>,
example_tx: Sender<ExampleMsg>,
example_rx: Receiver<ExampleMsg>,
watch_tx: Sender<WatcherMsg>,
}
impl Background {
pub(super) fn new(channels: BackgroundChannels) -> Self {
let BackgroundChannels {
background: (background_tx, background_rx),
ci_fetch: (ci_fetch_tx, ci_fetch_rx),
clean: (clean_tx, clean_rx),
example: (example_tx, example_rx),
watch_tx,
} = channels;
Self {
tx: background_tx,
rx: background_rx,
ci_fetch_tx,
ci_fetch_rx,
clean_tx,
clean_rx,
example_tx,
example_rx,
watch_tx,
}
}
pub(super) fn background_sender(&self) -> Sender<BackgroundMsg> { self.tx.clone() }
pub(super) fn ci_fetch_sender(&self) -> Sender<CiFetchMsg> { self.ci_fetch_tx.clone() }
pub(super) fn clean_sender(&self) -> Sender<CleanMsg> { self.clean_tx.clone() }
pub(super) fn example_sender(&self) -> Sender<ExampleMsg> { self.example_tx.clone() }
pub(super) const fn background_receiver(&self) -> &Receiver<BackgroundMsg> { &self.rx }
pub(super) const fn ci_fetch_rx(&self) -> &Receiver<CiFetchMsg> { &self.ci_fetch_rx }
pub(super) const fn clean_rx(&self) -> &Receiver<CleanMsg> { &self.clean_rx }
pub(super) const fn example_rx(&self) -> &Receiver<ExampleMsg> { &self.example_rx }
pub(super) fn send_watcher(&self, msg: WatcherMsg) -> Result<(), SendError<WatcherMsg>> {
self.watch_tx.send(msg)
}
pub(super) fn swap_background_channel(
&mut self,
tx: Sender<BackgroundMsg>,
rx: Receiver<BackgroundMsg>,
) {
self.tx = tx;
self.rx = rx;
}
pub(super) fn replace_watcher_sender(&mut self, tx: Sender<WatcherMsg>) { self.watch_tx = tx; }
pub(super) fn register_item_background_services(&self, item: &RootItem) {
let started = std::time::Instant::now();
let abs_path = AbsolutePath::from(item.path().to_path_buf());
let repo_root = project::git_repo_root(&abs_path);
let has_repo_root = repo_root.is_some();
let _ = self.send_watcher(WatcherMsg::Register(WatchRequest {
project_label: abs_path.to_string_lossy().to_string(),
abs_path: abs_path.clone(),
repo_root,
}));
tracing::info!(
elapsed_ms = tui_pane::perf_log_ms(started.elapsed().as_millis()),
path = %item.display_path(),
has_repo_root,
"app_register_project_background_services"
);
}
}
#[cfg(test)]
#[allow(
clippy::expect_used,
clippy::unwrap_used,
reason = "tests should panic on unexpected values"
)]
mod tests {
use super::*;
use crate::channel;
fn make_msg() -> BackgroundMsg {
BackgroundMsg::RepoFetchQueued {
repo: crate::ci::OwnerRepo::new("owner", "repo"),
}
}
fn fresh() -> Background {
let (watch_tx, _watch_rx) = channel::unbounded();
Background::new(BackgroundChannels {
background: channel::unbounded(),
ci_fetch: channel::unbounded(),
clean: channel::unbounded(),
example: channel::unbounded(),
watch_tx,
})
}
#[test]
fn bg_sender_clone_round_trips_through_rx() {
let background = fresh();
let sender = background.background_sender();
sender
.send(make_msg())
.expect("send through cloned bg sender");
let received = background
.background_receiver()
.recv()
.expect("recv on background_rx");
assert!(matches!(received, BackgroundMsg::RepoFetchQueued { .. }));
}
#[test]
fn swap_bg_channel_routes_to_new_pair_only() {
let mut background = fresh();
let original_sender = background.background_sender();
let (new_tx, new_rx) = channel::unbounded();
background.swap_background_channel(new_tx, new_rx);
let _ = original_sender.send(make_msg());
assert!(
background.background_receiver().try_recv().is_err(),
"stale sender must not reach the swapped-in rx"
);
background
.background_sender()
.send(make_msg())
.expect("send through post-swap bg sender");
let received = background
.background_receiver()
.recv()
.expect("recv on swapped background_rx");
assert!(matches!(received, BackgroundMsg::RepoFetchQueued { .. }));
}
#[test]
fn send_watcher_delivers_to_watcher_channel() {
let (watch_tx, watch_rx) = channel::unbounded();
let background = Background::new(BackgroundChannels {
background: channel::unbounded(),
ci_fetch: channel::unbounded(),
clean: channel::unbounded(),
example: channel::unbounded(),
watch_tx,
});
background
.send_watcher(WatcherMsg::InitialRegistrationComplete)
.expect("send_watcher succeeds");
let received = watch_rx.recv().expect("recv on watch_rx");
assert!(matches!(received, WatcherMsg::InitialRegistrationComplete));
}
#[test]
fn replace_watcher_sender_redirects_send_watcher() {
let mut background = fresh();
let (new_watch_tx, new_watch_rx) = channel::unbounded();
background.replace_watcher_sender(new_watch_tx);
background
.send_watcher(WatcherMsg::InitialRegistrationComplete)
.expect("send_watcher succeeds post-replace");
let received = new_watch_rx.recv().expect("recv on new watcher rx");
assert!(matches!(received, WatcherMsg::InitialRegistrationComplete));
}
}