Skip to content

Commit

Permalink
feat: integrate github api OTA 4linux
Browse files Browse the repository at this point in the history
  • Loading branch information
alexVinarskis committed Sep 29, 2023
1 parent d740c34 commit 5f239db
Show file tree
Hide file tree
Showing 7 changed files with 377 additions and 4 deletions.
96 changes: 96 additions & 0 deletions lib/classes/ota_manager.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import 'dart:convert';
import 'dart:io';

import 'package:process_run/shell.dart';
import 'package:version/version.dart';

import '../configs/constants.dart';

class OtaManager {
static final shell = Shell();

// [tagname, releaseUrl, downloadUrl]
static Future<List<String>> checkLatestOta() async {
List<String> result = [];
try {
if (Platform.isLinux) {
ProcessResult pr = (await shell.run('${Constants.githubApiRequest} ${Constants.githubApiReleases}'))[0];
if (pr.exitCode != 0) {
return result;
}
Map<dynamic, dynamic> json = jsonDecode(pr.stdout.toString());

// fetch tagname & url
if (!json.containsKey(Constants.githubApiFieldTagname)) {
return result;
}
result.add(json[Constants.githubApiFieldTagname]);
if (!json.containsKey(Constants.githubApiFieldHtmlUrl)) {
return result;
}
result.add(json[Constants.githubApiFieldHtmlUrl]);

// fetch download url
if (!json.containsKey(Constants.githubApiFieldAssets)) {
return result;
}
for (Map<dynamic, dynamic> asset in json[Constants.githubApiFieldAssets]) {
if (asset.containsKey(Constants.githubApiFieldBrowserDownloadUrl) && asset[Constants.githubApiFieldBrowserDownloadUrl].toString().endsWith('.deb')) {
result.add(asset[Constants.githubApiFieldBrowserDownloadUrl]);
break;
}
}
return result;
} else {
// ToDo Windows integration;
return result;
}
} catch (e) {
return result;
}
}

static bool compareUpdateRequired(String tagname) {
Version currentVersion = Version.parse(Constants.applicationVersion.split('-')[0]);
Version latestVersion = Version.parse(tagname);
return latestVersion > currentVersion;
}

static Future<bool> downloadOta(String tagname, String downloadUrl) async {
bool result = true;
try {
if (Platform.isLinux) {
List<ProcessResult> prs = (await shell.run('''
rm -rf ${Constants.packagesLinuxDownloadPath}/*
mkdir -p ${Constants.packagesLinuxDownloadPath}
curl -L -A "User-Agent Mozilla" $downloadUrl -o ${Constants.packagesLinuxDownloadPath}/$tagname.deb
'''));
for (ProcessResult pr in prs) {
result = pr.exitCode == 0 && result;
}
return result;
} else {
// ToDo Windows integration;
return false;
}
} catch (e) {
return false;
}
}

static Future<bool> installOta(String tagname) async {
bool result = true;
try {
if (Platform.isLinux) {
ProcessResult pr = (await shell.run('pkexec bash -c "ss=0; apt install -y --allow-downgrades -f ${Constants.packagesLinuxDownloadPath}/$tagname.deb || ((ss++)); rm -rf ${Constants.packagesLinuxDownloadPath}/* || ((ss++)); exit \$ss"'))[0];
result = pr.exitCode == 0;
return result;
} else {
// ToDo Windows integration;
return false;
}
} catch (e) {
return false;
}
}
}
240 changes: 240 additions & 0 deletions lib/components/menu_ota.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
import 'dart:async';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:url_launcher/url_launcher.dart';
import '../classes/ota_manager.dart';
import '../configs/constants.dart';

enum OtaState {
hidden,
awaiting,
downloading,
installing,
downloadFailed,
installationFailed,
installationSucceeded,
}

class MenuOta extends StatefulWidget {
const MenuOta({super.key, this.paddingH = 0, this.paddingV = 0, this.backgroundColor = Colors.transparent});

final double paddingH;
final double paddingV;
final Color backgroundColor;

@override
State<MenuOta> createState() => MenuOtaState();
}

class MenuOtaState extends State<MenuOta> {
// assume running latest version by default
OtaState _otaState = OtaState.hidden;
// [tagname, releaseUrl, downloadUrl]
List<String> _targetVersion = [];
late Map<OtaState, String> otaStateTitles;

@override
void initState() {
super.initState();
OtaManager.checkLatestOta().then((latestOta) => _handleOtaState(latestOta));
}

void _handleOtaState(List<String> latestOta) {
if (latestOta.length < 3) {
return;
}
if (!OtaManager.compareUpdateRequired(latestOta[0])) {
return;
}
if (_otaState == OtaState.hidden) {
setState(() {
_targetVersion = latestOta;
_otaState = OtaState.awaiting;
});
}
}

void _getOta() async {
setState(() {
_otaState = OtaState.downloading;
});
bool downloaded = await OtaManager.downloadOta(_targetVersion[0], _targetVersion[2]);
if (!downloaded) {
setState(() {
_otaState = OtaState.downloadFailed;
});
return;
}
setState(() {
_otaState = OtaState.installing;
});
bool installed = await OtaManager.installOta(_targetVersion[0]);
setState(() {
if (installed) {
_otaState = OtaState.installationSucceeded;
} else {
_otaState = OtaState.installationFailed;
}
});
}

Widget _getProgressBar(var state, BuildContext context) {
switch (state) {
case OtaState.installing:
case OtaState.downloading:
return const LinearProgressIndicator(backgroundColor: Colors.transparent);
case OtaState.installationFailed:
case OtaState.downloadFailed:
return LinearProgressIndicator(backgroundColor: Colors.transparent, color: Theme.of(context).colorScheme.error, value: 1,);
case OtaState.installationSucceeded:
return const LinearProgressIndicator(backgroundColor: Colors.transparent, color: Colors.green, value: 1,);
default:
return const LinearProgressIndicator(backgroundColor: Colors.transparent, color: Colors.transparent,);
}
}

Widget _getIcon(var state, BuildContext context) {
switch (state) {
case OtaState.installationFailed:
case OtaState.downloadFailed:
return Icon(Icons.error_outline_rounded, color: Theme.of(context).colorScheme.error,);
case OtaState.installationSucceeded:
return const Icon(Icons.check_circle_outline_outlined, color: Colors.green,);
default:
return const Icon(Icons.browser_updated_rounded);
}
}

Future<void> _showDownloadModal() {
return showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(S.of(context)!.otaCardTitle),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"${S.of(context)!.otaAlertVersionCurrent}:\n${S.of(context)!.otaAlertVersionAvailable}:",
style: Theme.of(context).textTheme.bodyMedium,
),
Text(
" ${Constants.applicationVersion}\n ${_targetVersion[0]}",
style: GoogleFonts.sourceCodePro().copyWith(color: Theme.of(context).textTheme.bodyMedium!.color!),
),
],
),
Text(
"\n${S.of(context)!.otaAlertP1}",
style: Theme.of(context).textTheme.bodyMedium,
textAlign: TextAlign.justify,
),
Text(
S.of(context)!.otaAlertP2,
style: Theme.of(context).textTheme.bodyMedium,
textAlign: TextAlign.justify,
),
],
),
actions: <Widget>[
TextButton.icon(
style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.labelLarge,
),
icon: const Icon(Icons.link_rounded),
label: Text(S.of(context)!.otaAlertButtonRelease),
onPressed: () {
launchUrl(Uri.parse(_targetVersion[1]));
Navigator.of(context).pop();
},
),
TextButton.icon(
style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.labelLarge,
),
icon: const Icon(Icons.download_rounded),
label: Text(S.of(context)!.otaAlertButtonInstall),
onPressed: () {
_getOta();
Navigator.of(context).pop();
},
),
],
);
},
);
}

@override
Widget build(BuildContext context) {
otaStateTitles = {
OtaState.hidden : "",
OtaState.awaiting : S.of(context)!.otaCardSubtitleAwaiting,
OtaState.downloading : S.of(context)!.otaCardSubtitleDownloading,
OtaState.installing : S.of(context)!.otaCardSubtitleInstalling,
OtaState.downloadFailed : S.of(context)!.otaCardSubtitleDownloadFailed,
OtaState.installationFailed : S.of(context)!.otaCardSubtitleInstallationFailed,
OtaState.installationSucceeded : S.of(context)!.otaCardSubtitleInstallationSucceeded,
};

return AnimatedSwitcher(
duration: const Duration(milliseconds: Constants.animationMs),
child: _otaState != OtaState.hidden ? Card(
key: const Key("otaAvailableTrue"),
clipBehavior: Clip.antiAlias,
color: Colors.amber.withOpacity(0.4),
elevation: 0,
margin: EdgeInsets.symmetric(vertical: widget.paddingV, horizontal: widget.paddingH),
child: InkWell(
onTap: () async {
if (_otaState == OtaState.installing || _otaState == OtaState.downloading) {
return;
}
if (_otaState == OtaState.installationSucceeded) {
exit(0);
}
_showDownloadModal();
},
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.only(left: 15, right: 15),
child: _getIcon(_otaState, context),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 15),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
S.of(context)!.otaCardTitle,
style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 5,),
Text(otaStateTitles[_otaState].toString(), textAlign: TextAlign.justify,),
],
),
),
],
),
Align(alignment: Alignment.bottomCenter, child: _getProgressBar(_otaState, context),),
],
),
),
) : const SizedBox(
key: Key("otaAvailableFalse"),
),
);
}
}
14 changes: 11 additions & 3 deletions lib/configs/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ class Constants {
static const animationMs = 250;

static const authorName = 'alexVinarskis';
static const urlHomepage = 'https://github.com/alexVinarskis/dell-powermanager';
static const urlBugReport = 'https://github.com/alexVinarskis/dell-powermanager/issues/new/choose';
static const urlHomepage = 'https://github.com/${Constants.authorName}/dell-powermanager';
static const urlBugReport = 'https://github.com/${Constants.authorName}/dell-powermanager/issues/new/choose';
static const urlApi = 'https://api.github.com/repos/${Constants.authorName}/dell-powermanager';

static const packagesLinux = ['command-configure', 'srvadmin-hapi', 'libssl1.1'];
static const packagesWindows = [];
Expand All @@ -18,5 +19,12 @@ class Constants {
// These string shall also be hardcoded to ./package!
static const apiPathLinux = '/opt/dell/dcc/cctk';
static const applicationName = 'Dell Power Manager by VA';
static const applicationVersion = '0.5.0-3-g99820b7+20230924-225519';
static const applicationVersion = '0.1.0';

static const githubApiReleases = '${Constants.urlApi}/releases/latest';
static const githubApiRequest = 'curl -L -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28"';
static const githubApiFieldTagname = 'tag_name';
static const githubApiFieldAssets = 'assets';
static const githubApiFieldBrowserDownloadUrl = 'browser_download_url';
static const githubApiFieldHtmlUrl = 'html_url';
}
15 changes: 15 additions & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,21 @@
"dependenciesAlertP2" : "Dell Command | Configure",
"dependenciesAlertP3" : "CLI\nand its dependencies to operate. Press the button below\nto automatically install the following packages:\n\n",

"otaCardTitle" : "Update Available",
"otaCardSubtitleAwaiting" : "Tap for more info and install options",
"otaCardSubtitleDownloading" : "Please wait... Downloading...",
"otaCardSubtitleInstalling" : "Please wait... Installing...",
"otaCardSubtitleDownloadFailed" : "Download Failed :<",
"otaCardSubtitleInstallationFailed" : "Installation Failed :<",
"otaCardSubtitleInstallationSucceeded" : "Installation Succeeded. Please restart the app!",
"otaAlertTitle" : "Update Available",
"otaAlertButtonInstall" : "Download and Install",
"otaAlertButtonRelease" : "Release",
"otaAlertVersionCurrent" : "Current Version",
"otaAlertVersionAvailable" : "Available Version",
"otaAlertP1" : "Find more information and source code on the release page.",
"otaAlertP2" : "Press the button below to automatically install the update.",

"cctkThermalOptimizedTitle" : "Optimized",
"cctkThermalOptimizedDescription" : "This is the standard setting for cooling fan and processor heat management. This setting is a balance of performance., noise and temperature.",
"cctkThermalQuietTitle" : "Quiet",
Expand Down
Loading

0 comments on commit 5f239db

Please sign in to comment.