@@ -6,6 +6,7 @@ use gtk::{gio, glib, pango};
66use crate :: container:: Container ;
77use crate :: distrobox:: { ExportableApp , ExportableBinary } ;
88use crate :: gtk_utils:: reaction;
9+ use crate :: fakers:: Command ;
910
1011use std:: cell:: RefCell ;
1112
@@ -157,6 +158,43 @@ glib::wrapper! {
157158 @extends adw:: Dialog , gtk:: Widget ;
158159}
159160impl 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 \n Do 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