mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-05-07 10:56:41 +02:00
710 lines
22 KiB
Rust
710 lines
22 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
use std::fs;
|
|
use std::path::PathBuf;
|
|
use std::sync::Arc;
|
|
use std::sync::OnceLock;
|
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
|
use tokio::sync::Mutex;
|
|
use tokio::time::interval;
|
|
|
|
use crate::auto_updater::AutoUpdater;
|
|
use crate::browser_version_manager::BrowserVersionManager;
|
|
use crate::events;
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
pub struct VersionUpdateProgress {
|
|
pub current_browser: String,
|
|
pub total_browsers: usize,
|
|
pub completed_browsers: usize,
|
|
pub new_versions_found: usize,
|
|
pub browser_new_versions: usize, // New versions found for current browser
|
|
pub status: String, // "updating", "completed", "error"
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
pub struct BackgroundUpdateResult {
|
|
pub browser: String,
|
|
pub new_versions_count: usize,
|
|
pub total_versions_count: usize,
|
|
pub updated_successfully: bool,
|
|
pub error: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub(crate) struct BackgroundUpdateState {
|
|
last_update_time: u64,
|
|
update_interval_hours: u64,
|
|
}
|
|
|
|
impl Default for BackgroundUpdateState {
|
|
fn default() -> Self {
|
|
Self {
|
|
last_update_time: 0,
|
|
update_interval_hours: 12,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Extension of auto_updater.rs for background updates
|
|
pub struct VersionUpdater {
|
|
browser_version_manager: &'static BrowserVersionManager,
|
|
auto_updater: &'static AutoUpdater,
|
|
app_handle: Option<tauri::AppHandle>,
|
|
}
|
|
|
|
impl VersionUpdater {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
browser_version_manager: BrowserVersionManager::instance(),
|
|
auto_updater: AutoUpdater::instance(),
|
|
app_handle: None,
|
|
}
|
|
}
|
|
|
|
pub fn set_app_handle(&mut self, app_handle: tauri::AppHandle) {
|
|
self.app_handle = Some(app_handle);
|
|
}
|
|
|
|
fn get_cache_dir() -> Result<PathBuf, Box<dyn std::error::Error>> {
|
|
let cache_dir = crate::app_dirs::cache_dir().join("version_cache");
|
|
fs::create_dir_all(&cache_dir)?;
|
|
Ok(cache_dir)
|
|
}
|
|
|
|
pub(crate) fn get_background_update_state_file() -> Result<PathBuf, Box<dyn std::error::Error>> {
|
|
let cache_dir = Self::get_cache_dir()?;
|
|
Ok(cache_dir.join("background_update_state.json"))
|
|
}
|
|
|
|
pub(crate) fn load_background_update_state() -> BackgroundUpdateState {
|
|
let state_file = match Self::get_background_update_state_file() {
|
|
Ok(file) => file,
|
|
Err(_) => return BackgroundUpdateState::default(),
|
|
};
|
|
|
|
if !state_file.exists() {
|
|
return BackgroundUpdateState::default();
|
|
}
|
|
|
|
let content = match fs::read_to_string(&state_file) {
|
|
Ok(content) => content,
|
|
Err(_) => return BackgroundUpdateState::default(),
|
|
};
|
|
|
|
serde_json::from_str(&content).unwrap_or_default()
|
|
}
|
|
|
|
pub(crate) fn save_background_update_state(
|
|
state: &BackgroundUpdateState,
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|
let state_file = Self::get_background_update_state_file()?;
|
|
let content = serde_json::to_string_pretty(state)?;
|
|
fs::write(&state_file, content)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn get_current_timestamp() -> u64 {
|
|
SystemTime::now()
|
|
.duration_since(UNIX_EPOCH)
|
|
.unwrap_or_default()
|
|
.as_secs()
|
|
}
|
|
|
|
fn should_run_background_update() -> bool {
|
|
let state = Self::load_background_update_state();
|
|
let current_time = Self::get_current_timestamp();
|
|
let elapsed_secs = current_time.saturating_sub(state.last_update_time);
|
|
let update_interval_secs = state.update_interval_hours * 60 * 60;
|
|
|
|
// Run update if:
|
|
// 1. Never updated before (last_update_time == 0)
|
|
// 2. More than 3 hours have passed since last update
|
|
let should_update = state.last_update_time == 0 || elapsed_secs >= update_interval_secs;
|
|
|
|
if should_update {
|
|
log::debug!(
|
|
"Background update needed: last_update={}, elapsed={}h, required={}h",
|
|
state.last_update_time,
|
|
elapsed_secs / 3600,
|
|
state.update_interval_hours
|
|
);
|
|
} else {
|
|
log::debug!(
|
|
"Background update not needed: last_update={}, elapsed={}h, required={}h",
|
|
state.last_update_time,
|
|
elapsed_secs / 3600,
|
|
state.update_interval_hours
|
|
);
|
|
}
|
|
|
|
should_update
|
|
}
|
|
|
|
pub async fn check_and_run_startup_update(
|
|
&self,
|
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
// Always check for updates on launch
|
|
if let Some(ref app_handle) = self.app_handle {
|
|
log::info!("Running startup version update...");
|
|
|
|
match self.update_all_browser_versions(app_handle).await {
|
|
Ok(_) => {
|
|
// Update the persistent state after successful update
|
|
let state = BackgroundUpdateState {
|
|
last_update_time: Self::get_current_timestamp(),
|
|
update_interval_hours: 3,
|
|
};
|
|
|
|
if let Err(e) = Self::save_background_update_state(&state) {
|
|
log::error!("Failed to save background update state: {e}");
|
|
} else {
|
|
log::info!("Startup version update completed successfully");
|
|
}
|
|
}
|
|
Err(e) => {
|
|
log::error!("Startup version update failed: {e}");
|
|
return Err(e);
|
|
}
|
|
}
|
|
} else {
|
|
return Err("App handle not available for startup update".into());
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn start_background_updates(
|
|
&self,
|
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
println!(
|
|
"Starting background version update service (checking every 5 minutes for 3-hour intervals)"
|
|
);
|
|
|
|
// Run initial startup check
|
|
if let Err(e) = self.check_and_run_startup_update().await {
|
|
eprintln!("Startup version update failed: {e}");
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn run_background_task() {
|
|
let mut update_interval = interval(Duration::from_secs(5 * 60)); // Check every 5 minutes
|
|
update_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
|
|
|
|
loop {
|
|
update_interval.tick().await;
|
|
|
|
// Check if we should run an update based on persistent state
|
|
if !Self::should_run_background_update() {
|
|
continue;
|
|
}
|
|
|
|
println!("Starting background version update...");
|
|
|
|
// Get the updater instance for this update cycle
|
|
let updater = get_version_updater();
|
|
let result = {
|
|
let updater_guard = updater.lock().await;
|
|
if let Some(ref app_handle) = updater_guard.app_handle {
|
|
updater_guard.update_all_browser_versions(app_handle).await
|
|
} else {
|
|
Err("App handle not available for background update".into())
|
|
}
|
|
}; // Release the lock here
|
|
|
|
match result {
|
|
Ok(_) => {
|
|
// Update the persistent state after successful update
|
|
let state = BackgroundUpdateState {
|
|
last_update_time: Self::get_current_timestamp(),
|
|
update_interval_hours: 3,
|
|
};
|
|
|
|
if let Err(e) = Self::save_background_update_state(&state) {
|
|
eprintln!("Failed to save background update state: {e}");
|
|
} else {
|
|
println!("Background version update completed successfully");
|
|
}
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Background version update failed: {e}");
|
|
|
|
// Try to emit error event if we have an app handle
|
|
let updater_guard = updater.lock().await;
|
|
if let Some(ref _app_handle) = updater_guard.app_handle {
|
|
let progress = VersionUpdateProgress {
|
|
current_browser: "".to_string(),
|
|
total_browsers: 0,
|
|
completed_browsers: 0,
|
|
new_versions_found: 0,
|
|
browser_new_versions: 0,
|
|
status: "error".to_string(),
|
|
};
|
|
let _ = events::emit("version-update-progress", &progress);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn update_all_browser_versions(
|
|
&self,
|
|
app_handle: &tauri::AppHandle,
|
|
) -> Result<Vec<BackgroundUpdateResult>, Box<dyn std::error::Error + Send + Sync>> {
|
|
let supported_browsers = self.browser_version_manager.get_supported_browsers();
|
|
|
|
// Only fetch versions for active browsers (wayfern, camoufox) plus any
|
|
// deprecated browsers that still have existing profiles
|
|
let active_browsers = ["wayfern", "camoufox"];
|
|
let browsers_with_profiles: std::collections::HashSet<String> =
|
|
crate::profile::ProfileManager::instance()
|
|
.list_profiles()
|
|
.unwrap_or_default()
|
|
.iter()
|
|
.map(|p| p.browser.clone())
|
|
.collect();
|
|
|
|
let supported_browsers: Vec<String> = supported_browsers
|
|
.into_iter()
|
|
.filter(|b| active_browsers.contains(&b.as_str()) || browsers_with_profiles.contains(b))
|
|
.collect();
|
|
|
|
let total_browsers = supported_browsers.len();
|
|
let mut results = Vec::new();
|
|
let mut total_new_versions = 0;
|
|
|
|
// Emit initial progress
|
|
let initial_progress = VersionUpdateProgress {
|
|
current_browser: String::new(),
|
|
total_browsers,
|
|
completed_browsers: 0,
|
|
new_versions_found: 0,
|
|
browser_new_versions: 0,
|
|
status: "updating".to_string(),
|
|
};
|
|
|
|
if let Err(e) = events::emit("version-update-progress", &initial_progress) {
|
|
log::error!("Failed to emit initial progress: {e}");
|
|
}
|
|
|
|
for (index, browser) in supported_browsers.iter().enumerate() {
|
|
log::debug!("Updating browser versions for: {browser}");
|
|
|
|
// Emit progress update for current browser
|
|
let progress = VersionUpdateProgress {
|
|
current_browser: browser.clone(),
|
|
total_browsers,
|
|
completed_browsers: index,
|
|
new_versions_found: total_new_versions,
|
|
browser_new_versions: 0,
|
|
status: "updating".to_string(),
|
|
};
|
|
|
|
if let Err(e) = events::emit("version-update-progress", &progress) {
|
|
log::error!("Failed to emit progress for {browser}: {e}");
|
|
}
|
|
|
|
match self.update_browser_versions(browser).await {
|
|
Ok(new_versions_count) => {
|
|
results.push(BackgroundUpdateResult {
|
|
browser: browser.clone(),
|
|
new_versions_count,
|
|
total_versions_count: 0, // We don't track total for background updates
|
|
updated_successfully: true,
|
|
error: None,
|
|
});
|
|
|
|
total_new_versions += new_versions_count;
|
|
|
|
// Emit progress update with new versions found
|
|
let progress = VersionUpdateProgress {
|
|
current_browser: browser.clone(),
|
|
total_browsers,
|
|
completed_browsers: index,
|
|
new_versions_found: total_new_versions,
|
|
browser_new_versions: new_versions_count,
|
|
status: "updating".to_string(),
|
|
};
|
|
|
|
if let Err(e) = events::emit("version-update-progress", &progress) {
|
|
log::error!("Failed to emit progress with versions for {browser}: {e}");
|
|
}
|
|
}
|
|
Err(e) => {
|
|
results.push(BackgroundUpdateResult {
|
|
browser: browser.clone(),
|
|
new_versions_count: 0,
|
|
total_versions_count: 0,
|
|
updated_successfully: false,
|
|
error: Some(e.to_string()),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Emit completion
|
|
let final_progress = VersionUpdateProgress {
|
|
current_browser: String::new(),
|
|
total_browsers,
|
|
completed_browsers: total_browsers,
|
|
new_versions_found: total_new_versions,
|
|
browser_new_versions: 0,
|
|
status: "completed".to_string(),
|
|
};
|
|
|
|
if let Err(e) = events::emit("version-update-progress", &final_progress) {
|
|
eprintln!("Failed to emit completion progress: {e}");
|
|
}
|
|
|
|
// Always check for auto-updates — profiles may still be on older versions
|
|
// even if no new versions were found in the cache this cycle
|
|
println!(
|
|
"Checking for browser auto-updates (found {total_new_versions} new versions in cache)..."
|
|
);
|
|
self
|
|
.auto_updater
|
|
.check_for_updates_with_progress(app_handle)
|
|
.await;
|
|
|
|
Ok(results)
|
|
}
|
|
|
|
async fn update_browser_versions(
|
|
&self,
|
|
browser: &str,
|
|
) -> Result<usize, Box<dyn std::error::Error + Send + Sync>> {
|
|
self
|
|
.browser_version_manager
|
|
.update_browser_versions_incrementally(browser)
|
|
.await
|
|
}
|
|
|
|
pub async fn trigger_manual_update(
|
|
&self,
|
|
app_handle: &tauri::AppHandle,
|
|
) -> Result<Vec<BackgroundUpdateResult>, Box<dyn std::error::Error + Send + Sync>> {
|
|
let results = self.update_all_browser_versions(app_handle).await?;
|
|
|
|
// Update the persistent state after successful manual update
|
|
let state = BackgroundUpdateState {
|
|
last_update_time: Self::get_current_timestamp(),
|
|
update_interval_hours: 3,
|
|
};
|
|
|
|
if let Err(e) = Self::save_background_update_state(&state) {
|
|
log::error!("Failed to save background update state after manual update: {e}");
|
|
}
|
|
|
|
Ok(results)
|
|
}
|
|
|
|
pub async fn get_last_update_time(&self) -> Option<u64> {
|
|
let state = Self::load_background_update_state();
|
|
if state.last_update_time == 0 {
|
|
None
|
|
} else {
|
|
Some(state.last_update_time)
|
|
}
|
|
}
|
|
|
|
pub async fn get_time_until_next_update(&self) -> u64 {
|
|
let state = Self::load_background_update_state();
|
|
let current_time = Self::get_current_timestamp();
|
|
|
|
if state.last_update_time == 0 {
|
|
0 // No previous update, should update now
|
|
} else {
|
|
let elapsed = current_time.saturating_sub(state.last_update_time);
|
|
let update_interval_secs = state.update_interval_hours * 60 * 60;
|
|
|
|
update_interval_secs.saturating_sub(elapsed)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Global instance
|
|
static VERSION_UPDATER: OnceLock<Arc<Mutex<VersionUpdater>>> = OnceLock::new();
|
|
|
|
pub fn get_version_updater() -> Arc<Mutex<VersionUpdater>> {
|
|
VERSION_UPDATER
|
|
.get_or_init(|| Arc::new(Mutex::new(VersionUpdater::new())))
|
|
.clone()
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn trigger_manual_version_update(
|
|
app_handle: tauri::AppHandle,
|
|
) -> Result<Vec<BackgroundUpdateResult>, String> {
|
|
let updater = get_version_updater();
|
|
let updater_guard = updater.lock().await;
|
|
|
|
updater_guard
|
|
.trigger_manual_update(&app_handle)
|
|
.await
|
|
.map_err(|e| format!("Failed to trigger manual update: {e}"))
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn get_version_update_status() -> Result<(Option<u64>, u64), String> {
|
|
let updater = get_version_updater();
|
|
let updater_guard = updater.lock().await;
|
|
|
|
let last_update = updater_guard.get_last_update_time().await;
|
|
let time_until_next = updater_guard.get_time_until_next_update().await;
|
|
|
|
Ok((last_update, time_until_next))
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn clear_all_version_cache_and_refetch(
|
|
app_handle: tauri::AppHandle,
|
|
) -> Result<(), String> {
|
|
let api_client = crate::api_client::ApiClient::instance();
|
|
let version_updater = VersionUpdater::new();
|
|
|
|
// Clear all cache first
|
|
api_client
|
|
.clear_all_cache()
|
|
.map_err(|e| format!("Failed to clear version cache: {e}"))?;
|
|
|
|
// Disable all browsers during the update process
|
|
let supported_browsers = version_updater
|
|
.browser_version_manager
|
|
.get_supported_browsers();
|
|
|
|
// Load current state and disable all browsers
|
|
let mut state = version_updater
|
|
.auto_updater
|
|
.load_auto_update_state()
|
|
.map_err(|e| format!("Failed to load auto update state: {e}"))?;
|
|
for browser in &supported_browsers {
|
|
state.disabled_browsers.insert(browser.clone());
|
|
}
|
|
version_updater
|
|
.auto_updater
|
|
.save_auto_update_state(&state)
|
|
.map_err(|e| format!("Failed to save auto update state: {e}"))?;
|
|
|
|
let updater = get_version_updater();
|
|
let updater_guard = updater.lock().await;
|
|
|
|
let result = updater_guard
|
|
.trigger_manual_update(&app_handle)
|
|
.await
|
|
.map_err(|e| format!("Failed to trigger version update: {e}"));
|
|
|
|
// Re-enable all browsers after the update completes (regardless of success/failure)
|
|
let mut final_state = version_updater
|
|
.auto_updater
|
|
.load_auto_update_state()
|
|
.unwrap_or_default();
|
|
for browser in &supported_browsers {
|
|
final_state.disabled_browsers.remove(browser);
|
|
}
|
|
if let Err(e) = version_updater
|
|
.auto_updater
|
|
.save_auto_update_state(&final_state)
|
|
{
|
|
log::warn!("Failed to re-enable browsers after cache clear: {e}");
|
|
}
|
|
|
|
result?;
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use serial_test::serial;
|
|
use std::env;
|
|
use tempfile::TempDir;
|
|
|
|
fn setup_test_env() -> TempDir {
|
|
let temp_dir = TempDir::new().expect("Failed to create temp directory");
|
|
env::set_var("HOME", temp_dir.path());
|
|
temp_dir
|
|
}
|
|
|
|
fn cleanup_state_file() {
|
|
if let Ok(state_file) = VersionUpdater::get_background_update_state_file() {
|
|
let _ = fs::remove_file(&state_file);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[serial]
|
|
fn test_background_update_state_persistence() {
|
|
let _temp_dir = setup_test_env();
|
|
|
|
// Clean up any existing state file first
|
|
if let Ok(state_file) = VersionUpdater::get_background_update_state_file() {
|
|
let _ = fs::remove_file(&state_file);
|
|
}
|
|
|
|
// Create a test state
|
|
let test_state = BackgroundUpdateState {
|
|
last_update_time: 1609459200, // 2021-01-01 00:00:00 UTC
|
|
update_interval_hours: 3,
|
|
};
|
|
|
|
// Save the state
|
|
let save_result = VersionUpdater::save_background_update_state(&test_state);
|
|
assert!(save_result.is_ok(), "Should save state successfully");
|
|
|
|
// Verify file was created
|
|
let state_file = VersionUpdater::get_background_update_state_file().unwrap();
|
|
assert!(state_file.exists(), "State file should exist after saving");
|
|
|
|
// Read the file directly to verify contents
|
|
let file_content = fs::read_to_string(&state_file).expect("Should read state file");
|
|
let file_state: BackgroundUpdateState =
|
|
serde_json::from_str(&file_content).expect("Should parse state file");
|
|
|
|
// Verify the file contents match what we saved
|
|
assert_eq!(
|
|
file_state.last_update_time, test_state.last_update_time,
|
|
"File last_update_time should match. Expected: {}, Got: {}",
|
|
test_state.last_update_time, file_state.last_update_time
|
|
);
|
|
assert_eq!(
|
|
file_state.update_interval_hours, test_state.update_interval_hours,
|
|
"File update_interval_hours should match"
|
|
);
|
|
|
|
// Load the state back using the method
|
|
let loaded_state = VersionUpdater::load_background_update_state();
|
|
|
|
// Verify the values match
|
|
assert_eq!(
|
|
loaded_state.last_update_time, test_state.last_update_time,
|
|
"Loaded last_update_time should match. Expected: {}, Got: {}",
|
|
test_state.last_update_time, loaded_state.last_update_time
|
|
);
|
|
assert_eq!(
|
|
loaded_state.update_interval_hours, test_state.update_interval_hours,
|
|
"Loaded update_interval_hours should match"
|
|
);
|
|
|
|
// Clean up
|
|
cleanup_state_file();
|
|
}
|
|
|
|
#[test]
|
|
#[serial]
|
|
fn test_should_run_background_update_logic() {
|
|
let _temp_dir = setup_test_env();
|
|
|
|
// Clean up any existing state file first
|
|
cleanup_state_file();
|
|
|
|
let current_time = VersionUpdater::get_current_timestamp();
|
|
|
|
// Test with recent update (should not update)
|
|
let recent_state = BackgroundUpdateState {
|
|
last_update_time: current_time - 60, // 1 minute ago
|
|
update_interval_hours: 3,
|
|
};
|
|
|
|
// Save and test recent state
|
|
let save_result = VersionUpdater::save_background_update_state(&recent_state);
|
|
assert!(save_result.is_ok(), "Should save recent state successfully");
|
|
|
|
let should_update_recent = VersionUpdater::should_run_background_update();
|
|
assert!(
|
|
!should_update_recent,
|
|
"Should not update when last update was recent"
|
|
);
|
|
|
|
// Test with old update (should update)
|
|
let old_state = BackgroundUpdateState {
|
|
last_update_time: current_time - (4 * 60 * 60), // 4 hours ago
|
|
update_interval_hours: 3,
|
|
};
|
|
|
|
// Save and test old state
|
|
let save_result = VersionUpdater::save_background_update_state(&old_state);
|
|
assert!(save_result.is_ok(), "Should save old state successfully");
|
|
|
|
let should_update_old = VersionUpdater::should_run_background_update();
|
|
assert!(should_update_old, "Should update when last update was old");
|
|
|
|
// Test with never updated (should update)
|
|
let never_updated_state = BackgroundUpdateState {
|
|
last_update_time: 0,
|
|
update_interval_hours: 3,
|
|
};
|
|
|
|
let save_result = VersionUpdater::save_background_update_state(&never_updated_state);
|
|
assert!(
|
|
save_result.is_ok(),
|
|
"Should save never updated state successfully"
|
|
);
|
|
|
|
let should_update_never = VersionUpdater::should_run_background_update();
|
|
assert!(
|
|
should_update_never,
|
|
"Should update when never updated before"
|
|
);
|
|
|
|
// Clean up
|
|
cleanup_state_file();
|
|
}
|
|
|
|
#[test]
|
|
fn test_version_updater_creation() {
|
|
let updater = VersionUpdater::new();
|
|
|
|
// Should have valid references to services
|
|
assert!(
|
|
!std::ptr::eq(
|
|
updater.browser_version_manager as *const _,
|
|
std::ptr::null()
|
|
),
|
|
"Version service should not be null"
|
|
);
|
|
assert!(
|
|
!std::ptr::eq(updater.auto_updater as *const _, std::ptr::null()),
|
|
"Auto updater should not be null"
|
|
);
|
|
assert!(
|
|
updater.app_handle.is_none(),
|
|
"App handle should initially be None"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_current_timestamp() {
|
|
let timestamp1 = VersionUpdater::get_current_timestamp();
|
|
|
|
// Should be a reasonable timestamp (after year 2020)
|
|
assert!(
|
|
timestamp1 > 1577836800,
|
|
"Timestamp should be after 2020-01-01"
|
|
); // 2020-01-01 00:00:00 UTC
|
|
|
|
// Should be before year 2100
|
|
assert!(
|
|
timestamp1 < 4102444800,
|
|
"Timestamp should be before 2100-01-01"
|
|
); // 2100-01-01 00:00:00 UTC
|
|
|
|
// Wait a tiny bit and check it increases
|
|
std::thread::sleep(std::time::Duration::from_millis(1));
|
|
let timestamp2 = VersionUpdater::get_current_timestamp();
|
|
assert!(timestamp2 >= timestamp1, "Timestamp should not decrease");
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_version_updater_singleton() {
|
|
let updater1 = get_version_updater();
|
|
let updater2 = get_version_updater();
|
|
|
|
// Should return the same Arc instance
|
|
assert!(
|
|
Arc::ptr_eq(&updater1, &updater2),
|
|
"Should return same singleton instance"
|
|
);
|
|
}
|
|
}
|