[fix] expose retry and manual download after update failure

This commit is contained in:
dijunkun
2026-11-04 13:59:06 +08:00
parent 049cc0c8de
commit 47e16622f8
6 changed files with 123 additions and 8 deletions
@@ -1980,9 +1980,14 @@ void GuiApplication::BindMainCallbacks(bool gallery_window) {
if (updater_ && updater_->busy()) updater_->Cancel();
#endif
});
main->on_open_download([this, main] {
main->on_open_manual_download([this] {
const auto& latest = update_checker_->latest();
OpenUrl(latest && !latest->download_url.empty() ? latest->download_url
: "https://crossdesk.cn");
});
main->on_open_download([this, main] {
#if CROSSDESK_HAS_UPDATER
const auto& latest = update_checker_->latest();
if (updater_ && updater_->busy()) return;
if (updater_ && latest && updater_->Available(latest->update_envelope)) {
main->set_about_open(false);
@@ -1991,8 +1996,7 @@ void GuiApplication::BindMainCallbacks(bool gallery_window) {
return;
}
#endif
OpenUrl(latest && !latest->download_url.empty() ? latest->download_url
: "https://crossdesk.cn");
main->invoke_open_manual_download();
});
main->on_confirm_update([this, main] {
if (!main->get_update_confirm_open()) return;
@@ -2004,8 +2008,12 @@ void GuiApplication::BindMainCallbacks(bool gallery_window) {
!updater_->Available(latest->update_envelope)) return;
updater_->Start(latest->update_envelope);
// Disable both windows immediately, before the next polling tick.
for (const auto& window : {ui_->main, ui_->wall})
for (const auto& window : {ui_->main, ui_->wall}) {
window->set_update_install_busy(updater_->busy());
window->set_update_install_failed(
updater_->state() == updater::Controller::State::Failed);
window->set_update_install_status("");
}
#endif
});
about::Populate(ui_->Window(gallery_window));
@@ -2743,18 +2751,22 @@ void GuiApplication::PollUpdateCheck() {
}
const auto& latest = update_checker_->latest();
const bool can_install = latest && updater_->Available(latest->update_envelope);
const bool failed = updater_->state() == updater::Controller::State::Failed;
const int language = localization::detail::ClampLanguageIndex(localization_language_index_);
std::string status;
if (updater_->busy()) {
status = localization::update_preparing[language];
if (updater_->state() == updater::Controller::State::Downloading)
status += " " + std::to_string(updater_->progress()) + "%";
} else if (updater_->state() == updater::Controller::State::Failed) {
status = localization::update_install_failed[language] + "\n" + updater_->error();
} else if (failed) {
status = (can_install ? localization::update_install_failed[language]
: localization::update_install_failed_manual[language]) +
"\n" + updater_->error();
}
for (const auto& main : {ui_->main, ui_->wall}) {
main->set_update_install_available(can_install);
main->set_update_install_busy(updater_->busy());
main->set_update_install_failed(failed);
main->set_update_install_status(UiText(status));
}
}
@@ -519,6 +519,8 @@ inline constexpr TranslationRow kTranslationRows[] = {
{"请核对控制端指纹。", {"请核对控制端指纹。", "Check the fingerprint on the controller.", "Сверьте отпечаток с управляющим устройством.", "請核對控制端指紋。", "操作側のフィンガープリントと照合してください。", "제어 장치의 지문과 대조하세요.", "Mit dem Fingerabdruck des steuernden Geräts vergleichen.", "Vérifiez l’empreinte sur l’appareil de contrôle.", "Verifique la huella en el dispositivo de control.", "Confira a impressão digital no dispositivo controlador."}},
{"配对信息已复制。", {"配对信息已复制。", "Pairing information copied.", "Данные сопряжения скопированы.", "配對資訊已複製。", "ペアリング情報をコピーしました。", "페어링 정보를 복사했습니다.", "Kopplungsdaten kopiert.", "Informations d’association copiées.", "Información de emparejamiento copiada.", "Informações de pareamento copiadas."}},
{"无法复制配对信息,请重试。", {"无法复制配对信息,请重试。", "Unable to copy pairing information. Please try again.", "Не удалось скопировать данные сопряжения. Повторите попытку.", "無法複製配對資訊,請重試。", "ペアリング情報をコピーできません。もう一度お試しください。", "페어링 정보를 복사할 수 없습니다. 다시 시도하세요.", "Kopplungsdaten konnten nicht kopiert werden. Bitte erneut versuchen.", "Impossible de copier les informations d’association. Réessayez.", "No se pudo copiar la información de emparejamiento. Inténtelo de nuevo.", "Não foi possível copiar as informações de pareamento. Tente novamente."}},
{"重试更新", {"重试更新", "Retry update", "Повторить", "重試更新", "再試行", "다시 시도", "Erneut versuchen", "Réessayer", "Reintentar", "Tentar novamente"}},
{"更新未完成,请手动下载安装。", {"更新未完成,请手动下载安装。", "Update did not complete. Download and install manually.", "Обновление не завершено. Скачайте и установите вручную.", "更新未完成,請手動下載安裝。", "アップデートが完了しませんでした。手動でダウンロードしてインストールしてください。", "업데이트가 완료되지 않았습니다. 수동으로 다운로드하여 설치하세요.", "Das Update wurde nicht abgeschlossen. Laden Sie es herunter und installieren Sie es manuell.", "La mise à jour n'a pas été effectuée. Téléchargez-la et installez-la manuellement.", "La actualización no se completó. Descárguela e instálela manualmente.", "A atualização não foi concluída. Baixe e instale manualmente."}},
};
struct NamedTranslation { const char* name; const char* literal; };
@@ -733,10 +735,12 @@ inline constexpr NamedTranslation kNamedTranslations[] = {
{"access_website", "访问官网: "},
{"update", "更新"},
{"install_update", "安装更新"},
{"retry_update", "重试更新"},
{"manual_download", "手动下载"},
{"install_update_confirm", "下载完成后将关闭 CrossDesk 并安装更新,当前连接和文件传输会中断。安装需要系统授权,完成后重新启动。是否继续?"},
{"update_preparing", "正在准备更新…"},
{"update_install_failed", "更新未完成,可重试或手动安装。"},
{"update_install_failed_manual", "更新未完成,请手动下载安装。"},
{"connection_alias", "修改名称"},
{"delete_connection", "删除连接"},
{"connect_to_this_connection", "发起连接"},
@@ -1229,10 +1233,12 @@ inline constexpr const char* kSlintLiterals[] = {
X(access_website) \
X(update) \
X(install_update) \
X(retry_update) \
X(manual_download) \
X(install_update_confirm) \
X(update_preparing) \
X(update_install_failed) \
X(update_install_failed_manual) \
X(connection_alias) \
X(delete_connection) \
X(connect_to_this_connection) \
+13 -1
View File
@@ -108,6 +108,7 @@ export global UiStrings {
in-out property <string> later: "Later";
in-out property <string> download: "Download";
in-out property <string> install-update: "Install update";
in-out property <string> retry-update: "Retry update";
in-out property <string> install-update-confirm;
in-out property <string> input-password: "Input password";
in-out property <string> reinput-password: "Incorrect password, please try again";
@@ -593,6 +594,7 @@ export component MainWindow inherits Window {
in property <bool> update-available: false;
in property <bool> update-install-available: false;
in property <bool> update-install-busy: false;
in property <bool> update-install-failed: false;
changed update-install-available => {
if !root.update-install-available { root.update-confirm-open = false; }
}
@@ -600,7 +602,8 @@ export component MainWindow inherits Window {
if root.update-install-busy { root.update-confirm-open = false; }
}
in property <string> update-install-status: "";
private property <string> update-action-text: root.update-install-available ? UiStrings.install-update : UiStrings.download;
private property <string> update-action-text: !root.update-install-available ? UiStrings.download
: root.update-install-failed && !root.update-install-busy ? UiStrings.retry-update : UiStrings.install-update;
in property <UpdateCheckStatus> update-check-status: idle;
private property <string> update-status-text:
root.update-check-status == UpdateCheckStatus.checking ? UiStrings.checking-for-updates :
@@ -695,6 +698,7 @@ export component MainWindow inherits Window {
callback cancel-self-hosted-settings;
callback browse-save-path;
callback open-download;
callback open-manual-download;
callback confirm-update;
callback cancel-update;
callback open-website;
@@ -3300,6 +3304,14 @@ export component MainWindow inherits Window {
text: UiStrings.later;
clicked => { root.cancel-update(); root.update-open = false; }
}
if root.update-install-failed && root.update-install-available: WorkbenchButton {
width: max(110px, manual-download-metrics.preferred-width + 24px);
height: 34px;
enabled: !root.update-install-busy;
text: UiStrings.download;
clicked => { root.open-manual-download(); }
manual-download-metrics := Text { visible: false; text: UiStrings.download; font-size: 12px; }
}
WorkbenchButton {
width: max(110px, update-action-metrics.preferred-width + 24px);
height: 34px;
@@ -169,6 +169,7 @@ inline int ApplyMainWindowStrings(
strings.set_view_release_notes(Text(localization::view_release_notes[language]));
strings.set_download(Text(localization::manual_download[language]));
strings.set_install_update(Text(localization::install_update[language]));
strings.set_retry_update(Text(localization::retry_update[language]));
strings.set_install_update_confirm(
Text(localization::install_update_confirm[language]));
strings.set_input_password(Text(localization::input_password[language]));
@@ -3,6 +3,7 @@
#include "ui/model_update.h"
#include "ui/ui_localization.h"
#include <algorithm>
#include <iostream>
#include <fstream>
#include <cstdlib>
@@ -330,6 +331,60 @@ bool VerifyGalleryPopups(RenderTestPlatform& platform) {
return Check(!wall->get_gallery_adding(), "Add dialog Cancel must respond immediately");
}
bool VerifyUpdateRecovery(RenderTestPlatform& platform) {
auto main = CreateMainWindow();
auto& screen = *platform.adapter;
const float width = main->get_minimum_window_width();
const float height = main->get_minimum_window_height();
main->window().set_size(slint::LogicalSize({width, height}));
main->set_update_available(true);
main->set_update_install_available(true);
main->set_latest_version("v2.0.0");
int install_requests = 0, manual_downloads = 0;
main->on_open_download([&] { ++install_requests; main->set_update_confirm_open(true); });
main->on_open_manual_download([&] { ++manual_downloads; });
main->show();
screen.Settle();
const float button_y = height / 2 + std::min(488.f, height - 32) / 2 - 41;
const float action_x = width / 2 + 161;
const float manual_x = action_x - 118;
const auto action_pixels = [&] {
return screen.Region(int(action_x - 40), int(button_y - 12), 80, 24);
};
const auto install_label = action_pixels();
main->set_update_install_failed(true);
main->set_update_install_status(crossdesk::ui_localization::Text(
crossdesk::localization::update_install_failed[0] + "\nDownload failed"));
screen.Settle();
if (!Check(action_pixels() != install_label,
"A failed update must visibly replace Install update with Retry update")) return false;
screen.Click(manual_x, button_y);
if (!Check(manual_downloads == 1 && install_requests == 0 && !main->get_update_confirm_open(),
"Manual download must remain reachable after failure without starting automatic installation")) return false;
screen.Click(action_x, button_y);
if (!Check(install_requests == 1 && main->get_update_confirm_open(),
"Retry must open the installation confirmation")) return false;
screen.Key(slint::platform::key_codes::Escape);
if (!Check(!main->get_update_confirm_open(), "Retry confirmation must remain cancellable")) return false;
main->set_update_install_busy(true);
screen.Settle();
screen.Click(action_x, button_y);
screen.Click(manual_x, button_y);
if (!Check(install_requests == 1 && manual_downloads == 1,
"Recovery buttons must reject repeated clicks while an update is running")) return false;
main->set_update_install_busy(false);
main->set_update_install_available(false);
main->set_update_install_status(crossdesk::ui_localization::Text(
crossdesk::localization::update_install_failed_manual[0]));
// The application's primary action falls back to manual download when the
// current installation or signed manifest no longer supports automatic updates.
main->on_open_download([&] { ++manual_downloads; });
screen.Settle();
screen.Click(action_x, button_y);
return Check(manual_downloads == 2 && !main->get_update_confirm_open(),
"An unavailable automatic retry must retain the primary manual download action");
}
bool VerifyServerPanel(RenderTestPlatform& platform) {
auto panel = crossdesk::ui::ServerWindow::create();
auto& screen = *platform.adapter;
@@ -377,5 +432,6 @@ int main() {
if (!VerifyGalleryPopups(*backend)) return 3;
if (!VerifyServerPanel(*backend)) return 4;
if (!VerifyAutostartSettings(*backend)) return 5;
if (!VerifyUpdateRecovery(*backend)) return 6;
std::cout << "Slint popup rendering and local input passed.\n";
}
+29 -1
View File
@@ -10571,6 +10571,30 @@
"fr": "Impossible de copier les informations d’association. Réessayez.",
"es": "No se pudo copiar la información de emparejamiento. Inténtelo de nuevo.",
"pt-BR": "Não foi possível copiar as informações de pareamento. Tente novamente."
},
"重试更新": {
"zh-Hans": "重试更新",
"en": "Retry update",
"ru": "Повторить",
"zh-Hant": "重試更新",
"ja": "再試行",
"ko": "다시 시도",
"de": "Erneut versuchen",
"fr": "Réessayer",
"es": "Reintentar",
"pt-BR": "Tentar novamente"
},
"更新未完成,请手动下载安装。": {
"zh-Hans": "更新未完成,请手动下载安装。",
"en": "Update did not complete. Download and install manually.",
"ru": "Обновление не завершено. Скачайте и установите вручную.",
"zh-Hant": "更新未完成,請手動下載安裝。",
"ja": "アップデートが完了しませんでした。手動でダウンロードしてインストールしてください。",
"ko": "업데이트가 완료되지 않았습니다. 수동으로 다운로드하여 설치하세요.",
"de": "Das Update wurde nicht abgeschlossen. Laden Sie es herunter und installieren Sie es manuell.",
"fr": "La mise à jour n'a pas été effectuée. Téléchargez-la et installez-la manuellement.",
"es": "La actualización no se completó. Descárguela e instálela manualmente.",
"pt-BR": "A atualização não foi concluída. Baixe e instale manualmente."
}
},
"desktop": {
@@ -10784,10 +10808,12 @@
"access_website": "访问官网: ",
"update": "更新",
"install_update": "安装更新",
"retry_update": "重试更新",
"manual_download": "手动下载",
"install_update_confirm": "下载完成后将关闭 CrossDesk 并安装更新,当前连接和文件传输会中断。安装需要系统授权,完成后重新启动。是否继续?",
"update_preparing": "正在准备更新…",
"update_install_failed": "更新未完成,可重试或手动安装。",
"update_install_failed_manual": "更新未完成,请手动下载安装。",
"connection_alias": "修改名称",
"delete_connection": "删除连接",
"connect_to_this_connection": "发起连接",
@@ -11574,7 +11600,9 @@
"暂无可信设备",
"请核对控制端指纹。",
"配对信息已复制。",
"无法复制配对信息,请重试。"
"无法复制配对信息,请重试。",
"重试更新",
"更新未完成,请手动下载安装。"
],
"android": [
" · 已完成",