use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use crate::dreaming::{
apply_dreaming_plan, plan_memory_dreaming, DreamingConfig, DreamingOutcome, DreamingPlan,
};
use crate::memory::MemoryStore;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StorageSnapshot {
pub capacity_bytes: u64,
pub free_bytes: u64,
}
pub fn measure_storage(memory_path: &Path) -> io::Result<StorageSnapshot> {
let measurement_path = existing_ancestor(memory_path);
Ok(StorageSnapshot {
capacity_bytes: fs2::total_space(&measurement_path)?,
free_bytes: fs2::available_space(&measurement_path)?,
})
}
#[must_use]
pub fn auto_free_space_preference_path(memory_path: &Path) -> PathBuf {
let filename = memory_path
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("formal-ai-memory.lino");
memory_path.with_file_name(format!("{filename}.auto-free-space"))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AutoFreeSpaceChoice {
NeverAsked,
Declined,
Enabled,
}
#[must_use]
pub fn auto_free_space_choice(memory_path: &Path) -> AutoFreeSpaceChoice {
match fs::read_to_string(auto_free_space_preference_path(memory_path)) {
Ok(value) if value.trim() == "enabled" => AutoFreeSpaceChoice::Enabled,
Ok(value) if value.trim() == "disabled" => AutoFreeSpaceChoice::Declined,
_ => AutoFreeSpaceChoice::NeverAsked,
}
}
#[must_use]
pub fn auto_free_space_enabled(memory_path: &Path) -> bool {
auto_free_space_choice(memory_path) == AutoFreeSpaceChoice::Enabled
}
pub fn persist_auto_free_space_choice(memory_path: &Path, enabled: bool) -> io::Result<()> {
let preference = auto_free_space_preference_path(memory_path);
if let Some(parent) = preference.parent() {
if !parent.as_os_str().is_empty() {
fs::create_dir_all(parent)?;
}
}
fs::write(preference, if enabled { "enabled\n" } else { "disabled\n" })
}
pub fn plan_for_real_storage(
store: &MemoryStore,
memory_path: &Path,
incoming_bytes: u64,
) -> io::Result<DreamingPlan> {
let snapshot = measure_storage(memory_path)?;
Ok(plan_memory_dreaming(
store.events(),
&DreamingConfig {
storage_capacity_bytes: Some(snapshot.capacity_bytes),
free_bytes: Some(snapshot.free_bytes),
incoming_bytes,
..DreamingConfig::default()
},
))
}
pub fn apply_auto_free_space_for_write(
store: &mut MemoryStore,
memory_path: &Path,
incoming_bytes: u64,
) -> io::Result<Option<(DreamingPlan, DreamingOutcome)>> {
if !auto_free_space_enabled(memory_path) {
return Ok(None);
}
let snapshot = measure_storage(memory_path)?;
Ok(apply_auto_free_space_with_snapshot(
store,
memory_path,
incoming_bytes,
snapshot,
))
}
#[must_use]
pub fn apply_auto_free_space_with_snapshot(
store: &mut MemoryStore,
memory_path: &Path,
incoming_bytes: u64,
snapshot: StorageSnapshot,
) -> Option<(DreamingPlan, DreamingOutcome)> {
if !auto_free_space_enabled(memory_path) {
return None;
}
let plan = plan_memory_dreaming(
store.events(),
&DreamingConfig {
storage_capacity_bytes: Some(snapshot.capacity_bytes),
free_bytes: Some(snapshot.free_bytes),
incoming_bytes,
..DreamingConfig::default()
},
);
let outcome = apply_dreaming_plan(store, &plan);
Some((plan, outcome))
}
fn existing_ancestor(path: &Path) -> PathBuf {
let mut candidate = if path.is_dir() {
path.to_path_buf()
} else {
path.parent()
.filter(|parent| !parent.as_os_str().is_empty())
.unwrap_or_else(|| Path::new("."))
.to_path_buf()
};
while !candidate.exists() {
if !candidate.pop() {
return PathBuf::from(".");
}
}
candidate
}