feature(updater): Implement auto-updating system using AppCast protocol

This commit is contained in:
2026-06-22 02:30:50 +04:00
parent c23b4bd0da
commit 602d294da4
18 changed files with 524 additions and 9 deletions
@@ -0,0 +1,99 @@
import 'dart:io';
import 'package:auto_updater/auto_updater.dart';
import 'package:injectable/injectable.dart';
import 'package:moonwell_launcher/config.dart';
import 'package:moonwell_launcher/features/launcher/data/launcher_log_service.dart';
abstract interface class LauncherUpdateClient {
Future<void> setFeedUrl(String feedUrl);
Future<void> disableScheduledChecks();
Future<void> checkInBackground();
}
final class AutoUpdaterLauncherUpdateClient implements LauncherUpdateClient {
@override
Future<void> setFeedUrl(String feedUrl) => autoUpdater.setFeedURL(feedUrl);
@override
Future<void> disableScheduledChecks() =>
autoUpdater.setScheduledCheckInterval(0);
@override
Future<void> checkInBackground() =>
autoUpdater.checkForUpdates(inBackground: true);
}
@lazySingleton
class LauncherUpdateService {
LauncherUpdateService(LauncherLogService launcherLogService)
: this.forTesting(
launcherLogService: launcherLogService,
updateClient: AutoUpdaterLauncherUpdateClient(),
isWindows: Platform.isWindows,
);
LauncherUpdateService.forTesting({
required LauncherLogService launcherLogService,
required LauncherUpdateClient updateClient,
required bool isWindows,
}) : _launcherLogService = launcherLogService,
_updateClient = updateClient,
_isWindows = isWindows;
final LauncherLogService _launcherLogService;
final LauncherUpdateClient _updateClient;
final bool _isWindows;
bool _hasChecked = false;
Future<void> checkForUpdates({
String feedUrl = Config.launcherAppcastUrl,
}) async {
if (!_isWindows || _hasChecked) {
return;
}
final normalizedFeedUrl = feedUrl.trim();
if (normalizedFeedUrl.isEmpty) {
await _launcherLogService.info(
'Launcher self-update is disabled because no AppCast URL is configured.',
);
return;
}
final uri = Uri.tryParse(normalizedFeedUrl);
if (uri == null ||
!uri.isAbsolute ||
uri.scheme != 'https' ||
!uri.hasAuthority ||
uri.host.isEmpty) {
await _launcherLogService.error(
'Launcher self-update AppCast URL is invalid.',
details: <String, Object?>{'feedUrl': normalizedFeedUrl},
);
return;
}
_hasChecked = true;
try {
await _updateClient.setFeedUrl(normalizedFeedUrl);
await _updateClient.disableScheduledChecks();
await _updateClient.checkInBackground();
await _launcherLogService.info(
'Launcher self-update check started.',
details: <String, Object?>{'feedUrl': normalizedFeedUrl},
);
} catch (error, stackTrace) {
await _launcherLogService.error(
'Launcher self-update check failed.',
details: <String, Object?>{'feedUrl': normalizedFeedUrl},
error: error,
stackTrace: stackTrace,
);
}
}
}