fix(update): check valid update files
Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
parent
d4a1430c27
commit
4638e0f526
10 changed files with 1484 additions and 269 deletions
|
|
@ -7,10 +7,10 @@ import 'package:flutter_hbb/models/platform_model.dart';
|
|||
import 'package:get/get.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
final _isExtracting = false.obs;
|
||||
const _eventKeyUpdateMe = 'update-me';
|
||||
const _eventKeyUpdateMeReady = 'update-me-ready';
|
||||
|
||||
void handleUpdate(String releasePageUrl) {
|
||||
_isExtracting.value = false;
|
||||
String downloadUrl = releasePageUrl.replaceAll('tag', 'download');
|
||||
String version = downloadUrl.substring(downloadUrl.lastIndexOf('/') + 1);
|
||||
final String downloadFile =
|
||||
|
|
@ -23,46 +23,112 @@ void handleUpdate(String releasePageUrl) {
|
|||
}
|
||||
downloadUrl = '$downloadUrl/$downloadFile';
|
||||
|
||||
SimpleWrapper downloadId = SimpleWrapper('');
|
||||
SimpleWrapper<String> downloadId = SimpleWrapper('');
|
||||
SimpleWrapper<VoidCallback> onCanceled = SimpleWrapper(() {});
|
||||
SimpleWrapper<bool> pendingCancel = SimpleWrapper(false);
|
||||
SimpleWrapper<Future<void> Function()> cancelDownload =
|
||||
SimpleWrapper(() async {});
|
||||
gFFI.dialogManager.dismissAll();
|
||||
gFFI.dialogManager.show((setState, close, context) {
|
||||
cancelDownload.value = () async {
|
||||
final id = downloadId.value;
|
||||
if (id.isEmpty) {
|
||||
pendingCancel.value = true;
|
||||
return;
|
||||
}
|
||||
pendingCancel.value = false;
|
||||
onCanceled.value();
|
||||
await bind.mainSetCommon(key: 'cancel-downloader', value: id);
|
||||
// Wait for the downloader to be removed.
|
||||
for (int i = 0; i < 10; i++) {
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
final isCanceled = 'error:Downloader not found' ==
|
||||
await bind.mainGetCommon(key: 'download-data-$id');
|
||||
if (isCanceled) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
close();
|
||||
};
|
||||
return CustomAlertDialog(
|
||||
title: Obx(() => Text(translate(_isExtracting.isTrue
|
||||
? 'Preparing for installation ...'
|
||||
: 'Downloading {$appName}'))),
|
||||
content:
|
||||
UpdateProgress(releasePageUrl, downloadUrl, downloadId, onCanceled)
|
||||
.marginSymmetric(horizontal: 8)
|
||||
.paddingOnly(top: 12),
|
||||
title: Text(translate('Downloading {$appName}')),
|
||||
content: UpdateProgress(releasePageUrl, downloadUrl, downloadId,
|
||||
onCanceled, pendingCancel, cancelDownload)
|
||||
.marginSymmetric(horizontal: 8)
|
||||
.paddingOnly(top: 12),
|
||||
actions: [
|
||||
if (_isExtracting.isFalse) dialogButton(translate('Cancel'), onPressed: () async {
|
||||
onCanceled.value();
|
||||
await bind.mainSetCommon(
|
||||
key: 'cancel-downloader', value: downloadId.value);
|
||||
// Wait for the downloader to be removed.
|
||||
for (int i = 0; i < 10; i++) {
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
final isCanceled = 'error:Downloader not found' ==
|
||||
await bind.mainGetCommon(
|
||||
key: 'download-data-${downloadId.value}');
|
||||
if (isCanceled) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
close();
|
||||
dialogButton(translate('Cancel'), onPressed: () async {
|
||||
await cancelDownload.value();
|
||||
}, isOutline: true),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
void _showUpdateError(String releasePageUrl, String error,
|
||||
{bool showRetry = true}) {
|
||||
debugPrint('Update error: $error');
|
||||
final dialogManager = gFFI.dialogManager;
|
||||
|
||||
jumplink() {
|
||||
launchUrl(Uri.parse(releasePageUrl));
|
||||
dialogManager.dismissAll();
|
||||
}
|
||||
|
||||
retry() {
|
||||
dialogManager.dismissAll();
|
||||
handleUpdate(releasePageUrl);
|
||||
}
|
||||
|
||||
dialogManager.dismissAll();
|
||||
dialogManager.show(
|
||||
(setState, close, context) => CustomAlertDialog(
|
||||
title: null,
|
||||
content: SelectionArea(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
msgboxContent('custom-nocancel-nook-hasclose', 'Error',
|
||||
'download-new-version-failed-tip'),
|
||||
const SizedBox(height: 8),
|
||||
Text(error),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
dialogButton('Download', onPressed: jumplink),
|
||||
if (showRetry) dialogButton('Retry', onPressed: retry),
|
||||
dialogButton('Close', onPressed: close),
|
||||
],
|
||||
),
|
||||
tag: 'custom-nocancel-nook-hasclose-Error-Error',
|
||||
);
|
||||
}
|
||||
|
||||
void _showPreparingForInstallation() {
|
||||
gFFI.dialogManager.dismissAll();
|
||||
gFFI.dialogManager.show(
|
||||
(setState, close, context) => CustomAlertDialog(
|
||||
title: Text(translate('Preparing for installation ...')),
|
||||
content: const LinearProgressIndicator(
|
||||
minHeight: 20,
|
||||
borderRadius: BorderRadius.all(Radius.circular(5)),
|
||||
).marginSymmetric(horizontal: 8).paddingOnly(top: 12),
|
||||
actions: const [],
|
||||
),
|
||||
tag: 'preparing-for-installation',
|
||||
);
|
||||
}
|
||||
|
||||
class UpdateProgress extends StatefulWidget {
|
||||
final String releasePageUrl;
|
||||
final String downloadUrl;
|
||||
final SimpleWrapper downloadId;
|
||||
final SimpleWrapper onCanceled;
|
||||
UpdateProgress(
|
||||
this.releasePageUrl, this.downloadUrl, this.downloadId, this.onCanceled,
|
||||
final SimpleWrapper<String> downloadId;
|
||||
final SimpleWrapper<VoidCallback> onCanceled;
|
||||
final SimpleWrapper<bool> pendingCancel;
|
||||
final SimpleWrapper<Future<void> Function()> cancelDownload;
|
||||
UpdateProgress(this.releasePageUrl, this.downloadUrl, this.downloadId,
|
||||
this.onCanceled, this.pendingCancel, this.cancelDownload,
|
||||
{Key? key})
|
||||
: super(key: key);
|
||||
|
||||
|
|
@ -76,7 +142,6 @@ class UpdateProgressState extends State<UpdateProgress> {
|
|||
int _downloadedSize = 0;
|
||||
int _getDataFailedCount = 0;
|
||||
final String _eventKeyDownloadNewVersion = 'download-new-version';
|
||||
final String _eventKeyExtractUpdateDmg = 'extract-update-dmg';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -88,11 +153,6 @@ class UpdateProgressState extends State<UpdateProgress> {
|
|||
_eventKeyDownloadNewVersion, handleDownloadNewVersion,
|
||||
replace: true);
|
||||
bind.mainSetCommon(key: 'download-new-version', value: widget.downloadUrl);
|
||||
if (isMacOS) {
|
||||
platformFFI.registerEventHandler(_eventKeyExtractUpdateDmg,
|
||||
_eventKeyExtractUpdateDmg, handleExtractUpdateDmg,
|
||||
replace: true);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -100,10 +160,6 @@ class UpdateProgressState extends State<UpdateProgress> {
|
|||
cancelQueryTimer();
|
||||
platformFFI.unregisterEventHandler(
|
||||
_eventKeyDownloadNewVersion, _eventKeyDownloadNewVersion);
|
||||
if (isMacOS) {
|
||||
platformFFI.unregisterEventHandler(
|
||||
_eventKeyExtractUpdateDmg, _eventKeyExtractUpdateDmg);
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
@ -118,6 +174,9 @@ class UpdateProgressState extends State<UpdateProgress> {
|
|||
_timer = Timer.periodic(const Duration(milliseconds: 300), (timer) {
|
||||
_updateDownloadData();
|
||||
});
|
||||
if (widget.pendingCancel.value) {
|
||||
await widget.cancelDownload.value();
|
||||
}
|
||||
} else {
|
||||
if (evt.containsKey('error')) {
|
||||
_onError(evt['error'] as String);
|
||||
|
|
@ -128,47 +187,9 @@ class UpdateProgressState extends State<UpdateProgress> {
|
|||
}
|
||||
}
|
||||
|
||||
// `isExtractDmg` is true when handling extract-update-dmg event.
|
||||
// It's a rare case that the dmg file is corrupted and cannot be extracted.
|
||||
void _onError(String error, {bool isExtractDmg = false}) {
|
||||
void _onError(String error) {
|
||||
cancelQueryTimer();
|
||||
|
||||
debugPrint(
|
||||
'${isExtractDmg ? "Extract" : "Download"} new version error: $error');
|
||||
final msgBoxType = 'custom-nocancel-nook-hasclose';
|
||||
final msgBoxTitle = 'Error';
|
||||
final msgBoxText = 'download-new-version-failed-tip';
|
||||
final dialogManager = gFFI.dialogManager;
|
||||
|
||||
close() {
|
||||
dialogManager.dismissAll();
|
||||
}
|
||||
|
||||
jumplink() {
|
||||
launchUrl(Uri.parse(widget.releasePageUrl));
|
||||
dialogManager.dismissAll();
|
||||
}
|
||||
|
||||
retry() {
|
||||
dialogManager.dismissAll();
|
||||
handleUpdate(widget.releasePageUrl);
|
||||
}
|
||||
|
||||
final List<Widget> buttons = [
|
||||
dialogButton('Download', onPressed: jumplink),
|
||||
if (!isExtractDmg) dialogButton('Retry', onPressed: retry),
|
||||
dialogButton('Close', onPressed: close),
|
||||
];
|
||||
dialogManager.dismissAll();
|
||||
dialogManager.show(
|
||||
(setState, close, context) => CustomAlertDialog(
|
||||
title: null,
|
||||
content: SelectionArea(
|
||||
child: msgboxContent(msgBoxType, msgBoxTitle, msgBoxText)),
|
||||
actions: buttons,
|
||||
),
|
||||
tag: '$msgBoxType-$msgBoxTitle-$msgBoxTitle',
|
||||
);
|
||||
_showUpdateError(widget.releasePageUrl, error);
|
||||
}
|
||||
|
||||
void _updateDownloadData() {
|
||||
|
|
@ -212,13 +233,7 @@ class UpdateProgressState extends State<UpdateProgress> {
|
|||
_onError('The download file size is 0.');
|
||||
} else {
|
||||
setState(() {});
|
||||
if (isMacOS) {
|
||||
bind.mainSetCommon(
|
||||
key: 'extract-update-dmg', value: widget.downloadUrl);
|
||||
_isExtracting.value = true;
|
||||
} else {
|
||||
updateMsgBox();
|
||||
}
|
||||
updateMsgBox();
|
||||
}
|
||||
} else {
|
||||
setState(() {});
|
||||
|
|
@ -236,28 +251,41 @@ class UpdateProgressState extends State<UpdateProgress> {
|
|||
gFFI.dialogManager,
|
||||
onSubmit: () {
|
||||
debugPrint('Downloaded, update to new version now');
|
||||
if (isMacOS) {
|
||||
_showPreparingForInstallation();
|
||||
platformFFI.registerEventHandler(
|
||||
_eventKeyUpdateMeReady, _eventKeyUpdateMeReady, (evt) async {
|
||||
platformFFI.unregisterEventHandler(
|
||||
_eventKeyUpdateMeReady, _eventKeyUpdateMeReady);
|
||||
gFFI.dialogManager.dismissAll();
|
||||
}, replace: true);
|
||||
}
|
||||
platformFFI.registerEventHandler(_eventKeyUpdateMe, _eventKeyUpdateMe,
|
||||
(evt) async {
|
||||
platformFFI.unregisterEventHandler(
|
||||
_eventKeyUpdateMe, _eventKeyUpdateMe);
|
||||
if (isMacOS) {
|
||||
platformFFI.unregisterEventHandler(
|
||||
_eventKeyUpdateMeReady, _eventKeyUpdateMeReady);
|
||||
}
|
||||
if (evt.containsKey('error')) {
|
||||
_showUpdateError(widget.releasePageUrl, evt['error'] as String,
|
||||
showRetry: false);
|
||||
}
|
||||
}, replace: true);
|
||||
bind.mainSetCommon(key: 'update-me', value: widget.downloadUrl);
|
||||
},
|
||||
submitTimeout: 5,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> handleExtractUpdateDmg(Map<String, dynamic> evt) async {
|
||||
_isExtracting.value = false;
|
||||
if (evt.containsKey('err') && (evt['err'] as String).isNotEmpty) {
|
||||
_onError(evt['err'] as String, isExtractDmg: true);
|
||||
} else {
|
||||
updateMsgBox();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
getValue() => _totalSize == null
|
||||
? 0.0
|
||||
: (_totalSize == 0 ? 1.0 : _downloadedSize / _totalSize!);
|
||||
return LinearProgressIndicator(
|
||||
value: _isExtracting.isTrue ? null : getValue(),
|
||||
value: getValue(),
|
||||
minHeight: 20,
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
backgroundColor: Colors.grey[300],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue