use std::fs::{File, Metadata};
use std::io::{BufReader, Read, Seek, SeekFrom};
use std::path::Path;
mod errors;
pub use errors::StaartError;
type Result<T> = std::result::Result<T, StaartError>;
pub struct TailedFile<T> {
path: T,
pos: u64,
meta: Metadata,
}
impl<T> TailedFile<T>
where
T: AsRef<Path> + Copy,
{
pub fn new(path: T) -> Result<TailedFile<T>> {
let f = File::open(path)?;
let meta = f.metadata()?;
let pos = meta.len();
Ok(TailedFile { path, pos, meta })
}
pub fn read(&mut self) -> Result<Vec<u8>> {
let fd = File::open(self.path)?;
self.check_rotate(&fd)?;
self.check_truncate(&fd)?;
let mut reader = BufReader::with_capacity(65536, &fd);
let mut data: [u8; 65536] = [0u8; 65536];
reader.seek(SeekFrom::Start(self.pos))?;
let n: u64 = reader.read(&mut data)?.try_into()?;
self.pos += n;
let data: Vec<u8> = data.into_iter().take(n.try_into()?).collect();
Ok(data)
}
pub fn read_and<F: Fn(&[u8])>(&mut self, f: F) -> Result<()> {
let data = self.read()?;
f(&data);
Ok(())
}
#[cfg(target_os = "linux")]
fn check_rotate(&mut self, fd: &File) -> Result<()> {
use std::os::linux::fs::MetadataExt;
let meta = fd.metadata()?;
let inode = meta.st_ino();
if inode != self.meta.st_ino() {
self.pos = 0;
self.meta = meta;
}
Ok(())
}
#[cfg(target_os = "windows")]
fn check_rotate(&mut self, fd: &File) -> Result<()> {
use std::os::windows::fs::MetadataExt;
let meta = fd.metadata()?;
let created_at = meta.creation_time();
if created_at != self.meta.creation_time() {
self.pos = 0;
self.meta = meta;
}
Ok(())
}
#[cfg(target_os = "macos")]
fn check_rotate(&mut self, fd: &File) -> Result<()> {
use std::os::unix::fs::MetadataExt;
let meta = fd.metadata()?;
let inode = meta.ino();
if inode != self.meta.ino() {
self.pos = 0;
self.meta = meta;
}
Ok(())
}
#[cfg(target_os = "linux")]
fn check_truncate(&mut self, fd: &File) -> Result<()> {
use std::os::linux::fs::MetadataExt;
let meta = fd.metadata()?;
let inode = meta.st_ino();
let len = meta.len();
if inode == self.meta.st_ino() && len < self.pos {
self.pos = 0;
}
Ok(())
}
#[cfg(target_os = "windows")]
fn check_truncate(&mut self, fd: &File) -> Result<()> {
use std::os::windows::fs::MetadataExt;
let meta = fd.metadata()?;
let created_at = meta.creation_time();
let len = meta.len();
if created_at == self.meta.creation_time() && len < self.pos {
self.pos = 0;
}
Ok(())
}
#[cfg(target_os = "macos")]
fn check_truncate(&mut self, fd: &File) -> Result<()> {
use std::os::unix::fs::MetadataExt;
let meta = fd.metadata()?;
let inode = meta.ino();
let len = meta.len();
if inode == self.meta.ino() && len < self.pos {
self.pos = 0;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::io::Write;
use super::*;
#[test]
fn tailed_file() {
let dir = tempfile::tempdir().unwrap();
let path = &dir.path().join("test.file");
let _f = File::create(path).unwrap();
let tailed_file = TailedFile::new(&path);
assert!(tailed_file.is_ok())
}
#[test]
fn test_read() {
let dir = tempfile::tempdir().unwrap();
let path = &dir.path().join("test.file");
let test_data = b"Some data";
let mut f = File::create(path).unwrap();
let mut tailed_file = TailedFile::new(&path).unwrap();
f.write_all(test_data).unwrap();
let data = tailed_file.read().unwrap();
assert_eq!(data.len(), test_data.len());
assert_eq!(tailed_file.pos, 9);
}
#[test]
fn test_check_rotate() {
let dir = tempfile::tempdir().unwrap();
let path = &dir.path().join("test.file");
let path2 = &dir.path().join("test2.file");
let test_data = b"Some data";
let more_test_data = b"fun";
let mut f = File::create(path).unwrap();
f.write_all(test_data).unwrap();
let mut tailed_file = TailedFile::new(&path).unwrap();
std::fs::rename(path, path2).unwrap();
let mut f = File::create(path).unwrap();
f.write_all(more_test_data).unwrap();
tailed_file.check_rotate(&f).unwrap();
#[cfg(target_os = "linux")]
{
use std::os::linux::fs::MetadataExt;
assert_eq!(tailed_file.meta.st_ino(), f.metadata().unwrap().st_ino())
}
#[cfg(target_os = "macos")]
{
use std::os::unix::fs::MetadataExt;
assert_eq!(tailed_file.meta.ino(), f.metadata().unwrap().ino())
}
#[cfg(target_os = "windows")]
{
use std::os::windows::fs::MetadataExt;
assert_eq!(
tailed_file.meta.creation_time(),
f.metadata().unwrap().creation_time()
)
}
}
#[test]
fn test_check_truncate() {
let dir = tempfile::tempdir().unwrap();
let path = &dir.path().join("test.file");
let test_data = b"Some data";
let more_test_data = b"fun";
let mut f = File::create(path).unwrap();
f.write_all(test_data).unwrap();
let mut tailed_file = TailedFile::new(&path).unwrap();
let mut f = File::create(path).unwrap();
f.write_all(more_test_data).unwrap();
tailed_file.check_truncate(&f).unwrap();
assert_eq!(tailed_file.pos, 0)
}
}