Compare commits
No commits in common. "1842419f102e0e974e83faa567d6c00dd80afbbc" and "65dd107514f3fc42d882781d738f00e0bcae74ee" have entirely different histories.
1842419f10
...
65dd107514
53
src/main.rs
|
|
@ -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,16 +91,12 @@ 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 {
|
|
||||||
Command::Daemon => {
|
|
||||||
log::info!("🚀 Starting 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
|
||||||
|
|
@ -118,43 +112,6 @@ async fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 🔄 Ensure there's always a config.yml present
|
// 🔄 Ensure there's always a config.yml present
|
||||||
ensure_config();
|
ensure_config();
|
||||||
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 981 B |
|
Before Width: | Height: | Size: 955 B |
|
Before Width: | Height: | Size: 913 B |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |