max / balanced_breakfast
- Co-Authored-By
- Claude Opus 4.8 (1M context) <noreply@anthropic.com>
57 files changed,
+1592 insertions,
-903 deletions
| @@ -14,9 +14,9 @@ | |||
| 14 | 14 | ||
| 15 | 15 | use state::AppState; | |
| 16 | 16 | use std::sync::Arc; | |
| 17 | - | use tauri::Manager; | |
| 18 | 17 | #[cfg(not(any(target_os = "ios", target_os = "android")))] | |
| 19 | 18 | use tauri::Emitter; | |
| 19 | + | use tauri::Manager; | |
| 20 | 20 | ||
| 21 | 21 | /// Build the Tauri app with all commands registered. | |
| 22 | 22 | pub fn build_app() -> tauri::Builder<tauri::Wry> { |
| @@ -7,7 +7,7 @@ | |||
| 7 | 7 | #[cfg(not(any(target_os = "ios", target_os = "android")))] | |
| 8 | 8 | use tauri::{Emitter, Manager}; | |
| 9 | 9 | use tracing::info; | |
| 10 | - | use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; | |
| 10 | + | use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt}; | |
| 11 | 11 | ||
| 12 | 12 | fn main() { | |
| 13 | 13 | tracing_subscriber::registry() | |
| @@ -30,7 +30,13 @@ | |||
| 30 | 30 | "BalancedBreakfast", | |
| 31 | 31 | true, | |
| 32 | 32 | &[ | |
| 33 | - | &MenuItem::with_id(app, "about", "About Balanced Breakfast", true, None::<&str>)?, | |
| 33 | + | &MenuItem::with_id( | |
| 34 | + | app, | |
| 35 | + | "about", | |
| 36 | + | "About Balanced Breakfast", | |
| 37 | + | true, | |
| 38 | + | None::<&str>, | |
| 39 | + | )?, | |
| 34 | 40 | &PredefinedMenuItem::separator(app)?, | |
| 35 | 41 | &PredefinedMenuItem::hide(app, Some("Hide Balanced Breakfast"))?, | |
| 36 | 42 | &PredefinedMenuItem::hide_others(app, Some("Hide Others"))?, | |
| @@ -48,8 +54,20 @@ | |||
| 48 | 54 | &MenuItem::with_id(app, "refresh", "Refresh Feeds", true, Some("CmdOrCtrl+R"))?, | |
| 49 | 55 | &MenuItem::with_id(app, "add_feed", "Add Feed...", true, Some("CmdOrCtrl+N"))?, | |
| 50 | 56 | &PredefinedMenuItem::separator(app)?, | |
| 51 | - | &MenuItem::with_id(app, "import_opml", "Import OPML...", true, Some("CmdOrCtrl+I"))?, | |
| 52 | - | &MenuItem::with_id(app, "export_opml", "Export OPML...", true, Some("CmdOrCtrl+E"))?, | |
| 57 | + | &MenuItem::with_id( | |
| 58 | + | app, | |
| 59 | + | "import_opml", | |
| 60 | + | "Import OPML...", | |
| 61 | + | true, | |
| 62 | + | Some("CmdOrCtrl+I"), | |
| 63 | + | )?, | |
| 64 | + | &MenuItem::with_id( | |
| 65 | + | app, | |
| 66 | + | "export_opml", | |
| 67 | + | "Export OPML...", | |
| 68 | + | true, | |
| 69 | + | Some("CmdOrCtrl+E"), | |
| 70 | + | )?, | |
| 53 | 71 | &PredefinedMenuItem::separator(app)?, | |
| 54 | 72 | #[cfg(not(target_os = "macos"))] | |
| 55 | 73 | &PredefinedMenuItem::quit(app, Some("Exit"))?, | |
| @@ -89,9 +107,21 @@ | |||
| 89 | 107 | "Help", | |
| 90 | 108 | true, | |
| 91 | 109 | &[ | |
| 92 | - | &MenuItem::with_id(app, "check_updates", "Check for Updates...", true, None::<&str>)?, | |
| 110 | + | &MenuItem::with_id( | |
| 111 | + | app, | |
| 112 | + | "check_updates", | |
| 113 | + | "Check for Updates...", | |
| 114 | + | true, | |
| 115 | + | None::<&str>, | |
| 116 | + | )?, | |
| 93 | 117 | &PredefinedMenuItem::separator(app)?, | |
| 94 | - | &MenuItem::with_id(app, "about", "About Balanced Breakfast", true, None::<&str>)?, | |
| 118 | + | &MenuItem::with_id( | |
| 119 | + | app, | |
| 120 | + | "about", | |
| 121 | + | "About Balanced Breakfast", | |
| 122 | + | true, | |
| 123 | + | None::<&str>, | |
| 124 | + | )?, | |
| 95 | 125 | ], | |
| 96 | 126 | )?; | |
| 97 | 127 | ||
| @@ -104,10 +134,7 @@ | |||
| 104 | 134 | } | |
| 105 | 135 | #[cfg(not(target_os = "macos"))] | |
| 106 | 136 | { | |
| 107 | - | Menu::with_items( | |
| 108 | - | app, | |
| 109 | - | &[&file_menu, &edit_menu, &view_menu, &help_menu], | |
| 110 | - | ) | |
| 137 | + | Menu::with_items(app, &[&file_menu, &edit_menu, &view_menu, &help_menu]) | |
| 111 | 138 | } | |
| 112 | 139 | }) | |
| 113 | 140 | .on_menu_event(|app, event| { |
| @@ -243,10 +243,13 @@ | |||
| 243 | 243 | /// Create a SyncKitClient from the saved or env-var API key. | |
| 244 | 244 | fn load_sync_client(data_dir: &std::path::Path) -> Option<SyncKitClient> { | |
| 245 | 245 | let api_key = load_api_key(data_dir)?; | |
| 246 | - | let server_url = std::env::var("BB_SYNC_SERVER_URL") | |
| 247 | - | .unwrap_or_else(|_| SYNC_SERVER_URL.to_string()); | |
| 246 | + | let server_url = | |
| 247 | + | std::env::var("BB_SYNC_SERVER_URL").unwrap_or_else(|_| SYNC_SERVER_URL.to_string()); | |
| 248 | 248 | info!(%server_url, "SyncKit client configured"); | |
| 249 | - | let client = SyncKitClient::new(SyncKitConfig { server_url, api_key }); | |
| 249 | + | let client = SyncKitClient::new(SyncKitConfig { | |
| 250 | + | server_url, | |
| 251 | + | api_key, | |
| 252 | + | }); | |
| 250 | 253 | match client.try_load_key_from_keychain() { | |
| 251 | 254 | Ok(true) => info!("Sync encryption key loaded from keychain"), | |
| 252 | 255 | Ok(false) => debug!("No sync encryption key in keychain"), | |
| @@ -350,7 +353,10 @@ | |||
| 350 | 353 | }; | |
| 351 | 354 | ||
| 352 | 355 | for plugin_id in plugin_ids { | |
| 353 | - | let interval = task_state.orchestrator.fetch_interval_secs(&plugin_id).await; | |
| 356 | + | let interval = task_state | |
| 357 | + | .orchestrator | |
| 358 | + | .fetch_interval_secs(&plugin_id) | |
| 359 | + | .await; | |
| 354 | 360 | if interval == 0 { | |
| 355 | 361 | continue; // Auto-fetch disabled for this plugin | |
| 356 | 362 | } | |
| @@ -420,7 +426,8 @@ | |||
| 420 | 426 | error!(error = %e, %plugin_id, category = %category_str, "Auto-fetch failed"); | |
| 421 | 427 | ||
| 422 | 428 | // Check if the circuit breaker just tripped | |
| 423 | - | if let Ok(true) = task_state.orchestrator.is_circuit_broken(&plugin_id).await | |
| 429 | + | if let Ok(true) = | |
| 430 | + | task_state.orchestrator.is_circuit_broken(&plugin_id).await | |
| 424 | 431 | { | |
| 425 | 432 | info!(%plugin_id, "Circuit breaker tripped, emitting event"); | |
| 426 | 433 | if let Err(emit_err) = app_handle.emit( | |
| @@ -503,9 +510,6 @@ | |||
| 503 | 510 | } | |
| 504 | 511 | ||
| 505 | 512 | let now = chrono::Utc::now(); | |
| 506 | - | let feed_times: Vec<_> = feeds | |
| 507 | - | .iter() | |
| 508 | - | .map(|f| f.last_fetch.as_deref()) | |
| 509 | - | .collect(); | |
| 513 | + | let feed_times: Vec<_> = feeds.iter().map(|f| f.last_fetch.as_deref()).collect(); | |
| 510 | 514 | Ok(any_feed_due(&feed_times, interval_secs, now)) | |
| 511 | 515 | } |
| @@ -1,8 +1,8 @@ | |||
| 1 | 1 | //! Background sync scheduler with exponential backoff and SSE push notifications. | |
| 2 | 2 | ||
| 3 | - | use bb_core::scheduler::exponential_backoff_secs; | |
| 4 | 3 | use crate::state::AppState; | |
| 5 | 4 | use crate::sync_service; | |
| 5 | + | use bb_core::scheduler::exponential_backoff_secs; | |
| 6 | 6 | use std::sync::Arc; | |
| 7 | 7 | use tauri::{AppHandle, Emitter, Manager}; | |
| 8 | 8 | use tracing::{debug, error, info, warn}; | |
| @@ -63,7 +63,8 @@ | |||
| 63 | 63 | warn!(error = %e, "Sync changelog retention check failed"); | |
| 64 | 64 | } | |
| 65 | 65 | ||
| 66 | - | let client: Arc<synckit_client::SyncKitClient> = match state.sync_client.read().clone() { | |
| 66 | + | let client: Arc<synckit_client::SyncKitClient> = match state.sync_client.read().clone() | |
| 67 | + | { | |
| 67 | 68 | Some(c) => c, | |
| 68 | 69 | None => continue, | |
| 69 | 70 | }; | |
| @@ -105,7 +106,12 @@ | |||
| 105 | 106 | ||
| 106 | 107 | // Check if sync interval has elapsed (skip check if SSE-triggered) | |
| 107 | 108 | if !sse_triggered { | |
| 108 | - | let interval_minutes: u64 = match sync_service::get_sync_state(pool, "sync_interval_minutes").await { | |
| 109 | + | let interval_minutes: u64 = match sync_service::get_sync_state( | |
| 110 | + | pool, | |
| 111 | + | "sync_interval_minutes", | |
| 112 | + | ) | |
| 113 | + | .await | |
| 114 | + | { | |
| 109 | 115 | Ok(v) => match v.parse() { | |
| 110 | 116 | Ok(mins) => mins, | |
| 111 | 117 | Err(e) => { | |
| @@ -191,11 +197,9 @@ | |||
| 191 | 197 | backoff_until = Some(chrono::Utc::now() + chrono::Duration::hours(1)); | |
| 192 | 198 | } else { | |
| 193 | 199 | consecutive_failures += 1; | |
| 194 | - | let backoff_secs = | |
| 195 | - | exponential_backoff_secs(consecutive_failures, 15) * 60; | |
| 200 | + | let backoff_secs = exponential_backoff_secs(consecutive_failures, 15) * 60; | |
| 196 | 201 | backoff_until = Some( | |
| 197 | - | chrono::Utc::now() | |
| 198 | - | + chrono::Duration::seconds(backoff_secs as i64), | |
| 202 | + | chrono::Utc::now() + chrono::Duration::seconds(backoff_secs as i64), | |
| 199 | 203 | ); | |
| 200 | 204 | warn!( | |
| 201 | 205 | error = %e, |