use std::io;
use std::path::Path;
use shep_client::{Client, RequestError};
use shep_core::protocol::{LogStream, LogTail, ProcessInfo, RpcErrorCode};
use super::missing_log_note;
use super::read_tail;
use crate::commands::rpc::typed_request_error;
use crate::exit::ExitCode;
use crate::output::Streams;
use crate::remote::ssh_hint;
#[derive(Clone, Copy)]
pub(super) enum LogSource<'a> {
Local,
Host {
client: &'a Client,
name: &'a str,
},
}
pub(super) enum FileTail {
Lines(Vec<String>),
NoPath,
Missing(String),
Unreadable(String),
}
impl LogSource<'_> {
pub(super) async fn read(
&self,
info: &ProcessInfo,
stream: LogStream,
path: &str,
lines: usize,
) -> Result<FileTail, RequestError> {
let label = stream_label(stream);
match self {
Self::Local => {
let stamped = info.log_timestamps.unwrap_or(true);
Ok(match read_tail(Path::new(path), lines, stamped) {
Ok((lines, _truncated)) => FileTail::Lines(lines),
Err(err) if err.kind() == io::ErrorKind::NotFound => {
FileTail::Missing(missing_log_note(label, Path::new(path)))
}
Err(err) => FileTail::Unreadable(format!("failed to read {path}: {err}")),
})
}
Self::Host { client, name } => {
let lines = u32::try_from(lines).unwrap_or(u32::MAX);
let tail = match client.tail_log(info.id, stream, lines).await {
Ok(tail) => tail,
Err(RequestError::Rpc(rpc)) if rpc.code == RpcErrorCode::NotFound => {
return Ok(FileTail::Missing(format!(
"no {label} log to read: the sheep is gone from {name}"
)));
}
Err(err) => return Err(err),
};
Ok(match tail {
LogTail::Lines { lines, .. } => FileTail::Lines(lines),
LogTail::NoPath => FileTail::NoPath,
LogTail::Missing { path } => {
FileTail::Missing(format!("no {label} log at {path} on {name} yet"))
}
LogTail::Unreadable { path, reason } => {
FileTail::Unreadable(format!("failed to read {path} on {name}: {reason}"))
}
_ => FileTail::Unreadable(format!(
"failed to read {path} on {name}: this client does not understand \
the answer"
)),
})
}
}
}
pub(super) fn stop(&self, streams: &mut Streams<'_>, err: &RequestError) -> ExitCode {
match (self, err) {
(Self::Host { name, .. }, RequestError::Rpc(rpc))
if rpc.code == RpcErrorCode::Unsupported =>
{
let words = ["shep", "daemon", "reload"].map(String::from);
let message = format!(
"the shepherd on {name} predates remote log reads; run `{}` to load a \
newer one",
ssh_hint(name, &words)
);
streams.fail(ExitCode::Unsupported, &message)
}
_ => typed_request_error(streams, err),
}
}
}
pub(super) fn stream_label(stream: LogStream) -> &'static str {
match stream {
LogStream::Err => "err",
_ => "out",
}
}