@ -475,9 +475,8 @@ impl Update {
let mut stream = response . bytes_stream ( ) ;
while let Some ( chunk ) = stream . next ( ) . await {
let chunk = chunk ? ;
let bytes = chunk . as_ref ( ) . to_vec ( ) ;
on_chunk ( bytes . len ( ) , content_length ) ;
buffer . extend ( bytes ) ;
on_chunk ( chunk . len ( ) , content_length ) ;
buffer . extend ( chunk ) ;
}
on_download_finish ( ) ;
@ -508,114 +507,167 @@ impl Update {
fn install_inner ( & self , _bytes : Vec < u8 > ) -> Result < ( ) > {
Ok ( ( ) )
}
}
#[ cfg(windows) ]
enum WindowsUpdaterType {
Nsis ,
Msi ,
}
// Windows
//
// ### Expected structure:
// ├── [AppName]_[version]_x64.msi.zip # ZIP generated by tauri-bundler
// │ └──[AppName]_[version]_x64.msi # Application MSI
// ├── [AppName]_[version]_x64-setup.exe.zip # ZIP generated by tauri-bundler
// │ └──[AppName]_[version]_x64-setup.exe # NSIS installer
// └── ...
//
// ## MSI
// Update server can provide a MSI for Windows. (Generated with tauri-bundler from *Wix*)
// To replace current version of the application. In later version we'll offer
// incremental update to push specific binaries.
//
// ## EXE
// Update server can provide a custom EXE (installer) who can run any task.
#[ cfg(windows) ]
#[ cfg(windows) ]
impl WindowsUpdaterType {
fn extension ( & self ) -> & str {
match self {
WindowsUpdaterType ::Nsis = > ".exe" ,
WindowsUpdaterType ::Msi = > ".msi" ,
}
}
}
#[ cfg(windows) ]
impl Config {
fn install_mode ( & self ) -> crate ::config ::WindowsUpdateInstallMode {
self . windows
. as_ref ( )
. map ( | w | w . install_mode . clone ( ) )
. unwrap_or_default ( )
}
}
/// Windows
#[ cfg(windows) ]
impl Update {
/// ### Expected structure:
/// ├── [AppName]_[version]_x64.msi # Application MSI
/// ├── [AppName]_[version]_x64-setup.exe # NSIS installer
/// ├── [AppName]_[version]_x64.msi.zip # ZIP generated by tauri-bundler
/// │ └──[AppName]_[version]_x64.msi # Application MSI
/// ├── [AppName]_[version]_x64-setup.exe.zip # ZIP generated by tauri-bundler
/// │ └──[AppName]_[version]_x64-setup.exe # NSIS installer
/// └── ...
fn install_inner ( & self , bytes : Vec < u8 > ) -> Result < ( ) > {
use std ::fs ;
use windows_sys ::{
w ,
Win32 ::UI ::{ Shell ::ShellExecuteW , WindowsAndMessaging ::SW_SHOW } ,
} ;
// FIXME: We need to create a memory buffer with the MSI and then run it.
// (instead of extracting the MSI to a temp path)
//
// The tricky part is the MSI need to be exposed and spawned so the memory allocation
// shouldn't drop but we should be able to pass the reference so we can drop it once the installation
// is done, otherwise we have a huge memory leak.
let ( updater_type , path , _temp ) = Self ::extract ( & bytes ) ? ;
let tmp_dir = tempfile ::Builder ::new ( ) . tempdir ( ) ? . into_path ( ) ;
let archive = Cursor ::new ( bytes ) ;
let mut extractor = zip ::ZipArchive ::new ( archive ) ? ;
extractor . extract ( & tmp_dir ) ? ;
let install_mode = self . config . install_mode ( ) ;
let mut installer_args = self . installer_args ( ) ;
match updater_type {
WindowsUpdaterType ::Nsis = > {
installer_args . extend ( install_mode . nsis_args ( ) . iter ( ) . map ( OsStr ::new ) ) ;
}
WindowsUpdaterType ::Msi = > {
installer_args . extend ( install_mode . msiexec_args ( ) . iter ( ) . map ( OsStr ::new ) ) ;
installer_args . push ( OsStr ::new ( "/promptrestart" ) ) ;
}
} ;
let paths = fs ::read_dir ( & tmp_dir ) ? ;
if let Some ( on_before_exit ) = self . on_before_exit . as_ref ( ) {
on_before_exit ( ) ;
}
let install_mode = self
. config
. windows
. as_ref ( )
. map ( | w | w . install_mode . clone ( ) )
. unwrap_or_default ( ) ;
let mut installer_args = self
. installer_args
let file = encode_wide ( path ) ;
let parameters = encode_wide ( installer_args . join ( OsStr ::new ( " " ) ) ) ;
unsafe {
ShellExecuteW (
0 ,
w ! ( "open" ) ,
file . as_ptr ( ) ,
parameters . as_ptr ( ) ,
std ::ptr ::null ( ) ,
SW_SHOW ,
)
} ;
std ::process ::exit ( 0 ) ;
}
fn installer_args ( & self ) -> Vec < & OsStr > {
self . installer_args
. iter ( )
. map ( OsStr ::new )
. collect ::< Vec < _ > > ( ) ;
. collect ::< Vec < _ > > ( )
}
fn extract ( bytes : & [ u8 ] ) -> Result < ( WindowsUpdaterType , PathBuf , Option < tempfile ::TempPath > ) > {
#[ cfg(feature = " zip " ) ]
if infer ::archive ::is_zip ( bytes ) {
return Self ::extract_zip ( bytes ) ;
}
Self ::extract_exe ( bytes )
}
#[ cfg(feature = " zip " ) ]
fn extract_zip (
bytes : & [ u8 ] ,
) -> Result < ( WindowsUpdaterType , PathBuf , Option < tempfile ::TempPath > ) > {
let tmp_dir = tempfile ::Builder ::new ( ) . tempdir ( ) ? . into_path ( ) ;
let archive = Cursor ::new ( bytes ) ;
let mut extractor = zip ::ZipArchive ::new ( archive ) ? ;
extractor . extract ( & tmp_dir ) ? ;
let paths = std ::fs ::read_dir ( & tmp_dir ) ? ;
for path in paths {
let found_path = path ? . path ( ) ;
// we support 2 type of files exe & msi for now
// If it's an `exe` we expect an NSIS installer.
if found_path . extension ( ) = = Some ( OsStr ::new ( "exe" ) ) {
installer_args . extend ( install_mode . nsis_args ( ) . iter ( ) . map ( OsStr ::new ) ) ;
} else if found_path . extension ( ) = = Some ( OsStr ::new ( "msi" ) ) {
installer_args . extend ( install_mode . msiexec_args ( ) . iter ( ) . map ( OsStr ::new ) ) ;
installer_args . push ( OsStr ::new ( "/promptrestart" ) ) ;
} else {
continue ;
let ext = found_path . extension ( ) ;
if ext = = Some ( OsStr ::new ( "exe" ) ) {
return Ok ( ( WindowsUpdaterType ::Nsis , found_path , None ) ) ;
} else if ext = = Some ( OsStr ::new ( "msi" ) ) {
return Ok ( ( WindowsUpdaterType ::Msi , found_path , None ) ) ;
}
}
if let Some ( on_before_exit ) = self . on_before_exit . as_ref ( ) {
on_before_exit ( ) ;
}
Err ( crate ::Error ::BinaryNotFoundInArchive )
}
let file = encode_wide ( found_path . as_os_str ( ) ) ;
let parameters = encode_wide ( installer_args . join ( OsStr ::new ( " " ) ) . as_os_str ( ) ) ;
unsafe {
ShellExecuteW (
0 ,
w ! ( "open" ) ,
file . as_ptr ( ) ,
parameters . as_ptr ( ) ,
std ::ptr ::null ( ) ,
SW_SHOW ,
)
} ;
fn extract_exe (
bytes : & [ u8 ] ,
) -> Result < ( WindowsUpdaterType , PathBuf , Option < tempfile ::TempPath > ) > {
use std ::io ::Write ;
std ::process ::exit ( 0 ) ;
}
let updater_type = if infer ::app ::is_exe ( bytes ) {
WindowsUpdaterType ::Nsis
} else if infer ::archive ::is_msi ( bytes ) {
WindowsUpdaterType ::Msi
} else {
return Err ( crate ::Error ::InvalidUpdaterFormat ) ;
} ;
Ok ( ( ) )
let ext = updater_type . extension ( ) ;
let mut temp_file = tempfile ::Builder ::new ( ) . suffix ( ext ) . tempfile ( ) ? ;
temp_file . write_all ( bytes ) ? ;
let temp_path = temp_file . into_temp_path ( ) ;
Ok ( ( updater_type , temp_path . to_path_buf ( ) , Some ( temp_path ) ) )
}
}
// Linux (AppImage)
//
// ### Expected structure:
// ├── [AppName]_[version]_amd64.AppImage.tar.gz # GZ generated by tauri-bundler
// │ └──[AppName]_[version]_amd64.AppImage # Application AppImage
// └── ...
//
// We should have an AppImage already installed to be able to copy and install
// the extract_path is the current AppImage path
// tmp_dir is where our new AppImage is found
#[ cfg(any(
target_os = "linux" ,
target_os = "dragonfly" ,
target_os = "freebsd" ,
target_os = "netbsd" ,
target_os = "openbsd"
) ) ]
/ // Linux (AppImage)
#[ cfg(any(
target_os = "linux" ,
target_os = "dragonfly" ,
target_os = "freebsd" ,
target_os = "netbsd" ,
target_os = "openbsd"
) ) ]
impl Update {
/// ### Expected structure:
/// ├── [AppName]_[version]_amd64.AppImage.tar.gz # GZ generated by tauri-bundler
/// │ └──[AppName]_[version]_amd64.AppImage # Application AppImage
/// └── ...
///
/// We should have an AppImage already installed to be able to copy and install
/// the extract_path is the current AppImage path
/// tmp_dir is where our new AppImage is found
fn install_inner ( & self , bytes : Vec < u8 > ) -> Result < ( ) > {
use flate2 ::read ::GzDecoder ;
use std ::os ::unix ::fs ::{ MetadataExt , PermissionsExt } ;
let archive = Cursor ::new ( bytes ) ;
let extract_path_metadata = self . extract_path . metadata ( ) ? ;
let tmp_dir_locations = vec! [
@ -641,43 +693,56 @@ impl Update {
// create a backup of our current app image
std ::fs ::rename ( & self . extract_path , tmp_app_image ) ? ;
// extract the buffer to the tmp_dir
// we extract our signed archive into our final directory without any temp file
let decoder = GzDecoder ::new ( archive ) ;
let mut archive = tar ::Archive ::new ( decoder ) ;
for mut entry in archive . entries ( ) ? . flatten ( ) {
if let Ok ( path ) = entry . path ( ) {
if path . extension ( ) = = Some ( OsStr ::new ( "AppImage" ) ) {
// if something went wrong during the extraction, we should restore previous app
if let Err ( err ) = entry . unpack ( & self . extract_path ) {
std ::fs ::rename ( tmp_app_image , & self . extract_path ) ? ;
return Err ( err . into ( ) ) ;
#[ cfg(feature = " zip " ) ]
if infer ::archive ::is_gz ( & bytes ) {
// extract the buffer to the tmp_dir
// we extract our signed archive into our final directory without any temp file
let archive = Cursor ::new ( bytes ) ;
let decoder = flate2 ::read ::GzDecoder ::new ( archive ) ;
let mut archive = tar ::Archive ::new ( decoder ) ;
for mut entry in archive . entries ( ) ? . flatten ( ) {
if let Ok ( path ) = entry . path ( ) {
if path . extension ( ) = = Some ( OsStr ::new ( "AppImage" ) ) {
// if something went wrong during the extraction, we should restore previous app
if let Err ( err ) = entry . unpack ( & self . extract_path ) {
std ::fs ::rename ( tmp_app_image , & self . extract_path ) ? ;
return Err ( err . into ( ) ) ;
}
// early finish we have everything we need here
return Ok ( ( ) ) ;
}
// early finish we have everything we need here
return Ok ( ( ) ) ;
}
}
// if we have not returned early we should restore the backup
std ::fs ::rename ( tmp_app_image , & self . extract_path ) ? ;
return Err ( Error ::BinaryNotFoundInArchive ) ;
}
// if we have not returned early we should restore the backup
std ::fs ::rename ( tmp_app_image , & self . extract_path ) ? ;
return Err ( Error ::BinaryNotFoundInArchive ) ;
return match std ::fs ::write ( & self . extract_path , bytes ) {
Err ( err ) = > {
// if something went wrong during the extraction, we should restore previous app
std ::fs ::rename ( tmp_app_image , & self . extract_path ) ? ;
Err ( err . into ( ) )
}
Ok ( _ ) = > Ok ( ( ) ) ,
} ;
}
}
}
Err ( Error ::TempDirNotOnSameMountPoint )
}
}
// MacOS
//
// ### Expected structure:
// ├── [AppName]_[version]_x64.app.tar.gz # GZ generated by tauri-bundler
// │ └──[AppName].app # Main application
// │ └── Contents # Application contents...
// │ └── ...
// └── ...
#[ cfg(target_os = " macos " ) ]
/ // MacOS
#[ cfg(target_os = " macos " ) ]
impl Update {
/// ### Expected structure:
/// ├── [AppName]_[version]_x64.app.tar.gz # GZ generated by tauri-bundler
/// │ └──[AppName].app # Main application
/// │ └── Contents # Application contents ...
/// │ └── ...
/// └── ...
fn install_inner ( & self , bytes : Vec < u8 > ) -> Result < ( ) > {
use flate2 ::read ::GzDecoder ;
@ -889,7 +954,7 @@ fn base64_to_string(base64_string: &str) -> Result<String> {
Ok ( result )
}
#[ cfg( target_os = " windows" )]
#[ cfg( windows)]
fn encode_wide ( string : impl AsRef < OsStr > ) -> Vec < u16 > {
use std ::os ::windows ::ffi ::OsStrExt ;