Skip to content

Commit 06a8aa4

Browse files
committed
fix(wizard): add startup splash and tighten update checks
1 parent b117b0e commit 06a8aa4

4 files changed

Lines changed: 221 additions & 17 deletions

File tree

src/app.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ enum UpdateStatus {
121121
Checking,
122122
Available(UpdateInfo),
123123
Current,
124-
Error(String),
124+
Error,
125125
}
126126

127127
struct App {
@@ -276,7 +276,7 @@ impl App {
276276
}
277277
}
278278
UpdateCheckState::Error(error) => {
279-
self.update_status = UpdateStatus::Error(error.clone());
279+
self.update_status = UpdateStatus::Error;
280280
if outcome.manual {
281281
self.last_status = format!("Update check failed: {error}");
282282
}
@@ -862,7 +862,7 @@ impl App {
862862
UpdateStatus::Checking => "Update: checking...".to_string(),
863863
UpdateStatus::Available(info) => format!("Update: {} available", info.version),
864864
UpdateStatus::Current => format!("Update: current ({})", env!("CARGO_PKG_VERSION")),
865-
UpdateStatus::Error(_) => "Update: check failed".to_string(),
865+
UpdateStatus::Error => "Update: check failed".to_string(),
866866
}
867867
}
868868

@@ -871,8 +871,8 @@ impl App {
871871
"Opening Setup Wizard...".to_string()
872872
} else if let UpdateStatus::Available(info) = &self.update_status {
873873
format!("Update available - {}", info.version)
874-
} else if let UpdateStatus::Error(error) = &self.update_status {
875-
truncate(&format!("Update check failed: {error}"), 44)
874+
} else if matches!(self.update_status, UpdateStatus::Error) {
875+
"Update check failed".to_string()
876876
} else if self.config_error.is_some() {
877877
"Config error - open Setup Wizard".to_string()
878878
} else if self.syncing {

src/platform.rs

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ pub fn eject_drive(root: &Path) -> Result<()> {
4545
pub fn open_path(path: &Path) -> Result<()> {
4646
#[cfg(target_os = "windows")]
4747
{
48-
return run_hidden("explorer.exe", &[&path.display().to_string()]);
48+
return start_process_windows(&path.display().to_string());
4949
}
5050
#[cfg(target_os = "macos")]
5151
{
@@ -62,7 +62,7 @@ pub fn open_path(path: &Path) -> Result<()> {
6262
pub fn open_url(url: &str) -> Result<()> {
6363
#[cfg(target_os = "windows")]
6464
{
65-
return run_hidden("explorer.exe", &[url]);
65+
return start_process_windows(url);
6666
}
6767
#[cfg(target_os = "macos")]
6868
{
@@ -116,6 +116,68 @@ pub fn prompt_for_update(current_version: &str, latest_version: &str) -> bool {
116116
)
117117
}
118118

119+
pub fn show_wizard_loading_indicator(signal_path: &Path) -> Result<()> {
120+
#[cfg(target_os = "windows")]
121+
{
122+
let signal_path = powershell_single_quoted(&signal_path.display().to_string());
123+
let script = format!(
124+
"Add-Type -AssemblyName System.Windows.Forms; \
125+
Add-Type -AssemblyName System.Drawing; \
126+
$signal = '{signal_path}'; \
127+
$form = New-Object System.Windows.Forms.Form; \
128+
$form.Text = 'ShadowSync'; \
129+
$form.StartPosition = 'CenterScreen'; \
130+
$form.Size = New-Object System.Drawing.Size(360, 128); \
131+
$form.TopMost = $true; \
132+
$form.FormBorderStyle = 'FixedDialog'; \
133+
$form.ControlBox = $false; \
134+
$form.MinimizeBox = $false; \
135+
$form.MaximizeBox = $false; \
136+
$label = New-Object System.Windows.Forms.Label; \
137+
$label.Text = 'Opening Setup Wizard...'; \
138+
$label.AutoSize = $true; \
139+
$label.Location = New-Object System.Drawing.Point(22, 18); \
140+
$label.Font = New-Object System.Drawing.Font('Segoe UI', 11); \
141+
$bar = New-Object System.Windows.Forms.ProgressBar; \
142+
$bar.Style = 'Marquee'; \
143+
$bar.MarqueeAnimationSpeed = 25; \
144+
$bar.Size = New-Object System.Drawing.Size(300, 20); \
145+
$bar.Location = New-Object System.Drawing.Point(22, 54); \
146+
$hint = New-Object System.Windows.Forms.Label; \
147+
$hint.Text = 'This should only take a moment.'; \
148+
$hint.AutoSize = $true; \
149+
$hint.Location = New-Object System.Drawing.Point(22, 82); \
150+
$hint.ForeColor = [System.Drawing.Color]::DimGray; \
151+
$form.Controls.Add($label); \
152+
$form.Controls.Add($bar); \
153+
$form.Controls.Add($hint); \
154+
$timer = New-Object System.Windows.Forms.Timer; \
155+
$timer.Interval = 150; \
156+
$timer.Add_Tick({{ if (-not (Test-Path -LiteralPath $signal)) {{ $form.Close() }} }}); \
157+
$timer.Start(); \
158+
$timeout = New-Object System.Windows.Forms.Timer; \
159+
$timeout.Interval = 20000; \
160+
$timeout.Add_Tick({{ $form.Close() }}); \
161+
$timeout.Start(); \
162+
[void]$form.ShowDialog()"
163+
);
164+
return run_hidden_detached(
165+
"powershell.exe",
166+
&[
167+
"-NoLogo",
168+
"-NoProfile",
169+
"-NonInteractive",
170+
"-ExecutionPolicy",
171+
"Bypass",
172+
"-Command",
173+
&script,
174+
],
175+
);
176+
}
177+
#[allow(unreachable_code)]
178+
Ok(())
179+
}
180+
119181
pub fn sleep_short(duration: Duration) {
120182
thread::sleep(duration);
121183
}
@@ -227,6 +289,44 @@ fn run_hidden(program: &str, args: &[&str]) -> Result<()> {
227289
}
228290
}
229291

292+
#[cfg(target_os = "windows")]
293+
fn run_hidden_detached(program: &str, args: &[&str]) -> Result<()> {
294+
use std::os::windows::process::CommandExt;
295+
296+
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
297+
Command::new(program)
298+
.creation_flags(CREATE_NO_WINDOW)
299+
.args(args)
300+
.spawn()
301+
.with_context(|| format!("failed to start {program}"))?;
302+
Ok(())
303+
}
304+
305+
#[cfg(target_os = "windows")]
306+
fn start_process_windows(target: &str) -> Result<()> {
307+
let command = format!(
308+
"Start-Process -FilePath '{}'",
309+
powershell_single_quoted(target)
310+
);
311+
run_hidden(
312+
"powershell.exe",
313+
&[
314+
"-NoLogo",
315+
"-NoProfile",
316+
"-NonInteractive",
317+
"-ExecutionPolicy",
318+
"Bypass",
319+
"-Command",
320+
&command,
321+
],
322+
)
323+
}
324+
325+
#[cfg(target_os = "windows")]
326+
fn powershell_single_quoted(value: &str) -> String {
327+
value.replace('\'', "''")
328+
}
329+
230330
#[cfg(not(target_os = "windows"))]
231331
fn run_status(program: &str, args: &[&str]) -> Result<()> {
232332
let status = Command::new(program)

src/update.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ use serde::{Deserialize, Serialize};
99

1010
use crate::config::AppPaths;
1111

12-
const GITHUB_LATEST_RELEASE_API: &str =
13-
"https://api.github.com/repos/RadNotRed/ShadowSync/releases/latest";
12+
const GITHUB_RELEASES_API: &str =
13+
"https://api.github.com/repos/RadNotRed/ShadowSync/releases?per_page=10";
1414
pub const RELEASES_PAGE_URL: &str = "https://github.com/RadNotRed/ShadowSync/releases";
1515
const UPDATE_CACHE_FILE: &str = "update-state.json";
1616
const AUTOMATIC_CHECK_INTERVAL: Duration = Duration::from_secs(24 * 60 * 60);
@@ -48,6 +48,8 @@ struct CachedUpdateState {
4848
struct GitHubRelease {
4949
tag_name: String,
5050
html_url: String,
51+
#[serde(default)]
52+
draft: bool,
5153
}
5254

5355
pub fn load_cached_available_update(paths: &AppPaths, current_version: &str) -> Option<UpdateInfo> {
@@ -119,17 +121,22 @@ fn fetch_latest_release(current_version: &str) -> Result<Option<UpdateInfo>> {
119121
.timeout(Duration::from_secs(10))
120122
.build()
121123
.context("failed to build update client")?;
122-
let release = client
123-
.get(GITHUB_LATEST_RELEASE_API)
124+
let releases = client
125+
.get(GITHUB_RELEASES_API)
124126
.header("User-Agent", format!("ShadowSync/{current_version}"))
125127
.header("Accept", "application/vnd.github+json")
128+
.header("X-GitHub-Api-Version", "2022-11-28")
126129
.send()
127130
.context("failed to contact GitHub Releases")?
128131
.error_for_status()
129132
.context("GitHub Releases responded with an error")?
130-
.json::<GitHubRelease>()
133+
.json::<Vec<GitHubRelease>>()
131134
.context("failed to parse the GitHub release response")?;
132135

136+
let Some(release) = releases.into_iter().find(|release| !release.draft) else {
137+
return Ok(None);
138+
};
139+
133140
let version = normalize_tag(&release.tag_name);
134141
if !is_newer_version(&version, current_version) {
135142
return Ok(None);

0 commit comments

Comments
 (0)