Update loop incident — что было сломано¶
Несколько релизов подряд у юзеров случался бесконечный update prompt. Кликнул «обновить», скачался installer, поставился, перезапустился — снова prompt. И так каждый раз.
Не у всех — только у тех кто на single-file publish. И только когда мы накладывали update поверх previous single-file install.
Цепочка проблем¶
Шаг 1: single-file reflection¶
Уже описано. Reflection в single-file publish возвращает null или пустую строку. AppUpdateCheck думает что версия unknown, что разное с 1.0.0 → есть update → prompt.
Это корневая причина. Лечится marker файлом.
Шаг 2: маркер удалялся installer'ом¶
После добавления marker всё должно было работать. Не работало. Почему — потому что наш installer удалял %LocalAppData%\MiamiGraphics\ целиком при uninstall старой версии.
Type: filesandordirs; Name: "{localappdata}\MiamiGraphics"
Type: filesandordirs; Name: "{userappdata}\MiamiGraphics"
При upgrade Inno делает UninstallPrevious (uninstall старой версии перед установкой новой). UninstallDelete срабатывает — %LocalAppData%\MiamiGraphics\ удаляется. С marker'ом внутри.
После install новой версии:
- Marker отсутствует.
- AppUpdateCheck → reflection → пусто →
current = "unknown". - Latest в Supabase = "1.0.1" (та что мы только что установили).
hasUpdate = "unknown" != "1.0.1"→ true.- Prompt появляется опять.
Шаг 3: пытались записать marker в installer¶
Очевидное решение — пусть installer пишет marker сразу после install. Через Inno [Code] Pascal Script:
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssPostInstall then begin
SaveStringToFile(
ExpandConstant('{localappdata}\MiamiGraphics\config\installed_version.txt'),
'{#MyAppVersion}',
False);
end;
end;
Не сработало из-за порядка:
- Inno делает
UninstallPrevious→ удаляет%LocalAppData%\MiamiGraphics\. - Inno копирует файлы программы в
Program Files. - Inno вызывает
ssPostInstall→ пишет marker. - Шаг 3 происходит, но только если PostInstall достиг этого callback'а.
Если юзер кликнул «Cancel» во время install (или у installer'а упал error на копировании) — ssPostInstall не вызывается. Marker остаётся отсутствующим. При первом запуске старой/новой версии — опять loop.
Шаг 4: финальное решение — пишем marker ДО installer'а¶
Решение которое работает: наш собственный код пишет marker до того как мы запустим installer:
// 1. Скачали installer, проверили SHA
// 2. Пишем marker = "1.0.1" в локальный config
WriteInstalledVersionMarker(row.Version);
// 3. Запускаем installer через helper
Process.Start(...);
// 4. Сами exit'имся
Логика — мы знаем какую версию будет ставить юзер (это та строка из app_versions которая привела к prompt'у). Записываем её до запуска installer'а, в %LocalAppData%.
Дальше installer:
- UninstallPrevious удаляет старый install.
UninstallDeleteудаляет%LocalAppData%\MiamiGraphics\включая marker. ← опять проблема!
Шаг 5: убрать MiamiGraphics из UninstallDelete¶
Чтобы marker выживал uninstall старой версии — нужно не удалять %LocalAppData%\MiamiGraphics\ в [UninstallDelete]:
; Удаляем только OBSOLETE legacy папки, не текущую MiamiGraphics
Type: filesandordirs; Name: "{localappdata}\Miami Graphics" ← с пробелом, legacy
Type: filesandordirs; Name: "{localappdata}\HunterGraphics" ← старое название
; БЫЛО: Type: filesandordirs; Name: "{localappdata}\MiamiGraphics" ← без пробела, current — НЕ удаляем
Текущую MiamiGraphics\ не трогаем. Marker и backup переживают upgrade.
При полном uninstall (юзер кликает «Удалить программу» в «Программы и компоненты») — UninstallDelete тогда удаляет MiamiGraphics\. Это правильно — юзер явно удаляет всё.
Но как Inno различить «upgrade» vs «full uninstall»? Никак — это технически одно и то же. Решение — не удалять при любом uninstall, оставлять MiamiGraphics\ всегда. Юзер если захочет — почистит через Settings → Reset (factoryReset) или вручную.
Это правильнее семантически — %LocalAppData%\<app>\ это юзерские данные, не часть install'а.
Итог¶
Marker файл живёт через upgrade. Reflection остался fallback'ом. hasUpdate = latest != current сравнение работает корректно.
Если у юзера всё-таки есть update loop (марker как-то потёрся) — есть recovery path: Settings → Reset cache → factoryReset снесёт всё и перезапустит лаунчер. После перезапуска первый AppUpdateCheck покажет prompt, install запишет marker, дальше работает.
Что мы сделали по итогу¶
- Marker файл
installed_version.txt— основной источник версии для single-file builds. - Marker пишется ДО installer'а (в
AppUpdateInstallAsync). %LocalAppData%\MiamiGraphics\НЕ удаляется при upgrade.hasUpdate = latest != current(не >) — для rollback-релизов.AppIdстабильный в Inno — гарантирует UninstallPrevious.- PowerShell helper для запуска installer'а после нашего exit'а — без него
installer.exeне может перезаписать запущенныйMiami Graphics.exe.
Все эти шесть деталей надо иметь одновременно. Любая дыра возвращает loop.