11use std:: fs;
2+ use serde:: Serialize ;
23use tauri:: { Emitter , Manager } ;
34use tauri_plugin_http:: reqwest;
45use tauri_plugin_shell:: ShellExt ;
56
7+ #[ derive( Serialize ) ]
8+ struct InstalledApp {
9+ app_id : String ,
10+ name : String ,
11+ version : String ,
12+ }
13+
14+ #[ derive( Serialize ) ]
15+ struct UpdateAvailable {
16+ app_id : String ,
17+ new_version : String ,
18+ branch : String ,
19+ }
20+
621// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
722#[ tauri:: command]
823fn greet ( name : & str ) -> String {
@@ -312,7 +327,58 @@ fn check_file_exists(path: String) -> bool {
312327}
313328
314329#[ tauri:: command]
315- async fn get_installed_flatpaks ( app : tauri:: AppHandle ) -> Result < Vec < String > , String > {
330+ async fn get_installed_flatpaks ( app : tauri:: AppHandle ) -> Result < Vec < InstalledApp > , String > {
331+ let shell = app. shell ( ) ;
332+
333+ // Detect if we're running inside a flatpak
334+ let is_flatpak = std:: env:: var ( "FLATPAK_ID" ) . is_ok ( ) ;
335+
336+ let output = if is_flatpak {
337+ // Inside flatpak, use flatpak-spawn to execute on the host
338+ shell
339+ . command ( "flatpak-spawn" )
340+ . args ( [ "--host" , "flatpak" , "list" , "--app" , "--columns=application,name,version" ] )
341+ . output ( )
342+ . await
343+ . map_err ( |e| format ! ( "Failed to execute flatpak-spawn: {}" , e) ) ?
344+ } else {
345+ // Outside flatpak, use flatpak directly
346+ shell
347+ . command ( "flatpak" )
348+ . args ( [ "list" , "--app" , "--columns=application,name,version" ] )
349+ . output ( )
350+ . await
351+ . map_err ( |e| format ! ( "Failed to execute flatpak: {}" , e) ) ?
352+ } ;
353+
354+ if !output. status . success ( ) {
355+ let error = String :: from_utf8_lossy ( & output. stderr ) ;
356+ return Err ( format ! ( "Flatpak command failed: {}" , error) ) ;
357+ }
358+
359+ let stdout = String :: from_utf8_lossy ( & output. stdout ) ;
360+ let apps: Vec < InstalledApp > = stdout
361+ . lines ( )
362+ . filter ( |line| !line. trim ( ) . is_empty ( ) )
363+ . filter_map ( |line| {
364+ let parts: Vec < & str > = line. split ( '\t' ) . collect ( ) ;
365+ if parts. len ( ) >= 3 {
366+ Some ( InstalledApp {
367+ app_id : parts[ 0 ] . trim ( ) . to_string ( ) ,
368+ name : parts[ 1 ] . trim ( ) . to_string ( ) ,
369+ version : parts[ 2 ] . trim ( ) . to_string ( ) ,
370+ } )
371+ } else {
372+ None
373+ }
374+ } )
375+ . collect ( ) ;
376+
377+ Ok ( apps)
378+ }
379+
380+ #[ tauri:: command]
381+ async fn get_available_updates ( app : tauri:: AppHandle ) -> Result < Vec < UpdateAvailable > , String > {
316382 let shell = app. shell ( ) ;
317383
318384 // Detect if we're running inside a flatpak
@@ -322,15 +388,15 @@ async fn get_installed_flatpaks(app: tauri::AppHandle) -> Result<Vec<String>, St
322388 // Inside flatpak, use flatpak-spawn to execute on the host
323389 shell
324390 . command ( "flatpak-spawn" )
325- . args ( [ "--host" , "flatpak" , "list " , "--app " , "--columns=application" ] )
391+ . args ( [ "--host" , "flatpak" , "remote-ls " , "--updates " , "--columns=application,version,branch " ] )
326392 . output ( )
327393 . await
328394 . map_err ( |e| format ! ( "Failed to execute flatpak-spawn: {}" , e) ) ?
329395 } else {
330396 // Outside flatpak, use flatpak directly
331397 shell
332398 . command ( "flatpak" )
333- . args ( [ "list " , "--app " , "--columns=application" ] )
399+ . args ( [ "remote-ls " , "--updates " , "--columns=application,version,branch " ] )
334400 . output ( )
335401 . await
336402 . map_err ( |e| format ! ( "Failed to execute flatpak: {}" , e) ) ?
@@ -342,13 +408,89 @@ async fn get_installed_flatpaks(app: tauri::AppHandle) -> Result<Vec<String>, St
342408 }
343409
344410 let stdout = String :: from_utf8_lossy ( & output. stdout ) ;
345- let app_ids : Vec < String > = stdout
411+ let updates : Vec < UpdateAvailable > = stdout
346412 . lines ( )
347413 . filter ( |line| !line. trim ( ) . is_empty ( ) )
348- . map ( |line| line. trim ( ) . to_string ( ) )
414+ . filter_map ( |line| {
415+ let parts: Vec < & str > = line. split ( '\t' ) . collect ( ) ;
416+ if parts. len ( ) >= 3 {
417+ Some ( UpdateAvailable {
418+ app_id : parts[ 0 ] . trim ( ) . to_string ( ) ,
419+ new_version : parts[ 1 ] . trim ( ) . to_string ( ) ,
420+ branch : parts[ 2 ] . trim ( ) . to_string ( ) ,
421+ } )
422+ } else if parts. len ( ) >= 2 {
423+ // Sometimes version might be empty, branch in position 2
424+ Some ( UpdateAvailable {
425+ app_id : parts[ 0 ] . trim ( ) . to_string ( ) ,
426+ new_version : String :: new ( ) ,
427+ branch : parts. get ( 1 ) . unwrap_or ( & "stable" ) . trim ( ) . to_string ( ) ,
428+ } )
429+ } else {
430+ None
431+ }
432+ } )
349433 . collect ( ) ;
350434
351- Ok ( app_ids)
435+ Ok ( updates)
436+ }
437+
438+ #[ tauri:: command]
439+ async fn update_flatpak ( app : tauri:: AppHandle , app_id : String ) -> Result < ( ) , String > {
440+ app. emit (
441+ "install-output" ,
442+ format ! ( "Iniciando actualización de {}..." , app_id) ,
443+ )
444+ . map_err ( |e| format ! ( "Failed to emit: {}" , e) ) ?;
445+
446+ let shell = app. shell ( ) ;
447+
448+ // Detect if we're running inside a flatpak
449+ let is_flatpak = std:: env:: var ( "FLATPAK_ID" ) . is_ok ( ) ;
450+
451+ let ( mut rx, _child) = if is_flatpak {
452+ // Inside flatpak, use flatpak-spawn to execute on the host
453+ shell
454+ . command ( "flatpak-spawn" )
455+ . args ( [ "--host" , "flatpak" , "update" , "-y" , & app_id] )
456+ . spawn ( )
457+ . map_err ( |e| format ! ( "Failed to spawn flatpak-spawn: {}" , e) ) ?
458+ } else {
459+ // Outside flatpak, use flatpak directly
460+ shell
461+ . command ( "flatpak" )
462+ . args ( [ "update" , "-y" , & app_id] )
463+ . spawn ( )
464+ . map_err ( |e| format ! ( "Failed to spawn flatpak: {}" , e) ) ?
465+ } ;
466+
467+ // Read output in real-time
468+ while let Some ( event) = rx. recv ( ) . await {
469+ match event {
470+ tauri_plugin_shell:: process:: CommandEvent :: Stdout ( line) => {
471+ let output = String :: from_utf8_lossy ( & line) ;
472+ app. emit ( "install-output" , output. to_string ( ) )
473+ . map_err ( |e| format ! ( "Failed to emit event: {}" , e) ) ?;
474+ }
475+ tauri_plugin_shell:: process:: CommandEvent :: Stderr ( line) => {
476+ let output = String :: from_utf8_lossy ( & line) ;
477+ app. emit ( "install-output" , output. to_string ( ) )
478+ . map_err ( |e| format ! ( "Failed to emit event: {}" , e) ) ?;
479+ }
480+ tauri_plugin_shell:: process:: CommandEvent :: Error ( err) => {
481+ app. emit ( "install-error" , err)
482+ . map_err ( |e| format ! ( "Failed to emit error: {}" , e) ) ?;
483+ }
484+ tauri_plugin_shell:: process:: CommandEvent :: Terminated ( payload) => {
485+ app. emit ( "install-completed" , payload. code . unwrap_or ( -1 ) )
486+ . map_err ( |e| format ! ( "Failed to emit completion: {}" , e) ) ?;
487+ break ;
488+ }
489+ _ => { }
490+ }
491+ }
492+
493+ Ok ( ( ) )
352494}
353495
354496#[ cfg_attr( mobile, tauri:: mobile_entry_point) ]
@@ -373,7 +515,9 @@ pub fn run() {
373515 download_and_cache_image,
374516 get_cached_image_path,
375517 check_file_exists,
376- get_installed_flatpaks
518+ get_installed_flatpaks,
519+ get_available_updates,
520+ update_flatpak
377521 ] )
378522 . run ( tauri:: generate_context!( ) )
379523 . expect ( "error while running tauri application" ) ;
0 commit comments