feature(updater): Implement auto-updating system using AppCast protocol
This commit is contained in:
@@ -2,6 +2,9 @@ final class Config {
|
||||
static const launcherApiBaseUrl = String.fromEnvironment(
|
||||
'MOONWELL_API_BASE_URL',
|
||||
);
|
||||
static const launcherAppcastUrl = String.fromEnvironment(
|
||||
'MOONWELL_APPCAST_URL',
|
||||
);
|
||||
static const launcherLogFileName = 'launcher.log';
|
||||
static const gameExecutableName = 'Wow.exe';
|
||||
static const cacheDirectoryName = 'Cache';
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-2
@@ -3,6 +3,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:moonwell_launcher/app/mw_app.dart';
|
||||
import 'package:moonwell_launcher/features/launcher/data/launcher_update_service.dart';
|
||||
import 'package:moonwell_launcher/service_container.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
@@ -10,6 +11,9 @@ Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await windowManager.ensureInitialized();
|
||||
|
||||
await configureDependencies(env: 'flutter');
|
||||
final launcherUpdateService = getIt<LauncherUpdateService>();
|
||||
|
||||
WindowOptions windowOptions = WindowOptions(
|
||||
size: Size(960, 540),
|
||||
center: true,
|
||||
@@ -22,10 +26,9 @@ Future<void> main() async {
|
||||
windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||
await windowManager.show();
|
||||
await windowManager.focus();
|
||||
await launcherUpdateService.checkForUpdates();
|
||||
}),
|
||||
);
|
||||
|
||||
await configureDependencies(env: 'flutter');
|
||||
|
||||
runApp(const MoonWellApp());
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import 'features/launcher/application/client_sync_use_case.dart' as _i757;
|
||||
import 'features/launcher/data/game_installation_service.dart' as _i315;
|
||||
import 'features/launcher/data/launcher_api_client.dart' as _i703;
|
||||
import 'features/launcher/data/launcher_log_service.dart' as _i43;
|
||||
import 'features/launcher/data/launcher_update_service.dart' as _i605;
|
||||
import 'features/preferences/data/repositories/local_preferences_repository.dart'
|
||||
as _i658;
|
||||
import 'features/preferences/domain/repositories/preferences_repository.dart'
|
||||
@@ -47,6 +48,9 @@ extension GetItInjectableX on _i174.GetIt {
|
||||
gh.lazySingleton<_i558.FlutterSecureStorage>(
|
||||
() => flutterSecureStorageModule.storage,
|
||||
);
|
||||
gh.lazySingleton<_i605.LauncherUpdateService>(
|
||||
() => _i605.LauncherUpdateService(gh<_i43.LauncherLogService>()),
|
||||
);
|
||||
gh.lazySingleton<_i703.LauncherApiClient>(
|
||||
() => _i703.LauncherApiClient(
|
||||
launcherLogService: gh<_i43.LauncherLogService>(),
|
||||
|
||||
Reference in New Issue
Block a user