feat(download): add progress bar (#8)

* feat(download): add poor man's progress bar

* clippy

* changefile

* hide when section details is expanded

* resize details section

* fix artifacts?

* hide download controls
This commit is contained in:
Amr Bashir
2023-01-24 15:51:36 +02:00
committed by GitHub
parent 5163faa3da
commit eba1392081
7 changed files with 178 additions and 23 deletions

View File

@@ -0,0 +1,5 @@
---
"nsis_download": "patch"
---
Add download progress bar

View File

@@ -1,5 +1,5 @@
[workspace]
members = [ "crates/*" ]
members = ["crates/*"]
resolver = "2"
[workspace.package]
@@ -7,13 +7,24 @@ authors = ["Tauri Programme within the Commons Conservancy"]
edition = "2021"
license = "MIT or Apache-2.0"
[workspace.dependencies]
pluginapi = { path = "./crates/pluginapi" }
[workspace.dependencies.windows-sys]
version = "0.45.0"
features = ["Win32_Foundation", "Win32_Globalization", "Win32_System_Diagnostics_ToolHelp", "Win32_System_Memory", "Win32_System_Threading"]
features = [
"Win32_Foundation",
"Win32_Globalization",
"Win32_System_Diagnostics_ToolHelp",
"Win32_System_Memory",
"Win32_System_Threading",
"Win32_UI_WindowsAndMessaging",
"Win32_UI_Controls",
]
[profile.release]
codegen-units = 1
lto = true
opt-level = "s"
panic = "abort"
strip = "symbols"
strip = "symbols"

View File

@@ -15,8 +15,9 @@ workspace = true
crate-type = ["rlib", "cdylib"]
[dependencies]
pluginapi = { path = "../pluginapi" }
pluginapi.workspace = true
ureq = { version = "2", default-features = false, features = ["tls"] }
progress-streams = "1.1"
[dependencies.windows-sys]
workspace = true

View File

@@ -1,7 +1,18 @@
use std::{fs, io, path::Path};
use pluginapi::{exdll_init, popstring, pushint, stack_t, wchar_t};
use windows_sys::Win32::Foundation::HWND;
use progress_streams::ProgressReader;
use windows_sys::Win32::{
Foundation::HWND,
UI::{
Controls::{PBM_SETPOS, PROGRESS_CLASSW, WC_STATICW},
WindowsAndMessaging::{
CreateWindowExW, FindWindowExW, GetWindowLongPtrW, SendMessageW, SetWindowPos,
SetWindowTextW, ShowWindow, GWL_STYLE, SWP_FRAMECHANGED, SWP_NOSIZE, SW_HIDE,
WM_GETFONT, WM_SETFONT, WS_CHILD, WS_VISIBLE,
},
},
};
/// Download a file from an URL to a path.
///
@@ -10,7 +21,7 @@ use windows_sys::Win32::Foundation::HWND;
/// This function always expects 2 strings on the stack ($1: url, $2: path) and will panic otherwise.
#[no_mangle]
pub unsafe extern "C" fn Download(
_hwnd_parent: HWND,
hwnd_parent: HWND,
string_size: u32,
variables: *mut wchar_t,
stacktop: *mut *mut stack_t,
@@ -20,14 +31,76 @@ pub unsafe extern "C" fn Download(
let url = popstring().unwrap();
let path = popstring().unwrap();
let status = download_file(&url, &path);
let status = download_file(hwnd_parent, &url, &path);
pushint(status);
}
fn download_file(url: &str, path: &str) -> i32 {
let path = Path::new(path);
let _ = fs::remove_file(path);
let _ = fs::create_dir_all(path.parent().unwrap_or_else(|| Path::new("./")));
fn download_file(hwnd_parent: HWND, url: &str, path: &str) -> i32 {
let childhwnd;
let mut progress_bar: HWND = 0;
let mut progress_text: HWND = 0;
let mut downloading_text: HWND = 0;
let mut details_section: HWND = 0;
let mut details_section_resized = false;
let mut details_section_resized_back = false;
if hwnd_parent != 0 {
childhwnd = find_window(hwnd_parent, "#32770");
if childhwnd != 0 {
details_section = find_window(childhwnd, "SysListView32");
let expanded = is_visible(details_section);
unsafe {
progress_bar = CreateWindowExW(
0,
PROGRESS_CLASSW,
std::ptr::null(),
WS_CHILD | WS_VISIBLE,
0,
if expanded { 40 } else { 75 },
450,
18,
childhwnd,
0,
0,
std::ptr::null(),
);
downloading_text = CreateWindowExW(
0,
WC_STATICW,
std::ptr::null(),
WS_CHILD | WS_VISIBLE,
0,
if expanded { 60 } else { 95 },
450,
18,
childhwnd,
0,
0,
std::ptr::null(),
);
progress_text = CreateWindowExW(
0,
WC_STATICW,
std::ptr::null(),
WS_CHILD | WS_VISIBLE,
0,
if expanded { 78 } else { 113 },
450,
18,
childhwnd,
0,
0,
std::ptr::null(),
);
let font = SendMessageW(childhwnd, WM_GETFONT, 0, 0);
SendMessageW(downloading_text, WM_SETFONT, font as _, 0);
SendMessageW(progress_text, WM_SETFONT, font as _, 0);
};
}
}
let response = match ureq::get(url).call() {
Ok(data) => data,
@@ -39,18 +112,78 @@ fn download_file(url: &str, path: &str) -> i32 {
}
};
let total = response
.header("Content-Length")
.unwrap_or("0")
.parse::<u128>()
.unwrap();
let mut read = 0;
let mut reader = response.into_reader();
let mut reader = ProgressReader::new(&mut reader, |progress: usize| {
let expanded = is_visible(details_section);
if expanded && !details_section_resized {
unsafe {
SetWindowPos(progress_bar, 0, 0, 40, 0, 0, SWP_NOSIZE);
SetWindowPos(downloading_text, 0, 0, 60, 0, 0, SWP_NOSIZE);
SetWindowPos(progress_text, 0, 0, 78, 0, 0, SWP_NOSIZE);
if fs::File::create(path)
.and_then(|mut file| io::copy(&mut reader, &mut file))
.is_err()
// Check if file was created
|| !Path::new(&path).exists()
{
return 1;
}
SetWindowPos(details_section, 0, 0, 100, 450, 120, SWP_FRAMECHANGED);
}
details_section_resized = true;
}
0
read += progress;
let percentage = (read as f64 / total as f64) * 100.0;
unsafe { SendMessageW(progress_bar, PBM_SETPOS, percentage as _, 0) };
let text = pluginapi::encode_wide(format!(
"{} / {} KiB - {:.2}%",
read / 1024,
total / 1024,
percentage,
));
unsafe { SetWindowTextW(progress_text, text.as_ptr()) };
let text = pluginapi::encode_wide(format!("Downloading {} ...", url));
unsafe { SetWindowTextW(downloading_text, text.as_ptr()) };
if percentage >= 100. && !details_section_resized_back {
unsafe {
ShowWindow(progress_bar, SW_HIDE);
ShowWindow(progress_text, SW_HIDE);
ShowWindow(downloading_text, SW_HIDE);
SetWindowPos(details_section, 0, 0, 41, 450, 180, SWP_FRAMECHANGED);
}
details_section_resized_back = true;
}
});
let path = Path::new(path);
fs::create_dir_all(path.parent().unwrap_or_else(|| Path::new("."))).unwrap();
let mut file = fs::File::options()
.create(true)
.write(true)
.truncate(true)
.open(path)
.unwrap();
let res = io::copy(&mut reader, &mut file);
i32::from(res.is_err())
}
fn find_window(parent: HWND, class: impl AsRef<str>) -> HWND {
let class = pluginapi::encode_wide(class.as_ref());
unsafe { FindWindowExW(parent, 0, class.as_ptr(), std::ptr::null()) }
}
fn is_visible(hwnd: HWND) -> bool {
let style = unsafe { GetWindowLongPtrW(hwnd, GWL_STYLE) };
(style & !WS_VISIBLE as i32) != style
}
#[cfg(test)]
@@ -61,6 +194,7 @@ mod tests {
fn it_downloads() {
assert_eq!(
download_file(
0,
"https://go.microsoft.com/fwlink/p/?LinkId=2124703",
"wv2setup.exe"
),

View File

@@ -15,7 +15,7 @@ version = "0.1.0"
crate-type = [ "rlib", "cdylib" ]
[dependencies]
pluginapi = { path = "../pluginapi" }
pluginapi.workspace = true
semver = "1.0.16"
[dependencies.windows-sys]

View File

@@ -79,7 +79,7 @@ pub unsafe fn pushint(int: i32) {
pushstring(int.to_string())
}
fn encode_wide(string: impl AsRef<OsStr>) -> Vec<u16> {
pub fn encode_wide(string: impl AsRef<OsStr>) -> Vec<u16> {
string.as_ref().encode_wide().chain(once(0)).collect()
}

View File

@@ -1,6 +1,5 @@
Name "demo"
OutFile "demo.exe"
ShowInstDetails show
Unicode true
!addplugindir ".\target\release"
@@ -16,6 +15,11 @@ Unicode true
!addplugindir "$%CARGO_BUILD_TARGET_DIR%\i686-pc-windows-msvc\release"
!addplugindir "$%CARGO_BUILD_TARGET_DIR%\i686-pc-windows-msvc\debug"
!include "MUI2.nsh"
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_LANGUAGE "English"
Section
nsis_semvercompare::SemverCompare "1.0.0" "1.1.0"
Pop $1