Skip to main content

gettext_mcp/io/
mod.rs

1//! Filesystem abstraction used by the store and manager.
2//!
3//! Production code uses [`FsFileStore`] from [`fs`]; tests can plug in
4//! their own [`FileStore`] impl for in-memory or fault-injected scenarios.
5
6pub mod fs;
7
8use std::path::Path;
9use std::time::SystemTime;
10
11use crate::error::GettextError;
12
13pub use fs::{cleanup_orphan_tmps, FsFileStore};
14
15/// File I/O contract used by [`crate::service::store::GettextStore`].
16///
17/// Implementations are expected to be `Send + Sync` so they can be shared
18/// behind an `Arc` across async tasks. Reads and writes are blocking by
19/// design (called from `spawn_blocking` in the store layer).
20pub trait FileStore: Send + Sync {
21    /// Read the file at `path` as a UTF-8 string. Implementations should
22    /// strip any leading UTF-8 BOM before returning.
23    fn read(&self, path: &Path) -> Result<String, GettextError>;
24
25    /// Atomically write `content` to `path`. Implementations should hold
26    /// an advisory lock on the target during the write where possible.
27    fn write(&self, path: &Path, content: &str) -> Result<(), GettextError>;
28
29    /// Atomically write a raw byte buffer to `path`. Used for binary
30    /// artifacts like `.mo` files. Default impl just routes through
31    /// [`FileStore::write`] by treating the bytes as UTF-8 — implementors
32    /// that need to honour arbitrary bytes (the production
33    /// [`FsFileStore`]) should override.
34    fn write_bytes(&self, path: &Path, bytes: &[u8]) -> Result<(), GettextError> {
35        let s = std::str::from_utf8(bytes).map_err(|e| {
36            GettextError::InvalidInput(format!("write_bytes default impl expects UTF-8: {e}"))
37        })?;
38        self.write(path, s)
39    }
40
41    /// Return the on-disk modified time of `path`, used by the store
42    /// manager to detect external edits and invalidate caches.
43    fn modified_time(&self, path: &Path) -> Result<SystemTime, GettextError>;
44
45    /// `true` if `path` exists on disk.
46    fn exists(&self, path: &Path) -> bool;
47}