From c712014bb90847c27d60bd9c9436839ec4281be6 Mon Sep 17 00:00:00 2001 From: John Rogers Date: Tue, 29 Jul 2025 11:39:46 +0100 Subject: [PATCH] feat: Allow millisecond offset for timeturner Co-authored-by: aider (gemini/gemini-2.5-pro-preview-05-06) --- src/api.rs | 5 ++++- src/config.rs | 8 +++++++- src/main.rs | 1 + src/system.rs | 12 +++++++----- static/index.html | 4 +++- static/script.js | 3 +++ 6 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/api.rs b/src/api.rs index 19e58a7..e1a15cb 100644 --- a/src/api.rs +++ b/src/api.rs @@ -303,7 +303,7 @@ mod tests { let new_config_json = serde_json::json!({ "hardwareOffsetMs": 55, "defaultNudgeMs": 2, - "timeturnerOffset": { "hours": 1, "minutes": 2, "seconds": 3, "frames": 4 } + "timeturnerOffset": { "hours": 1, "minutes": 2, "seconds": 3, "frames": 4, "milliseconds": 5 } }); let req = test::TestRequest::post() @@ -315,15 +315,18 @@ mod tests { assert_eq!(resp.hardware_offset_ms, 55); assert_eq!(resp.timeturner_offset.hours, 1); + assert_eq!(resp.timeturner_offset.milliseconds, 5); let final_config = app_state.config.lock().unwrap(); assert_eq!(final_config.hardware_offset_ms, 55); assert_eq!(final_config.timeturner_offset.hours, 1); + assert_eq!(final_config.timeturner_offset.milliseconds, 5); // Test that the file was written assert!(fs::metadata(config_path).is_ok()); let contents = fs::read_to_string(config_path).unwrap(); assert!(contents.contains("hardwareOffsetMs: 55")); assert!(contents.contains("hours: 1")); + assert!(contents.contains("milliseconds: 5")); // Cleanup let _ = fs::remove_file(config_path); diff --git a/src/config.rs b/src/config.rs index 9b2cb5d..16cd774 100644 --- a/src/config.rs +++ b/src/config.rs @@ -19,11 +19,17 @@ pub struct TimeturnerOffset { pub minutes: i64, pub seconds: i64, pub frames: i64, + #[serde(default)] + pub milliseconds: i64, } impl TimeturnerOffset { pub fn is_active(&self) -> bool { - self.hours != 0 || self.minutes != 0 || self.seconds != 0 || self.frames != 0 + self.hours != 0 + || self.minutes != 0 + || self.seconds != 0 + || self.frames != 0 + || self.milliseconds != 0 } } diff --git a/src/main.rs b/src/main.rs index 698eabe..d7d40e4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -52,6 +52,7 @@ timeturnerOffset: minutes: 0 seconds: 0 frames: 0 + milliseconds: 0 "#; /// If no `config.yml` exists alongside the binary, write out the default. diff --git a/src/system.rs b/src/system.rs index 1d2ed7d..c3918f6 100644 --- a/src/system.rs +++ b/src/system.rs @@ -57,7 +57,7 @@ pub fn calculate_target_time(frame: &LtcFrame, config: &Config) -> DateTime Result { @@ -177,6 +177,7 @@ mod tests { minutes: 5, seconds: 10, frames: 12, // 12 frames at 25fps is 480ms + milliseconds: 20, }; let target_time = calculate_target_time(&frame, &config); @@ -184,8 +185,8 @@ mod tests { assert_eq!(target_time.hour(), 11); assert_eq!(target_time.minute(), 25); assert_eq!(target_time.second(), 40); - // 480ms - assert_eq!(target_time.nanosecond(), 480_000_000); + // 480ms + 20ms = 500ms + assert_eq!(target_time.nanosecond(), 500_000_000); } #[test] @@ -197,14 +198,15 @@ mod tests { minutes: -5, seconds: -10, frames: -12, // -480ms + milliseconds: -80, }; let target_time = calculate_target_time(&frame, &config); assert_eq!(target_time.hour(), 9); assert_eq!(target_time.minute(), 15); - assert_eq!(target_time.second(), 20); - assert_eq!(target_time.nanosecond(), 0); + assert_eq!(target_time.second(), 19); + assert_eq!(target_time.nanosecond(), 920_000_000); } #[test] diff --git a/static/index.html b/static/index.html index 79bfd80..d5969db 100644 --- a/static/index.html +++ b/static/index.html @@ -50,7 +50,7 @@
- + @@ -58,6 +58,8 @@ + +
diff --git a/static/script.js b/static/script.js index ad9178c..d15d9a6 100644 --- a/static/script.js +++ b/static/script.js @@ -20,6 +20,7 @@ document.addEventListener('DOMContentLoaded', () => { m: document.getElementById('offset-m'), s: document.getElementById('offset-s'), f: document.getElementById('offset-f'), + ms: document.getElementById('offset-ms'), }; const saveConfigButton = document.getElementById('save-config'); const manualSyncButton = document.getElementById('manual-sync'); @@ -84,6 +85,7 @@ document.addEventListener('DOMContentLoaded', () => { offsetInputs.m.value = data.timeturnerOffset.minutes; offsetInputs.s.value = data.timeturnerOffset.seconds; offsetInputs.f.value = data.timeturnerOffset.frames; + offsetInputs.ms.value = data.timeturnerOffset.milliseconds || 0; nudgeValueInput.value = data.defaultNudgeMs; } catch (error) { console.error('Error fetching config:', error); @@ -99,6 +101,7 @@ document.addEventListener('DOMContentLoaded', () => { minutes: parseInt(offsetInputs.m.value, 10) || 0, seconds: parseInt(offsetInputs.s.value, 10) || 0, frames: parseInt(offsetInputs.f.value, 10) || 0, + milliseconds: parseInt(offsetInputs.ms.value, 10) || 0, } };