Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions apps/src-tauri/src/app_shell/tray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,19 +86,19 @@ pub(crate) fn setup_tray(app: &tauri::AppHandle) -> Result<(), tauri::Error> {
fn build_tray_menu(app: &tauri::AppHandle) -> Result<Menu<tauri::Wry>, tauri::Error> {
let show_main = MenuItem::with_id(app, TRAY_MENU_SHOW_MAIN, "显示主窗口", true, None::<&str>)?;
let summary = codexmanager_service::read_tray_usage_reset_summary();
let (primary_label, secondary_label) =
tray_usage_reset_labels(summary.primary_resets_at, summary.secondary_resets_at);
let (five_hour_label, seven_day_label) =
tray_usage_reset_labels(summary.five_hour_resets_at, summary.seven_day_resets_at);
let primary = MenuItem::with_id(
app,
TRAY_MENU_PRIMARY_RESET,
primary_label,
five_hour_label,
false,
None::<&str>,
)?;
let secondary = MenuItem::with_id(
app,
TRAY_MENU_SECONDARY_RESET,
secondary_label,
seven_day_label,
false,
None::<&str>,
)?;
Expand Down Expand Up @@ -151,12 +151,12 @@ pub(crate) fn refresh_tray_menu_after_usage_update(app: &tauri::AppHandle) {
}

fn tray_usage_reset_labels(
primary_resets_at: Option<i64>,
secondary_resets_at: Option<i64>,
five_hour_resets_at: Option<i64>,
seven_day_resets_at: Option<i64>,
) -> (String, String) {
(
format!("5小时重置:{}", format_tray_reset_time(secondary_resets_at)),
format!("7天重置:{}", format_tray_reset_time(primary_resets_at)),
format!("5小时重置:{}", format_tray_reset_time(five_hour_resets_at)),
format!("7天重置:{}", format_tray_reset_time(seven_day_resets_at)),
)
}

Expand Down Expand Up @@ -241,23 +241,23 @@ mod tests {
assert_eq!(stale_labels.0, "5小时重置:暂无");
assert_eq!(stale_labels.1, "7天重置:暂无");

let primary_resets_at = 1_700_000_000;
let secondary_resets_at = 1_700_604_800;
let five_hour_resets_at = 1_700_000_000;
let seven_day_resets_at = 1_700_604_800;
let updated_labels =
tray_usage_reset_labels(Some(primary_resets_at), Some(secondary_resets_at));
tray_usage_reset_labels(Some(five_hour_resets_at), Some(seven_day_resets_at));
assert_ne!(updated_labels, stale_labels);
assert_eq!(
updated_labels.0,
format!(
"5小时重置:{}",
format_tray_reset_time(Some(primary_resets_at))
format_tray_reset_time(Some(five_hour_resets_at))
)
);
assert_eq!(
updated_labels.1,
format!(
"7天重置:{}",
format_tray_reset_time(Some(secondary_resets_at))
format_tray_reset_time(Some(seven_day_resets_at))
)
);
}
Expand Down
109 changes: 97 additions & 12 deletions crates/service/src/usage/usage_tray_summary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,21 @@ use codexmanager_core::storage::now_ts;

use crate::storage_helpers::open_storage;

const MINUTES_PER_DAY: i64 = 24 * 60;
const WINDOW_ROUNDING_BIAS_MINUTES: i64 = 3;

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct TrayUsageResetSummary {
pub primary_resets_at: Option<i64>,
pub secondary_resets_at: Option<i64>,
pub primary_known_count: usize,
pub secondary_known_count: usize,
pub five_hour_resets_at: Option<i64>,
pub seven_day_resets_at: Option<i64>,
pub five_hour_known_count: usize,
pub seven_day_known_count: usize,
}

#[derive(Clone, Copy)]
enum ResetWindow {
FiveHour,
SevenDay,
}

pub fn read_tray_usage_reset_summary() -> TrayUsageResetSummary {
Expand All @@ -20,16 +29,51 @@ pub fn read_tray_usage_reset_summary() -> TrayUsageResetSummary {
let now = now_ts();
let mut summary = TrayUsageResetSummary::default();
for item in items {
if let Some(resets_at) = future_ts(item.resets_at, now) {
summary.primary_known_count += 1;
summary.primary_resets_at = min_ts(summary.primary_resets_at, resets_at);
add_reset_window(
&mut summary,
item.window_minutes,
item.resets_at,
ResetWindow::FiveHour,
now,
);
add_reset_window(
&mut summary,
item.secondary_window_minutes,
item.secondary_resets_at,
ResetWindow::SevenDay,
now,
);
}
summary
}

fn add_reset_window(
summary: &mut TrayUsageResetSummary,
window_minutes: Option<i64>,
resets_at: Option<i64>,
fallback: ResetWindow,
now: i64,
) {
let Some(resets_at) = future_ts(resets_at, now) else {
return;
};
let window = match window_minutes {
Some(minutes) if minutes > MINUTES_PER_DAY + WINDOW_ROUNDING_BIAS_MINUTES => {
ResetWindow::SevenDay
}
if let Some(resets_at) = future_ts(item.secondary_resets_at, now) {
summary.secondary_known_count += 1;
summary.secondary_resets_at = min_ts(summary.secondary_resets_at, resets_at);
Some(_) => ResetWindow::FiveHour,
None => fallback,
};
match window {
ResetWindow::FiveHour => {
summary.five_hour_known_count += 1;
summary.five_hour_resets_at = min_ts(summary.five_hour_resets_at, resets_at);
}
ResetWindow::SevenDay => {
summary.seven_day_known_count += 1;
summary.seven_day_resets_at = min_ts(summary.seven_day_resets_at, resets_at);
}
}
summary
}

fn future_ts(value: Option<i64>, now: i64) -> Option<i64> {
Expand All @@ -46,7 +90,7 @@ fn min_ts(current: Option<i64>, candidate: i64) -> Option<i64> {

#[cfg(test)]
mod tests {
use super::{future_ts, min_ts};
use super::{add_reset_window, future_ts, min_ts, ResetWindow, TrayUsageResetSummary};

#[test]
fn future_ts_ignores_missing_or_elapsed_values() {
Expand All @@ -62,4 +106,45 @@ mod tests {
assert_eq!(min_ts(Some(180), 120), Some(120));
assert_eq!(min_ts(Some(90), 120), Some(90));
}

#[test]
fn single_primary_seven_day_window_is_not_reported_as_five_hour() {
let mut summary = TrayUsageResetSummary::default();

add_reset_window(
&mut summary,
Some(10_080),
Some(200),
ResetWindow::FiveHour,
100,
);

assert_eq!(summary.five_hour_resets_at, None);
assert_eq!(summary.five_hour_known_count, 0);
assert_eq!(summary.seven_day_resets_at, Some(200));
assert_eq!(summary.seven_day_known_count, 1);
}

#[test]
fn reset_windows_are_classified_by_duration_when_fields_are_swapped() {
let mut summary = TrayUsageResetSummary::default();

add_reset_window(
&mut summary,
Some(10_080),
Some(300),
ResetWindow::FiveHour,
100,
);
add_reset_window(
&mut summary,
Some(300),
Some(200),
ResetWindow::SevenDay,
100,
);

assert_eq!(summary.five_hour_resets_at, Some(200));
assert_eq!(summary.seven_day_resets_at, Some(300));
}
}