Compare commits

...
Author SHA1 Message Date
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 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.2",
"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.2"
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.2"
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.2</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.2",
"identifier": "com.donutbrowser", "identifier": "com.donutbrowser",
"build": { "build": {
"beforeDevCommand": "pnpm dev", "beforeDevCommand": "pnpm dev",