d65720c671
The oneliner is now stored as `OnelinerState` enum which can represent three states: `Ready`, `Empty` or `Error`. This allows the client to differentiate between a case with no medical content and a case where the oneliner generation failed. The `OnelinerState` enum is serialized to JSON and stored in `oneliner.json` file. The `OnelinerEntry` struct has been updated to reflect this change. The client-desktop application has been updated to handle the new `OnelinerState` enum and display appropriate UI elements for each state. The server-side code has also been updated to read and write the `OnelinerState` enum, and to handle the new file format. The `reset_case_artefacts` function in `server/src/routes/case_actions.rs` has been updated to remove `oneliner.txt` and create `oneliner.json` instead. The tests have been updated to reflect these changes.
84 lines
3.0 KiB
Rust
84 lines
3.0 KiB
Rust
use std::path::{Path, PathBuf};
|
|
|
|
use doctate_common::oneliners::{ONELINER_FILENAME, OnelinerState};
|
|
use serde::{Deserialize, Serialize};
|
|
use time::OffsetDateTime;
|
|
use uuid::Uuid;
|
|
|
|
/// Absolute on-disk location of a case directory.
|
|
///
|
|
/// Single source of truth for the layout `<data_path>/<slug>/<case_id>/`.
|
|
/// Use everywhere instead of inlining `.join(slug).join(case_id)` so the
|
|
/// layout can evolve in one place.
|
|
pub fn case_dir(data_path: &Path, slug: &str, case_id: &str) -> PathBuf {
|
|
data_path.join(slug).join(case_id)
|
|
}
|
|
|
|
/// Filename of the soft-delete marker inside a case directory.
|
|
pub const DELETE_MARKER: &str = ".deleted";
|
|
|
|
/// Soft-delete marker payload. One file per deleted case; all cases removed
|
|
/// in the same user click share the same `batch` UUID. `deleted_at` is an
|
|
/// RFC3339 timestamp — used to find the most recent batch for "Undo last
|
|
/// delete".
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct DeleteMarker {
|
|
pub batch: Uuid,
|
|
pub deleted_at: String,
|
|
}
|
|
|
|
/// True iff the case directory contains a `.deleted` marker file.
|
|
pub async fn is_deleted(case_dir: &Path) -> bool {
|
|
tokio::fs::try_exists(case_dir.join(DELETE_MARKER))
|
|
.await
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
/// Read and parse the `.deleted` marker. Returns `None` if absent or malformed
|
|
/// (treated identically — a malformed marker still means "deleted" via
|
|
/// `is_deleted`, but cannot participate in batch-undo).
|
|
pub async fn read_delete_marker(case_dir: &Path) -> Option<DeleteMarker> {
|
|
let bytes = tokio::fs::read(case_dir.join(DELETE_MARKER)).await.ok()?;
|
|
serde_json::from_slice(&bytes).ok()
|
|
}
|
|
|
|
/// Write the `.deleted` marker as JSON. Overwrites any existing marker.
|
|
pub async fn write_delete_marker(case_dir: &Path, marker: &DeleteMarker) -> std::io::Result<()> {
|
|
let bytes = serde_json::to_vec(marker)
|
|
.map_err(|e| std::io::Error::other(format!("serialize delete marker: {e}")))?;
|
|
tokio::fs::write(case_dir.join(DELETE_MARKER), bytes).await
|
|
}
|
|
|
|
/// Read the persisted oneliner state for a case.
|
|
///
|
|
/// Returns `(Some(state), Some(mtime))` when `oneliner.json` exists and
|
|
/// parses. Missing file → `(None, None)`. A file that exists but fails
|
|
/// to parse is treated as missing and logged — the next transcription
|
|
/// job will overwrite it with a well-formed state.
|
|
pub async fn read_oneliner_state(
|
|
case_dir: &Path,
|
|
) -> (Option<OnelinerState>, Option<OffsetDateTime>) {
|
|
let path = case_dir.join(ONELINER_FILENAME);
|
|
let bytes = match tokio::fs::read(&path).await {
|
|
Ok(b) => b,
|
|
Err(_) => return (None, None),
|
|
};
|
|
let state: OnelinerState = match serde_json::from_slice(&bytes) {
|
|
Ok(s) => s,
|
|
Err(e) => {
|
|
tracing::warn!(
|
|
path = %path.display(),
|
|
error = %e,
|
|
"malformed oneliner.json; treating as missing"
|
|
);
|
|
return (None, None);
|
|
}
|
|
};
|
|
let mtime = tokio::fs::metadata(&path)
|
|
.await
|
|
.ok()
|
|
.and_then(|m| m.modified().ok())
|
|
.map(OffsetDateTime::from);
|
|
(Some(state), mtime)
|
|
}
|