use crate::model::app::AppContext;
use serde_yaml;
use std::fs;
use std::path::Path;
use std::sync::mpsc;
use std::thread;
use std::time::{Duration, SystemTime};
#[derive(Debug)]
pub struct YamlFileLock {
lock_file: String,
_process_id: u32,
}
impl YamlFileLock {
pub fn dummy() -> Result<Self, Box<dyn std::error::Error>> {
Ok(Self {
lock_file: String::new(),
_process_id: 0,
})
}
pub fn acquire(yaml_path: &str) -> Result<Self, Box<dyn std::error::Error>> {
let lock_file = format!("{}.lock", yaml_path);
let process_id = std::process::id();
if Path::new(&lock_file).exists() {
if let Ok(contents) = fs::read_to_string(&lock_file) {
if let Ok(existing_pid) = contents.trim().parse::<u32>() {
#[cfg(unix)]
{
if unsafe { libc::kill(existing_pid as libc::pid_t, 0) } == 0 {
return Err(format!(
"YAML file {} already in use by process {}",
yaml_path, existing_pid
)
.into());
}
}
}
}
}
fs::write(&lock_file, process_id.to_string())?;
Ok(Self {
lock_file,
_process_id: process_id,
})
}
}
impl Drop for YamlFileLock {
fn drop(&mut self) {
let _ = fs::remove_file(&self.lock_file);
}
}
#[derive(Debug)]
pub struct LiveYamlSync {
_yaml_path: String,
sender: mpsc::Sender<YamlSyncMessage>,
_lock: YamlFileLock,
}
#[derive(Debug)]
enum YamlSyncMessage {
SaveComplete(Box<AppContext>),
SaveBounds(String, crate::model::common::InputBounds),
SaveContent(String, String),
SaveScroll(String, usize, usize),
SaveActiveLayout(String),
Shutdown,
}
impl LiveYamlSync {
pub fn new(yaml_path: String, enable_sync: bool) -> Result<Self, Box<dyn std::error::Error>> {
if !enable_sync {
let (sender, _receiver) = mpsc::channel::<YamlSyncMessage>();
return Ok(Self {
_yaml_path: yaml_path,
sender,
_lock: YamlFileLock::dummy()?,
});
}
let lock = YamlFileLock::acquire(&yaml_path)?;
let (sender, receiver) = mpsc::channel::<YamlSyncMessage>();
let yaml_path_clone = yaml_path.clone();
thread::spawn(move || {
Self::sync_worker(yaml_path_clone, receiver);
});
Ok(Self {
_yaml_path: yaml_path,
sender,
_lock: lock,
})
}
fn sync_worker(yaml_path: String, receiver: mpsc::Receiver<YamlSyncMessage>) {
let mut last_save = SystemTime::now();
let mut pending_changes = Vec::new();
while let Ok(message) = receiver.recv_timeout(Duration::from_millis(100)) {
match message {
YamlSyncMessage::Shutdown => break,
_ => {
pending_changes.push(message);
let should_save = pending_changes.len() >= 10
|| last_save.elapsed().unwrap_or(Duration::ZERO)
> Duration::from_millis(500);
if should_save {
if let Err(e) = Self::apply_changes(&yaml_path, &pending_changes) {
log::error!("Failed to sync YAML: {}", e);
} else {
log::debug!("Synced {} changes to YAML", pending_changes.len());
}
pending_changes.clear();
last_save = SystemTime::now();
}
}
}
}
if !pending_changes.is_empty() {
let _ = Self::apply_changes(&yaml_path, &pending_changes);
}
}
fn apply_changes(
yaml_path: &str,
changes: &[YamlSyncMessage],
) -> Result<(), Box<dyn std::error::Error>> {
let yaml_content = fs::read_to_string(yaml_path)?;
let mut yaml_value: serde_yaml::Value = serde_yaml::from_str(&yaml_content)?;
for change in changes {
match change {
YamlSyncMessage::SaveComplete(app_context) => {
let serialized = serde_yaml::to_value(app_context)?;
yaml_value = serialized;
}
YamlSyncMessage::SaveBounds(muxbox_id, bounds) => {
Self::update_muxbox_bounds(&mut yaml_value, muxbox_id, bounds)?;
}
YamlSyncMessage::SaveContent(muxbox_id, content) => {
Self::update_muxbox_content(&mut yaml_value, muxbox_id, content)?;
}
YamlSyncMessage::SaveScroll(muxbox_id, scroll_x, scroll_y) => {
Self::update_muxbox_scroll(&mut yaml_value, muxbox_id, *scroll_x, *scroll_y)?;
}
YamlSyncMessage::SaveActiveLayout(layout_id) => {
Self::update_active_layout(&mut yaml_value, layout_id)?;
}
YamlSyncMessage::Shutdown => break,
}
}
let temp_path = format!("{}.tmp", yaml_path);
let updated_yaml = serde_yaml::to_string(&yaml_value)?;
fs::write(&temp_path, updated_yaml)?;
fs::rename(&temp_path, yaml_path)?;
Ok(())
}
fn update_muxbox_bounds(
yaml_value: &mut serde_yaml::Value,
muxbox_id: &str,
bounds: &crate::model::common::InputBounds,
) -> Result<(), Box<dyn std::error::Error>> {
if let Some(serde_yaml::Value::Mapping(map)) = Self::find_muxbox_mut(yaml_value, muxbox_id)
{
map.insert(
serde_yaml::Value::String("x1".to_string()),
serde_yaml::Value::String(bounds.x1.clone()),
);
map.insert(
serde_yaml::Value::String("y1".to_string()),
serde_yaml::Value::String(bounds.y1.clone()),
);
map.insert(
serde_yaml::Value::String("x2".to_string()),
serde_yaml::Value::String(bounds.x2.clone()),
);
map.insert(
serde_yaml::Value::String("y2".to_string()),
serde_yaml::Value::String(bounds.y2.clone()),
);
}
Ok(())
}
fn update_muxbox_content(
yaml_value: &mut serde_yaml::Value,
muxbox_id: &str,
content: &str,
) -> Result<(), Box<dyn std::error::Error>> {
if let Some(serde_yaml::Value::Mapping(map)) = Self::find_muxbox_mut(yaml_value, muxbox_id)
{
map.insert(
serde_yaml::Value::String("output".to_string()),
serde_yaml::Value::String(content.to_string()),
);
}
Ok(())
}
fn update_muxbox_scroll(
yaml_value: &mut serde_yaml::Value,
muxbox_id: &str,
scroll_x: usize,
scroll_y: usize,
) -> Result<(), Box<dyn std::error::Error>> {
if let Some(serde_yaml::Value::Mapping(map)) = Self::find_muxbox_mut(yaml_value, muxbox_id)
{
map.insert(
serde_yaml::Value::String("scroll_x".to_string()),
serde_yaml::Value::Number(serde_yaml::Number::from(scroll_x)),
);
map.insert(
serde_yaml::Value::String("scroll_y".to_string()),
serde_yaml::Value::Number(serde_yaml::Number::from(scroll_y)),
);
}
Ok(())
}
fn update_active_layout(
yaml_value: &mut serde_yaml::Value,
layout_id: &str,
) -> Result<(), Box<dyn std::error::Error>> {
if let Some(layouts) = yaml_value
.get_mut("app")
.and_then(|app| app.get_mut("layouts"))
{
if let serde_yaml::Value::Sequence(layout_list) = layouts {
for layout in layout_list.iter_mut() {
if let serde_yaml::Value::Mapping(map) = layout {
map.insert(
serde_yaml::Value::String("active".to_string()),
serde_yaml::Value::Bool(false),
);
if let Some(serde_yaml::Value::String(id)) =
map.get(&serde_yaml::Value::String("id".to_string()))
{
if id == layout_id {
map.insert(
serde_yaml::Value::String("active".to_string()),
serde_yaml::Value::Bool(true),
);
}
}
}
}
}
}
Ok(())
}
fn find_muxbox_mut<'a>(
yaml_value: &'a mut serde_yaml::Value,
muxbox_id: &str,
) -> Option<&'a mut serde_yaml::Value> {
if let Some(app) = yaml_value.get_mut("app") {
if let Some(serde_yaml::Value::Sequence(layout_list)) = app.get_mut("layouts") {
for layout in layout_list.iter_mut() {
if let Some(children) = layout.get_mut("children") {
if let Some(found) = Self::find_muxbox_in_children_mut(children, muxbox_id)
{
return Some(found);
}
}
}
}
}
None
}
fn find_muxbox_in_children_mut<'a>(
children: &'a mut serde_yaml::Value,
muxbox_id: &str,
) -> Option<&'a mut serde_yaml::Value> {
if let serde_yaml::Value::Sequence(child_list) = children {
for child in child_list.iter_mut() {
if let serde_yaml::Value::Mapping(map) = child {
if let Some(serde_yaml::Value::String(id)) =
map.get(&serde_yaml::Value::String("id".to_string()))
{
if id == muxbox_id {
return Some(child);
}
}
if let Some(nested_children) =
child.get_mut(serde_yaml::Value::String("children".to_string()))
{
if let Some(found) =
Self::find_muxbox_in_children_mut(nested_children, muxbox_id)
{
return Some(found);
}
}
}
}
}
None
}
pub fn save_complete_state(&self, app_context: &AppContext) {
let _ = self
.sender
.send(YamlSyncMessage::SaveComplete(Box::new(app_context.clone())));
}
pub fn save_bounds(&self, muxbox_id: &str, bounds: &crate::model::common::InputBounds) {
let _ = self.sender.send(YamlSyncMessage::SaveBounds(
muxbox_id.to_string(),
bounds.clone(),
));
}
pub fn save_content(&self, muxbox_id: &str, content: &str) {
let _ = self.sender.send(YamlSyncMessage::SaveContent(
muxbox_id.to_string(),
content.to_string(),
));
}
pub fn save_scroll(&self, muxbox_id: &str, scroll_x: usize, scroll_y: usize) {
let _ = self.sender.send(YamlSyncMessage::SaveScroll(
muxbox_id.to_string(),
scroll_x,
scroll_y,
));
}
pub fn save_active_layout(&self, layout_id: &str) {
let _ = self
.sender
.send(YamlSyncMessage::SaveActiveLayout(layout_id.to_string()));
}
}
impl Drop for LiveYamlSync {
fn drop(&mut self) {
let _ = self.sender.send(YamlSyncMessage::Shutdown);
thread::sleep(Duration::from_millis(200)); }
}