use super::{
load_routine_from_dir, read_routine_toml, remove_routine_dir, routines_dir, slugify,
write_routine, RuntimeState,
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
struct LegacyScheduledState {
#[serde(default)]
last_scheduled_trigger_at: Option<u64>,
}
pub fn migrate_prompt_files() {
migrate_prompt_files_from_dir(&routines_dir());
}
pub(crate) fn migrate_prompt_files_from_dir(dir: &std::path::Path) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
if !entry.file_type().is_ok_and(|ft| ft.is_dir()) {
continue;
}
let old = entry.path().join("prompt.txt");
let new = entry.path().join("prompt.md");
if old.exists() && !new.exists() {
if let Err(err) = std::fs::rename(&old, &new) {
log::warn!(
"migrate_prompt_files: failed to rename {}: {err}",
old.display()
);
}
}
}
}
pub fn migrate_prompts_to_subfolder() {
migrate_prompts_to_subfolder_from_dir(&routines_dir());
}
pub(crate) fn migrate_prompts_to_subfolder_from_dir(dir: &std::path::Path) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
if !entry.file_type().is_ok_and(|ft| ft.is_dir()) {
continue;
}
if !entry.path().join("routine.toml").exists() {
continue;
}
let prompts_dir = entry.path().join("prompts");
if let Err(err) = crate::utils::fs_perms::create_private_dir_all(&prompts_dir) {
log::warn!(
"migrate_prompts_to_subfolder: failed to create {}: {err}",
prompts_dir.display()
);
continue;
}
let old_compiled = entry.path().join("prompt.md");
let new_compiled = prompts_dir.join("prompt.compiled.md");
if old_compiled.exists() && !new_compiled.exists() {
if let Err(err) = std::fs::rename(&old_compiled, &new_compiled) {
log::warn!(
"migrate_prompts_to_subfolder: failed to rename {}: {err}",
old_compiled.display()
);
}
}
let pure = prompts_dir.join("prompt.pure.md");
if !pure.exists() {
let legacy_prompt = read_routine_toml(&entry.path().join("routine.toml"))
.and_then(|toml| toml.prompt)
.unwrap_or_default();
if let Err(err) = std::fs::write(&pure, legacy_prompt.as_bytes()) {
log::warn!(
"migrate_prompts_to_subfolder: failed to write {}: {err}",
pure.display()
);
}
}
}
}
pub fn migrate_routine_dirs() {
migrate_routine_dirs_from_dir(&routines_dir());
}
pub(crate) fn migrate_routine_dirs_from_dir(dir: &std::path::Path) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
if !entry.file_type().is_ok_and(|ft| ft.is_dir()) {
continue;
}
let dir_name = entry.file_name().to_string_lossy().to_string();
let Some(routine) = load_routine_from_dir(&dir_name) else {
continue;
};
let slug = slugify(&routine.title);
if slug == dir_name {
continue;
}
if let Err(err) = write_routine(&routine) {
log::warn!("migrate_routine_dirs: failed to write {slug:?}: {err}; leaving legacy dir");
continue;
}
if let Err(err) = remove_routine_dir(&dir_name) {
log::warn!("migrate_routine_dirs: failed to remove legacy dir {dir_name:?}: {err}");
}
}
}
pub fn migrate_compiled_prompt_filename() {
migrate_compiled_prompt_filename_from_dir(&routines_dir());
}
pub(crate) fn migrate_compiled_prompt_filename_from_dir(dir: &std::path::Path) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
if !entry.file_type().is_ok_and(|ft| ft.is_dir()) {
continue;
}
let prompts_dir = entry.path().join("prompts");
let old = prompts_dir.join("prompt.compiled.md");
let new = prompts_dir.join("prompt.compiled.local.md");
if old.exists() && !new.exists() {
if let Err(err) = std::fs::rename(&old, &new) {
log::warn!(
"migrate_compiled_prompt_filename: failed to rename {}: {err}",
old.display()
);
}
}
}
}
pub fn migrate_trigger_logs() {
migrate_trigger_logs_from_dir(&routines_dir());
}
pub(crate) fn migrate_trigger_logs_from_dir(dir: &std::path::Path) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
if !entry.file_type().is_ok_and(|ft| ft.is_dir()) {
continue;
}
let routine_dir = entry.path();
let old_sched = routine_dir.join("scheduled.local.toml");
let new_sched = routine_dir.join("scheduled.log");
if old_sched.exists() && !new_sched.exists() {
if let Some(ts) = std::fs::read_to_string(&old_sched)
.ok()
.and_then(|text| toml::from_str::<LegacyScheduledState>(&text).ok())
.and_then(|state| state.last_scheduled_trigger_at)
{
let line = format!("{ts}\n");
if let Err(err) = std::fs::write(&new_sched, line.as_bytes()) {
log::warn!(
"migrate_trigger_logs: failed to write {}: {err}",
new_sched.display()
);
continue;
}
}
let _ = std::fs::remove_file(&old_sched);
}
let new_manual = routine_dir.join("manual.log");
if !new_manual.exists() {
if let Some(ts) = std::fs::read_to_string(routine_dir.join("state.local.toml"))
.ok()
.and_then(|text| toml::from_str::<RuntimeState>(&text).ok())
.and_then(|state| state.last_manual_trigger_at)
{
let line = format!("{ts}\n");
if let Err(err) = std::fs::write(&new_manual, line.as_bytes()) {
log::warn!(
"migrate_trigger_logs: failed to write {}: {err}",
new_manual.display()
);
}
}
}
}
}