Compare commits

..

No commits in common. "1842419f102e0e974e83faa567d6c00dd80afbbc" and "65dd107514f3fc42d882781d738f00e0bcae74ee" have entirely different histories.

22 changed files with 30 additions and 68 deletions

View file

@ -36,8 +36,6 @@ struct Args {
enum Command { enum Command {
/// Run as a background daemon providing a web UI. /// Run as a background daemon providing a web UI.
Daemon, Daemon,
/// Stop the running daemon process.
Kill,
} }
/// Default config content, embedded in the binary. /// Default config content, embedded in the binary.
@ -93,65 +91,24 @@ async fn main() {
let log_buffer = logger::setup_logger(); let log_buffer = logger::setup_logger();
let args = Args::parse(); let args = Args::parse();
if let Some(command) = &args.command { if let Some(Command::Daemon) = &args.command {
match command { log::info!("🚀 Starting daemon...");
Command::Daemon => {
log::info!("🚀 Starting daemon...");
// Create files for stdout and stderr in the current directory // Create files for stdout and stderr in the current directory
let stdout = let stdout = fs::File::create("daemon.out").expect("Could not create daemon.out");
fs::File::create("daemon.out").expect("Could not create daemon.out"); let stderr = fs::File::create("daemon.err").expect("Could not create daemon.err");
let stderr =
fs::File::create("daemon.err").expect("Could not create daemon.err");
let daemonize = Daemonize::new() let daemonize = Daemonize::new()
.pid_file("ntp_timeturner.pid") // Create a PID file .pid_file("ntp_timeturner.pid") // Create a PID file
.working_directory(".") // Keep the same working directory .working_directory(".") // Keep the same working directory
.stdout(stdout) .stdout(stdout)
.stderr(stderr); .stderr(stderr);
match daemonize.start() { match daemonize.start() {
Ok(_) => { /* Process is now daemonized */ } Ok(_) => { /* Process is now daemonized */ }
Err(e) => { Err(e) => {
log::error!("Error daemonizing: {}", e); log::error!("Error daemonizing: {}", e);
return; // Exit if daemonization fails return; // Exit if daemonization fails
}
}
}
Command::Kill => {
log::info!("🛑 Stopping daemon...");
let pid_file = "ntp_timeturner.pid";
match fs::read_to_string(pid_file) {
Ok(pid_str) => {
let pid_str = pid_str.trim();
log::info!("Found daemon with PID: {}", pid_str);
match std::process::Command::new("kill").arg("-9").arg(format!("-{}", pid_str)).status() {
Ok(status) => {
if status.success() {
log::info!("✅ Daemon stopped successfully.");
if fs::remove_file(pid_file).is_err() {
log::warn!("Could not remove PID file '{}'. It may need to be removed manually.", pid_file);
}
} else {
log::error!("'kill' command failed with status: {}. The daemon may not be running, or you may not have permission to stop it.", status);
log::warn!("Attempting to remove stale PID file '{}'...", pid_file);
if fs::remove_file(pid_file).is_ok() {
log::info!("Removed stale PID file.");
} else {
log::warn!("Could not remove PID file.");
}
}
}
Err(e) => {
log::error!("Failed to execute 'kill' command. Is 'kill' in your PATH? Error: {}", e);
}
}
}
Err(_) => {
log::error!("Could not read PID file '{}'. Is the daemon running in this directory?", pid_file);
}
}
return;
} }
} }
} }
@ -248,9 +205,7 @@ async fn main() {
let state = sync_state.lock().unwrap(); let state = sync_state.lock().unwrap();
let config = sync_config.lock().unwrap(); let config = sync_config.lock().unwrap();
if config.is_auto_sync_paused() { if config.auto_sync_enabled && state.latest.is_some() {
log::info!("Auto-sync is temporarily paused.");
} else if config.auto_sync_enabled && state.latest.is_some() {
let delta = state.get_ewma_clock_delta(); let delta = state.get_ewma_clock_delta();
let frame = state.latest.as_ref().unwrap(); let frame = state.latest.as_ref().unwrap();

View file

@ -45,13 +45,21 @@ pub fn calculate_target_time(frame: &LtcFrame, config: &Config) -> DateTime<Loca
let timecode_secs = let timecode_secs =
frame.hours as i64 * 3600 + frame.minutes as i64 * 60 + frame.seconds as i64; frame.hours as i64 * 3600 + frame.minutes as i64 * 60 + frame.seconds as i64;
// Timecode is always treated as wall-clock time. NDF scaling is not applied // Total duration in seconds as a rational number, including frames
// as the LTC source appears to be pre-compensated.
let total_duration_secs = let total_duration_secs =
Ratio::new(timecode_secs, 1) + Ratio::new(frame.frames as i64, 1) / frame.frame_rate; Ratio::new(timecode_secs, 1) + Ratio::new(frame.frames as i64, 1) / frame.frame_rate;
// For non-drop-frame fractional rates (23.98, 29.97), timecode runs slower than wall clock.
// We need to scale the timecode duration up to get wall clock time.
// The scaling factor is 1001/1000. For drop-frame, this isn't necessary.
let scaled_duration_secs = if *frame.frame_rate.denom() == 1001 && !frame.is_drop_frame {
total_duration_secs * Ratio::new(1001, 1000)
} else {
total_duration_secs
};
// Convert to milliseconds // Convert to milliseconds
let total_ms = (total_duration_secs * Ratio::new(1000, 1)) let total_ms = (scaled_duration_secs * Ratio::new(1000, 1))
.round() .round()
.to_integer(); .to_integer();
@ -149,20 +157,19 @@ pub fn nudge_clock(microseconds: i64) -> Result<(), ()> {
pub fn set_date(date: &str) -> Result<(), ()> { pub fn set_date(date: &str) -> Result<(), ()> {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
let datetime_str = format!("{} 10:00:00", date);
let success = Command::new("sudo") let success = Command::new("sudo")
.arg("date") .arg("date")
.arg("--set") .arg("--set")
.arg(&datetime_str) .arg(date)
.status() .status()
.map(|s| s.success()) .map(|s| s.success())
.unwrap_or(false); .unwrap_or(false);
if success { if success {
log::info!("Set system date and time to {}", datetime_str); log::info!("Set system date to {}", date);
Ok(()) Ok(())
} else { } else {
log::error!("Failed to set system date and time"); log::error!("Failed to set system date");
Err(()) Err(())
} }
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 981 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 955 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 913 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB