use std::fs::{self, File};
use std::io::{BufRead, Seek};
use std::path::PathBuf;
#[doc(inline)]
use crate::error::PathError;
pub fn append_open(filename: &str) -> Result<File, PathError> {
fs::OpenOptions::new()
.create(true)
.append(true)
.open(filename)
.map_err(|e| PathError::FileAccess(filename.to_string(), e.to_string()))
}
pub fn rw_open(filename: &str) -> Result<File, PathError> {
fs::OpenOptions::new()
.read(true)
.write(true)
.open(filename)
.map_err(|e| PathError::FileAccess(filename.to_string(), e.to_string()))
}
pub(crate) enum FileKind {
LogFile,
StackFile
}
impl From<FileKind> for PathError {
fn from(kind: FileKind) -> PathError {
match kind {
FileKind::LogFile => PathError::InvalidTimelogPath,
FileKind::StackFile => PathError::InvalidStackPath
}
}
}
pub(crate) fn canonical_filename(file: &str, kind: FileKind) -> Result<String, PathError> {
if file.is_empty() {
return Err(PathError::FilenameMissing);
}
let mut dir = PathBuf::from(file);
let filename = dir
.file_name()
.ok_or(PathError::FilenameMissing)?
.to_os_string();
dir.pop();
let mut candir = fs::canonicalize(dir)
.map_err(|e| PathError::InvalidPath(file.to_string(), e.to_string()))?;
candir.push(filename);
candir.into_os_string().into_string().map_err(|_| kind.into())
}
pub fn pop_last_line(file: &mut File) -> Option<String> {
let (line, pos) = find_last_line(file)?;
file.set_len(pos).ok()?;
Some(line)
}
pub fn find_last_line(file: &mut File) -> Option<(String, u64)> {
let mut last_pos = 0u64;
let mut last_line = String::new();
if file.metadata().ok()?.len() == 0u64 {
return None;
}
let mut reader = std::io::BufReader::new(file);
while let Some((line, pos)) = last_line_and_pos(&mut reader) {
last_pos = pos;
last_line = line.trim_end().to_string();
}
Some((last_line, last_pos))
}
fn last_line_and_pos<R>(reader: &mut R) -> Option<(String, u64)>
where
R: BufRead + Seek
{
let pos = reader.stream_position().ok()?;
let mut line = String::new();
(0 != reader.read_line(&mut line).ok()?).then_some((line, pos))
}