Skip to content

Commit bfc972a

Browse files
Resolve ia CLI path for GUI launches (fixes 'ia not installed' on macOS)
GUI apps launched from Finder/Dock don't inherit the shell PATH, so a bare Command::new("ia") failed to find ia when installed under ~/.local/bin or a Homebrew prefix. Resolve ia's absolute path via the login shell, falling back to common install locations.
1 parent a830525 commit bfc972a

4 files changed

Lines changed: 72 additions & 17 deletions

File tree

src-tauri/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "archive-ui"
3-
version = "0.1.0"
3+
version = "0.1.1"
44
edition = "2021"
55
license = "GPL-2.0-only"
66

src-tauri/src/commands.rs

Lines changed: 69 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::fs;
33
use std::io::{BufRead, BufReader, Read};
44
use std::path::{Path, PathBuf};
55
use std::process::{Command, Stdio};
6-
use std::sync::Mutex;
6+
use std::sync::{Mutex, OnceLock};
77
use tauri::{AppHandle, Emitter, Manager};
88

99
// ── Shared helpers ─────────────────────────────────────────────────────────────
@@ -135,26 +135,82 @@ pub struct UploadMeta {
135135
language: String,
136136
}
137137

138-
/// Confirm the `ia` CLI is installed, returning a friendly error otherwise.
139-
fn ensure_ia() -> Result<(), String> {
140-
Command::new("ia")
138+
/// Locate the `ia` executable. GUI apps launched from Finder/Dock on macOS do
139+
/// not inherit the user's shell `PATH`, so a bare `Command::new("ia")` fails to
140+
/// find `ia` when it lives somewhere like `~/.local/bin` or a Homebrew prefix.
141+
/// Resolve the absolute path once: prefer whatever a login shell reports (it
142+
/// sources the user's profile and full PATH), then fall back to common install
143+
/// locations. Returns `None` only if `ia` genuinely can't be found.
144+
fn ia_bin() -> Option<&'static str> {
145+
static CACHE: OnceLock<Option<String>> = OnceLock::new();
146+
CACHE.get_or_init(resolve_ia).as_deref()
147+
}
148+
149+
fn resolve_ia() -> Option<String> {
150+
// 1. Already on PATH (e.g. `cargo tauri dev` launched from a terminal).
151+
let on_path = Command::new("ia")
141152
.arg("--version")
142153
.stdout(Stdio::null())
143154
.stderr(Stdio::null())
144155
.status()
145-
.map_err(|_| {
146-
"The 'ia' CLI is not installed.\nInstall it with: pip install internetarchive"
147-
.to_string()
148-
})?;
149-
Ok(())
156+
.map(|s| s.success())
157+
.unwrap_or(false);
158+
if on_path {
159+
return Some("ia".to_string());
160+
}
161+
162+
// 2. Ask the user's login shell, which loads their profile and real PATH.
163+
if let Ok(shell) = std::env::var("SHELL") {
164+
if let Ok(out) = Command::new(&shell).args(["-lic", "command -v ia"]).output() {
165+
// Profile scripts may print noise; take the last line that is a
166+
// real, existing path.
167+
let resolved = String::from_utf8_lossy(&out.stdout)
168+
.lines()
169+
.map(str::trim)
170+
.filter(|l| !l.is_empty())
171+
.rfind(|l| Path::new(l).is_file())
172+
.map(str::to_string);
173+
if resolved.is_some() {
174+
return resolved;
175+
}
176+
}
177+
}
178+
179+
// 3. Probe common install locations directly.
180+
let home = std::env::var("HOME").unwrap_or_default();
181+
let mut candidates = vec![
182+
format!("{home}/.local/bin/ia"),
183+
"/opt/homebrew/bin/ia".to_string(),
184+
"/usr/local/bin/ia".to_string(),
185+
"/usr/bin/ia".to_string(),
186+
];
187+
// pip `--user` installs land under ~/Library/Python/<ver>/bin on macOS.
188+
if let Ok(entries) = fs::read_dir(format!("{home}/Library/Python")) {
189+
for e in entries.flatten() {
190+
candidates.push(e.path().join("bin").join("ia").to_string_lossy().to_string());
191+
}
192+
}
193+
candidates.into_iter().find(|p| Path::new(p).is_file())
194+
}
195+
196+
/// A `Command` for the resolved `ia` binary, or a friendly error if it's missing.
197+
fn ia_command() -> Result<Command, String> {
198+
let bin = ia_bin().ok_or(
199+
"The 'ia' CLI is not installed.\nInstall it with: pip install internetarchive",
200+
)?;
201+
Ok(Command::new(bin))
202+
}
203+
204+
/// Confirm the `ia` CLI can be found, returning a friendly error otherwise.
205+
fn ensure_ia() -> Result<(), String> {
206+
ia_command().map(|_| ())
150207
}
151208

152209
/// Sign in once per batch: write the archive.org S3 keys via `ia configure`.
153210
#[tauri::command]
154211
pub async fn configure_account(username: String, password: String) -> Result<(), String> {
155212
tauri::async_runtime::spawn_blocking(move || {
156-
ensure_ia()?;
157-
let cfg = Command::new("ia")
213+
let cfg = ia_command()?
158214
.args([
159215
"configure",
160216
&format!("--username={}", username),
@@ -249,12 +305,11 @@ pub struct ItemInfo {
249305
#[tauri::command]
250306
pub async fn inspect_item(identifier: String) -> Result<ItemInfo, String> {
251307
tauri::async_runtime::spawn_blocking(move || {
252-
ensure_ia()?;
253308
let id = identifier.trim();
254309
if id.is_empty() {
255310
return Err("No identifier provided.".to_string());
256311
}
257-
let out = Command::new("ia")
312+
let out = ia_command()?
258313
.args(["metadata", id])
259314
.output()
260315
.map_err(|e| format!("ia metadata failed: {e}"))?;
@@ -376,7 +431,7 @@ fn upload_blocking(app: &AppHandle, meta: &UploadMeta, files: &[String]) -> Resu
376431
args.push("--checksum".into());
377432
args.push("--retries=10".into());
378433

379-
let mut child = Command::new("ia")
434+
let mut child = ia_command()?
380435
.args(&args)
381436
.stdout(Stdio::piped())
382437
.stderr(Stdio::piped())

src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"productName": "Archive UI",
3-
"version": "0.1.0",
3+
"version": "0.1.1",
44
"identifier": "com.whatev-indus.archive-ui",
55
"app": {
66
"withGlobalTauri": true,

0 commit comments

Comments
 (0)