mirror of
https://github.com/tauri-apps/nsis-tauri-utils.git
synced 2026-01-31 00:45:23 +01:00
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:
5
.changes/download-progressbar.md
Normal file
5
.changes/download-progressbar.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"nsis_download": "patch"
|
||||
---
|
||||
|
||||
Add download progress bar
|
||||
17
Cargo.toml
17
Cargo.toml
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
),
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
6
demo.nsi
6
demo.nsi
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user