Files
umbrix/lib/features/app_update/model/remote_version_entity.dart
Umbrix Developer 597d9f59ae
Some checks failed
CI / run (push) Has been cancelled
feat: Add portable ZIP update for Windows
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
2026-01-20 10:55:14 +03:00

121 lines
3.9 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:umbrix/core/model/environment.dart';
part 'remote_version_entity.freezed.dart';
@Freezed()
class RemoteVersionEntity with _$RemoteVersionEntity {
const RemoteVersionEntity._();
const factory RemoteVersionEntity({
required String version,
required String buildNumber,
required String releaseTag,
required bool preRelease,
required String url,
required DateTime publishedAt,
required Environment flavor,
@Default([]) List<ReleaseAsset> assets,
}) = _RemoteVersionEntity;
String get presentVersion => flavor == Environment.prod ? version : "$version ${flavor.name}";
/// Найти asset по расширению файла с умным определением
String? findAssetByExtension(String extension) {
try {
// Для Linux используем приоритет: .deb > .rpm > .AppImage
if (extension == '.deb' || extension == '.rpm' || extension == '.AppImage') {
final priorities = ['.deb', '.rpm', '.AppImage'];
for (final ext in priorities) {
try {
final asset = assets.firstWhere((asset) => asset.name.endsWith(ext));
return asset.downloadUrl;
} catch (_) {
continue;
}
}
return null;
}
// Для 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: 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(targetExt));
return asset.downloadUrl;
} catch (_) {
return null;
}
}
// Для macOS - ищем .dmg
if (extension == '.dmg') {
// Сначала ищем universal или arm64 (для M1/M2)
for (final pattern in ['universal', 'arm64', 'apple-silicon']) {
try {
final asset = assets.firstWhere(
(asset) => asset.name.toLowerCase().contains(pattern) && asset.name.endsWith('.dmg'),
);
return asset.downloadUrl;
} catch (_) {
continue;
}
}
// Если не нашли - берём любой .dmg
try {
final asset = assets.firstWhere((asset) => asset.name.endsWith('.dmg'));
return asset.downloadUrl;
} catch (_) {
return null;
}
}
// Для других расширений - прямой поиск
return assets.firstWhere((asset) => asset.name.endsWith(extension)).downloadUrl;
} catch (_) {
return null;
}
}
}
@freezed
class ReleaseAsset with _$ReleaseAsset {
const factory ReleaseAsset({
required String name,
required String downloadUrl,
required int size,
}) = _ReleaseAsset;
}