refactor(core): load APPIMAGE and APPDIR env vars on startup [TRI-007] [TRI-041]

This commit is contained in:
Lucas Nogueira 2022-01-09 10:57:29 -03:00
parent 4de285c396
commit 7209fdf732
No known key found for this signature in database
GPG Key ID: 2714B66BCFB01F7F
16 changed files with 193 additions and 100 deletions

5
.changes/core-env.md Normal file
View File

@ -0,0 +1,5 @@
---
"tauri": patch
---
The `process`, `path` and `updater` APIs now takes a `tauri::Env` argument, used to force environment variables load on startup to prevent env var update attacks.

View File

@ -37,6 +37,28 @@ impl PackageInfo {
}
}
/// Information about environment variables.
#[derive(Debug, Clone)]
pub struct Env {
/// The APPIMAGE environment variable.
#[cfg(target_os = "linux")]
pub appimage: Option<std::ffi::OsString>,
/// The APPDIR environment variable.
#[cfg(target_os = "linux")]
pub appdir: Option<std::ffi::OsString>,
}
impl Default for Env {
fn default() -> Self {
Self {
#[cfg(target_os = "linux")]
appimage: std::env::var_os("APPIMAGE"),
#[cfg(target_os = "linux")]
appdir: std::env::var_os("APPDIR"),
}
}
}
/// The result type of `tauri-utils`.
pub type Result<T> = std::result::Result<T, Error>;

View File

@ -4,12 +4,9 @@
//! Platform helper functions.
use std::{
env,
path::{PathBuf, MAIN_SEPARATOR},
};
use std::path::{PathBuf, MAIN_SEPARATOR};
use crate::PackageInfo;
use crate::{Env, PackageInfo};
/// Try to determine the current target triple.
///
@ -76,7 +73,7 @@ pub fn target_triple() -> crate::Result<String> {
/// `${exe_dir}/../lib/${exe_name}`.
///
/// On MacOS, it's `${exe_dir}../Resources` (inside .app).
pub fn resource_dir(package_info: &PackageInfo) -> crate::Result<PathBuf> {
pub fn resource_dir(package_info: &PackageInfo, env: &Env) -> crate::Result<PathBuf> {
let exe = std::env::current_exe()?;
let exe_dir = exe.parent().expect("failed to get exe directory");
let curr_dir = exe_dir.display().to_string();
@ -93,10 +90,11 @@ pub fn resource_dir(package_info: &PackageInfo) -> crate::Result<PathBuf> {
if curr_dir.ends_with("/data/usr/bin") {
// running from the deb bundle dir
Ok(exe_dir.join(format!("../lib/{}", package_info.package_name())))
} else if let Ok(appdir) = env::var("APPDIR") {
} else if let Some(appdir) = &env.appdir {
let appdir: &std::path::Path = appdir.as_ref();
Ok(PathBuf::from(format!(
"{}/usr/lib/{}",
appdir,
appdir.display(),
package_info.package_name()
)))
} else {

View File

@ -9,7 +9,7 @@ use std::{
path::{Component, Path, PathBuf},
};
use crate::{Config, PackageInfo};
use crate::{Config, Env, PackageInfo};
use serde_repr::{Deserialize_repr, Serialize_repr};
@ -83,6 +83,7 @@ pub enum BaseDirectory {
/// authors: "tauri",
/// description: "a tauri test",
/// },
/// &Default::default(),
/// "path/to/something",
/// Some(BaseDirectory::Config)
/// ).expect("failed to resolve path");
@ -91,6 +92,7 @@ pub enum BaseDirectory {
pub fn resolve_path<P: AsRef<Path>>(
config: &Config,
package_info: &PackageInfo,
env: &Env,
path: P,
dir: Option<BaseDirectory>,
) -> crate::api::Result<PathBuf> {
@ -113,7 +115,7 @@ pub fn resolve_path<P: AsRef<Path>>(
BaseDirectory::Runtime => runtime_dir(),
BaseDirectory::Template => template_dir(),
BaseDirectory::Video => video_dir(),
BaseDirectory::Resource => resource_dir(package_info),
BaseDirectory::Resource => resource_dir(package_info, env),
BaseDirectory::App => app_dir(config),
BaseDirectory::Current => Some(env::current_dir()?),
BaseDirectory::Log => log_dir(config),
@ -229,8 +231,8 @@ pub fn video_dir() -> Option<PathBuf> {
}
/// Returns the path to the resource directory of this app.
pub fn resource_dir(package_info: &PackageInfo) -> Option<PathBuf> {
crate::utils::platform::resource_dir(package_info).ok()
pub fn resource_dir(package_info: &PackageInfo, env: &Env) -> Option<PathBuf> {
crate::utils::platform::resource_dir(package_info, env).ok()
}
/// Returns the path to the suggested directory for your app config files.

View File

@ -4,6 +4,8 @@
//! Types and functions related to child processes management.
use crate::Env;
use std::{
env,
path::PathBuf,
@ -16,12 +18,13 @@ mod command;
pub use command::*;
/// Gets the current binary.
pub fn current_binary() -> Option<PathBuf> {
#[allow(unused_variables)]
pub fn current_binary(env: &Env) -> Option<PathBuf> {
let mut current_binary = None;
// if we are running with an APP Image, we should return the app image path
#[cfg(target_os = "linux")]
if let Some(app_image_path) = env::var_os("APPIMAGE") {
if let Some(app_image_path) = &env.appimage {
current_binary = Some(PathBuf::from(app_image_path));
}
@ -37,8 +40,8 @@ pub fn current_binary() -> Option<PathBuf> {
}
/// Restarts the process.
pub fn restart() {
if let Some(path) = current_binary() {
pub fn restart(env: &Env) {
if let Some(path) = current_binary(env) {
StdCommand::new(path)
.spawn()
.expect("application failed to start");

View File

@ -19,8 +19,8 @@ use crate::{
Dispatch, ExitRequestedEventAction, RunEvent, Runtime,
},
sealed::{ManagerBase, RuntimeOrDispatch},
utils::assets::Assets,
utils::config::{Config, WindowUrl},
utils::{assets::Assets, Env},
Context, Invoke, InvokeError, InvokeResponse, Manager, StateManager, Window,
};
@ -150,6 +150,7 @@ impl<R: Runtime> GlobalWindowEvent<R> {
/// The path resolver is a helper for the application-specific [`crate::api::path`] APIs.
#[derive(Debug, Clone)]
pub struct PathResolver {
env: Env,
config: Arc<Config>,
package_info: PackageInfo,
}
@ -157,7 +158,7 @@ pub struct PathResolver {
impl PathResolver {
/// Returns the path to the resource directory of this app.
pub fn resource_dir(&self) -> Option<PathBuf> {
crate::api::path::resource_dir(&self.package_info)
crate::api::path::resource_dir(&self.package_info, &self.env)
}
/// Returns the path to the suggested directory for your app config files.
@ -407,6 +408,7 @@ macro_rules! shared_app_impl {
/// The path resolver for the application.
pub fn path_resolver(&self) -> PathResolver {
PathResolver {
env: self.state::<Env>().inner().clone(),
config: self.manager.config(),
package_info: self.manager.package_info().clone(),
}
@ -432,6 +434,11 @@ macro_rules! shared_app_impl {
self.manager.package_info()
}
/// Gets the managed [`Env`].
pub fn env(&self) -> Env {
self.state::<Env>().inner().clone()
}
/// The application's asset resolver.
pub fn asset_resolver(&self) -> AssetResolver<R> {
AssetResolver {
@ -990,6 +997,8 @@ impl<R: Runtime> Builder<R> {
},
};
app.manage(Env::default());
#[cfg(feature = "system-tray")]
if let Some(system_tray) = self.system_tray {
let mut ids = HashMap::new();

View File

@ -75,17 +75,21 @@ impl Module {
.and_then(|r| r.json)
.map_err(InvokeError::from)
}),
Self::Process(cmd) => resolver
.respond_async(async move { cmd.run().and_then(|r| r.json).map_err(InvokeError::from) }),
Self::Process(cmd) => resolver.respond_async(async move {
cmd
.run(window)
.and_then(|r| r.json)
.map_err(InvokeError::from)
}),
Self::Fs(cmd) => resolver.respond_async(async move {
cmd
.run(config, &package_info)
.run(window, config, &package_info)
.and_then(|r| r.json)
.map_err(InvokeError::from)
}),
Self::Path(cmd) => resolver.respond_async(async move {
cmd
.run(config, &package_info)
.run(window, config, &package_info)
.and_then(|r| r.json)
.map_err(InvokeError::from)
}),

View File

@ -8,7 +8,7 @@ use crate::{
dir, file,
path::{resolve_path, BaseDirectory},
},
Config, PackageInfo,
Config, Env, Manager, PackageInfo, Runtime, Window,
};
use serde::{Deserialize, Serialize};
@ -97,15 +97,17 @@ pub enum Cmd {
impl Cmd {
#[allow(unused_variables)]
pub fn run(
pub fn run<R: Runtime>(
self,
window: Window<R>,
config: Arc<Config>,
package_info: &PackageInfo,
) -> crate::Result<InvokeResponse> {
let env = window.state::<Env>().inner();
match self {
#[cfg(fs_read_text_file)]
Self::ReadTextFile { path, options } => {
read_text_file(&config, package_info, path, options).map(Into::into)
read_text_file(&config, package_info, env, path, options).map(Into::into)
}
#[cfg(not(fs_read_text_file))]
Self::ReadTextFile { .. } => Err(crate::Error::ApiNotAllowlisted(
@ -114,7 +116,7 @@ impl Cmd {
#[cfg(fs_read_binary_file)]
Self::ReadBinaryFile { path, options } => {
read_binary_file(&config, package_info, path, options).map(Into::into)
read_binary_file(&config, package_info, env, path, options).map(Into::into)
}
#[cfg(not(fs_read_binary_file))]
Self::ReadBinaryFile { .. } => Err(crate::Error::ApiNotAllowlisted(
@ -126,7 +128,7 @@ impl Cmd {
path,
contents,
options,
} => write_file(&config, package_info, path, contents, options).map(Into::into),
} => write_file(&config, package_info, env, path, contents, options).map(Into::into),
#[cfg(not(fs_write_file))]
Self::WriteFile { .. } => Err(crate::Error::ApiNotAllowlisted(
"fs > writeFile".to_string(),
@ -137,7 +139,7 @@ impl Cmd {
path,
contents,
options,
} => write_binary_file(&config, package_info, path, contents, options).map(Into::into),
} => write_binary_file(&config, package_info, env, path, contents, options).map(Into::into),
#[cfg(not(fs_write_binary_file))]
Self::WriteBinaryFile { .. } => Err(crate::Error::ApiNotAllowlisted(
"writeBinaryFile".to_string(),
@ -145,7 +147,7 @@ impl Cmd {
#[cfg(fs_read_dir)]
Self::ReadDir { path, options } => {
read_dir(&config, package_info, path, options).map(Into::into)
read_dir(&config, package_info, env, path, options).map(Into::into)
}
#[cfg(not(fs_read_dir))]
Self::ReadDir { .. } => Err(crate::Error::ApiNotAllowlisted("fs > readDir".to_string())),
@ -155,13 +157,13 @@ impl Cmd {
source,
destination,
options,
} => copy_file(&config, package_info, source, destination, options).map(Into::into),
} => copy_file(&config, package_info, env, source, destination, options).map(Into::into),
#[cfg(not(fs_copy_file))]
Self::CopyFile { .. } => Err(crate::Error::ApiNotAllowlisted("fs > copyFile".to_string())),
#[cfg(fs_create_dir)]
Self::CreateDir { path, options } => {
create_dir(&config, package_info, path, options).map(Into::into)
create_dir(&config, package_info, env, path, options).map(Into::into)
}
#[cfg(not(fs_create_dir))]
Self::CreateDir { .. } => Err(crate::Error::ApiNotAllowlisted(
@ -170,7 +172,7 @@ impl Cmd {
#[cfg(fs_remove_dir)]
Self::RemoveDir { path, options } => {
remove_dir(&config, package_info, path, options).map(Into::into)
remove_dir(&config, package_info, env, path, options).map(Into::into)
}
#[cfg(not(fs_remove_dir))]
Self::RemoveDir { .. } => Err(crate::Error::ApiNotAllowlisted(
@ -179,7 +181,7 @@ impl Cmd {
#[cfg(fs_remove_file)]
Self::RemoveFile { path, options } => {
remove_file(&config, package_info, path, options).map(Into::into)
remove_file(&config, package_info, env, path, options).map(Into::into)
}
#[cfg(not(fs_remove_file))]
Self::RemoveFile { .. } => Err(crate::Error::ApiNotAllowlisted(
@ -191,7 +193,7 @@ impl Cmd {
old_path,
new_path,
options,
} => rename_file(&config, package_info, old_path, new_path, options).map(Into::into),
} => rename_file(&config, package_info, env, old_path, new_path, options).map(Into::into),
#[cfg(not(fs_rename_file))]
Self::RenameFile { .. } => Err(crate::Error::ApiNotAllowlisted(
"fs > renameFile".to_string(),
@ -205,6 +207,7 @@ impl Cmd {
pub fn read_dir(
config: &Config,
package_info: &PackageInfo,
env: &Env,
path: PathBuf,
options: Option<DirOperationOptions>,
) -> crate::Result<Vec<dir::DiskEntry>> {
@ -213,8 +216,11 @@ pub fn read_dir(
} else {
(false, None)
};
dir::read_dir(resolve_path(config, package_info, path, dir)?, recursive)
.map_err(crate::Error::FailedToExecuteApi)
dir::read_dir(
resolve_path(config, package_info, env, path, dir)?,
recursive,
)
.map_err(crate::Error::FailedToExecuteApi)
}
/// Copies a file.
@ -222,14 +228,15 @@ pub fn read_dir(
pub fn copy_file(
config: &Config,
package_info: &PackageInfo,
env: &Env,
source: PathBuf,
destination: PathBuf,
options: Option<FileOperationOptions>,
) -> crate::Result<()> {
let (src, dest) = match options.and_then(|o| o.dir) {
Some(dir) => (
resolve_path(config, package_info, source, Some(dir.clone()))?,
resolve_path(config, package_info, destination, Some(dir))?,
resolve_path(config, package_info, env, source, Some(dir.clone()))?,
resolve_path(config, package_info, env, destination, Some(dir))?,
),
None => (source, destination),
};
@ -242,6 +249,7 @@ pub fn copy_file(
pub fn create_dir(
config: &Config,
package_info: &PackageInfo,
env: &Env,
path: PathBuf,
options: Option<DirOperationOptions>,
) -> crate::Result<()> {
@ -250,7 +258,7 @@ pub fn create_dir(
} else {
(false, None)
};
let resolved_path = resolve_path(config, package_info, path, dir)?;
let resolved_path = resolve_path(config, package_info, env, path, dir)?;
if recursive {
fs::create_dir_all(resolved_path)?;
} else {
@ -265,6 +273,7 @@ pub fn create_dir(
pub fn remove_dir(
config: &Config,
package_info: &PackageInfo,
env: &Env,
path: PathBuf,
options: Option<DirOperationOptions>,
) -> crate::Result<()> {
@ -273,7 +282,7 @@ pub fn remove_dir(
} else {
(false, None)
};
let resolved_path = resolve_path(config, package_info, path, dir)?;
let resolved_path = resolve_path(config, package_info, env, path, dir)?;
if recursive {
fs::remove_dir_all(resolved_path)?;
} else {
@ -288,10 +297,11 @@ pub fn remove_dir(
pub fn remove_file(
config: &Config,
package_info: &PackageInfo,
env: &Env,
path: PathBuf,
options: Option<FileOperationOptions>,
) -> crate::Result<()> {
let resolved_path = resolve_path(config, package_info, path, options.and_then(|o| o.dir))?;
let resolved_path = resolve_path(config, package_info, env, path, options.and_then(|o| o.dir))?;
fs::remove_file(resolved_path)?;
Ok(())
}
@ -301,14 +311,15 @@ pub fn remove_file(
pub fn rename_file(
config: &Config,
package_info: &PackageInfo,
env: &Env,
old_path: PathBuf,
new_path: PathBuf,
options: Option<FileOperationOptions>,
) -> crate::Result<()> {
let (old, new) = match options.and_then(|o| o.dir) {
Some(dir) => (
resolve_path(config, package_info, old_path, Some(dir.clone()))?,
resolve_path(config, package_info, new_path, Some(dir))?,
resolve_path(config, package_info, env, old_path, Some(dir.clone()))?,
resolve_path(config, package_info, env, new_path, Some(dir))?,
),
None => (old_path, new_path),
};
@ -320,6 +331,7 @@ pub fn rename_file(
pub fn write_file(
config: &Config,
package_info: &PackageInfo,
env: &Env,
path: PathBuf,
contents: String,
options: Option<FileOperationOptions>,
@ -327,6 +339,7 @@ pub fn write_file(
File::create(resolve_path(
config,
package_info,
env,
path,
options.and_then(|o| o.dir),
)?)
@ -340,6 +353,7 @@ pub fn write_file(
pub fn write_binary_file(
config: &Config,
package_info: &PackageInfo,
env: &Env,
path: PathBuf,
contents: String,
options: Option<FileOperationOptions>,
@ -350,6 +364,7 @@ pub fn write_binary_file(
File::create(resolve_path(
config,
package_info,
env,
path,
options.and_then(|o| o.dir),
)?)
@ -364,12 +379,14 @@ pub fn write_binary_file(
pub fn read_text_file(
config: &Config,
package_info: &PackageInfo,
env: &Env,
path: PathBuf,
options: Option<FileOperationOptions>,
) -> crate::Result<String> {
file::read_string(resolve_path(
config,
package_info,
env,
path,
options.and_then(|o| o.dir),
)?)
@ -381,12 +398,14 @@ pub fn read_text_file(
pub fn read_binary_file(
config: &Config,
package_info: &PackageInfo,
env: &Env,
path: PathBuf,
options: Option<FileOperationOptions>,
) -> crate::Result<Vec<u8>> {
file::read_binary(resolve_path(
config,
package_info,
env,
path,
options.and_then(|o| o.dir),
)?)

View File

@ -6,7 +6,7 @@ use super::InvokeResponse;
use serde::Deserialize;
#[cfg(notification_all)]
use crate::api::notification::Notification;
use crate::{api::notification::Notification, Env, Manager};
use crate::{Config, PackageInfo, Runtime, Window};
use std::sync::Arc;
@ -55,7 +55,7 @@ impl Cmd {
Self::Notification { .. } => Err(crate::Error::ApiNotAllowlisted("notification".to_string())),
Self::IsNotificationPermissionGranted => {
#[cfg(notification_all)]
return is_permission_granted(&config, package_info).map(Into::into);
return is_permission_granted(&window, &config, package_info).map(Into::into);
#[cfg(not(notification_all))]
Ok(false.into())
}
@ -84,11 +84,13 @@ pub fn send(options: NotificationOptions, config: &Config) -> crate::Result<Invo
}
#[cfg(notification_all)]
pub fn is_permission_granted(
pub fn is_permission_granted<R: Runtime>(
window: &Window<R>,
config: &Config,
package_info: &PackageInfo,
) -> crate::Result<InvokeResponse> {
let settings = crate::settings::read_settings(config, package_info);
let settings =
crate::settings::read_settings(config, package_info, window.state::<Env>().inner());
if let Some(allow_notification) = settings.allow_notification {
Ok(allow_notification.into())
} else {
@ -102,7 +104,8 @@ pub fn request_permission<R: Runtime>(
config: &Config,
package_info: &PackageInfo,
) -> crate::Result<String> {
let mut settings = crate::settings::read_settings(config, package_info);
let mut settings =
crate::settings::read_settings(config, package_info, window.state::<Env>().inner());
if let Some(allow_notification) = settings.allow_notification {
return Ok(if allow_notification {
PERMISSION_GRANTED.to_string()
@ -123,7 +126,12 @@ pub fn request_permission<R: Runtime>(
let answer = rx.recv().unwrap();
settings.allow_notification = Some(answer);
crate::settings::write_settings(config, package_info, settings)?;
crate::settings::write_settings(
config,
package_info,
window.state::<Env>().inner(),
settings,
)?;
if answer {
Ok(PERMISSION_GRANTED.to_string())

View File

@ -3,7 +3,9 @@
// SPDX-License-Identifier: MIT
use super::InvokeResponse;
use crate::{api::path::BaseDirectory, Config, PackageInfo};
use crate::{api::path::BaseDirectory, Config, PackageInfo, Runtime, Window};
#[cfg(path_all)]
use crate::{Env, Manager};
use serde::Deserialize;
#[cfg(path_all)]
use std::path::{Component, Path, PathBuf, MAIN_SEPARATOR};
@ -42,16 +44,22 @@ pub enum Cmd {
impl Cmd {
#[allow(unused_variables)]
pub fn run(
pub fn run<R: Runtime>(
self,
window: Window<R>,
config: Arc<Config>,
package_info: &PackageInfo,
) -> crate::Result<InvokeResponse> {
#[cfg(path_all)]
return match self {
Cmd::ResolvePath { directory, path } => {
resolve_path_handler(&config, package_info, path, directory).map(Into::into)
}
Cmd::ResolvePath { directory, path } => resolve_path_handler(
&config,
package_info,
window.state::<Env>().inner(),
path,
directory,
)
.map(Into::into),
Cmd::Resolve { paths } => resolve(paths).map(Into::into),
Cmd::Normalize { path } => normalize(path).map(Into::into),
Cmd::Join { paths } => join(paths).map(Into::into),
@ -69,10 +77,11 @@ impl Cmd {
pub fn resolve_path_handler(
config: &Config,
package_info: &PackageInfo,
env: &Env,
path: String,
directory: Option<BaseDirectory>,
) -> crate::Result<PathBuf> {
crate::api::path::resolve_path(config, package_info, path, directory).map_err(Into::into)
crate::api::path::resolve_path(config, package_info, env, path, directory).map_err(Into::into)
}
#[cfg(path_all)]

View File

@ -5,7 +5,7 @@
use std::process::exit;
use super::InvokeResponse;
use crate::api::process::restart;
use crate::{api::process::restart, Manager, Runtime, Window};
use serde::Deserialize;
/// The API descriptor.
@ -20,10 +20,10 @@ pub enum Cmd {
}
impl Cmd {
pub fn run(self) -> crate::Result<InvokeResponse> {
pub fn run<R: Runtime>(self, window: Window<R>) -> crate::Result<InvokeResponse> {
match self {
Self::Relaunch => Ok({
restart();
restart(&window.state());
().into()
}),
Self::Exit { exit_code } => {

View File

@ -108,7 +108,7 @@ pub use {
self::utils::{
assets::Assets,
config::{Config, WindowUrl},
PackageInfo,
Env, PackageInfo,
},
self::window::{Monitor, Window},
};

View File

@ -11,7 +11,7 @@ use crate::{
file::read_binary,
path::{resolve_path, BaseDirectory},
},
Config, PackageInfo,
Config, Env, PackageInfo,
};
use serde::{Deserialize, Serialize};
use std::{
@ -30,10 +30,15 @@ pub struct Settings {
}
/// Gets the path to the settings file.
fn get_settings_path(config: &Config, package_info: &PackageInfo) -> crate::api::Result<PathBuf> {
fn get_settings_path(
config: &Config,
package_info: &PackageInfo,
env: &Env,
) -> crate::api::Result<PathBuf> {
resolve_path(
config,
package_info,
env,
".tauri-settings",
Some(BaseDirectory::App),
)
@ -44,9 +49,10 @@ fn get_settings_path(config: &Config, package_info: &PackageInfo) -> crate::api:
pub(crate) fn write_settings(
config: &Config,
package_info: &PackageInfo,
env: &Env,
settings: Settings,
) -> crate::Result<()> {
let settings_path = get_settings_path(config, package_info)?;
let settings_path = get_settings_path(config, package_info, env)?;
let settings_folder = Path::new(&settings_path).parent().unwrap();
if !settings_folder.exists() {
std::fs::create_dir(settings_folder)?;
@ -60,8 +66,8 @@ pub(crate) fn write_settings(
}
/// Reads the settings from the file system.
pub fn read_settings(config: &Config, package_info: &PackageInfo) -> Settings {
if let Ok(settings_path) = get_settings_path(config, package_info) {
pub fn read_settings(config: &Config, package_info: &PackageInfo, env: &Env) -> Settings {
if let Ok(settings_path) = get_settings_path(config, package_info, env) {
if settings_path.exists() {
read_binary(settings_path)
.and_then(|settings| bincode::deserialize(&settings).map_err(Into::into))

View File

@ -3,7 +3,10 @@
// SPDX-License-Identifier: MIT
use super::error::{Error, Result};
use crate::api::{file::Extract, version};
use crate::{
api::{file::Extract, version},
Env,
};
use base64::decode;
use http::StatusCode;
use minisign_verify::{PublicKey, Signature};
@ -171,6 +174,8 @@ impl RemoteRelease {
#[derive(Debug)]
pub struct UpdateBuilder<'a> {
/// Environment information.
pub env: Env,
/// Current version we are running to compare with announced version
pub current_version: &'a str,
/// The URLs to checks updates. We suggest at least one fallback on a different domain.
@ -181,22 +186,17 @@ pub struct UpdateBuilder<'a> {
pub executable_path: Option<PathBuf>,
}
impl<'a> Default for UpdateBuilder<'a> {
fn default() -> Self {
// Create new updater instance and return an Update
impl<'a> UpdateBuilder<'a> {
pub fn new(env: Env) -> Self {
UpdateBuilder {
env,
urls: Vec::new(),
target: None,
executable_path: None,
current_version: env!("CARGO_PKG_VERSION"),
}
}
}
// Create new updater instance and return an Update
impl<'a> UpdateBuilder<'a> {
pub fn new() -> Self {
UpdateBuilder::default()
}
#[allow(dead_code)]
pub fn url(mut self, url: String) -> Self {
@ -267,7 +267,7 @@ impl<'a> UpdateBuilder<'a> {
};
// Get the extract_path from the provided executable_path
let extract_path = extract_path_from_executable(&executable_path);
let extract_path = extract_path_from_executable(&self.env, &executable_path);
// Set SSL certs for linux if they aren't available.
// We do not require to recheck in the download_and_install as we use
@ -357,6 +357,7 @@ impl<'a> UpdateBuilder<'a> {
// create our new updater
Ok(Update {
env: self.env,
target,
extract_path,
should_update,
@ -372,12 +373,14 @@ impl<'a> UpdateBuilder<'a> {
}
}
pub fn builder<'a>() -> UpdateBuilder<'a> {
UpdateBuilder::new()
pub fn builder<'a>(env: Env) -> UpdateBuilder<'a> {
UpdateBuilder::new(env)
}
#[derive(Debug, Clone)]
pub struct Update {
/// Environment information.
pub env: Env,
/// Update description
pub body: Option<String>,
/// Should we update or not
@ -418,7 +421,7 @@ impl Update {
// be set with our APPIMAGE env variable, we don't need to do
// anythin with it yet
#[cfg(target_os = "linux")]
if env::var_os("APPIMAGE").is_none() {
if self.env.appimage.is_none() {
return Err(Error::UnsupportedPlatform);
}
@ -718,7 +721,7 @@ pub fn get_updater_target() -> Option<String> {
}
/// Get the extract_path from the provided executable_path
pub fn extract_path_from_executable(executable_path: &Path) -> PathBuf {
pub fn extract_path_from_executable(env: &Env, executable_path: &Path) -> PathBuf {
// Return the path of the current executable by default
// Example C:\Program Files\My App\
let extract_path = executable_path
@ -748,7 +751,7 @@ pub fn extract_path_from_executable(executable_path: &Path) -> PathBuf {
// We should use APPIMAGE exposed env variable
// This is where our APPIMAGE should sit and should be replaced
#[cfg(target_os = "linux")]
if let Some(app_image_path) = env::var_os("APPIMAGE") {
if let Some(app_image_path) = &env.appimage {
return PathBuf::from(app_image_path);
}
@ -905,9 +908,10 @@ mod test {
#[cfg(target_os = "macos")]
#[test]
fn test_app_name_in_path() {
let executable = extract_path_from_executable(Path::new(
"/Applications/updater-example.app/Contents/MacOS/updater-example",
));
let executable = extract_path_from_executable(
&crate::Env::default(),
Path::new("/Applications/updater-example.app/Contents/MacOS/updater-example"),
);
let app_name = macos_app_name_in_path(&executable);
assert!(executable.ends_with("updater-example.app"));
assert_eq!(app_name, "updater-example.app".to_string());
@ -921,7 +925,7 @@ mod test {
.with_body(generate_sample_raw_json())
.create();
let check_update = block!(builder()
let check_update = block!(builder(Default::default())
.current_version("0.0.0")
.url(mockito::server_url())
.build());
@ -940,7 +944,7 @@ mod test {
.with_body(generate_sample_raw_json())
.create();
let check_update = block!(builder()
let check_update = block!(builder(Default::default())
.current_version("0.0.0")
.url(mockito::server_url())
.build());
@ -959,7 +963,7 @@ mod test {
.with_body(generate_sample_raw_json())
.create();
let check_update = block!(builder()
let check_update = block!(builder(Default::default())
.current_version("0.0.0")
.target("win64")
.url(mockito::server_url())
@ -985,7 +989,7 @@ mod test {
.with_body(generate_sample_raw_json())
.create();
let check_update = block!(builder()
let check_update = block!(builder(Default::default())
.current_version("10.0.0")
.url(mockito::server_url())
.build());
@ -1008,7 +1012,7 @@ mod test {
))
.create();
let check_update = block!(builder()
let check_update = block!(builder(Default::default())
.current_version("1.0.0")
.url(format!(
"{}/darwin/{{{{current_version}}}}",
@ -1035,7 +1039,7 @@ mod test {
))
.create();
let check_update = block!(builder()
let check_update = block!(builder(Default::default())
.current_version("1.0.0")
.url(format!(
"{}/win64/{{{{current_version}}}}",
@ -1061,7 +1065,7 @@ mod test {
))
.create();
let check_update = block!(builder()
let check_update = block!(builder(Default::default())
.current_version("10.0.0")
.url(format!(
"{}/darwin/{{{{current_version}}}}",
@ -1083,7 +1087,7 @@ mod test {
.with_body(generate_sample_raw_json())
.create();
let check_update = block!(builder()
let check_update = block!(builder(Default::default())
.url("http://badurl.www.tld/1".into())
.url(mockito::server_url())
.current_version("0.0.1")
@ -1103,7 +1107,7 @@ mod test {
.with_body(generate_sample_raw_json())
.create();
let check_update = block!(builder()
let check_update = block!(builder(Default::default())
.urls(&["http://badurl.www.tld/1".into(), mockito::server_url(),])
.current_version("0.0.1")
.build());
@ -1122,7 +1126,7 @@ mod test {
.with_body(generate_sample_bad_json())
.create();
let check_update = block!(builder()
let check_update = block!(builder(Default::default())
.url(mockito::server_url())
.current_version("0.0.1")
.build());
@ -1186,7 +1190,7 @@ mod test {
let tmp_dir_path = tmp_dir_unwrap.path();
// configure the updater
let check_update = block!(builder()
let check_update = block!(builder(Default::default())
.url(mockito::server_url())
// It should represent the executable path, that's why we add my_app.exe in our
// test path -- in production you shouldn't have to provide it

View File

@ -336,7 +336,7 @@ use crate::{
api::{dialog::ask, process::restart},
runtime::Runtime,
utils::config::UpdaterConfig,
Window,
Env, Manager, Window,
};
use std::sync::mpsc::channel;
@ -381,8 +381,9 @@ pub(crate) async fn check_update_with_dialog<R: Runtime>(
window: Window<R>,
) {
if let Some(endpoints) = updater_config.endpoints.clone() {
let env = window.state::<Env>().inner().clone();
// check updates
match self::core::builder()
match self::core::builder(env)
.urls(&endpoints[..])
.current_version(&package_info.version)
.build()
@ -448,8 +449,9 @@ pub(crate) fn listener<R: Runtime>(
let window = window.clone();
let window_isolation = window.clone();
let pubkey = pubkey.clone();
let env = window.state::<Env>().inner().clone();
match self::core::builder()
match self::core::builder(env)
.urls(&endpoints[..])
.current_version(&package_info.version)
.build()
@ -558,13 +560,14 @@ Release Notes:
updater.download_and_install(pubkey.clone()).await?;
// Ask user if we need to restart the application
let env = window.state::<Env>().inner().clone();
ask(
Some(&window),
"Ready to Restart",
"The installation was successful, do you want to restart the application now?",
|should_exit| {
move |should_exit| {
if should_exit {
restart();
restart(&env);
}
},
);

View File

@ -9,7 +9,7 @@
#[cfg(not(any(feature = "api-all", feature = "shell-all", feature = "shell-execute")))]
fn main() {
eprintln!("Not supported without `api-all`, `shell-all` or `shell-execute`")
eprintln!("Not supported without `api-all`, `shell-all` and `shell-execute`")
}
#[cfg(any(feature = "api-all", feature = "shell-all", feature = "shell-execute"))]
@ -25,6 +25,7 @@ fn main() {
let script_path = resolve_path(
context.config(),
context.package_info(),
&Default::default(),
"assets/index.js",
Some(BaseDirectory::Resource),
)