Skip to content

Commit 7291c2e

Browse files
Merge remote-tracking branch 'Parent/v2' into Current
2 parents 5cd05e4 + 9c655e3 commit 7291c2e

17 files changed

+1790
-0
lines changed

src/commands.rs

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
2+
// SPDX-License-Identifier: Apache-2.0
3+
// SPDX-License-Identifier: MIT
4+
5+
use crate::{Result, Update, UpdaterExt};
6+
7+
use serde::Serialize;
8+
use tauri::{ipc::Channel, Manager, ResourceId, Runtime, Webview};
9+
10+
use std::time::Duration;
11+
use url::Url;
12+
13+
#[derive(Debug, Serialize)]
14+
#[serde(tag = "event", content = "data")]
15+
pub enum DownloadEvent {
16+
#[serde(rename_all = "camelCase")]
17+
Started {
18+
content_length: Option<u64>,
19+
},
20+
#[serde(rename_all = "camelCase")]
21+
Progress {
22+
chunk_length: usize,
23+
},
24+
Finished,
25+
}
26+
27+
#[derive(Serialize, Default)]
28+
#[serde(rename_all = "camelCase")]
29+
pub(crate) struct Metadata {
30+
rid: Option<ResourceId>,
31+
available: bool,
32+
current_version: String,
33+
version: String,
34+
date: Option<String>,
35+
body: Option<String>,
36+
}
37+
38+
#[tauri::command]
39+
pub(crate) async fn check<R: Runtime>(
40+
webview: Webview<R>,
41+
headers: Option<Vec<(String, String)>>,
42+
timeout: Option<u64>,
43+
proxy: Option<String>,
44+
target: Option<String>,
45+
) -> Result<Metadata> {
46+
let mut builder = webview.updater_builder();
47+
if let Some(headers) = headers {
48+
for (k, v) in headers {
49+
builder = builder.header(k, v)?;
50+
}
51+
}
52+
if let Some(timeout) = timeout {
53+
builder = builder.timeout(Duration::from_secs(timeout));
54+
}
55+
if let Some(ref proxy) = proxy {
56+
let url = Url::parse(proxy.as_str())?;
57+
builder = builder.proxy(url);
58+
}
59+
if let Some(target) = target {
60+
builder = builder.target(target);
61+
}
62+
63+
let updater = builder.build()?;
64+
let update = updater.check().await?;
65+
let mut metadata = Metadata::default();
66+
if let Some(update) = update {
67+
metadata.available = true;
68+
metadata.current_version = update.current_version.clone();
69+
metadata.version = update.version.clone();
70+
metadata.date = update.date.map(|d| d.to_string());
71+
metadata.body = update.body.clone();
72+
metadata.rid = Some(webview.resources_table().add(update));
73+
}
74+
75+
Ok(metadata)
76+
}
77+
78+
#[tauri::command]
79+
pub(crate) async fn download_and_install<R: Runtime>(
80+
webview: Webview<R>,
81+
rid: ResourceId,
82+
on_event: Channel,
83+
) -> Result<()> {
84+
let update = webview.resources_table().get::<Update>(rid)?;
85+
86+
let mut first_chunk = true;
87+
88+
update
89+
.download_and_install(
90+
|chunk_length, content_length| {
91+
if first_chunk {
92+
first_chunk = !first_chunk;
93+
let _ = on_event.send(DownloadEvent::Started { content_length });
94+
}
95+
let _ = on_event.send(DownloadEvent::Progress { chunk_length });
96+
},
97+
|| {
98+
let _ = on_event.send(&DownloadEvent::Finished);
99+
},
100+
)
101+
.await?;
102+
103+
Ok(())
104+
}

src/config.rs

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
2+
// SPDX-License-Identifier: Apache-2.0
3+
// SPDX-License-Identifier: MIT
4+
5+
use std::{ffi::OsString, fmt::Display};
6+
7+
use serde::{Deserialize, Deserializer};
8+
use url::Url;
9+
10+
/// Install modes for the Windows update.
11+
#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
12+
#[serde(rename_all = "camelCase")]
13+
pub enum WindowsUpdateInstallMode {
14+
/// Specifies there's a basic UI during the installation process, including a final dialog box at the end.
15+
BasicUi,
16+
/// The quiet mode means there's no user interaction required.
17+
/// Requires admin privileges if the installer does.
18+
Quiet,
19+
/// Specifies unattended mode, which means the installation only shows a progress bar.
20+
Passive,
21+
}
22+
23+
impl WindowsUpdateInstallMode {
24+
/// Returns the associated `msiexec.exe` arguments.
25+
pub fn msiexec_args(&self) -> &'static [&'static str] {
26+
match self {
27+
Self::BasicUi => &["/qb+"],
28+
Self::Quiet => &["/quiet"],
29+
Self::Passive => &["/passive"],
30+
}
31+
}
32+
33+
/// Returns the associated nsis arguments.
34+
pub fn nsis_args(&self) -> &'static [&'static str] {
35+
match self {
36+
Self::Passive => &["/P", "/R"],
37+
Self::Quiet => &["/S", "/R"],
38+
_ => &[],
39+
}
40+
}
41+
}
42+
43+
impl Display for WindowsUpdateInstallMode {
44+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45+
write!(
46+
f,
47+
"{}",
48+
match self {
49+
Self::BasicUi => "basicUI",
50+
Self::Quiet => "quiet",
51+
Self::Passive => "passive",
52+
}
53+
)
54+
}
55+
}
56+
57+
impl Default for WindowsUpdateInstallMode {
58+
fn default() -> Self {
59+
Self::Passive
60+
}
61+
}
62+
63+
#[derive(Debug, Clone, Deserialize, Default)]
64+
#[serde(rename_all = "camelCase")]
65+
pub struct WindowsConfig {
66+
/// Additional arguments given to the NSIS or WiX installer.
67+
#[serde(
68+
default,
69+
alias = "installer-args",
70+
deserialize_with = "deserialize_os_string"
71+
)]
72+
pub installer_args: Vec<OsString>,
73+
/// Updating mode, defaults to `passive` mode.
74+
///
75+
/// See [`WindowsUpdateInstallMode`] for more info.
76+
#[serde(default, alias = "install-mode")]
77+
pub install_mode: WindowsUpdateInstallMode,
78+
}
79+
80+
fn deserialize_os_string<'de, D>(deserializer: D) -> Result<Vec<OsString>, D::Error>
81+
where
82+
D: Deserializer<'de>,
83+
{
84+
Ok(Vec::<String>::deserialize(deserializer)?
85+
.into_iter()
86+
.map(OsString::from)
87+
.collect::<Vec<_>>())
88+
}
89+
90+
/// Updater configuration.
91+
#[derive(Debug, Clone, Deserialize, Default)]
92+
#[serde(rename_all = "camelCase")]
93+
pub struct Config {
94+
/// Updater endpoints.
95+
#[serde(default)]
96+
pub endpoints: Vec<UpdaterEndpoint>,
97+
/// Signature public key.
98+
pub pubkey: String,
99+
/// The Windows configuration for the updater.
100+
pub windows: Option<WindowsConfig>,
101+
}
102+
103+
/// A URL to an updater server.
104+
///
105+
/// The URL must use the `https` scheme on production.
106+
#[derive(Debug, PartialEq, Eq, Clone)]
107+
pub struct UpdaterEndpoint(pub Url);
108+
109+
impl std::fmt::Display for UpdaterEndpoint {
110+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111+
write!(f, "{}", self.0)
112+
}
113+
}
114+
115+
impl<'de> Deserialize<'de> for UpdaterEndpoint {
116+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
117+
where
118+
D: Deserializer<'de>,
119+
{
120+
let url = Url::deserialize(deserializer)?;
121+
#[cfg(all(not(debug_assertions), not(feature = "schema")))]
122+
{
123+
if url.scheme() != "https" {
124+
return Err(serde::de::Error::custom(
125+
"The configured updater endpoint must use the `https` protocol.",
126+
));
127+
}
128+
}
129+
Ok(Self(url))
130+
}
131+
}

src/error.rs

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
2+
// SPDX-License-Identifier: Apache-2.0
3+
// SPDX-License-Identifier: MIT
4+
5+
use serde::{Serialize, Serializer};
6+
use thiserror::Error;
7+
8+
/// All errors that can occur while running the updater.
9+
#[derive(Debug, Error)]
10+
#[non_exhaustive]
11+
pub enum Error {
12+
/// Endpoints are not sent.
13+
#[error("Updater does not have any endpoints set.")]
14+
EmptyEndpoints,
15+
/// IO errors.
16+
#[error(transparent)]
17+
Io(#[from] std::io::Error),
18+
/// Semver errors.
19+
#[error(transparent)]
20+
Semver(#[from] semver::Error),
21+
/// Serialization errors.
22+
#[error(transparent)]
23+
Serialization(#[from] serde_json::Error),
24+
/// Could not fetch a valid response from the server.
25+
#[error("Could not fetch a valid release JSON from the remote")]
26+
ReleaseNotFound,
27+
/// Unsupported app architecture.
28+
#[error("Unsupported application architecture, expected one of `x86`, `x86_64`, `arm` or `aarch64`.")]
29+
UnsupportedArch,
30+
/// Operating system is not supported.
31+
#[error("Unsupported OS, expected one of `linux`, `darwin` or `windows`.")]
32+
UnsupportedOs,
33+
/// Failed to determine updater package extract path
34+
#[error("Failed to determine updater package extract path.")]
35+
FailedToDetermineExtractPath,
36+
/// Url parsing errors.
37+
#[error(transparent)]
38+
UrlParse(#[from] url::ParseError),
39+
/// `reqwest` crate errors.
40+
#[error(transparent)]
41+
Reqwest(#[from] reqwest::Error),
42+
/// The platform was not found on the updater JSON response.
43+
#[error("the platform `{0}` was not found on the response `platforms` object")]
44+
TargetNotFound(String),
45+
/// Download failed
46+
#[error("`{0}`")]
47+
Network(String),
48+
/// `minisign_verify` errors.
49+
#[error(transparent)]
50+
Minisign(#[from] minisign_verify::Error),
51+
/// `base64` errors.
52+
#[error(transparent)]
53+
Base64(#[from] base64::DecodeError),
54+
/// UTF8 Errors in signature.
55+
#[error("The signature {0} could not be decoded, please check if it is a valid base64 string. The signature must be the contents of the `.sig` file generated by the Tauri bundler, as a string.")]
56+
SignatureUtf8(String),
57+
/// `zip` errors.
58+
#[error(transparent)]
59+
Extract(#[from] zip::result::ZipError),
60+
/// Temp dir is not on same mount mount. This prevents our updater to rename the AppImage to a temp file.
61+
#[error("temp directory is not on the same mount point as the AppImage")]
62+
TempDirNotOnSameMountPoint,
63+
#[error("binary for the current target not found in the archive")]
64+
BinaryNotFoundInArchive,
65+
#[error(transparent)]
66+
Http(#[from] http::Error),
67+
#[error(transparent)]
68+
Tauri(#[from] tauri::Error),
69+
}
70+
71+
impl Serialize for Error {
72+
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
73+
where
74+
S: Serializer,
75+
{
76+
serializer.serialize_str(self.to_string().as_ref())
77+
}
78+
}
79+
80+
pub type Result<T> = std::result::Result<T, Error>;

0 commit comments

Comments
 (0)