Skip to content

Commit 5ee9865

Browse files
committed
Show confirmation when conflicting binary found on host
1 parent d1eaa03 commit 5ee9865

File tree

1 file changed

+99
-18
lines changed

1 file changed

+99
-18
lines changed

src/dialogs/exportable_apps_dialog.rs

Lines changed: 99 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use gtk::{gio, glib, pango};
66
use crate::container::Container;
77
use crate::distrobox::{ExportableApp, ExportableBinary};
88
use crate::gtk_utils::reaction;
9+
use crate::fakers::Command;
910

1011
use std::cell::RefCell;
1112

@@ -157,6 +158,43 @@ glib::wrapper! {
157158
@extends adw::Dialog, gtk::Widget;
158159
}
159160
impl ExportableAppsDialog {
161+
/// Check if a binary exists on the host system
162+
/// Handles both binary names (e.g., "nvim") and paths (e.g., "/usr/bin/nvim")
163+
async fn binary_exists_on_host(container: &Container, binary_name_or_path: &str) -> bool {
164+
// If it contains a '/', treat it as a path
165+
if binary_name_or_path.contains('/') {
166+
// For paths, we need to check on the host using a command
167+
// We'll use 'test -e' which returns 0 if the file exists
168+
let expanded_path = if binary_name_or_path.starts_with("~/") {
169+
// Expand home on host - use $HOME variable
170+
format!("$HOME/{}", &binary_name_or_path[2..])
171+
} else {
172+
binary_name_or_path.to_string()
173+
};
174+
175+
let mut cmd = Command::new("test");
176+
cmd.args(["-e", &expanded_path]);
177+
178+
let command_runner = container.root_store().command_runner();
179+
if let Ok(output) = command_runner.output(cmd).await {
180+
output.status.success()
181+
} else {
182+
false
183+
}
184+
} else {
185+
// It's a binary name, check using 'which' on the host
186+
let mut cmd = Command::new("which");
187+
cmd.arg(binary_name_or_path);
188+
189+
let command_runner = container.root_store().command_runner();
190+
if let Ok(output) = command_runner.output(cmd).await {
191+
output.status.success()
192+
} else {
193+
false
194+
}
195+
}
196+
}
197+
160198
pub fn new(container: &Container) -> Self {
161199
let this: Self = glib::Object::builder()
162200
.property("container", container)
@@ -253,28 +291,45 @@ impl ExportableAppsDialog {
253291
.connect_apply(move |entry| {
254292
let binary_name = entry.text().to_string();
255293
if !binary_name.is_empty() {
256-
let task = this_clone.container().export_binary(&binary_name);
257-
entry.set_text("");
258-
259-
// Monitor task status to show error toasts
260294
let this = this_clone.clone();
261295
let binary_name_clone = binary_name.clone();
262-
reaction!(task.status(), move |status: String| {
263-
match status.as_str() {
264-
"failed" => {
265-
let error_ref = task.error();
266-
let error_msg = if let Some(err) = error_ref.as_ref() {
267-
format!("Failed to export '{}': {}", binary_name_clone, err)
268-
} else {
269-
format!("Failed to export '{}'", binary_name_clone)
270-
};
271-
let toast = adw::Toast::new(&error_msg);
272-
toast.set_timeout(5);
273-
this.imp().toast_overlay.add_toast(toast);
274-
}
275-
_ => {}
296+
let container = this_clone.container();
297+
298+
// Check if binary exists on host and show confirmation dialog if needed
299+
glib::spawn_future_local(async move {
300+
let exists = Self::binary_exists_on_host(&container, &binary_name).await;
301+
302+
if exists {
303+
// Show confirmation dialog
304+
let dialog = adw::AlertDialog::new(
305+
Some("Binary Already Exists on Host"),
306+
Some(&format!(
307+
"The binary '{}' already exists on your host system.\n\nDo you want to continue?",
308+
binary_name_clone
309+
)),
310+
);
311+
dialog.add_response("cancel", "Cancel");
312+
dialog.add_response("export", "Export Anyway");
313+
dialog.set_response_appearance("export", adw::ResponseAppearance::Destructive);
314+
dialog.set_default_response(Some("cancel"));
315+
dialog.set_close_response("cancel");
316+
317+
let this_inner = this.clone();
318+
let binary_name_inner = binary_name_clone.clone();
319+
dialog.connect_response(None, move |_dialog, response| {
320+
if response == "export" {
321+
this_inner.do_export_binary(&binary_name_inner);
322+
}
323+
});
324+
325+
dialog.present(Some(&this));
326+
} else {
327+
// No conflict, export directly
328+
this.do_export_binary(&binary_name_clone);
276329
}
277330
});
331+
332+
entry.set_text("");
278333
}
279334
});
280335

@@ -283,6 +338,32 @@ impl ExportableAppsDialog {
283338

284339
this
285340
}
341+
342+
/// Helper method to perform the actual export of a binary
343+
fn do_export_binary(&self, binary_name: &str) {
344+
let task = self.container().export_binary(binary_name);
345+
346+
// Monitor task status to show error toasts
347+
let this = self.clone();
348+
let binary_name_clone = binary_name.to_string();
349+
reaction!(task.status(), move |status: String| {
350+
match status.as_str() {
351+
"failed" => {
352+
let error_ref = task.error();
353+
let error_msg = if let Some(err) = error_ref.as_ref() {
354+
format!("Failed to export '{}': {}", binary_name_clone, err)
355+
} else {
356+
format!("Failed to export '{}'", binary_name_clone)
357+
};
358+
let toast = adw::Toast::new(&error_msg);
359+
toast.set_timeout(5);
360+
this.imp().toast_overlay.add_toast(toast);
361+
}
362+
_ => {}
363+
}
364+
});
365+
}
366+
286367
pub fn build_row(&self, app: &ExportableApp) -> adw::ActionRow {
287368
// Create the action row
288369
let row = adw::ActionRow::new();

0 commit comments

Comments
 (0)