use std::sync::Arc;
use futures_util::StreamExt;
use tokio::task::AbortHandle;
use super::{Response, Shared};
use crate::docker::DockerClient;
const LOG_TAIL: usize = 500;
const LOG_BATCH: usize = 256;
#[derive(Default)]
pub struct Watch {
tasks: Vec<AbortHandle>,
}
impl Watch {
pub fn start(&mut self, client: &DockerClient, out: &Arc<Shared>, container_id: String) {
self.stop();
let logs = tokio::spawn(stream_logs(
client.clone(),
out.clone(),
container_id.clone(),
));
let stats = tokio::spawn(stream_stats(client.clone(), out.clone(), container_id));
self.tasks = vec![logs.abort_handle(), stats.abort_handle()];
}
pub fn stop(&mut self) {
for task in self.tasks.drain(..) {
task.abort();
}
}
}
async fn stream_logs(client: DockerClient, out: Arc<Shared>, container_id: String) {
let mut batches = std::pin::pin!(client.logs(&container_id, LOG_TAIL).ready_chunks(LOG_BATCH));
while let Some(batch) = batches.next().await {
let mut text = String::new();
let mut ended = false;
for chunk in batch {
match chunk {
Ok(chunk) => text.push_str(&chunk),
Err(_) => ended = true,
}
}
if !text.is_empty() {
out.send(Response::Logs {
container_id: container_id.clone(),
text,
});
}
if ended {
break;
}
}
}
async fn stream_stats(client: DockerClient, out: Arc<Shared>, container_id: String) {
let mut readings = std::pin::pin!(client.stats(&container_id));
while let Some(Ok(sample)) = readings.next().await {
out.send(Response::Stats {
container_id: container_id.clone(),
sample,
});
}
}