Two update methods now available: 1. EXE installer (requires UAC, shows SmartScreen without signature) Changes: - Added .zip asset detection with priority (portable/windows/win) - ZIP auto-install: extract → replace files → restart - Priority: .zip → .exe (portable first for better UX) - Batch script handles file replacement after app closes - No administrator rights needed for ZIP updates Benefits of ZIP: ✅ No UAC prompts ✅ No SmartScreen warnings ✅ Fast updates (just file replacement) ✅ Perfect for testing without code signing ✅ Fallback to .exe if .zip not available
This commit is contained in:
@@ -38,23 +38,41 @@ class RemoteVersionEntity with _$RemoteVersionEntity {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Для Windows - ищем .exe с приоритетом x64
|
||||
if (extension == '.exe') {
|
||||
// Сначала ищем x64 setup/installer
|
||||
for (final pattern in ['x64', 'amd64', 'win64', 'setup', 'installer']) {
|
||||
try {
|
||||
final asset = assets.firstWhere(
|
||||
(asset) => asset.name.toLowerCase().contains(pattern) && asset.name.endsWith('.exe'),
|
||||
);
|
||||
return asset.downloadUrl;
|
||||
} catch (_) {
|
||||
continue;
|
||||
// Для Windows - ищем .exe или .zip
|
||||
if (extension == '.exe' || extension == '.zip') {
|
||||
final targetExt = extension;
|
||||
|
||||
// Приоритет для zip: portable -> windows -> любой .zip
|
||||
if (targetExt == '.zip') {
|
||||
for (final pattern in ['portable', 'windows', 'win']) {
|
||||
try {
|
||||
final asset = assets.firstWhere(
|
||||
(asset) => asset.name.toLowerCase().contains(pattern) && asset.name.endsWith('.zip'),
|
||||
);
|
||||
return asset.downloadUrl;
|
||||
} catch (_) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Если не нашли специфичный - берём любой .exe
|
||||
// Приоритет для exe: x64 setup/installer
|
||||
if (targetExt == '.exe') {
|
||||
for (final pattern in ['x64', 'amd64', 'win64', 'setup', 'installer']) {
|
||||
try {
|
||||
final asset = assets.firstWhere(
|
||||
(asset) => asset.name.toLowerCase().contains(pattern) && asset.name.endsWith('.exe'),
|
||||
);
|
||||
return asset.downloadUrl;
|
||||
} catch (_) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Если не нашли специфичный - берём любой с нужным расширением
|
||||
try {
|
||||
final asset = assets.firstWhere((asset) => asset.name.endsWith('.exe'));
|
||||
final asset = assets.firstWhere((asset) => asset.name.endsWith(targetExt));
|
||||
return asset.downloadUrl;
|
||||
} catch (_) {
|
||||
return null;
|
||||
|
||||
@@ -73,9 +73,15 @@ class NewVersionDialog extends HookConsumerWidget with PresLogger {
|
||||
|
||||
// Определяем нужное расширение файла
|
||||
String fileExt = '';
|
||||
if (Platform.isWindows)
|
||||
fileExt = '.exe';
|
||||
else if (Platform.isMacOS)
|
||||
if (Platform.isWindows) {
|
||||
// Для Windows приоритет: .zip (portable) → .exe (installer)
|
||||
// ZIP не требует UAC и подписи кода!
|
||||
fileExt = '.zip';
|
||||
final zipUrl = newVersion.findAssetByExtension('.zip');
|
||||
if (zipUrl == null) {
|
||||
fileExt = '.exe'; // Fallback на .exe если нет .zip
|
||||
}
|
||||
} else if (Platform.isMacOS)
|
||||
fileExt = '.dmg';
|
||||
else if (Platform.isLinux) fileExt = '.deb';
|
||||
|
||||
@@ -166,6 +172,84 @@ class NewVersionDialog extends HookConsumerWidget with PresLogger {
|
||||
}
|
||||
}
|
||||
|
||||
// Windows portable ZIP update
|
||||
if (Platform.isWindows && fileExt == '.zip') {
|
||||
try {
|
||||
if (context.mounted) {
|
||||
CustomToast('Установка обновления из ZIP...', type: AlertType.info).show(context);
|
||||
}
|
||||
|
||||
// Получить путь к исполняемому файлу приложения
|
||||
final exePath = Platform.resolvedExecutable;
|
||||
final appDir = Directory(exePath).parent.path;
|
||||
|
||||
// Распаковать во временную папку
|
||||
final tempDir = Directory('${Directory.systemTemp.path}\\umbrix_update_${DateTime.now().millisecondsSinceEpoch}');
|
||||
await tempDir.create(recursive: true);
|
||||
|
||||
loggy.info('Extracting ZIP to: ${tempDir.path}');
|
||||
|
||||
// Распаковка через PowerShell
|
||||
final extractResult = await Process.run(
|
||||
'powershell',
|
||||
[
|
||||
'-Command',
|
||||
'Expand-Archive',
|
||||
'-Path',
|
||||
'"$savePath"',
|
||||
'-DestinationPath',
|
||||
'"${tempDir.path}"',
|
||||
'-Force'
|
||||
],
|
||||
);
|
||||
|
||||
if (extractResult.exitCode != 0) {
|
||||
throw Exception('Failed to extract ZIP: ${extractResult.stderr}');
|
||||
}
|
||||
|
||||
loggy.info('ZIP extracted successfully');
|
||||
|
||||
// Скрипт для замены файлов после закрытия приложения
|
||||
final updateScript = '''
|
||||
@echo off
|
||||
echo Waiting for application to close...
|
||||
timeout /t 3 /nobreak > nul
|
||||
|
||||
echo Updating files...
|
||||
xcopy /E /Y "${tempDir.path}\\*" "$appDir\\"
|
||||
|
||||
echo Cleanup...
|
||||
rmdir /S /Q "${tempDir.path}"
|
||||
|
||||
echo Starting application...
|
||||
start "" "$exePath"
|
||||
|
||||
echo Update complete!
|
||||
del "%~f0"
|
||||
''';
|
||||
|
||||
final scriptPath = '${Directory.systemTemp.path}\\umbrix_update.bat';
|
||||
await File(scriptPath).writeAsString(updateScript);
|
||||
|
||||
if (context.mounted) {
|
||||
CustomToast.success('Обновление установлено! Приложение перезагрузится...').show(context);
|
||||
context.pop();
|
||||
}
|
||||
|
||||
// Запустить скрипт и закрыть приложение
|
||||
await Process.start('cmd', ['/c', scriptPath], mode: ProcessStartMode.detached);
|
||||
|
||||
// Задержка перед выходом
|
||||
Future.delayed(const Duration(seconds: 1), () {
|
||||
exit(0);
|
||||
});
|
||||
|
||||
return;
|
||||
} catch (e) {
|
||||
loggy.warning('Failed to install from ZIP: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Для других платформ или если автоустановка не сработала - просто открываем файл
|
||||
final result = await OpenFile.open(savePath);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user