mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-08-13 10:28:10 +02:00
Switched to WorkManager for reliability (#608)
This commit is contained in:
@@ -12,7 +12,7 @@ import 'package:permission_handler/permission_handler.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:dynamic_color/dynamic_color.dart';
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart';
|
import 'package:workmanager/workmanager.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
// ignore: implementation_imports
|
// ignore: implementation_imports
|
||||||
import 'package:easy_localization/src/easy_localization_controller.dart';
|
import 'package:easy_localization/src/easy_localization_controller.dart';
|
||||||
@@ -23,7 +23,7 @@ const String currentVersion = '0.14.31';
|
|||||||
const String currentReleaseTag =
|
const String currentReleaseTag =
|
||||||
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
|
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
|
||||||
|
|
||||||
const int bgUpdateCheckAlarmId = 666;
|
const String bgUpdateTaskId = 'bgUpdate';
|
||||||
|
|
||||||
List<MapEntry<Locale, String>> supportedLocales = const [
|
List<MapEntry<Locale, String>> supportedLocales = const [
|
||||||
MapEntry(Locale('en'), 'English'),
|
MapEntry(Locale('en'), 'English'),
|
||||||
@@ -71,6 +71,17 @@ Future<void> loadTranslations() async {
|
|||||||
fallbackTranslations: controller.fallbackTranslations);
|
fallbackTranslations: controller.fallbackTranslations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@pragma('vm:entry-point')
|
||||||
|
void bgTaskDispatcher() {
|
||||||
|
Workmanager().executeTask((taskId, params) {
|
||||||
|
if (taskId == bgUpdateTaskId) {
|
||||||
|
return bgUpdateTask(taskId, params);
|
||||||
|
} else {
|
||||||
|
return Future.value(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
try {
|
try {
|
||||||
@@ -88,7 +99,7 @@ void main() async {
|
|||||||
);
|
);
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||||
}
|
}
|
||||||
await AndroidAlarmManager.initialize();
|
await Workmanager().initialize(bgTaskDispatcher);
|
||||||
runApp(MultiProvider(
|
runApp(MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
ChangeNotifierProvider(create: (context) => AppsProvider()),
|
ChangeNotifierProvider(create: (context) => AppsProvider()),
|
||||||
@@ -158,7 +169,7 @@ class _ObtainiumState extends State<Obtainium> {
|
|||||||
var actualUpdateInterval = settingsProvider.updateInterval;
|
var actualUpdateInterval = settingsProvider.updateInterval;
|
||||||
if (existingUpdateInterval != actualUpdateInterval) {
|
if (existingUpdateInterval != actualUpdateInterval) {
|
||||||
if (actualUpdateInterval == 0) {
|
if (actualUpdateInterval == 0) {
|
||||||
AndroidAlarmManager.cancel(bgUpdateCheckAlarmId);
|
Workmanager().cancelByUniqueName(bgUpdateTaskId);
|
||||||
} else {
|
} else {
|
||||||
var settingChanged = existingUpdateInterval != -1;
|
var settingChanged = existingUpdateInterval != -1;
|
||||||
var lastCheckWasTooLongAgo = actualUpdateInterval != 0 &&
|
var lastCheckWasTooLongAgo = actualUpdateInterval != 0 &&
|
||||||
@@ -168,12 +179,10 @@ class _ObtainiumState extends State<Obtainium> {
|
|||||||
if (settingChanged || lastCheckWasTooLongAgo) {
|
if (settingChanged || lastCheckWasTooLongAgo) {
|
||||||
logs.add(
|
logs.add(
|
||||||
'Update interval was set to ${actualUpdateInterval.toString()} (reason: ${settingChanged ? 'setting changed' : 'last check was ${settingsProvider.lastBGCheckTime.toLocal().toString()}'}).');
|
'Update interval was set to ${actualUpdateInterval.toString()} (reason: ${settingChanged ? 'setting changed' : 'last check was ${settingsProvider.lastBGCheckTime.toLocal().toString()}'}).');
|
||||||
AndroidAlarmManager.periodic(
|
Workmanager().registerPeriodicTask(
|
||||||
Duration(minutes: actualUpdateInterval),
|
bgUpdateTaskId, "BG Update Main Loop",
|
||||||
bgUpdateCheckAlarmId,
|
initialDelay: Duration(minutes: actualUpdateInterval),
|
||||||
bgUpdateCheck,
|
existingWorkPolicy: ExistingWorkPolicy.replace);
|
||||||
rescheduleOnReboot: true,
|
|
||||||
wakeup: true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
existingUpdateInterval = actualUpdateInterval;
|
existingUpdateInterval = actualUpdateInterval;
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart';
|
import 'package:workmanager/workmanager.dart';
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -591,10 +591,10 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
height16,
|
height16,
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
AndroidAlarmManager.oneShot(
|
Workmanager().registerOneOffTask(
|
||||||
const Duration(seconds: 0),
|
'$bgUpdateTaskId+Manual', bgUpdateTaskId,
|
||||||
bgUpdateCheckAlarmId + 200,
|
existingWorkPolicy:
|
||||||
bgUpdateCheck);
|
ExistingWorkPolicy.replace);
|
||||||
showMessage(tr('bgTaskStarted'), context);
|
showMessage(tr('bgTaskStarted'), context);
|
||||||
},
|
},
|
||||||
child: Text(tr('runBgCheckNow')))
|
child: Text(tr('runBgCheckNow')))
|
||||||
|
@@ -7,7 +7,7 @@ import 'dart:io';
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart';
|
import 'package:workmanager/workmanager.dart';
|
||||||
import 'package:android_intent_plus/flag.dart';
|
import 'package:android_intent_plus/flag.dart';
|
||||||
import 'package:android_package_installer/android_package_installer.dart';
|
import 'package:android_package_installer/android_package_installer.dart';
|
||||||
import 'package:android_package_manager/android_package_manager.dart';
|
import 'package:android_package_manager/android_package_manager.dart';
|
||||||
@@ -1356,268 +1356,276 @@ class _APKOriginWarningDialogState extends State<APKOriginWarningDialog> {
|
|||||||
/// If there is an error, the offending app is moved to the back of the line of remaining apps, and the task is retried.
|
/// If there is an error, the offending app is moved to the back of the line of remaining apps, and the task is retried.
|
||||||
/// If an app repeatedly fails to install up to its retry limit, the user is notified.
|
/// If an app repeatedly fails to install up to its retry limit, the user is notified.
|
||||||
///
|
///
|
||||||
@pragma('vm:entry-point')
|
Future<bool> bgUpdateTask(String taskId, Map<String, dynamic>? params) async {
|
||||||
Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await EasyLocalization.ensureInitialized();
|
await EasyLocalization.ensureInitialized();
|
||||||
await AndroidAlarmManager.initialize();
|
await Workmanager().initialize(bgTaskDispatcher);
|
||||||
await loadTranslations();
|
await loadTranslations();
|
||||||
|
|
||||||
LogsProvider logs = LogsProvider();
|
LogsProvider logs = LogsProvider();
|
||||||
NotificationsProvider notificationsProvider = NotificationsProvider();
|
try {
|
||||||
AppsProvider appsProvider = AppsProvider(isBg: true);
|
NotificationsProvider notificationsProvider = NotificationsProvider();
|
||||||
await appsProvider.loadApps();
|
AppsProvider appsProvider = AppsProvider(isBg: true);
|
||||||
|
await appsProvider.loadApps();
|
||||||
|
|
||||||
int maxAttempts = 4;
|
int maxAttempts = 4;
|
||||||
|
|
||||||
params ??= {};
|
params ??= {};
|
||||||
if (params['toCheck'] == null) {
|
if (params['toCheck'] == null) {
|
||||||
appsProvider.settingsProvider.lastBGCheckTime = DateTime.now();
|
appsProvider.settingsProvider.lastBGCheckTime = DateTime.now();
|
||||||
}
|
}
|
||||||
List<MapEntry<String, int>> toCheck = <MapEntry<String, int>>[
|
List<MapEntry<String, int>> toCheck = <MapEntry<String, int>>[
|
||||||
...(params['toCheck']
|
...(params['toCheck']?.map((str) {
|
||||||
?.map((entry) => MapEntry<String, int>(
|
var temp = str.split(',');
|
||||||
entry['key'] as String, entry['value'] as int))
|
return MapEntry<String, int>(temp[0], int.parse(temp[1]));
|
||||||
.toList() ??
|
}).toList() ??
|
||||||
appsProvider
|
appsProvider
|
||||||
.getAppsSortedByUpdateCheckTime(
|
.getAppsSortedByUpdateCheckTime(
|
||||||
onlyCheckInstalledOrTrackOnlyApps: appsProvider
|
onlyCheckInstalledOrTrackOnlyApps: appsProvider
|
||||||
.settingsProvider.onlyCheckInstalledOrTrackOnlyApps)
|
.settingsProvider.onlyCheckInstalledOrTrackOnlyApps)
|
||||||
.map((e) => MapEntry(e, 0)))
|
.map((e) => MapEntry(e, 0)))
|
||||||
];
|
];
|
||||||
List<MapEntry<String, int>> toInstall = <MapEntry<String, int>>[
|
List<MapEntry<String, int>> toInstall = <MapEntry<String, int>>[
|
||||||
...(params['toInstall']
|
...(params['toInstall']?.map((str) {
|
||||||
?.map((entry) => MapEntry<String, int>(
|
var temp = str.split(',');
|
||||||
entry['key'] as String, entry['value'] as int))
|
return MapEntry<String, int>(temp[0], int.parse(temp[1]));
|
||||||
.toList() ??
|
}).toList() ??
|
||||||
(<List<MapEntry<String, int>>>[]))
|
(<List<MapEntry<String, int>>>[]))
|
||||||
];
|
];
|
||||||
|
|
||||||
var netResult = await (Connectivity().checkConnectivity());
|
var netResult = await (Connectivity().checkConnectivity());
|
||||||
|
|
||||||
if (netResult == ConnectivityResult.none) {
|
if (netResult == ConnectivityResult.none) {
|
||||||
var networkBasedRetryInterval = 15;
|
var networkBasedRetryInterval = 15;
|
||||||
var nextRegularCheck = appsProvider.settingsProvider.lastBGCheckTime
|
var nextRegularCheck = appsProvider.settingsProvider.lastBGCheckTime
|
||||||
.add(Duration(minutes: appsProvider.settingsProvider.updateInterval));
|
.add(Duration(minutes: appsProvider.settingsProvider.updateInterval));
|
||||||
var potentialNetworkRetryCheck =
|
var potentialNetworkRetryCheck =
|
||||||
DateTime.now().add(Duration(minutes: networkBasedRetryInterval));
|
DateTime.now().add(Duration(minutes: networkBasedRetryInterval));
|
||||||
var shouldRetry = potentialNetworkRetryCheck.isBefore(nextRegularCheck);
|
var shouldRetry = potentialNetworkRetryCheck.isBefore(nextRegularCheck);
|
||||||
logs.add(
|
logs.add(
|
||||||
'BG update task $taskId: No network. Will ${shouldRetry ? 'retry in $networkBasedRetryInterval minutes' : 'not retry'}.');
|
'BG task $taskId: No network. Will ${shouldRetry ? 'retry in $networkBasedRetryInterval minutes' : 'not retry'}.');
|
||||||
AndroidAlarmManager.oneShot(
|
await Workmanager().registerOneOffTask("$taskId+Retry", taskId,
|
||||||
const Duration(minutes: 15), taskId + 1, bgUpdateCheck,
|
initialDelay: const Duration(minutes: 15),
|
||||||
params: {
|
existingWorkPolicy: ExistingWorkPolicy.replace,
|
||||||
'toCheck': toCheck
|
inputData: {
|
||||||
.map((entry) => {'key': entry.key, 'value': entry.value})
|
'toCheck':
|
||||||
.toList(),
|
toCheck.map((entry) => '${entry.key},${entry.value}').toList(),
|
||||||
'toInstall': toInstall
|
'toInstall': toInstall
|
||||||
.map((entry) => {'key': entry.key, 'value': entry.value})
|
.map((entry) => '${entry.key},${entry.value}')
|
||||||
.toList(),
|
.toList(),
|
||||||
});
|
});
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var networkRestricted = false;
|
|
||||||
if (appsProvider.settingsProvider.bgUpdatesOnWiFiOnly) {
|
|
||||||
networkRestricted = (netResult != ConnectivityResult.wifi) &&
|
|
||||||
(netResult != ConnectivityResult.ethernet);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool installMode =
|
|
||||||
toCheck.isEmpty; // Task is either in update mode or install mode
|
|
||||||
|
|
||||||
logs.add(
|
|
||||||
'BG ${installMode ? 'install' : 'update'} task $taskId: Started (${installMode ? toInstall.length : toCheck.length}).');
|
|
||||||
|
|
||||||
if (!installMode) {
|
|
||||||
// If in update mode, we check for updates.
|
|
||||||
// We divide the results into 4 groups:
|
|
||||||
// - toNotify - Apps with updates that the user will be notified about (can't be silently installed)
|
|
||||||
// - toRetry - Apps with update check errors that will be retried in a while
|
|
||||||
// - toThrow - Apps with update check errors that the user will be notified about (no retry)
|
|
||||||
// After grouping the updates, we take care of toNotify and toThrow first
|
|
||||||
// Then if toRetry is not empty, we schedule another update task to run in a while
|
|
||||||
// If toRetry is empty, we take care of schedule another task that will run in install mode (toCheck is empty)
|
|
||||||
|
|
||||||
// Init. vars.
|
|
||||||
List<App> updates = []; // All updates found (silent and non-silent)
|
|
||||||
List<App> toNotify =
|
|
||||||
[]; // All non-silent updates that the user will be notified about
|
|
||||||
List<MapEntry<String, int>> toRetry =
|
|
||||||
[]; // All apps that got errors while checking
|
|
||||||
var retryAfterXSeconds =
|
|
||||||
0; // How long to wait until the next attempt (if there are errors)
|
|
||||||
MultiAppMultiError?
|
|
||||||
errors; // All errors including those that will lead to a retry
|
|
||||||
MultiAppMultiError toThrow =
|
|
||||||
MultiAppMultiError(); // All errors that will not lead to a retry, just a notification
|
|
||||||
CheckingUpdatesNotification notif = CheckingUpdatesNotification(
|
|
||||||
plural('apps', toCheck.length)); // The notif. to show while checking
|
|
||||||
|
|
||||||
// Set a bool for when we're no on wifi/wired and the user doesn't want to download apps in that state
|
|
||||||
var networkRestricted = false;
|
var networkRestricted = false;
|
||||||
if (appsProvider.settingsProvider.bgUpdatesOnWiFiOnly) {
|
if (appsProvider.settingsProvider.bgUpdatesOnWiFiOnly) {
|
||||||
var netResult = await (Connectivity().checkConnectivity());
|
|
||||||
networkRestricted = (netResult != ConnectivityResult.wifi) &&
|
networkRestricted = (netResult != ConnectivityResult.wifi) &&
|
||||||
(netResult != ConnectivityResult.ethernet);
|
(netResult != ConnectivityResult.ethernet);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
bool installMode =
|
||||||
// Check for updates
|
toCheck.isEmpty; // Task is either in update mode or install mode
|
||||||
notificationsProvider.notify(notif, cancelExisting: true);
|
|
||||||
updates = await appsProvider.checkUpdates(
|
|
||||||
specificIds: toCheck.map((e) => e.key).toList(),
|
|
||||||
sp: appsProvider.settingsProvider);
|
|
||||||
} catch (e) {
|
|
||||||
// If there were errors, group them into toRetry and toThrow based on max retry count per app
|
|
||||||
if (e is Map) {
|
|
||||||
updates = e['updates'];
|
|
||||||
errors = e['errors'];
|
|
||||||
errors!.rawErrors.forEach((key, err) {
|
|
||||||
logs.add(
|
|
||||||
'BG update task $taskId: Got error on checking for $key \'${err.toString()}\'.');
|
|
||||||
var toCheckApp = toCheck.where((element) => element.key == key).first;
|
|
||||||
if (toCheckApp.value < maxAttempts) {
|
|
||||||
toRetry.add(MapEntry(toCheckApp.key, toCheckApp.value + 1));
|
|
||||||
// Next task interval is based on the error with the longest retry time
|
|
||||||
var minRetryIntervalForThisApp = err is RateLimitError
|
|
||||||
? (err.remainingMinutes * 60)
|
|
||||||
: e is ClientException
|
|
||||||
? (15 * 60)
|
|
||||||
: pow(toCheckApp.value + 1, 2).toInt();
|
|
||||||
if (minRetryIntervalForThisApp > retryAfterXSeconds) {
|
|
||||||
retryAfterXSeconds = minRetryIntervalForThisApp;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
toThrow.add(key, err, appName: errors?.appIdNames[key]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// We don't expect to ever get here in any situation so no need to catch (but log it in case)
|
|
||||||
logs.add('Fatal error in BG update task: ${e.toString()}');
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
notificationsProvider.cancel(notif.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter out updates that will be installed silently (the rest go into toNotify)
|
logs.add(
|
||||||
for (var i = 0; i < updates.length; i++) {
|
'BG ${installMode ? 'install' : 'update'} task $taskId: Started (${installMode ? toInstall.length : toCheck.length}).');
|
||||||
if (networkRestricted ||
|
|
||||||
!(await appsProvider.canInstallSilently(updates[i]))) {
|
|
||||||
if (updates[i].additionalSettings['skipUpdateNotifications'] != true) {
|
|
||||||
toNotify.add(updates[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the update notification
|
if (!installMode) {
|
||||||
if (toNotify.isNotEmpty) {
|
// If in update mode, we check for updates.
|
||||||
notificationsProvider.notify(UpdateNotification(toNotify));
|
// We divide the results into 4 groups:
|
||||||
}
|
// - toNotify - Apps with updates that the user will be notified about (can't be silently installed)
|
||||||
|
// - toRetry - Apps with update check errors that will be retried in a while
|
||||||
|
// - toThrow - Apps with update check errors that the user will be notified about (no retry)
|
||||||
|
// After grouping the updates, we take care of toNotify and toThrow first
|
||||||
|
// Then if toRetry is not empty, we schedule another update task to run in a while
|
||||||
|
// If toRetry is empty, we take care of schedule another task that will run in install mode (toCheck is empty)
|
||||||
|
|
||||||
// Send the error notifications (grouped by error string)
|
// Init. vars.
|
||||||
if (toThrow.rawErrors.isNotEmpty) {
|
List<App> updates = []; // All updates found (silent and non-silent)
|
||||||
for (var element in toThrow.idsByErrorString.entries) {
|
List<App> toNotify =
|
||||||
notificationsProvider.notify(ErrorCheckingUpdatesNotification(
|
[]; // All non-silent updates that the user will be notified about
|
||||||
errors!.errorsAppsString(element.key, element.value),
|
List<MapEntry<String, int>> toRetry =
|
||||||
id: Random().nextInt(10000)));
|
[]; // All apps that got errors while checking
|
||||||
}
|
var retryAfterXSeconds =
|
||||||
}
|
0; // How long to wait until the next attempt (if there are errors)
|
||||||
|
MultiAppMultiError?
|
||||||
|
errors; // All errors including those that will lead to a retry
|
||||||
|
MultiAppMultiError toThrow =
|
||||||
|
MultiAppMultiError(); // All errors that will not lead to a retry, just a notification
|
||||||
|
CheckingUpdatesNotification notif = CheckingUpdatesNotification(
|
||||||
|
plural('apps', toCheck.length)); // The notif. to show while checking
|
||||||
|
|
||||||
// if there are update checks to retry, schedule a retry task
|
// Set a bool for when we're no on wifi/wired and the user doesn't want to download apps in that state
|
||||||
if (toRetry.isNotEmpty) {
|
var networkRestricted = false;
|
||||||
logs.add(
|
if (appsProvider.settingsProvider.bgUpdatesOnWiFiOnly) {
|
||||||
'BG update task $taskId: Will retry in $retryAfterXSeconds seconds.');
|
var netResult = await (Connectivity().checkConnectivity());
|
||||||
AndroidAlarmManager.oneShot(
|
networkRestricted = (netResult != ConnectivityResult.wifi) &&
|
||||||
Duration(seconds: retryAfterXSeconds), taskId + 1, bgUpdateCheck,
|
(netResult != ConnectivityResult.ethernet);
|
||||||
params: {
|
|
||||||
'toCheck': toRetry
|
|
||||||
.map((entry) => {'key': entry.key, 'value': entry.value})
|
|
||||||
.toList(),
|
|
||||||
'toInstall': toInstall
|
|
||||||
.map((entry) => {'key': entry.key, 'value': entry.value})
|
|
||||||
.toList(),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// If there are no more update checks, schedule an install task
|
|
||||||
logs.add(
|
|
||||||
'BG update task $taskId: Done. Scheduling install task to run immediately.');
|
|
||||||
AndroidAlarmManager.oneShot(
|
|
||||||
const Duration(minutes: 0), taskId + 1, bgUpdateCheck,
|
|
||||||
params: {
|
|
||||||
'toCheck': [],
|
|
||||||
'toInstall': toInstall
|
|
||||||
.map((entry) => {'key': entry.key, 'value': entry.value})
|
|
||||||
.toList()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// In install mode...
|
|
||||||
// If you haven't explicitly been given updates to install (which is the case for new tasks), grab all available silent updates
|
|
||||||
if (toInstall.isEmpty && !networkRestricted) {
|
|
||||||
var temp = appsProvider.findExistingUpdates(installedOnly: true);
|
|
||||||
for (var i = 0; i < temp.length; i++) {
|
|
||||||
if (await appsProvider
|
|
||||||
.canInstallSilently(appsProvider.apps[temp[i]]!.app)) {
|
|
||||||
toInstall.add(MapEntry(temp[i], 0));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
var didCompleteInstalling = false;
|
|
||||||
var tempObtArr = toInstall.where((element) => element.key == obtainiumId);
|
|
||||||
if (tempObtArr.isNotEmpty) {
|
|
||||||
// Move obtainium to the end of the list as it must always install last
|
|
||||||
var obt = tempObtArr.first;
|
|
||||||
toInstall = moveStrToEndMapEntryWithCount(toInstall, obt);
|
|
||||||
}
|
|
||||||
// Loop through all updates and install each
|
|
||||||
for (var i = 0; i < toInstall.length; i++) {
|
|
||||||
var appId = toInstall[i].key;
|
|
||||||
var retryCount = toInstall[i].value;
|
|
||||||
try {
|
try {
|
||||||
logs.add(
|
// Check for updates
|
||||||
'BG install task $taskId: Attempting to update $appId in the background.');
|
notificationsProvider.notify(notif, cancelExisting: true);
|
||||||
await appsProvider.downloadAndInstallLatestApps([appId], null,
|
updates = await appsProvider.checkUpdates(
|
||||||
notificationsProvider: notificationsProvider);
|
specificIds: toCheck.map((e) => e.key).toList(),
|
||||||
await Future.delayed(const Duration(
|
sp: appsProvider.settingsProvider);
|
||||||
seconds:
|
|
||||||
5)); // Just in case task ending causes install fail (not clear)
|
|
||||||
if (i == (toCheck.length - 1)) {
|
|
||||||
didCompleteInstalling = true;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// If you got an error, move the offender to the back of the line (increment their fail count) and schedule another task to continue installing shortly
|
// If there were errors, group them into toRetry and toThrow based on max retry count per app
|
||||||
logs.add(
|
if (e is Map) {
|
||||||
'BG install task $taskId: Got error on updating $appId \'${e.toString()}\'.');
|
updates = e['updates'];
|
||||||
if (retryCount < maxAttempts) {
|
errors = e['errors'];
|
||||||
var remainingSeconds = retryCount;
|
errors!.rawErrors.forEach((key, err) {
|
||||||
logs.add(
|
logs.add(
|
||||||
'BG install task $taskId: Will continue in $remainingSeconds seconds (with $appId moved to the end of the line).');
|
'BG task $taskId: Got error on checking for $key \'${err.toString()}\'.');
|
||||||
var remainingToInstall = moveStrToEndMapEntryWithCount(
|
var toCheckApp =
|
||||||
toInstall.sublist(i), MapEntry(appId, retryCount + 1));
|
toCheck.where((element) => element.key == key).first;
|
||||||
AndroidAlarmManager.oneShot(
|
if (toCheckApp.value < maxAttempts) {
|
||||||
Duration(seconds: remainingSeconds), taskId + 1, bgUpdateCheck,
|
toRetry.add(MapEntry(toCheckApp.key, toCheckApp.value + 1));
|
||||||
params: {
|
// Next task interval is based on the error with the longest retry time
|
||||||
'toCheck': toCheck
|
var minRetryIntervalForThisApp = err is RateLimitError
|
||||||
.map((entry) => {'key': entry.key, 'value': entry.value})
|
? (err.remainingMinutes * 60)
|
||||||
.toList(),
|
: e is ClientException
|
||||||
'toInstall': remainingToInstall
|
? (15 * 60)
|
||||||
.map((entry) => {'key': entry.key, 'value': entry.value})
|
: pow(toCheckApp.value + 1, 2).toInt();
|
||||||
.toList(),
|
if (minRetryIntervalForThisApp > retryAfterXSeconds) {
|
||||||
});
|
retryAfterXSeconds = minRetryIntervalForThisApp;
|
||||||
break;
|
}
|
||||||
|
} else {
|
||||||
|
toThrow.add(key, err, appName: errors?.appIdNames[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// If the offender has reached its fail limit, notify the user and remove it from the list (task can continue)
|
// We don't expect to ever get here in any situation so no need to catch (but log it in case)
|
||||||
toInstall.removeAt(i);
|
logs.add('Fatal error in BG task: ${e.toString()}');
|
||||||
i--;
|
rethrow;
|
||||||
notificationsProvider
|
}
|
||||||
.notify(ErrorCheckingUpdatesNotification(e.toString()));
|
} finally {
|
||||||
|
notificationsProvider.cancel(notif.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out updates that will be installed silently (the rest go into toNotify)
|
||||||
|
for (var i = 0; i < updates.length; i++) {
|
||||||
|
if (networkRestricted ||
|
||||||
|
!(await appsProvider.canInstallSilently(updates[i]))) {
|
||||||
|
if (updates[i].additionalSettings['skipUpdateNotifications'] !=
|
||||||
|
true) {
|
||||||
|
toNotify.add(updates[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send the update notification
|
||||||
|
if (toNotify.isNotEmpty) {
|
||||||
|
notificationsProvider.notify(UpdateNotification(toNotify));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the error notifications (grouped by error string)
|
||||||
|
if (toThrow.rawErrors.isNotEmpty) {
|
||||||
|
for (var element in toThrow.idsByErrorString.entries) {
|
||||||
|
notificationsProvider.notify(ErrorCheckingUpdatesNotification(
|
||||||
|
errors!.errorsAppsString(element.key, element.value),
|
||||||
|
id: Random().nextInt(10000)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there are update checks to retry, schedule a retry task
|
||||||
|
if (toRetry.isNotEmpty) {
|
||||||
|
logs.add('BG task $taskId: Will retry in $retryAfterXSeconds seconds.');
|
||||||
|
await Workmanager().registerOneOffTask("$taskId+Retry", taskId,
|
||||||
|
initialDelay: Duration(seconds: retryAfterXSeconds),
|
||||||
|
existingWorkPolicy: ExistingWorkPolicy.replace,
|
||||||
|
inputData: {
|
||||||
|
'toCheck': toRetry
|
||||||
|
.map((entry) => '${entry.key},${entry.value}')
|
||||||
|
.toList(),
|
||||||
|
'toInstall': toInstall
|
||||||
|
.map((entry) => '${entry.key},${entry.value}')
|
||||||
|
.toList(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// If there are no more update checks, schedule an install task
|
||||||
|
logs.add(
|
||||||
|
'BG task $taskId: Done. Scheduling install task to run immediately.');
|
||||||
|
await Workmanager().registerOneOffTask(
|
||||||
|
"$bgUpdateTaskId+Install", taskId,
|
||||||
|
existingWorkPolicy: ExistingWorkPolicy.replace,
|
||||||
|
inputData: {
|
||||||
|
'toCheck': <String>[],
|
||||||
|
'toInstall': toInstall
|
||||||
|
.map((entry) => '${entry.key},${entry.value}')
|
||||||
|
.toList()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// In install mode...
|
||||||
|
// If you haven't explicitly been given updates to install (which is the case for new tasks), grab all available silent updates
|
||||||
|
if (toInstall.isEmpty && !networkRestricted) {
|
||||||
|
var temp = appsProvider.findExistingUpdates(installedOnly: true);
|
||||||
|
for (var i = 0; i < temp.length; i++) {
|
||||||
|
if (await appsProvider
|
||||||
|
.canInstallSilently(appsProvider.apps[temp[i]]!.app)) {
|
||||||
|
toInstall.add(MapEntry(temp[i], 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var didCompleteInstalling = false;
|
||||||
|
var tempObtArr = toInstall.where((element) => element.key == obtainiumId);
|
||||||
|
if (tempObtArr.isNotEmpty) {
|
||||||
|
// Move obtainium to the end of the list as it must always install last
|
||||||
|
var obt = tempObtArr.first;
|
||||||
|
toInstall = moveStrToEndMapEntryWithCount(toInstall, obt);
|
||||||
|
}
|
||||||
|
// Loop through all updates and install each
|
||||||
|
for (var i = 0; i < toInstall.length; i++) {
|
||||||
|
var appId = toInstall[i].key;
|
||||||
|
var retryCount = toInstall[i].value;
|
||||||
|
try {
|
||||||
|
logs.add(
|
||||||
|
'BG task $taskId: Attempting to update $appId in the background.');
|
||||||
|
await appsProvider.downloadAndInstallLatestApps([appId], null,
|
||||||
|
notificationsProvider: notificationsProvider);
|
||||||
|
await Future.delayed(const Duration(
|
||||||
|
seconds:
|
||||||
|
5)); // Just in case task ending causes install fail (not clear)
|
||||||
|
if (i == (toCheck.length - 1)) {
|
||||||
|
didCompleteInstalling = true;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// If you got an error, move the offender to the back of the line (increment their fail count) and schedule another task to continue installing shortly
|
||||||
|
logs.add(
|
||||||
|
'BG task $taskId: Got error on updating $appId \'${e.toString()}\'.');
|
||||||
|
if (retryCount < maxAttempts) {
|
||||||
|
var remainingSeconds = retryCount;
|
||||||
|
logs.add(
|
||||||
|
'BG task $taskId: Will continue in $remainingSeconds seconds (with $appId moved to the end of the line).');
|
||||||
|
var remainingToInstall = moveStrToEndMapEntryWithCount(
|
||||||
|
toInstall.sublist(i), MapEntry(appId, retryCount + 1));
|
||||||
|
await Workmanager().registerOneOffTask("$taskId+Retry", taskId,
|
||||||
|
initialDelay: Duration(seconds: remainingSeconds),
|
||||||
|
existingWorkPolicy: ExistingWorkPolicy.replace,
|
||||||
|
inputData: {
|
||||||
|
'toCheck': toCheck
|
||||||
|
.map((entry) => '${entry.key},${entry.value}')
|
||||||
|
.toList(),
|
||||||
|
'toInstall': remainingToInstall
|
||||||
|
.map((entry) => '${entry.key},${entry.value}')
|
||||||
|
.toList(),
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// If the offender has reached its fail limit, notify the user and remove it from the list (task can continue)
|
||||||
|
toInstall.removeAt(i);
|
||||||
|
i--;
|
||||||
|
notificationsProvider
|
||||||
|
.notify(ErrorCheckingUpdatesNotification(e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (didCompleteInstalling || toInstall.isEmpty) {
|
||||||
|
logs.add('BG task $taskId: Done.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (didCompleteInstalling || toInstall.isEmpty) {
|
return true;
|
||||||
logs.add('BG install task $taskId: Done.');
|
} catch (e) {
|
||||||
}
|
logs.add(e.toString());
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
18
pubspec.lock
18
pubspec.lock
@@ -1,14 +1,6 @@
|
|||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
android_alarm_manager_plus:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: android_alarm_manager_plus
|
|
||||||
sha256: "82fb28c867c4b3dd7e9157728e46426b8916362f977dbba46b949210f00099f4"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.0.3"
|
|
||||||
android_intent_plus:
|
android_intent_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -931,6 +923,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
version: "1.1.2"
|
||||||
|
workmanager:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: workmanager
|
||||||
|
sha256: ed13530cccd28c5c9959ad42d657cd0666274ca74c56dea0ca183ddd527d3a00
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.5.2"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -956,5 +956,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
version: "3.1.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.1.0 <4.0.0"
|
dart: ">=3.1.2 <4.0.0"
|
||||||
flutter: ">=3.13.0"
|
flutter: ">=3.13.0"
|
||||||
|
@@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 0.14.31+223 # When changing this, update the tag in main() accordingly
|
version: 0.14.31+224 # When changing this, update the tag in main() accordingly
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.0.0 <4.0.0'
|
sdk: '>=3.0.0 <4.0.0'
|
||||||
@@ -57,7 +57,6 @@ dependencies:
|
|||||||
ref: main
|
ref: main
|
||||||
android_package_manager: ^0.6.0
|
android_package_manager: ^0.6.0
|
||||||
share_plus: ^7.0.0
|
share_plus: ^7.0.0
|
||||||
android_alarm_manager_plus: ^3.0.0
|
|
||||||
sqflite: ^2.2.0+3
|
sqflite: ^2.2.0+3
|
||||||
easy_localization: ^3.0.1
|
easy_localization: ^3.0.1
|
||||||
android_intent_plus: ^4.0.0
|
android_intent_plus: ^4.0.0
|
||||||
@@ -66,6 +65,7 @@ dependencies:
|
|||||||
hsluv: ^1.1.3
|
hsluv: ^1.1.3
|
||||||
connectivity_plus: ^5.0.0
|
connectivity_plus: ^5.0.0
|
||||||
shared_storage: ^0.8.0
|
shared_storage: ^0.8.0
|
||||||
|
workmanager: ^0.5.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Reference in New Issue
Block a user