use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use ts_rs::TS;
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export, export_to = "bindings/")]
#[serde(tag = "type")]
pub enum FileSystemEvent {
FileCreated {
path: PathBuf,
#[serde(default)]
frontmatter: Option<serde_json::Value>,
#[serde(default)]
parent_path: Option<PathBuf>,
},
FileDeleted {
path: PathBuf,
#[serde(default)]
parent_path: Option<PathBuf>,
},
FileRenamed {
old_path: PathBuf,
new_path: PathBuf,
},
FileMoved {
path: PathBuf,
#[serde(default)]
old_parent: Option<PathBuf>,
#[serde(default)]
new_parent: Option<PathBuf>,
},
MetadataChanged {
path: PathBuf,
frontmatter: serde_json::Value,
},
ContentsChanged {
path: PathBuf,
body: String,
},
SyncStarted {
doc_name: String,
},
SyncCompleted {
doc_name: String,
files_synced: usize,
},
SyncStatusChanged {
status: String,
#[serde(default)]
error: Option<String>,
},
SyncProgress {
completed: usize,
total: usize,
},
SendSyncMessage {
doc_name: String,
message: Vec<u8>,
is_body: bool,
},
}
impl FileSystemEvent {
pub fn file_created(path: PathBuf) -> Self {
Self::FileCreated {
path,
frontmatter: None,
parent_path: None,
}
}
pub fn file_created_with_metadata(
path: PathBuf,
frontmatter: Option<serde_json::Value>,
parent_path: Option<PathBuf>,
) -> Self {
Self::FileCreated {
path,
frontmatter,
parent_path,
}
}
pub fn file_deleted(path: PathBuf) -> Self {
Self::FileDeleted {
path,
parent_path: None,
}
}
pub fn file_deleted_with_parent(path: PathBuf, parent_path: Option<PathBuf>) -> Self {
Self::FileDeleted { path, parent_path }
}
pub fn file_renamed(old_path: PathBuf, new_path: PathBuf) -> Self {
Self::FileRenamed { old_path, new_path }
}
pub fn file_moved(
path: PathBuf,
old_parent: Option<PathBuf>,
new_parent: Option<PathBuf>,
) -> Self {
Self::FileMoved {
path,
old_parent,
new_parent,
}
}
pub fn metadata_changed(path: PathBuf, frontmatter: serde_json::Value) -> Self {
Self::MetadataChanged { path, frontmatter }
}
pub fn contents_changed(path: PathBuf, body: String) -> Self {
Self::ContentsChanged { path, body }
}
pub fn sync_started(doc_name: String) -> Self {
Self::SyncStarted { doc_name }
}
pub fn sync_completed(doc_name: String, files_synced: usize) -> Self {
Self::SyncCompleted {
doc_name,
files_synced,
}
}
pub fn sync_status_changed(status: impl Into<String>, error: Option<String>) -> Self {
Self::SyncStatusChanged {
status: status.into(),
error,
}
}
pub fn sync_progress(completed: usize, total: usize) -> Self {
Self::SyncProgress { completed, total }
}
pub fn send_sync_message(doc_name: impl Into<String>, message: Vec<u8>, is_body: bool) -> Self {
Self::SendSyncMessage {
doc_name: doc_name.into(),
message,
is_body,
}
}
pub fn path(&self) -> Option<&PathBuf> {
match self {
Self::FileCreated { path, .. } => Some(path),
Self::FileDeleted { path, .. } => Some(path),
Self::FileRenamed { new_path, .. } => Some(new_path),
Self::FileMoved { path, .. } => Some(path),
Self::MetadataChanged { path, .. } => Some(path),
Self::ContentsChanged { path, .. } => Some(path),
Self::SyncStarted { .. } => None,
Self::SyncCompleted { .. } => None,
Self::SyncStatusChanged { .. } => None,
Self::SyncProgress { .. } => None,
Self::SendSyncMessage { .. } => None,
}
}
pub fn event_type(&self) -> &'static str {
match self {
Self::FileCreated { .. } => "FileCreated",
Self::FileDeleted { .. } => "FileDeleted",
Self::FileRenamed { .. } => "FileRenamed",
Self::FileMoved { .. } => "FileMoved",
Self::MetadataChanged { .. } => "MetadataChanged",
Self::ContentsChanged { .. } => "ContentsChanged",
Self::SyncStarted { .. } => "SyncStarted",
Self::SyncCompleted { .. } => "SyncCompleted",
Self::SyncStatusChanged { .. } => "SyncStatusChanged",
Self::SyncProgress { .. } => "SyncProgress",
Self::SendSyncMessage { .. } => "SendSyncMessage",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_file_created_event() {
let path = PathBuf::from("test.md");
let event = FileSystemEvent::file_created(path.clone());
assert_eq!(event.path(), Some(&path));
assert_eq!(event.event_type(), "FileCreated");
}
#[test]
fn test_file_deleted_event() {
let path = PathBuf::from("test.md");
let event = FileSystemEvent::file_deleted(path.clone());
assert_eq!(event.path(), Some(&path));
assert_eq!(event.event_type(), "FileDeleted");
}
#[test]
fn test_file_renamed_event() {
let new_path = PathBuf::from("new.md");
let event = FileSystemEvent::file_renamed(PathBuf::from("old.md"), new_path.clone());
assert_eq!(event.path(), Some(&new_path));
assert_eq!(event.event_type(), "FileRenamed");
}
#[test]
fn test_event_serialization() {
let event = FileSystemEvent::file_created_with_metadata(
PathBuf::from("test.md"),
Some(serde_json::json!({"title": "Test"})),
Some(PathBuf::from("index.md")),
);
let json = serde_json::to_string(&event).unwrap();
assert!(json.contains("FileCreated"));
assert!(json.contains("test.md"));
let parsed: FileSystemEvent = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.event_type(), "FileCreated");
}
#[test]
fn test_sync_events() {
let event = FileSystemEvent::sync_started("workspace".to_string());
assert_eq!(event.event_type(), "SyncStarted");
assert!(event.path().is_none());
let event = FileSystemEvent::sync_completed("workspace".to_string(), 10);
assert_eq!(event.event_type(), "SyncCompleted");
let event = FileSystemEvent::sync_status_changed("synced", None);
assert_eq!(event.event_type(), "SyncStatusChanged");
let event =
FileSystemEvent::sync_status_changed("error", Some("Connection failed".to_string()));
assert_eq!(event.event_type(), "SyncStatusChanged");
let event = FileSystemEvent::sync_progress(5, 10);
assert_eq!(event.event_type(), "SyncProgress");
}
}