Compare commits

...
Author SHA1 Message Date
zhom 141a5f06a4 docs: add a note that the app has automatic updates 2025-05-31 12:57:01 +04:00
zhom 7a3857c06a chore: version bump test 2025-05-31 12:53:50 +04:00
zhom ed26786fdb chore: cargo fmt 2025-05-31 12:38:14 +04:00
zhom 966268ff05 chore: version bump 2025-05-31 12:37:29 +04:00
zhom 87ae696d7a refactor: make app_auto_updater use shared extraction logic 2025-05-31 12:31:16 +04:00
zhom 7e92b290b6 chore: update description and readme 2025-05-31 12:14:54 +04:00
8 changed files with 47 additions and 68 deletions
+2
View File
@@ -27,6 +27,8 @@
## Download ## Download
> As of right now, the app is not signed by Apple. You need to have Gatekeeper disabled to run it. The app automatically checks for updates on each launch.
The app can be downloaded from the [releases page](https://github.com/zhom/donutbrowser/releases/latest). The app can be downloaded from the [releases page](https://github.com/zhom/donutbrowser/releases/latest).
## Supported Platforms ## Supported Platforms
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "donutbrowser", "name": "donutbrowser",
"private": true, "private": true,
"version": "0.2.1", "version": "0.2.3",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "next dev --turbopack", "dev": "next dev --turbopack",
+1 -1
View File
@@ -973,7 +973,7 @@ dependencies = [
[[package]] [[package]]
name = "donutbrowser" name = "donutbrowser"
version = "0.2.1" version = "0.2.3"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"base64 0.22.1", "base64 0.22.1",
+2 -2
View File
@@ -1,7 +1,7 @@
[package] [package]
name = "donutbrowser" name = "donutbrowser"
version = "0.2.1" version = "0.2.3"
description = "A Tauri App" description = "Browser Orchestrator"
authors = ["zhom@github"] authors = ["zhom@github"]
edition = "2021" edition = "2021"
+1 -1
View File
@@ -13,7 +13,7 @@
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1</string> <string>1</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>0.2.1</string> <string>0.2.3</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleIconFile</key> <key>CFBundleIconFile</key>
+37 -60
View File
@@ -6,6 +6,8 @@ use std::path::{Path, PathBuf};
use std::process::Command; use std::process::Command;
use tauri::Emitter; use tauri::Emitter;
use crate::extraction::Extractor;
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
pub struct AppReleaseAsset { pub struct AppReleaseAsset {
pub name: String, pub name: String,
@@ -370,73 +372,24 @@ impl AppAutoUpdater {
Ok(file_path) Ok(file_path)
} }
/// Extract the update (DMG on macOS) /// Extract the update using the extraction module
async fn extract_update( async fn extract_update(
&self, &self,
dmg_path: &Path, archive_path: &Path,
dest_dir: &Path, dest_dir: &Path,
) -> Result<PathBuf, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<PathBuf, Box<dyn std::error::Error + Send + Sync>> {
// For DMG files on macOS, we need to mount and copy the .app let extractor = Extractor::new();
let mount_point = dest_dir.join("mount");
fs::create_dir_all(&mount_point)?;
// Mount the DMG let extension = archive_path
let output = Command::new("hdiutil") .extension()
.args([ .and_then(|ext| ext.to_str())
"attach", .unwrap_or("");
"-nobrowse",
"-mountpoint",
mount_point.to_str().unwrap(),
dmg_path.to_str().unwrap(),
])
.output()?;
if !output.status.success() { match extension {
return Err( "dmg" => extractor.extract_dmg(archive_path, dest_dir).await,
format!( "zip" => extractor.extract_zip(archive_path, dest_dir).await,
"Failed to mount DMG: {}", _ => Err(format!("Unsupported archive format: {extension}").into()),
String::from_utf8_lossy(&output.stderr)
)
.into(),
);
} }
// Find the .app in the mount point
let app_entry = fs::read_dir(&mount_point)?
.filter_map(Result::ok)
.find(|entry| entry.path().extension().is_some_and(|ext| ext == "app"))
.ok_or("No .app found in DMG")?;
let app_path = dest_dir.join("extracted_app");
if app_path.exists() {
fs::remove_dir_all(&app_path)?;
}
// Copy the .app to extraction directory
let output = Command::new("cp")
.args([
"-R",
app_entry.path().to_str().unwrap(),
app_path.to_str().unwrap(),
])
.output()?;
if !output.status.success() {
return Err(
format!(
"Failed to copy app: {}",
String::from_utf8_lossy(&output.stderr)
)
.into(),
);
}
// Unmount the DMG
let _ = Command::new("hdiutil")
.args(["detach", mount_point.to_str().unwrap()])
.output();
Ok(app_path)
} }
/// Install the update by replacing the current app /// Install the update by replacing the current app
@@ -701,4 +654,28 @@ mod tests {
let url = url.unwrap(); let url = url.unwrap();
assert!(url.contains(".dmg")); assert!(url.contains(".dmg"));
} }
#[test]
fn test_extract_update_uses_extractor() {
// This test verifies that the extract_update method properly uses the Extractor
// We can't run the actual extraction in unit tests without real DMG files,
// but we can verify the method signature and basic logic
let updater = AppAutoUpdater::new();
// Test that unsupported formats would be rejected
let temp_dir = std::env::temp_dir();
let unsupported_file = temp_dir.join("test.rar");
// Create a mock runtime to test the logic
let rt = tokio::runtime::Runtime::new().unwrap();
// This would fail because .rar is not supported, which proves
// our method is using the Extractor logic
let result = rt.block_on(async { updater.extract_update(&unsupported_file, &temp_dir).await });
// Should fail with unsupported format error
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Unsupported archive format: rar"));
}
} }
+2 -2
View File
@@ -46,7 +46,7 @@ impl Extractor {
} }
} }
async fn extract_dmg( pub async fn extract_dmg(
&self, &self,
dmg_path: &Path, dmg_path: &Path,
dest_dir: &Path, dest_dir: &Path,
@@ -149,7 +149,7 @@ impl Extractor {
Ok(app_path) Ok(app_path)
} }
async fn extract_zip( pub async fn extract_zip(
&self, &self,
zip_path: &Path, zip_path: &Path,
dest_dir: &Path, dest_dir: &Path,
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "Donut Browser", "productName": "Donut Browser",
"version": "0.2.1", "version": "0.2.3",
"identifier": "com.donutbrowser", "identifier": "com.donutbrowser",
"build": { "build": {
"beforeDevCommand": "pnpm dev", "beforeDevCommand": "pnpm dev",