use chrono::{DateTime, Local};
use piki_core::DocumentStore;
use piki_gui::content::ContentProvider;
use std::time::SystemTime;
pub struct AutoSaveState {
pub last_change_time: Option<SystemTime>,
pub last_save_time: Option<SystemTime>,
pub is_saving: bool,
pub pending_save: bool,
pub original_content: String,
pub current_page: String,
}
impl AutoSaveState {
pub fn new() -> Self {
AutoSaveState {
last_change_time: None,
last_save_time: None,
is_saving: false,
pending_save: false,
original_content: String::new(),
current_page: String::new(),
}
}
pub fn mark_changed(&mut self) {
self.last_change_time = Some(SystemTime::now());
self.pending_save = true;
}
pub fn reset_for_page(&mut self, page_name: &str, content: &str) {
self.current_page = page_name.to_string();
self.original_content = content.to_string();
self.last_change_time = None;
self.last_save_time = None;
self.is_saving = false;
self.pending_save = false;
}
pub fn should_save(&self) -> bool {
!self.current_page.starts_with('!')
}
pub fn get_status_text(&self) -> String {
if self.is_saving {
return "Saving …".to_string();
}
if let Some(save_time) = self.last_save_time {
format_time_since(save_time)
} else if self.last_change_time.is_some() {
"not saved".to_string()
} else {
String::new()
}
}
pub fn trigger_save<T: ContentProvider + ?Sized>(
&mut self,
editor: &T,
store: &DocumentStore,
) -> Result<(), String> {
if !self.should_save() {
self.pending_save = false;
return Ok(());
}
if self.is_saving {
return Ok(());
}
let current_content = editor.get_content();
if current_content == self.original_content {
self.pending_save = false;
return Ok(());
}
self.is_saving = true;
self.pending_save = false;
let doc_result = store.load(&self.current_page);
let result = match doc_result {
Ok(mut doc) => {
doc.content = current_content.clone();
store.save(&doc)
}
Err(e) => Err(e),
};
match result {
Ok(()) => {
self.last_save_time = Some(SystemTime::now());
self.original_content = current_content;
self.is_saving = false;
Ok(())
}
Err(e) => {
self.is_saving = false;
Err(e)
}
}
}
}
impl Default for AutoSaveState {
fn default() -> Self {
Self::new()
}
}
pub fn format_time_since(time: SystemTime) -> String {
let now = SystemTime::now();
match now.duration_since(time) {
Ok(duration) => {
let secs = duration.as_secs();
if secs < 60 {
"saved just now".to_string()
} else if secs < 3600 {
let mins = secs / 60;
if mins == 1 {
"saved 1 min ago".to_string()
} else {
format!("saved {} min ago", mins)
}
} else if secs < 86400 {
let hours = secs / 3600;
if hours == 1 {
"saved 1 hour ago".to_string()
} else {
format!("saved {} hours ago", hours)
}
} else if secs < 604800 {
let days = secs / 86400;
if days == 1 {
"saved 1 day ago".to_string()
} else {
format!("saved {} days ago", days)
}
} else {
format_absolute_date(time)
}
}
Err(_) => "saved (time error)".to_string(),
}
}
fn format_absolute_date(time: SystemTime) -> String {
let datetime: DateTime<Local> = time.into();
format!("saved {}", datetime.format("%Y-%m-%d"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_autosave_state_new() {
let state = AutoSaveState::new();
assert!(state.last_change_time.is_none());
assert!(state.last_save_time.is_none());
assert!(!state.is_saving);
assert!(!state.pending_save);
}
#[test]
fn test_mark_changed() {
let mut state = AutoSaveState::new();
state.mark_changed();
assert!(state.last_change_time.is_some());
assert!(state.pending_save);
}
#[test]
fn test_should_save_plugin_page() {
let mut state = AutoSaveState::new();
state.reset_for_page("!index", "");
assert!(!state.should_save());
}
#[test]
fn test_should_save_normal_page() {
let mut state = AutoSaveState::new();
state.reset_for_page("frontpage", "");
assert!(state.should_save());
}
#[test]
fn test_format_time_just_now() {
let time = SystemTime::now();
let formatted = format_time_since(time);
assert_eq!(formatted, "saved just now");
}
#[test]
fn test_format_time_minutes() {
use std::time::Duration;
let time = SystemTime::now() - Duration::from_secs(150);
let formatted = format_time_since(time);
assert_eq!(formatted, "saved 2 min ago");
}
#[test]
fn test_format_time_hours() {
use std::time::Duration;
let time = SystemTime::now() - Duration::from_secs(7200);
let formatted = format_time_since(time);
assert_eq!(formatted, "saved 2 hours ago");
}
}