6 Commits

Author SHA1 Message Date
Gitea Actions
81407b51ee chore: bump version to 0.2.46 [skip ci] 2026-03-24 02:04:26 +00:00
Claude
a3a45cb308 Gate set_executable_permissions call with #[cfg(unix)]
All checks were successful
Release / Bump version and tag (push) Successful in 3s
Release / Build App (macOS) (push) Successful in 1m18s
Release / Build App (Windows) (push) Successful in 3m11s
Release / Build App (Linux) (push) Successful in 3m31s
The method is cfg(unix) but the call site wasn't gated, causing a
compile error on Windows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 19:04:19 -07:00
Gitea Actions
e0e1638327 chore: bump version to 0.2.45 [skip ci] 2026-03-23 20:53:55 +00:00
Claude
c4fffad027 Fix permissions on demand instead of every launch
Some checks failed
Release / Bump version and tag (push) Successful in 3s
Release / Build App (macOS) (push) Successful in 1m17s
Release / Build App (Windows) (push) Failing after 1m56s
Release / Build App (Linux) (push) Successful in 3m39s
Instead of chmod on every app start, catch EACCES (error 13) when
spawning sidecar or ffmpeg, fix permissions, then retry once:
- sidecar spawn: catches permission denied, runs set_executable_permissions
  on the sidecar dir, retries spawn
- ffmpeg: catches permission denied, chmod +x ffmpeg and ffprobe, retries

Zero overhead on normal launches. Only fixes permissions when actually needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 13:53:47 -07:00
Gitea Actions
618edf65ab chore: bump version to 0.2.44 [skip ci] 2026-03-23 20:45:32 +00:00
Claude
c5b8eb06c6 Fix permissions on already-extracted sidecar dirs
All checks were successful
Release / Bump version and tag (push) Successful in 3s
Release / Build App (macOS) (push) Successful in 1m20s
Release / Build App (Windows) (push) Successful in 2m59s
Release / Build App (Linux) (push) Successful in 3m35s
The chmod fix only ran after fresh extraction, but existing sidecar
dirs extracted by older versions still lacked execute permissions.
Now set_executable_permissions() runs on EVERY app launch (both the
early-return path for existing dirs and after fresh extraction).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 13:45:26 -07:00
5 changed files with 87 additions and 28 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "voice-to-notes", "name": "voice-to-notes",
"version": "0.2.43", "version": "0.2.46",
"description": "Desktop app for transcribing audio/video with speaker identification", "description": "Desktop app for transcribing audio/video with speaker identification",
"type": "module", "type": "module",
"scripts": { "scripts": {

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "voice-to-notes" name = "voice-to-notes"
version = "0.2.43" version = "0.2.46"
description = "Voice to Notes — desktop transcription with speaker identification" description = "Voice to Notes — desktop transcription with speaker identification"
authors = ["Voice to Notes Contributors"] authors = ["Voice to Notes Contributors"]
license = "MIT" license = "MIT"

View File

@@ -50,9 +50,37 @@ pub fn extract_audio(file_path: String, output_path: Option<String>) -> Result<S
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
cmd.creation_flags(0x08000000); cmd.creation_flags(0x08000000);
let status = cmd let status = match cmd.status() {
Ok(s) => s,
Err(e) if e.raw_os_error() == Some(13) => {
// Permission denied — fix permissions and retry
eprintln!("[media] Permission denied on ffmpeg, fixing permissions and retrying...");
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
if let Ok(meta) = std::fs::metadata(&ffmpeg) {
let mut perms = meta.permissions();
perms.set_mode(0o755);
let _ = std::fs::set_permissions(&ffmpeg, perms);
}
// Also fix ffprobe if it exists
let ffprobe = ffmpeg.replace("ffmpeg", "ffprobe");
if let Ok(meta) = std::fs::metadata(&ffprobe) {
let mut perms = meta.permissions();
perms.set_mode(0o755);
let _ = std::fs::set_permissions(&ffprobe, perms);
}
}
Command::new(&ffmpeg)
.args(["-y", "-i", &file_path, "-vn", "-acodec", "pcm_s16le", "-ar", "22050", "-ac", "1"])
.arg(output.to_str().unwrap())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::piped())
.status() .status()
.map_err(|e| format!("Failed to run ffmpeg: {e}"))?; .map_err(|e| format!("Failed to run ffmpeg after chmod: {e}"))?
}
Err(e) => return Err(format!("Failed to run ffmpeg: {e}")),
};
if !status.success() { if !status.success() {
return Err(format!("ffmpeg exited with status {status}")); return Err(format!("ffmpeg exited with status {status}"));

View File

@@ -113,23 +113,8 @@ impl SidecarManager {
)); ));
} }
// Make all binaries executable on Unix (sidecar, ffmpeg, ffprobe, etc.)
#[cfg(unix)] #[cfg(unix)]
{ Self::set_executable_permissions(&extract_dir);
use std::os::unix::fs::PermissionsExt;
if let Ok(entries) = std::fs::read_dir(&extract_dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_file() {
if let Ok(meta) = std::fs::metadata(&path) {
let mut perms = meta.permissions();
perms.set_mode(0o755);
let _ = std::fs::set_permissions(&path, perms);
}
}
}
}
}
Self::cleanup_old_sidecars(data_dir, &current_version); Self::cleanup_old_sidecars(data_dir, &current_version);
Ok(binary_path) Ok(binary_path)
@@ -214,6 +199,24 @@ impl SidecarManager {
/// Remove old sidecar-* directories that don't match the current version. /// Remove old sidecar-* directories that don't match the current version.
/// Called after the current version's sidecar is confirmed ready. /// Called after the current version's sidecar is confirmed ready.
/// Set execute permissions on all files in a directory (Unix only).
#[cfg(unix)]
fn set_executable_permissions(dir: &Path) {
use std::os::unix::fs::PermissionsExt;
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_file() {
if let Ok(meta) = std::fs::metadata(&path) {
let mut perms = meta.permissions();
perms.set_mode(0o755);
let _ = std::fs::set_permissions(&path, perms);
}
}
}
}
}
pub(crate) fn cleanup_old_sidecars(data_dir: &Path, current_version: &str) { pub(crate) fn cleanup_old_sidecars(data_dir: &Path, current_version: &str) {
let current_dir_name = format!("sidecar-{}", current_version); let current_dir_name = format!("sidecar-{}", current_version);
@@ -328,13 +331,41 @@ impl SidecarManager {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
cmd.creation_flags(0x08000000); cmd.creation_flags(0x08000000);
let child = cmd match cmd.spawn() {
.spawn() Ok(child) => {
.map_err(|e| format!("Failed to start sidecar binary: {e}"))?;
self.attach(child)?; self.attach(child)?;
self.wait_for_ready() self.wait_for_ready()
} }
Err(e) if e.raw_os_error() == Some(13) => {
// Permission denied — fix permissions and retry once
eprintln!("[sidecar-rs] Permission denied, fixing permissions and retrying...");
#[cfg(unix)]
if let Some(dir) = path.parent() {
Self::set_executable_permissions(dir);
}
let mut retry_cmd = Command::new(path);
retry_cmd
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(if let Some(data_dir) = DATA_DIR.get() {
let log_path = data_dir.join("sidecar.log");
std::fs::File::create(&log_path)
.map(Stdio::from)
.unwrap_or_else(|_| Stdio::inherit())
} else {
Stdio::inherit()
});
#[cfg(target_os = "windows")]
retry_cmd.creation_flags(0x08000000);
let child = retry_cmd
.spawn()
.map_err(|e| format!("Failed to start sidecar binary after chmod: {e}"))?;
self.attach(child)?;
self.wait_for_ready()
}
Err(e) => Err(format!("Failed to start sidecar binary: {e}")),
}
}
/// Spawn the Python sidecar in dev mode (system Python). /// Spawn the Python sidecar in dev mode (system Python).
fn start_python_dev(&self) -> Result<(), String> { fn start_python_dev(&self) -> Result<(), String> {

View File

@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "Voice to Notes", "productName": "Voice to Notes",
"version": "0.2.43", "version": "0.2.46",
"identifier": "com.voicetonotes.app", "identifier": "com.voicetonotes.app",
"build": { "build": {
"beforeDevCommand": "npm run dev", "beforeDevCommand": "npm run dev",