@ -23,7 +23,7 @@ use reqwest::{
} ;
} ;
use semver ::Version ;
use semver ::Version ;
use serde ::{ de ::Error as DeError , Deserialize , Deserializer , Serialize } ;
use serde ::{ de ::Error as DeError , Deserialize , Deserializer , Serialize } ;
use tauri ::{ utils ::platform ::current_exe , Resource} ;
use tauri ::{ utils ::platform ::current_exe , AppHandle, Resource, Runtim e} ;
use time ::OffsetDateTime ;
use time ::OffsetDateTime ;
use url ::Url ;
use url ::Url ;
@ -94,8 +94,13 @@ impl RemoteRelease {
pub type OnBeforeExit = Arc < dyn Fn ( ) + Send + Sync + ' static > ;
pub type OnBeforeExit = Arc < dyn Fn ( ) + Send + Sync + ' static > ;
pub type VersionComparator = Arc < dyn Fn ( Version , RemoteRelease ) -> bool + Send + Sync > ;
pub type VersionComparator = Arc < dyn Fn ( Version , RemoteRelease ) -> bool + Send + Sync > ;
type MainThreadClosure = Box < dyn FnOnce ( ) + Send + Sync + ' static > ;
type RunOnMainThread =
Box < dyn Fn ( MainThreadClosure ) -> std ::result ::Result < ( ) , tauri ::Error > + Send + Sync + ' static > ;
pub struct UpdaterBuilder {
pub struct UpdaterBuilder {
#[ allow(dead_code) ]
run_on_main_thread : RunOnMainThread ,
app_name : String ,
app_name : String ,
current_version : Version ,
current_version : Version ,
config : Config ,
config : Config ,
@ -112,18 +117,20 @@ pub struct UpdaterBuilder {
}
}
impl UpdaterBuilder {
impl UpdaterBuilder {
/// It's prefered to use [`crate::UpdaterExt::updater_builder`] instead of
pub ( crate ) fn new < R : Runtime > ( app : & AppHandle < R > , config : crate ::Config ) -> Self {
/// constructing a [`UpdaterBuilder`] with this function yourself
let app_ = app . clone ( ) ;
pub fn new ( app_name : String , current_version : Version , config : crate ::Config ) -> Self {
let run_on_main_thread =
move | f : Box < dyn FnOnce ( ) + Send + Sync + ' static > | app_ . run_on_main_thread ( f ) ;
Self {
Self {
run_on_main_thread : Box ::new ( run_on_main_thread ) ,
installer_args : config
installer_args : config
. windows
. windows
. as_ref ( )
. as_ref ( )
. map ( | w | w . installer_args . clone ( ) )
. map ( | w | w . installer_args . clone ( ) )
. unwrap_or_default ( ) ,
. unwrap_or_default ( ) ,
current_exe_args : Vec ::new ( ) ,
current_exe_args : Vec ::new ( ) ,
app_name ,
app_name : app . package_info ( ) . name . clone ( ) ,
current_version ,
current_version : app . package_info ( ) . version . clone ( ) ,
config ,
config ,
version_comparator : None ,
version_comparator : None ,
executable_path : None ,
executable_path : None ,
@ -259,6 +266,7 @@ impl UpdaterBuilder {
} ;
} ;
Ok ( Updater {
Ok ( Updater {
run_on_main_thread : Arc ::new ( self . run_on_main_thread ) ,
config : self . config ,
config : self . config ,
app_name : self . app_name ,
app_name : self . app_name ,
current_version : self . current_version ,
current_version : self . current_version ,
@ -291,6 +299,8 @@ impl UpdaterBuilder {
}
}
pub struct Updater {
pub struct Updater {
#[ allow(dead_code) ]
run_on_main_thread : Arc < RunOnMainThread > ,
config : Config ,
config : Config ,
app_name : String ,
app_name : String ,
current_version : Version ,
current_version : Version ,
@ -412,6 +422,7 @@ impl Updater {
let update = if should_update {
let update = if should_update {
Some ( Update {
Some ( Update {
run_on_main_thread : self . run_on_main_thread . clone ( ) ,
config : self . config . clone ( ) ,
config : self . config . clone ( ) ,
on_before_exit : self . on_before_exit . clone ( ) ,
on_before_exit : self . on_before_exit . clone ( ) ,
app_name : self . app_name . clone ( ) ,
app_name : self . app_name . clone ( ) ,
@ -440,6 +451,8 @@ impl Updater {
#[ derive(Clone) ]
#[ derive(Clone) ]
pub struct Update {
pub struct Update {
#[ allow(dead_code) ]
run_on_main_thread : Arc < RunOnMainThread > ,
config : Config ,
config : Config ,
#[ allow(unused) ]
#[ allow(unused) ]
on_before_exit : Option < OnBeforeExit > ,
on_before_exit : Option < OnBeforeExit > ,
@ -1031,42 +1044,85 @@ impl Update {
let cursor = Cursor ::new ( bytes ) ;
let cursor = Cursor ::new ( bytes ) ;
let mut extracted_files : Vec < PathBuf > = Vec ::new ( ) ;
let mut extracted_files : Vec < PathBuf > = Vec ::new ( ) ;
// the first file in the tar.gz will always be
// Create temp directories for backup and extraction
// <app_name>/Contents
let tmp_backup_dir = tempfile ::Builder ::new ( )
let tmp_dir = tempfile ::Builder ::new ( )
. prefix ( "tauri_current_app" )
. prefix ( "tauri_current_app" )
. tempdir ( ) ? ;
. tempdir ( ) ? ;
// create backup of our current app
let tmp_extract_dir = tempfile ::Builder ::new ( )
std ::fs ::rename ( & self . extract_path , tmp_dir . path ( ) ) ? ;
. prefix ( "tauri_updated_app" )
. tempdir ( ) ? ;
let decoder = GzDecoder ::new ( cursor ) ;
let decoder = GzDecoder ::new ( cursor ) ;
let mut archive = tar ::Archive ::new ( decoder ) ;
let mut archive = tar ::Archive ::new ( decoder ) ;
std ::fs ::create_dir ( & self . extract_path ) ? ;
// Extract files to temporary directory
for entry in archive . entries ( ) ? {
for entry in archive . entries ( ) ? {
let mut entry = entry ? ;
let mut entry = entry ? ;
// skip the first folder (should be the app name)
let collected_path : PathBuf = entry . path ( ) ? . iter ( ) . skip ( 1 ) . collect ( ) ;
let collected_path : PathBuf = entry . path ( ) ? . iter ( ) . skip ( 1 ) . collect ( ) ;
let extraction_path = & self . extract_path . join ( collected_path ) ;
let extraction_path = tmp_extract_dir . path ( ) . join ( & collected_path ) ;
// if something went wrong during the extraction, we should restore previous app
// Ensure parent directories exist
if let Err ( err ) = entry . unpack ( extraction_path ) {
if let Some ( parent ) = extraction_path . parent ( ) {
for file in extracted_files . iter ( ) . rev ( ) {
std ::fs ::create_dir_all ( parent ) ? ;
// delete all the files we extracted
}
if file . is_dir ( ) {
std ::fs ::remove_dir ( file ) ? ;
if let Err ( err ) = entry . unpack ( & extraction_path ) {
} else {
// Cleanup on error
std ::fs ::remove_file ( file ) ? ;
std ::fs ::remove_dir_all ( tmp_extract_dir . path ( ) ) . ok ( ) ;
}
return Err ( err . into ( ) ) ;
}
}
std ::fs ::rename ( tmp_dir . path ( ) , & self . extract_path ) ? ;
extracted_files . push ( extraction_path ) ;
}
// Try to move the current app to backup
let move_result = std ::fs ::rename (
& self . extract_path ,
tmp_backup_dir . path ( ) . join ( "current_app" ) ,
) ;
let need_authorization = if let Err ( err ) = move_result {
if err . kind ( ) = = std ::io ::ErrorKind ::PermissionDenied {
true
} else {
std ::fs ::remove_dir_all ( tmp_extract_dir . path ( ) ) . ok ( ) ;
return Err ( err . into ( ) ) ;
return Err ( err . into ( ) ) ;
}
}
} else {
false
} ;
extracted_files . push ( extraction_path . to_path_buf ( ) ) ;
if need_authorization {
// Use AppleScript to perform moves with admin privileges
let apple_script = format! (
"do shell script \"rm -rf '{src}' && mv -f '{new}' '{src}'\" with administrator privileges" ,
src = self . extract_path . display ( ) ,
new = tmp_extract_dir . path ( ) . display ( )
) ;
let ( tx , rx ) = std ::sync ::mpsc ::channel ( ) ;
let res = ( self . run_on_main_thread ) ( Box ::new ( move | | {
let mut script =
osakit ::Script ::new_from_source ( osakit ::Language ::AppleScript , & apple_script ) ;
script . compile ( ) . expect ( "invalid AppleScript" ) ;
let r = script . execute ( ) ;
tx . send ( r ) . unwrap ( ) ;
} ) ) ;
let result = rx . recv ( ) . unwrap ( ) ;
if res . is_err ( ) | | result . is_err ( ) {
std ::fs ::remove_dir_all ( tmp_extract_dir . path ( ) ) . ok ( ) ;
return Err ( Error ::Io ( std ::io ::Error ::new (
std ::io ::ErrorKind ::PermissionDenied ,
"Failed to move the new app into place" ,
) ) ) ;
}
} else {
// Remove existing directory if it exists
if self . extract_path . exists ( ) {
std ::fs ::remove_dir_all ( & self . extract_path ) ? ;
}
// Move the new app to the target path
std ::fs ::rename ( tmp_extract_dir . path ( ) , & self . extract_path ) ? ;
}
}
let _ = std ::process ::Command ::new ( "touch" )
let _ = std ::process ::Command ::new ( "touch" )