use std::{
fs::{self, File},
path::PathBuf,
};
use log::*;
#[cfg(feature = "py")]
use pyo3::prelude::*;
use serde::{Deserialize, Serialize};
use crate::{
db::{Database, DatabaseSession},
error::{Error, Result},
value::{Float, Value},
Dataset, Session,
};
#[cfg(test)]
mod test;
impl TryFrom<Value> for serde_json::Value {
type Error = Error;
fn try_from(value: Value) -> Result<Self> {
match value {
Value::Bool(x) => Ok(x.into()),
Value::Char(x) => Ok(x.to_string().into()),
Value::Map(x) => {
let mut res = serde_json::Map::with_capacity(x.len());
for (k, v) in &x {
res.insert(k.clone(), v.clone().try_into()?);
}
Ok(serde_json::Value::Object(res))
}
Value::Variant(x, _) => Ok(x.into()),
Value::StructVariant(_, _) => todo!(),
Value::Float(Float(x)) => Ok(x.into()),
Value::Int(x) => Ok(x.into()),
Value::String(x) => Ok(x.into()),
Value::Seq(x) => {
let y: Result<Vec<serde_json::Value>> =
x.into_iter().map(|x| x.try_into()).collect();
Ok(serde_json::Value::Array(y?))
}
Value::Unit => Ok(().into()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "py", pyclass)]
pub struct Json {
json_folder: PathBuf,
}
impl Json {
pub fn new<P: Into<PathBuf>>(json_folder: P) -> Self {
Self {
json_folder: json_folder.into(),
}
}
}
#[cfg(feature = "py")]
#[pymethods]
impl Json {
#[new]
fn py_new(json_folder: String) -> Self {
Self::new(json_folder)
}
fn get_sessions(&self) -> Result<Vec<JsonSession>> {
self.get_json_sessions()
}
fn add_session(&mut self, session: Session) -> Result<JsonSession> {
self.add_json_session(session)
}
#[pyo3(name = "remove_session")]
fn py_remove_session(&mut self, session: Session) -> Result<()> {
self.remove_session(session)
}
fn __repr__(&self) -> String {
format!("Json(path={})", self.json_folder.display())
}
}
impl Database for Json {
type Error = Error;
fn add_session(
&mut self,
session: Session,
) -> Result<Box<dyn DatabaseSession<Error = Self::Error>>> {
Ok(Box::new(self.add_json_session(session)?))
}
fn get_sessions(&self) -> Result<Vec<Box<dyn DatabaseSession<Error = Self::Error>>>> {
Ok(self
.get_json_sessions()?
.into_iter()
.map(|x| Box::new(x) as Box<dyn DatabaseSession<Error = Self::Error>>)
.collect())
}
fn remove_session(&mut self, _session: Session) -> Result<()> {
Err(Error::RemoveUnsupported)
}
}
impl Json {
pub fn add_json_session(&mut self, session: Session) -> Result<JsonSession> {
let filename = format!("{}.json", session.run.id);
let mut path = self.json_folder.clone();
fs::create_dir_all(&path)?;
path.push(filename);
let file = File::create(path)?;
serde_json::to_writer(file, &session)?;
let session_path = self.json_folder.clone();
Ok(JsonSession::new(session, session_path))
}
pub fn get_json_sessions(&self) -> Result<Vec<JsonSession>> {
let session_path = self.json_folder.clone();
let res = fs::read_dir(&self.json_folder)
.into_iter()
.flatten()
.filter_map(|x| x.ok())
.filter(|x| x.file_type().map(|y| y.is_file()).unwrap_or(false))
.inspect(|x| debug!("Trying to open session at {}", x.path().display()))
.filter_map(|x| File::open(x.path()).ok())
.filter_map(|x| -> Option<Session> {
match serde_json::from_reader(x) {
Ok(x) => Some(x),
Err(e) => {
info!("Failed to deserialize session: {}", e);
None
}
}
})
.map(move |x| JsonSession::new(x, session_path.clone()))
.collect();
Ok(res)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "py", pyclass(get_all))]
pub struct JsonSession {
session: Session,
path: PathBuf,
}
#[cfg(feature = "py")]
#[pymethods]
impl JsonSession {
#[getter]
fn get_software(&self) -> crate::Software {
self.session.software.clone()
}
#[getter]
fn get_run(&self) -> crate::Run {
self.session.run.clone()
}
fn __repr__(&self) -> String {
format!(
"JsonSession(software={}-{}, run={}, path={})",
self.session.software.name,
self.session.software.version,
self.session.run.date,
self.path.display()
)
}
#[pyo3(name = "add_dataset")]
fn py_add_dataset(&mut self, dataset: &Dataset) -> Result<()> {
self.add_dataset(dataset)
}
#[pyo3(name = "get_datasets")]
fn py_get_datasets(&self) -> Result<Vec<Dataset>> {
self.get_datasets()
}
#[pyo3(name = "remove_dataset")]
fn py_remove_dataset(&mut self, id: String) -> Result<()> {
let id = uuid::Uuid::parse_str(&id)?;
self.remove_dataset(&id)
}
}
impl JsonSession {
pub fn new<P: Into<PathBuf>>(session: Session, path: P) -> Self {
let mut path = path.into();
path.push(session.run.id.to_string());
Self { session, path }
}
}
impl DatabaseSession for JsonSession {
type Error = Error;
fn session(&self) -> &Session {
&self.session
}
fn add_dataset(&mut self, dataset: &crate::Dataset) -> Result<()> {
info!("Adding dataset: {dataset:?}");
let mut filename = self.path.clone();
fs::create_dir_all(&filename)?;
filename.push(format!("{}.json", dataset.id));
debug!("Writing dataset to {}", filename.display());
let file = File::create(&filename)?;
serde_json::to_writer(file, &dataset)?;
Ok(())
}
fn get_datasets(&self) -> Result<Vec<Dataset>> {
debug!("Looking for datasets at {}", self.path.display());
let res = fs::read_dir(&self.path)
.into_iter()
.flatten()
.filter_map(|x| x.ok())
.map(|x| File::open(x.path()))
.filter_map(|x| x.ok())
.filter_map(|x| -> Option<Dataset> {
serde_json::from_reader(x)
.map_err(|e| {
warn!("Error while deserializing dataset: {e}");
e
})
.ok()
})
.collect();
Ok(res)
}
fn remove_dataset(&mut self, _id: &uuid::Uuid) -> std::prelude::v1::Result<(), Self::Error> {
Err(Error::RemoveUnsupported)
}
}