use std::fs::{self, File, OpenOptions};
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::path::{Path, PathBuf};
use serde::Serialize;
use serde::de::DeserializeOwned;
#[derive(Debug, Clone)]
pub struct DataFiles {
pub subtopics: PathBuf,
pub questions: PathBuf,
pub answers: PathBuf,
pub train: PathBuf,
pub eval: PathBuf,
pub rejected: PathBuf,
}
impl DataFiles {
#[must_use]
pub fn new(project_dir: &Path) -> Self {
let data = project_dir.join("data");
Self {
subtopics: data.join("subtopics.jsonl"),
questions: data.join("questions.jsonl"),
answers: data.join("answers.jsonl"),
train: data.join("train.jsonl"),
eval: data.join("eval.jsonl"),
rejected: data.join("rejected.jsonl"),
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum DatasetError {
#[error("cannot access {}", path.display())]
Io {
path: PathBuf,
#[source]
source: io::Error,
},
#[error("{}:{line}: invalid record", path.display())]
Parse {
path: PathBuf,
line: usize,
#[source]
source: serde_json::Error,
},
}
fn io_error(path: &Path) -> impl FnOnce(io::Error) -> DatasetError + '_ {
move |source| DatasetError::Io {
path: path.to_path_buf(),
source,
}
}
pub fn read<T: DeserializeOwned>(path: &Path) -> Result<Vec<T>, DatasetError> {
let content = match fs::read(path) {
Ok(content) => content,
Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(Vec::new()),
Err(e) => return Err(io_error(path)(e)),
};
let complete = content.is_empty() || content.ends_with(b"\n");
let mut lines: Vec<&[u8]> = content.split(|byte| *byte == b'\n').collect();
if complete {
lines.pop();
}
let mut items = Vec::with_capacity(lines.len());
for (index, line) in lines.iter().enumerate() {
if line.trim_ascii().is_empty() {
continue;
}
match serde_json::from_slice(line) {
Ok(item) => items.push(item),
Err(_) if !complete && index + 1 == lines.len() && !is_json(line) => {
tracing::warn!("{}: ignoring an incomplete last line", path.display());
},
Err(source) => {
return Err(DatasetError::Parse {
path: path.to_path_buf(),
line: index + 1,
source,
});
},
}
}
Ok(items)
}
#[derive(Debug)]
pub struct Appender {
path: PathBuf,
file: File,
}
impl Appender {
pub fn open(path: &Path) -> Result<Self, DatasetError> {
if let Some(dir) = path.parent() {
fs::create_dir_all(dir).map_err(io_error(dir))?;
}
let mut file = OpenOptions::new()
.create(true)
.read(true)
.append(true)
.open(path)
.map_err(io_error(path))?;
end_last_line(&mut file).map_err(io_error(path))?;
Ok(Self {
path: path.to_path_buf(),
file,
})
}
pub fn append<T: Serialize>(&mut self, item: &T) -> Result<(), DatasetError> {
let mut line = serde_json::to_vec(item).map_err(|e| io_error(&self.path)(e.into()))?;
line.push(b'\n');
self.file
.write_all(&line)
.and_then(|()| self.file.flush())
.map_err(io_error(&self.path))
}
}
fn end_last_line(file: &mut File) -> io::Result<()> {
let len = file.metadata()?.len();
if len == 0 {
return Ok(());
}
let mut last = [0_u8; 1];
file.seek(SeekFrom::End(-1))?;
file.read_exact(&mut last)?;
if last[0] == b'\n' {
return Ok(());
}
let mut content = Vec::new();
file.seek(SeekFrom::Start(0))?;
file.read_to_end(&mut content)?;
let keep = content
.iter()
.rposition(|byte| *byte == b'\n')
.map_or(0, |index| index + 1);
if is_json(&content[keep..]) {
return file.write_all(b"\n");
}
file.set_len(u64::try_from(keep).map_err(io::Error::other)?)
}
fn is_json(line: &[u8]) -> bool {
serde_json::from_slice::<serde::de::IgnoredAny>(line).is_ok()
}
pub fn rewrite<T: Serialize>(path: &Path, items: &[T]) -> Result<(), DatasetError> {
let tmp = write_temp(path, "tmp", items)?;
fs::rename(&tmp, path).map_err(io_error(path))
}
fn write_temp<T: Serialize>(
path: &Path,
suffix: &str,
items: &[T],
) -> Result<PathBuf, DatasetError> {
let dir = path.parent().unwrap_or_else(|| Path::new("."));
fs::create_dir_all(dir).map_err(io_error(dir))?;
let name = path
.file_name()
.map_or_else(|| "data".into(), |name| name.to_string_lossy());
let tmp = dir.join(format!(".{name}.{suffix}"));
let mut buffer = Vec::new();
for item in items {
serde_json::to_writer(&mut buffer, item).map_err(|e| io_error(&tmp)(e.into()))?;
buffer.push(b'\n');
}
let mut file = File::create(&tmp).map_err(io_error(&tmp))?;
file.write_all(&buffer)
.and_then(|()| file.sync_all())
.map_err(io_error(&tmp))?;
Ok(tmp)
}
#[derive(Debug, Default)]
pub struct Rewrite {
staged: Vec<(PathBuf, PathBuf)>,
}
impl Rewrite {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn stage<T: Serialize>(&mut self, path: &Path, items: &[T]) -> Result<(), DatasetError> {
let tmp = write_temp(path, "staged.tmp", items)?;
self.staged.push((tmp, path.to_path_buf()));
Ok(())
}
pub fn commit(mut self) -> Result<(), DatasetError> {
let mut pending = std::mem::take(&mut self.staged).into_iter();
while let Some((tmp, path)) = pending.next() {
if let Err(source) = fs::rename(&tmp, &path) {
fs::remove_file(&tmp).ok();
self.staged = pending.collect();
return Err(io_error(&path)(source));
}
}
Ok(())
}
}
impl Drop for Rewrite {
fn drop(&mut self) {
for (tmp, _) in &self.staged {
fs::remove_file(tmp).ok();
}
}
}