support set interval

This commit is contained in:
Julian Freeman
2026-03-22 16:46:37 -04:00
parent 858cf85281
commit b94a976e5f
3 changed files with 48 additions and 16 deletions

View File

@@ -10,9 +10,15 @@ use tauri_plugin_store::StoreExt;
pub struct AppState { pub struct AppState {
pub is_paused: Arc<AtomicBool>, pub is_paused: Arc<AtomicBool>,
pub capture_interval_secs: std::sync::atomic::AtomicU64,
pub toggle_menu_item: std::sync::Mutex<Option<tauri::menu::MenuItem<tauri::Wry>>>, pub toggle_menu_item: std::sync::Mutex<Option<tauri::menu::MenuItem<tauri::Wry>>>,
} }
#[tauri::command]
pub fn update_interval(state: tauri::State<'_, AppState>, seconds: u64) {
state.capture_interval_secs.store(seconds, Ordering::SeqCst);
}
#[tauri::command] #[tauri::command]
pub fn toggle_pause(app: tauri::AppHandle, state: tauri::State<'_, AppState>) -> bool { pub fn toggle_pause(app: tauri::AppHandle, state: tauri::State<'_, AppState>) -> bool {
let current = state.is_paused.load(Ordering::SeqCst); let current = state.is_paused.load(Ordering::SeqCst);
@@ -78,26 +84,27 @@ pub fn get_timeline(date: String, base_dir: String) -> Vec<serde_json::Value> {
} }
pub fn start_engine(app: AppHandle) { pub fn start_engine(app: AppHandle) {
let state = app.state::<AppState>();
let is_paused = state.is_paused.clone();
// Start Cleanup routine // Start Cleanup routine
let app_clone = app.clone(); let app_cleanup = app.clone();
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
// Run once on startup, then every 24 hours // Run once on startup, then every 24 hours
let mut ticker = interval(Duration::from_secs(60 * 60 * 24)); let mut ticker = interval(Duration::from_secs(60 * 60 * 24));
loop { loop {
ticker.tick().await; ticker.tick().await;
if let Err(e) = run_cleanup(&app_clone).await { if let Err(e) = run_cleanup(&app_cleanup).await {
eprintln!("Cleanup error: {}", e); eprintln!("Cleanup error: {}", e);
} }
} }
}); });
let app_capture = app.clone();
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
// Check every 500ms to be precise about the 0th second let state = app_capture.state::<AppState>();
let is_paused = state.is_paused.clone();
// Check frequently to catch the aligned second
let mut ticker = interval(Duration::from_millis(500)); let mut ticker = interval(Duration::from_millis(500));
let mut last_minute = -1; let mut last_capture_time = 0;
loop { loop {
ticker.tick().await; ticker.tick().await;
@@ -106,16 +113,20 @@ pub fn start_engine(app: AppHandle) {
continue; continue;
} }
let now = Local::now(); let interval_secs = state.capture_interval_secs.load(Ordering::SeqCst);
let current_minute = now.format("%M").to_string().parse::<i32>().unwrap_or(0); if interval_secs == 0 { continue; }
let current_second = now.format("%S").to_string().parse::<i32>().unwrap_or(0);
// Trigger only if it's the 0th second and we haven't captured this minute yet let now = Local::now();
if current_second == 0 && current_minute != last_minute { let timestamp = now.timestamp() as u64;
last_minute = current_minute;
println!("Tick: capturing screen at {:02}:{:02}:00...", now.format("%H"), current_minute); // Trigger if aligned to interval and we haven't captured this exact second yet
if let Err(e) = capture_screens(&app).await { if timestamp % interval_secs == 0 && timestamp != last_capture_time {
last_capture_time = timestamp;
println!("Tick: capturing screen at {}...", now.format("%H:%M:%S"));
if let Err(e) = capture_screens(&app_capture).await {
eprintln!("Failed to capture screens: {}", e); eprintln!("Failed to capture screens: {}", e);
} else {
let _ = app_capture.emit("refresh-timeline", ());
} }
} }
} }

View File

@@ -20,11 +20,13 @@ pub fn run() {
engine::toggle_pause, engine::toggle_pause,
engine::get_pause_state, engine::get_pause_state,
engine::get_timeline, engine::get_timeline,
engine::get_image_base64 engine::get_image_base64,
engine::update_interval
]) ])
.setup(|app| { .setup(|app| {
app.manage(engine::AppState { app.manage(engine::AppState {
is_paused: Arc::new(AtomicBool::new(false)), is_paused: Arc::new(AtomicBool::new(false)),
capture_interval_secs: std::sync::atomic::AtomicU64::new(30),
toggle_menu_item: std::sync::Mutex::new(None), toggle_menu_item: std::sync::Mutex::new(None),
}); });
tray::create_tray(app.handle())?; tray::create_tray(app.handle())?;

View File

@@ -29,6 +29,7 @@ const isFullscreen = ref(false);
const isSettingsOpen = ref(false); const isSettingsOpen = ref(false);
const mergeScreens = ref(false); const mergeScreens = ref(false);
const retainDays = ref(30); const retainDays = ref(30);
const captureInterval = ref(30);
const hoveredTime = ref<string | null>(null); const hoveredTime = ref<string | null>(null);
const timelineRef = ref<HTMLElement | null>(null); const timelineRef = ref<HTMLElement | null>(null);
@@ -45,6 +46,11 @@ onMounted(async () => {
isSetupComplete.value = true; isSetupComplete.value = true;
mergeScreens.value = (await store.get("mergeScreens")) as boolean || false; mergeScreens.value = (await store.get("mergeScreens")) as boolean || false;
retainDays.value = (await store.get("retainDays")) as number || 30; retainDays.value = (await store.get("retainDays")) as number || 30;
captureInterval.value = (await store.get("captureInterval")) as number || 30;
// Sync initial interval to Rust
await invoke("update_interval", { seconds: captureInterval.value });
isPaused.value = await invoke("get_pause_state"); isPaused.value = await invoke("get_pause_state");
await loadTimeline(); await loadTimeline();
} }
@@ -77,7 +83,11 @@ const selectFolder = async () => {
const updateSettings = async () => { const updateSettings = async () => {
await store.set("mergeScreens", mergeScreens.value); await store.set("mergeScreens", mergeScreens.value);
await store.set("retainDays", retainDays.value); await store.set("retainDays", retainDays.value);
await store.set("captureInterval", captureInterval.value);
await store.save(); await store.save();
// Sync interval to Rust
await invoke("update_interval", { seconds: captureInterval.value });
}; };
const togglePauseState = async () => { const togglePauseState = async () => {
@@ -325,6 +335,15 @@ const handleTimelineClick = (e: MouseEvent) => {
</button> </button>
</div> </div>
<div class="space-y-4">
<div class="flex justify-between items-end">
<label class="text-sm font-bold text-[#86868B] uppercase tracking-widest ml-1">Capture Interval</label>
<span class="text-lg font-black text-[#007AFF]">{{ captureInterval }} Seconds</span>
</div>
<input type="range" v-model.number="captureInterval" min="10" max="600" step="10" @change="updateSettings" class="w-full h-2 bg-[#E5E5E7] rounded-full appearance-none cursor-pointer accent-[#007AFF]" />
<p class="text-[11px] text-[#86868B] font-medium leading-normal text-center">Frequency of snapshots (10s to 10m).</p>
</div>
<div class="space-y-4"> <div class="space-y-4">
<div class="flex justify-between items-end"> <div class="flex justify-between items-end">
<label class="text-sm font-bold text-[#86868B] uppercase tracking-widest ml-1">Retention Policy</label> <label class="text-sm font-bold text-[#86868B] uppercase tracking-widest ml-1">Retention Policy</label>