mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-08-04 02:08:43 +02:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eaa1a823db | ||
|
|
63900bd0ad |
@@ -126,6 +126,7 @@ jobs:
|
|||||||
uses: tauri-apps/tauri-action@v0
|
uses: tauri-apps/tauri-action@v0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
GITHUB_REF_NAME: ${{ github.ref_name }}
|
||||||
with:
|
with:
|
||||||
tagName: ${{ github.ref_name }}
|
tagName: ${{ github.ref_name }}
|
||||||
releaseName: "Donut Browser ${{ github.ref_name }}"
|
releaseName: "Donut Browser ${{ github.ref_name }}"
|
||||||
|
|||||||
@@ -94,6 +94,8 @@ jobs:
|
|||||||
uses: tauri-apps/tauri-action@v0
|
uses: tauri-apps/tauri-action@v0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
GITHUB_REF_NAME: "nightly-${{ steps.commit.outputs.hash }}"
|
||||||
|
GITHUB_SHA: ${{ github.sha }}
|
||||||
with:
|
with:
|
||||||
tagName: "nightly-${{ steps.commit.outputs.hash }}"
|
tagName: "nightly-${{ steps.commit.outputs.hash }}"
|
||||||
releaseName: "Donut Browser Nightly (Build ${{ steps.commit.outputs.hash }})"
|
releaseName: "Donut Browser Nightly (Build ${{ steps.commit.outputs.hash }})"
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "biome check src/ && tsc --noEmit && next lint",
|
"lint": "biome check src/ && tsc --noEmit && next lint",
|
||||||
"lint:rust": "cd src-tauri && cargo clippy --all-targets --all-features -- -D warnings -D clippy::all",
|
"lint:rust": "cd src-tauri && cargo clippy --all-targets --all-features -- -D warnings -D clippy::all && cargo fmt --all",
|
||||||
"tauri": "tauri",
|
"tauri": "tauri",
|
||||||
"shadcn:add": "pnpm dlx shadcn@latest add",
|
"shadcn:add": "pnpm dlx shadcn@latest add",
|
||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
|
|||||||
@@ -5,5 +5,23 @@ fn main() {
|
|||||||
println!("cargo:rustc-link-lib=framework=CoreServices");
|
println!("cargo:rustc-link-lib=framework=CoreServices");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Inject build version based on environment variables set by CI
|
||||||
|
if let Ok(tag_name) = std::env::var("GITHUB_REF_NAME") {
|
||||||
|
// This is set by GitHub Actions to the tag name (e.g., "v1.0.0" or "nightly-abc123")
|
||||||
|
println!("cargo:rustc-env=BUILD_VERSION={tag_name}");
|
||||||
|
} else if std::env::var("STABLE_RELEASE").is_ok() {
|
||||||
|
// Fallback for stable releases - use CARGO_PKG_VERSION with 'v' prefix
|
||||||
|
let version = std::env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "0.1.0".to_string());
|
||||||
|
println!("cargo:rustc-env=BUILD_VERSION=v{version}");
|
||||||
|
} else if let Ok(commit_hash) = std::env::var("GITHUB_SHA") {
|
||||||
|
// For nightly builds, use commit hash
|
||||||
|
let short_hash = &commit_hash[0..7.min(commit_hash.len())];
|
||||||
|
println!("cargo:rustc-env=BUILD_VERSION=nightly-{short_hash}");
|
||||||
|
} else {
|
||||||
|
// Development build fallback
|
||||||
|
let version = std::env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "0.1.0".to_string());
|
||||||
|
println!("cargo:rustc-env=BUILD_VERSION=dev-{version}");
|
||||||
|
}
|
||||||
|
|
||||||
tauri_build::build()
|
tauri_build::build()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,9 +51,10 @@ impl AppAutoUpdater {
|
|||||||
option_env!("STABLE_RELEASE").is_none()
|
option_env!("STABLE_RELEASE").is_none()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get current app version from Cargo.toml
|
/// Get current app version from build-time injection
|
||||||
pub fn get_current_version() -> String {
|
pub fn get_current_version() -> String {
|
||||||
env!("CARGO_PKG_VERSION").to_string()
|
// Use build-time injected version instead of CARGO_PKG_VERSION
|
||||||
|
env!("BUILD_VERSION").to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check for app updates
|
/// Check for app updates
|
||||||
@@ -63,37 +64,51 @@ impl AppAutoUpdater {
|
|||||||
let current_version = Self::get_current_version();
|
let current_version = Self::get_current_version();
|
||||||
let is_nightly = Self::is_nightly_build();
|
let is_nightly = Self::is_nightly_build();
|
||||||
|
|
||||||
println!("Checking for updates - Current version: {current_version}, Is nightly: {is_nightly}");
|
println!("=== App Update Check ===");
|
||||||
|
println!("Current version: {current_version}");
|
||||||
|
println!("Is nightly build: {is_nightly}");
|
||||||
|
println!("STABLE_RELEASE env: {:?}", option_env!("STABLE_RELEASE"));
|
||||||
|
|
||||||
let releases = self.fetch_app_releases().await?;
|
let releases = self.fetch_app_releases().await?;
|
||||||
|
println!("Fetched {} releases from GitHub", releases.len());
|
||||||
|
|
||||||
// Filter releases based on build type
|
// Filter releases based on build type
|
||||||
let filtered_releases: Vec<&AppRelease> = if is_nightly {
|
let filtered_releases: Vec<&AppRelease> = if is_nightly {
|
||||||
// For nightly builds, look for nightly releases
|
// For nightly builds, look for nightly releases
|
||||||
releases
|
let nightly_releases: Vec<&AppRelease> = releases
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|release| release.tag_name.starts_with("nightly-"))
|
.filter(|release| release.tag_name.starts_with("nightly-"))
|
||||||
.collect()
|
.collect();
|
||||||
|
println!("Found {} nightly releases", nightly_releases.len());
|
||||||
|
nightly_releases
|
||||||
} else {
|
} else {
|
||||||
// For stable builds, look for stable releases (semver format)
|
// For stable builds, look for stable releases (semver format)
|
||||||
releases
|
let stable_releases: Vec<&AppRelease> = releases
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|release| {
|
.filter(|release| {
|
||||||
release.tag_name.starts_with('v') && !release.tag_name.starts_with("nightly-")
|
release.tag_name.starts_with('v') && !release.tag_name.starts_with("nightly-")
|
||||||
})
|
})
|
||||||
.collect()
|
.collect();
|
||||||
|
println!("Found {} stable releases", stable_releases.len());
|
||||||
|
stable_releases
|
||||||
};
|
};
|
||||||
|
|
||||||
if filtered_releases.is_empty() {
|
if filtered_releases.is_empty() {
|
||||||
println!("No releases found for build type");
|
println!("No releases found for build type (nightly: {is_nightly})");
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the latest release
|
// Get the latest release
|
||||||
let latest_release = filtered_releases[0];
|
let latest_release = filtered_releases[0];
|
||||||
|
println!(
|
||||||
|
"Latest release: {} ({})",
|
||||||
|
latest_release.tag_name, latest_release.name
|
||||||
|
);
|
||||||
|
|
||||||
// Check if we need to update
|
// Check if we need to update
|
||||||
if self.should_update(¤t_version, &latest_release.tag_name, is_nightly) {
|
if self.should_update(¤t_version, &latest_release.tag_name, is_nightly) {
|
||||||
|
println!("Update available!");
|
||||||
|
|
||||||
// Find the appropriate asset for current platform
|
// Find the appropriate asset for current platform
|
||||||
if let Some(download_url) = self.get_download_url_for_platform(&latest_release.assets) {
|
if let Some(download_url) = self.get_download_url_for_platform(&latest_release.assets) {
|
||||||
let update_info = AppUpdateInfo {
|
let update_info = AppUpdateInfo {
|
||||||
@@ -105,8 +120,16 @@ impl AppAutoUpdater {
|
|||||||
published_at: latest_release.published_at.clone(),
|
published_at: latest_release.published_at.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Update info prepared: {} -> {}",
|
||||||
|
update_info.current_version, update_info.new_version
|
||||||
|
);
|
||||||
return Ok(Some(update_info));
|
return Ok(Some(update_info));
|
||||||
|
} else {
|
||||||
|
println!("No suitable download asset found for current platform");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
println!("No update needed");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
@@ -134,21 +157,36 @@ impl AppAutoUpdater {
|
|||||||
|
|
||||||
/// Determine if an update should be performed
|
/// Determine if an update should be performed
|
||||||
fn should_update(&self, current_version: &str, new_version: &str, is_nightly: bool) -> bool {
|
fn should_update(&self, current_version: &str, new_version: &str, is_nightly: bool) -> bool {
|
||||||
|
println!(
|
||||||
|
"Comparing versions: current={current_version}, new={new_version}, is_nightly={is_nightly}"
|
||||||
|
);
|
||||||
|
|
||||||
if is_nightly {
|
if is_nightly {
|
||||||
// For nightly builds, always update if there's a newer nightly
|
// For nightly builds, always update if there's a newer nightly
|
||||||
// Compare the commit hashes (assuming format: nightly-<commit_hash>)
|
|
||||||
if let (Some(current_hash), Some(new_hash)) = (
|
if let (Some(current_hash), Some(new_hash)) = (
|
||||||
current_version.strip_prefix("nightly-"),
|
current_version.strip_prefix("nightly-"),
|
||||||
new_version.strip_prefix("nightly-"),
|
new_version.strip_prefix("nightly-"),
|
||||||
) {
|
) {
|
||||||
return new_hash != current_hash;
|
// Different commit hashes mean we should update
|
||||||
|
let should_update = new_hash != current_hash;
|
||||||
|
println!("Nightly comparison: current_hash={current_hash}, new_hash={new_hash}, should_update={should_update}");
|
||||||
|
return should_update;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If current version doesn't have nightly prefix but we're in nightly mode,
|
||||||
|
// this could be a dev build or stable build upgrading to nightly
|
||||||
|
if !current_version.starts_with("nightly-") {
|
||||||
|
println!("Upgrading from non-nightly to nightly: {new_version}");
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
// If current version doesn't have nightly prefix, it's an upgrade from stable to nightly
|
|
||||||
!current_version.starts_with("nightly-")
|
|
||||||
} else {
|
} else {
|
||||||
// For stable builds, use semantic versioning comparison
|
// For stable builds, use semantic versioning comparison
|
||||||
self.is_version_newer(new_version, current_version)
|
let should_update = self.is_version_newer(new_version, current_version);
|
||||||
|
println!("Stable comparison: {new_version} > {current_version} = {should_update}");
|
||||||
|
return should_update;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compare semantic versions (returns true if version1 > version2)
|
/// Compare semantic versions (returns true if version1 > version2)
|
||||||
@@ -174,24 +212,68 @@ impl AppAutoUpdater {
|
|||||||
fn get_download_url_for_platform(&self, assets: &[AppReleaseAsset]) -> Option<String> {
|
fn get_download_url_for_platform(&self, assets: &[AppReleaseAsset]) -> Option<String> {
|
||||||
let arch = if cfg!(target_arch = "aarch64") {
|
let arch = if cfg!(target_arch = "aarch64") {
|
||||||
"aarch64"
|
"aarch64"
|
||||||
} else {
|
} else if cfg!(target_arch = "x86_64") {
|
||||||
"x64"
|
"x64"
|
||||||
|
} else {
|
||||||
|
"unknown"
|
||||||
};
|
};
|
||||||
|
|
||||||
// Look for macOS DMG with the appropriate architecture
|
println!("Looking for assets with architecture: {arch}");
|
||||||
for asset in assets {
|
for asset in assets {
|
||||||
if asset.name.contains(".dmg") && asset.name.contains(arch) {
|
println!("Found asset: {}", asset.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priority 1: Look for exact architecture match in DMG
|
||||||
|
for asset in assets {
|
||||||
|
if asset.name.contains(".dmg")
|
||||||
|
&& (asset.name.contains(&format!("_{arch}.dmg"))
|
||||||
|
|| asset.name.contains(&format!("-{arch}.dmg"))
|
||||||
|
|| asset.name.contains(&format!("_{arch}_"))
|
||||||
|
|| asset.name.contains(&format!("-{arch}-")))
|
||||||
|
{
|
||||||
|
println!("Found exact architecture match: {}", asset.name);
|
||||||
return Some(asset.browser_download_url.clone());
|
return Some(asset.browser_download_url.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: look for any macOS DMG
|
// Priority 2: Look for x86_64 variations if we're looking for x64
|
||||||
|
if arch == "x64" {
|
||||||
|
for asset in assets {
|
||||||
|
if asset.name.contains(".dmg")
|
||||||
|
&& (asset.name.contains("x86_64") || asset.name.contains("x86-64"))
|
||||||
|
{
|
||||||
|
println!("Found x86_64 variant: {}", asset.name);
|
||||||
|
return Some(asset.browser_download_url.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priority 3: Look for arm64 variations if we're looking for aarch64
|
||||||
|
if arch == "aarch64" {
|
||||||
|
for asset in assets {
|
||||||
|
if asset.name.contains(".dmg")
|
||||||
|
&& (asset.name.contains("arm64") || asset.name.contains("aarch64"))
|
||||||
|
{
|
||||||
|
println!("Found arm64 variant: {}", asset.name);
|
||||||
|
return Some(asset.browser_download_url.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priority 4: Fallback to any macOS DMG
|
||||||
for asset in assets {
|
for asset in assets {
|
||||||
if asset.name.contains(".dmg") {
|
if asset.name.contains(".dmg")
|
||||||
|
&& (asset.name.to_lowercase().contains("macos")
|
||||||
|
|| asset.name.to_lowercase().contains("darwin")
|
||||||
|
|| !asset.name.contains(".app.tar.gz"))
|
||||||
|
{
|
||||||
|
// Exclude app.tar.gz files
|
||||||
|
println!("Found fallback DMG: {}", asset.name);
|
||||||
return Some(asset.browser_download_url.clone());
|
return Some(asset.browser_download_url.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println!("No suitable asset found for platform");
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,14 +487,59 @@ impl AppAutoUpdater {
|
|||||||
/// Restart the application
|
/// Restart the application
|
||||||
async fn restart_application(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn restart_application(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let app_path = self.get_current_app_path()?;
|
let app_path = self.get_current_app_path()?;
|
||||||
|
let current_pid = std::process::id();
|
||||||
|
|
||||||
// Use open command to restart the app
|
// Create a temporary restart script
|
||||||
let _ = Command::new("open")
|
let temp_dir = std::env::temp_dir();
|
||||||
.args([app_path.to_str().unwrap()])
|
let script_path = temp_dir.join("donut_restart.sh");
|
||||||
.spawn()?;
|
|
||||||
|
|
||||||
// Exit current process after a short delay
|
// Create the restart script content
|
||||||
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
let script_content = format!(
|
||||||
|
r#"#!/bin/bash
|
||||||
|
# Wait for the current process to exit
|
||||||
|
while kill -0 {} 2>/dev/null; do
|
||||||
|
sleep 0.5
|
||||||
|
done
|
||||||
|
|
||||||
|
# Wait a bit more to ensure clean exit
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# Start the new application
|
||||||
|
open "{}"
|
||||||
|
|
||||||
|
# Clean up this script
|
||||||
|
rm "{}"
|
||||||
|
"#,
|
||||||
|
current_pid,
|
||||||
|
app_path.to_str().unwrap(),
|
||||||
|
script_path.to_str().unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Write the script to file
|
||||||
|
fs::write(&script_path, script_content)?;
|
||||||
|
|
||||||
|
// Make the script executable
|
||||||
|
let _ = Command::new("chmod")
|
||||||
|
.args(["+x", script_path.to_str().unwrap()])
|
||||||
|
.output();
|
||||||
|
|
||||||
|
// Execute the restart script in the background
|
||||||
|
let mut cmd = Command::new("bash");
|
||||||
|
cmd.arg(script_path.to_str().unwrap());
|
||||||
|
|
||||||
|
// Detach the process completely
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
use std::os::unix::process::CommandExt;
|
||||||
|
cmd.process_group(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let _child = cmd.spawn()?;
|
||||||
|
|
||||||
|
// Give the script a moment to start
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
|
||||||
|
|
||||||
|
// Exit the current process
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -448,6 +575,16 @@ pub fn get_app_version_info() -> Result<(String, bool), String> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn check_for_app_updates_manual() -> Result<Option<AppUpdateInfo>, String> {
|
||||||
|
println!("Manual app update check triggered");
|
||||||
|
let updater = AppAutoUpdater::new();
|
||||||
|
updater
|
||||||
|
.check_for_updates()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to check for app updates: {e}"))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -502,6 +639,27 @@ mod tests {
|
|||||||
|
|
||||||
// Upgrade from stable to nightly
|
// Upgrade from stable to nightly
|
||||||
assert!(updater.should_update("v1.0.0", "nightly-abc123", true));
|
assert!(updater.should_update("v1.0.0", "nightly-abc123", true));
|
||||||
|
|
||||||
|
// Upgrade from dev to nightly
|
||||||
|
assert!(updater.should_update("dev-0.1.0", "nightly-abc123", true));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_should_update_edge_cases() {
|
||||||
|
let updater = AppAutoUpdater::new();
|
||||||
|
|
||||||
|
// Test with different nightly formats
|
||||||
|
assert!(updater.should_update("nightly-abc123", "nightly-def456", true));
|
||||||
|
assert!(!updater.should_update("nightly-abc123", "nightly-abc123", true));
|
||||||
|
|
||||||
|
// Test stable version edge cases
|
||||||
|
assert!(updater.should_update("v0.9.9", "v1.0.0", false));
|
||||||
|
assert!(!updater.should_update("v1.0.0", "v0.9.9", false));
|
||||||
|
assert!(!updater.should_update("v1.0.0", "v1.0.0", false));
|
||||||
|
|
||||||
|
// Test version without 'v' prefix
|
||||||
|
assert!(updater.should_update("0.9.9", "v1.0.0", false));
|
||||||
|
assert!(updater.should_update("v0.9.9", "1.0.0", false));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -54,7 +54,8 @@ use auto_updater::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use app_auto_updater::{
|
use app_auto_updater::{
|
||||||
check_for_app_updates, download_and_install_app_update, get_app_version_info,
|
check_for_app_updates, check_for_app_updates_manual, download_and_install_app_update,
|
||||||
|
get_app_version_info,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -183,6 +184,7 @@ pub fn run() {
|
|||||||
// Add a small delay to ensure the app is fully loaded
|
// Add a small delay to ensure the app is fully loaded
|
||||||
tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
|
tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
|
||||||
|
|
||||||
|
println!("Starting app update check at startup...");
|
||||||
let updater = app_auto_updater::AppAutoUpdater::new();
|
let updater = app_auto_updater::AppAutoUpdater::new();
|
||||||
match updater.check_for_updates().await {
|
match updater.check_for_updates().await {
|
||||||
Ok(Some(update_info)) => {
|
Ok(Some(update_info)) => {
|
||||||
@@ -191,7 +193,11 @@ pub fn run() {
|
|||||||
update_info.current_version, update_info.new_version
|
update_info.current_version, update_info.new_version
|
||||||
);
|
);
|
||||||
// Emit update available event to the frontend
|
// Emit update available event to the frontend
|
||||||
let _ = app_handle_update.emit("app-update-available", &update_info);
|
if let Err(e) = app_handle_update.emit("app-update-available", &update_info) {
|
||||||
|
eprintln!("Failed to emit app update event: {e}");
|
||||||
|
} else {
|
||||||
|
println!("App update event emitted successfully");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
println!("No app updates available");
|
println!("No app updates available");
|
||||||
@@ -255,6 +261,7 @@ pub fn run() {
|
|||||||
remove_auto_update_download,
|
remove_auto_update_download,
|
||||||
is_auto_update_download,
|
is_auto_update_download,
|
||||||
check_for_app_updates,
|
check_for_app_updates,
|
||||||
|
check_for_app_updates_manual,
|
||||||
download_and_install_app_update,
|
download_and_install_app_update,
|
||||||
get_app_version_info,
|
get_app_version_info,
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ export default function Home() {
|
|||||||
|
|
||||||
// App auto-update functionality
|
// App auto-update functionality
|
||||||
const appUpdateNotifications = useAppUpdateNotifications();
|
const appUpdateNotifications = useAppUpdateNotifications();
|
||||||
|
const { checkForAppUpdatesManual } = appUpdateNotifications;
|
||||||
|
|
||||||
// Ensure we're on the client side to prevent hydration mismatches
|
// Ensure we're on the client side to prevent hydration mismatches
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export function useAppUpdateNotifications() {
|
|||||||
const [isUpdating, setIsUpdating] = useState(false);
|
const [isUpdating, setIsUpdating] = useState(false);
|
||||||
const [updateProgress, setUpdateProgress] = useState<string>("");
|
const [updateProgress, setUpdateProgress] = useState<string>("");
|
||||||
const [isClient, setIsClient] = useState(false);
|
const [isClient, setIsClient] = useState(false);
|
||||||
|
const [dismissedVersion, setDismissedVersion] = useState<string | null>(null);
|
||||||
|
|
||||||
// Ensure we're on the client side to prevent hydration mismatches
|
// Ensure we're on the client side to prevent hydration mismatches
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -26,10 +27,33 @@ export function useAppUpdateNotifications() {
|
|||||||
const update = await invoke<AppUpdateInfo | null>(
|
const update = await invoke<AppUpdateInfo | null>(
|
||||||
"check_for_app_updates",
|
"check_for_app_updates",
|
||||||
);
|
);
|
||||||
setUpdateInfo(update);
|
|
||||||
|
// Don't show update if this version was already dismissed
|
||||||
|
if (update && update.new_version !== dismissedVersion) {
|
||||||
|
setUpdateInfo(update);
|
||||||
|
} else if (update) {
|
||||||
|
console.log("Update available but dismissed:", update.new_version);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to check for app updates:", error);
|
console.error("Failed to check for app updates:", error);
|
||||||
}
|
}
|
||||||
|
}, [isClient, dismissedVersion]);
|
||||||
|
|
||||||
|
const checkForAppUpdatesManual = useCallback(async () => {
|
||||||
|
if (!isClient) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log("Triggering manual app update check...");
|
||||||
|
const update = await invoke<AppUpdateInfo | null>(
|
||||||
|
"check_for_app_updates_manual",
|
||||||
|
);
|
||||||
|
console.log("Manual check result:", update);
|
||||||
|
|
||||||
|
// Always show manual check results, even if previously dismissed
|
||||||
|
setUpdateInfo(update);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to manually check for app updates:", error);
|
||||||
|
}
|
||||||
}, [isClient]);
|
}, [isClient]);
|
||||||
|
|
||||||
const handleAppUpdate = useCallback(async (appUpdateInfo: AppUpdateInfo) => {
|
const handleAppUpdate = useCallback(async (appUpdateInfo: AppUpdateInfo) => {
|
||||||
@@ -56,9 +80,15 @@ export function useAppUpdateNotifications() {
|
|||||||
const dismissAppUpdate = useCallback(() => {
|
const dismissAppUpdate = useCallback(() => {
|
||||||
if (!isClient) return;
|
if (!isClient) return;
|
||||||
|
|
||||||
|
// Remember the dismissed version so we don't show it again
|
||||||
|
if (updateInfo) {
|
||||||
|
setDismissedVersion(updateInfo.new_version);
|
||||||
|
console.log("Dismissed app update version:", updateInfo.new_version);
|
||||||
|
}
|
||||||
|
|
||||||
setUpdateInfo(null);
|
setUpdateInfo(null);
|
||||||
toast.dismiss("app-update");
|
toast.dismiss("app-update");
|
||||||
}, [isClient]);
|
}, [isClient, updateInfo]);
|
||||||
|
|
||||||
// Listen for app update availability
|
// Listen for app update availability
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -116,10 +146,19 @@ export function useAppUpdateNotifications() {
|
|||||||
isClient,
|
isClient,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Check for app updates on startup
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isClient) return;
|
||||||
|
|
||||||
|
// Check for updates immediately on startup
|
||||||
|
void checkForAppUpdates();
|
||||||
|
}, [isClient, checkForAppUpdates]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
updateInfo,
|
updateInfo,
|
||||||
isUpdating,
|
isUpdating,
|
||||||
checkForAppUpdates,
|
checkForAppUpdates,
|
||||||
|
checkForAppUpdatesManual,
|
||||||
dismissAppUpdate,
|
dismissAppUpdate,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user