Skip to content

Commit f0db3f9

Browse files
authored
feat(updater): add download progress events (#3734)
1 parent 348a1ab commit f0db3f9

File tree

10 files changed

+389
-139
lines changed

10 files changed

+389
-139
lines changed

.changes/http-api-stream.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": patch
3+
---
4+
5+
Added `bytes_stream` method to `tauri::api::http::Response`.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": patch
3+
---
4+
5+
Added download progress events to the updater.

core/tauri/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ percent-encoding = "2.1"
7373
base64 = { version = "0.13", optional = true }
7474
clap = { version = "3", optional = true }
7575
notify-rust = { version = "4.5", optional = true }
76-
reqwest = { version = "0.11", features = [ "json", "multipart" ], optional = true }
76+
reqwest = { version = "0.11", features = [ "json", "multipart", "stream" ], optional = true }
7777
bytes = { version = "1", features = [ "serde" ], optional = true }
7878
attohttpc = { version = "0.18", features = [ "json", "form" ], optional = true }
7979
open = { version = "2.0", optional = true }
@@ -137,10 +137,10 @@ updater = [
137137
"fs-extract-api"
138138
]
139139
__updater-docs = [ "minisign-verify", "base64", "http-api", "dialog-ask" ]
140-
http-api = [ "attohttpc" ]
140+
http-api = [ "attohttpc", "bytes" ]
141141
shell-open-api = [ "open", "regex", "tauri-macros/shell-scope" ]
142142
fs-extract-api = [ "zip" ]
143-
reqwest-client = [ "reqwest", "bytes" ]
143+
reqwest-client = [ "reqwest" ]
144144
process-command-api = [ "shared_child", "os_pipe", "memchr" ]
145145
dialog = [ "rfd" ]
146146
notification = [ "notify-rust" ]

core/tauri/src/api/http.rs

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
//! Types and functions related to HTTP request.
66
77
use http::{header::HeaderName, Method};
8+
pub use http::{HeaderMap, StatusCode};
89
use serde::{Deserialize, Serialize};
910
use serde_json::Value;
1011
use serde_repr::{Deserialize_repr, Serialize_repr};
@@ -352,17 +353,85 @@ pub struct Response(ResponseType, reqwest::Response);
352353
#[derive(Debug)]
353354
pub struct Response(ResponseType, attohttpc::Response, Url);
354355

356+
#[cfg(not(feature = "reqwest-client"))]
357+
struct AttohttpcByteReader(attohttpc::ResponseReader);
358+
359+
#[cfg(not(feature = "reqwest-client"))]
360+
impl futures::Stream for AttohttpcByteReader {
361+
type Item = crate::api::Result<bytes::Bytes>;
362+
363+
fn poll_next(
364+
mut self: std::pin::Pin<&mut Self>,
365+
_cx: &mut futures::task::Context<'_>,
366+
) -> futures::task::Poll<Option<Self::Item>> {
367+
use std::io::Read;
368+
let mut buf = [0; 256];
369+
match self.0.read(&mut buf) {
370+
Ok(b) => {
371+
if b == 0 {
372+
futures::task::Poll::Ready(None)
373+
} else {
374+
futures::task::Poll::Ready(Some(Ok(buf[0..b].to_vec().into())))
375+
}
376+
}
377+
Err(_) => futures::task::Poll::Ready(None),
378+
}
379+
}
380+
}
381+
355382
impl Response {
383+
/// Get the [`StatusCode`] of this Response.
384+
pub fn status(&self) -> StatusCode {
385+
self.1.status()
386+
}
387+
388+
/// Get the headers of this Response.
389+
pub fn headers(&self) -> &HeaderMap {
390+
self.1.headers()
391+
}
392+
356393
/// Reads the response as raw bytes.
357394
pub async fn bytes(self) -> crate::api::Result<RawResponse> {
358-
let status = self.1.status().as_u16();
395+
let status = self.status().as_u16();
359396
#[cfg(feature = "reqwest-client")]
360397
let data = self.1.bytes().await?.to_vec();
361398
#[cfg(not(feature = "reqwest-client"))]
362399
let data = self.1.bytes()?;
363400
Ok(RawResponse { status, data })
364401
}
365402

403+
/// Convert the response into a Stream of [`bytes::Bytes`] from the body.
404+
///
405+
/// # Examples
406+
///
407+
/// ```no_run
408+
/// use futures::StreamExt;
409+
///
410+
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
411+
/// let client = tauri::api::http::ClientBuilder::new().build()?;
412+
/// let mut stream = client.send(tauri::api::http::HttpRequestBuilder::new("GET", "http://httpbin.org/ip")?)
413+
/// .await?
414+
/// .bytes_stream();
415+
///
416+
/// while let Some(item) = stream.next().await {
417+
/// println!("Chunk: {:?}", item?);
418+
/// }
419+
/// # Ok(())
420+
/// # }
421+
/// ```
422+
pub fn bytes_stream(self) -> impl futures::Stream<Item = crate::api::Result<bytes::Bytes>> {
423+
#[cfg(not(feature = "reqwest-client"))]
424+
{
425+
let (_, _, reader) = self.1.split();
426+
AttohttpcByteReader(reader)
427+
}
428+
#[cfg(feature = "reqwest-client")]
429+
{
430+
use futures::StreamExt;
431+
self.1.bytes_stream().map(|res| res.map_err(Into::into))
432+
}
433+
}
434+
366435
/// Reads the response.
367436
///
368437
/// Note that the body is serialized to a [`Value`].

core/tauri/src/app.rs

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -579,13 +579,9 @@ impl<R: Runtime> App<R> {
579579
impl<R: Runtime> App<R> {
580580
/// Runs the updater hook with built-in dialog.
581581
fn run_updater_dialog(&self) {
582-
let updater_config = self.manager.config().tauri.updater.clone();
583-
let package_info = self.manager.package_info().clone();
584582
let handle = self.handle();
585583

586-
crate::async_runtime::spawn(async move {
587-
updater::check_update_with_dialog(updater_config, package_info, handle).await
588-
});
584+
crate::async_runtime::spawn(async move { updater::check_update_with_dialog(handle).await });
589585
}
590586

591587
fn run_updater(&self) {
@@ -597,22 +593,18 @@ impl<R: Runtime> App<R> {
597593
if updater_config.dialog {
598594
// if updater dialog is enabled spawn a new task
599595
self.run_updater_dialog();
600-
let config = self.manager.config().tauri.updater.clone();
601-
let package_info = self.manager.package_info().clone();
602596
// When dialog is enabled, if user want to recheck
603597
// if an update is available after first start
604598
// invoke the Event `tauri://update` from JS or rust side.
605599
handle.listen_global(updater::EVENT_CHECK_UPDATE, move |_msg| {
606600
let handle = handle_.clone();
607-
let package_info = package_info.clone();
608-
let config = config.clone();
609601
// re-spawn task inside tokyo to launch the download
610602
// we don't need to emit anything as everything is handled
611603
// by the process (user is asked to restart at the end)
612604
// and it's handled by the updater
613-
crate::async_runtime::spawn(async move {
614-
updater::check_update_with_dialog(config, package_info, handle).await
615-
});
605+
crate::async_runtime::spawn(
606+
async move { updater::check_update_with_dialog(handle).await },
607+
);
616608
});
617609
} else {
618610
// we only listen for `tauri://update`

core/tauri/src/lib.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,8 +242,16 @@ pub enum UpdaterEvent {
242242
/// The update version.
243243
version: String,
244244
},
245-
/// The update is pending.
245+
/// The update is pending and about to be downloaded.
246246
Pending,
247+
/// The update download received a progress event.
248+
DownloadProgress {
249+
/// The amount that was downloaded on this iteration.
250+
/// Does not accumulate with previous chunks.
251+
chunk_length: usize,
252+
/// The total
253+
content_length: Option<u64>,
254+
},
247255
/// The update has been applied and the app is now up to date.
248256
Updated,
249257
/// The app is already up to date.

0 commit comments

Comments
 (0)