use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread::JoinHandle;
use std::time::Duration;
use super::LiveTail;
use crate::git_tree::{Stream, fold_stream, latest_response_path};
use crate::state::TailCell;
use crate::watch::Repaint;
const FOLLOW_POLL: Duration = Duration::from_millis(16);
struct Open {
path: PathBuf,
offset: u64,
partial: Vec<u8>,
}
pub struct Follower {
cell: TailCell,
at: Option<(LiveTail, Open)>,
}
impl Follower {
pub(crate) fn new(cell: TailCell) -> Self {
Self { cell, at: None }
}
pub fn pass(&mut self) -> bool {
let Some((ws, agent)) = crate::state::asked(&self.cell) else {
return self.clear();
};
self.reseat(&ws, &agent);
let Some((tail, open)) = self.at.as_mut() else {
return self.clear();
};
let Some(appended) = open.read_appended() else {
return false;
};
tail.stream.absorb(appended);
crate::state::publish_tail(&self.cell, Some(tail.clone()))
}
fn clear(&mut self) -> bool {
self.at = None;
crate::state::publish_tail(&self.cell, None)
}
fn reseat(&mut self, ws: &Path, agent: &str) {
let path = latest_response_path(ws, agent);
let same = match (&self.at, &path) {
(Some((tail, open)), Some(path)) => {
tail.ws == ws && tail.agent == agent && open.path == *path
}
_ => false,
};
if same {
return;
}
self.at = path.map(|path| {
(
LiveTail {
ws: ws.to_path_buf(),
agent: agent.to_owned(),
stream: Stream::default(),
},
Open {
path,
offset: 0,
partial: Vec::new(),
},
)
});
}
pub fn spawn(mut self, repaint: impl Repaint + 'static) -> FollowThread {
let stop = Arc::new(AtomicBool::new(false));
let flag = Arc::clone(&stop);
let handle = std::thread::spawn(move || {
while !flag.load(Ordering::Relaxed) {
if self.pass() {
repaint.request();
}
std::thread::park_timeout(FOLLOW_POLL);
}
});
FollowThread {
stop,
handle: Some(handle),
}
}
}
impl Open {
fn read_appended(&mut self) -> Option<Stream> {
let len = std::fs::metadata(&self.path).ok()?.len();
if len < self.offset {
self.offset = 0;
self.partial.clear();
}
if len == self.offset {
return None;
}
let fresh = read_from(&self.path, self.offset)?;
self.offset = len.min(self.offset + fresh.len() as u64);
self.partial.extend_from_slice(&fresh);
let end = self.partial.iter().rposition(|&b| b == b'\n')? + 1;
let folded = fold_stream(self.partial.get(..end)?);
self.partial.drain(..end);
Some(folded)
}
}
fn read_from(path: &Path, offset: u64) -> Option<Vec<u8>> {
use std::io::{Read, Seek, SeekFrom};
let mut file = std::fs::File::open(path).ok()?;
file.seek(SeekFrom::Start(offset)).ok()?;
let mut fresh = Vec::new();
file.read_to_end(&mut fresh).ok()?;
Some(fresh)
}
pub struct FollowThread {
stop: Arc<AtomicBool>,
handle: Option<JoinHandle<()>>,
}
impl Drop for FollowThread {
fn drop(&mut self) {
self.stop.store(true, Ordering::Relaxed);
if let Some(handle) = self.handle.take() {
handle.thread().unpark();
let _ = handle.join();
}
}
}