Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Async tail with notify

use std::{env, fs::File, io::{BufRead, BufReader, Seek, SeekFrom}, path::PathBuf};
use tokio::sync::mpsc;
use notify::{Watcher, RecursiveMode, EventKind, RecommendedWatcher};

#[tokio::main]
async fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() != 2 {
        eprintln!("Usage: {} <filename>", args[0]);
        std::process::exit(1);
    }
    let filename = PathBuf::from(&args[1]);

    let (tx, mut rx) = mpsc::channel(1);
    let tx2 = tx.clone();

    // Set up file watcher
    let mut watcher = RecommendedWatcher::new(
        move |res: Result<notify::Event, notify::Error>| {
            if let Ok(event) = res {
                if matches!(event.kind, EventKind::Modify(_)) {
                    let _ = tx2.try_send(());
                }
            }
        },
        notify::Config::default()
    ).expect("Failed to create watcher");
    watcher.watch(&filename, RecursiveMode::NonRecursive).expect("Failed to watch file");

    // Open file and seek to end
    let mut file = File::open(&filename).expect("Failed to open file");
    let mut pos = file.seek(SeekFrom::End(0)).expect("Failed to seek");

    loop {
        // Wait for a file change event
        rx.recv().await;
        // Re-open file and seek to last position
        let mut file = File::open(&filename).expect("Failed to open file");
        file.seek(SeekFrom::Start(pos)).expect("Failed to seek");
        let mut reader = BufReader::new(file);
        let mut buf = String::new();
        while reader.read_line(&mut buf).expect("Failed to read line") > 0 {
            print!("{}", buf);
            buf.clear();
        }
        pos = reader.stream_position().expect("Failed to get position");
    }
}
[package]
name = "async-tail"
version = "0.1.0"
edition = "2024"

[dependencies]
tokio = { version = "1", features = ["full"] }
notify = "6"