mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-30 12:30:16 +02:00
Merge branch 'main' into re7gog
This commit is contained in:
@@ -8,7 +8,6 @@ import 'dart:math';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:crypto/crypto.dart';
|
||||
|
||||
import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart';
|
||||
import 'package:android_intent_plus/flag.dart';
|
||||
import 'package:android_package_installer/android_package_installer.dart';
|
||||
import 'package:android_package_manager/android_package_manager.dart';
|
||||
@@ -621,7 +620,8 @@ class AppsProvider with ChangeNotifier {
|
||||
// Returns an array of Ids for Apps that were successfully downloaded, regardless of installation result
|
||||
Future<List<String>> downloadAndInstallLatestApps(
|
||||
List<String> appIds, BuildContext? context,
|
||||
{NotificationsProvider? notificationsProvider}) async {
|
||||
{NotificationsProvider? notificationsProvider,
|
||||
bool forceParallelDownloads = false}) async {
|
||||
notificationsProvider =
|
||||
notificationsProvider ?? context?.read<NotificationsProvider>();
|
||||
List<String> appsToInstall = [];
|
||||
@@ -742,7 +742,7 @@ class AppsProvider with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
if (!settingsProvider.parallelDownloads) {
|
||||
if (forceParallelDownloads || !settingsProvider.parallelDownloads) {
|
||||
for (var id in appsToInstall) {
|
||||
await updateFn(id);
|
||||
}
|
||||
@@ -1448,19 +1448,17 @@ class _APKOriginWarningDialogState extends State<APKOriginWarningDialog> {
|
||||
/// When toCheck is empty, the function is in "install mode" (else it is in "update mode").
|
||||
/// In update mode, all apps in toCheck are checked for updates (in parallel).
|
||||
/// If an update is available and it cannot be installed silently, the user is notified of the available update.
|
||||
/// If there are any errors, the task is run again for the remaining apps after a few minutes (based on the error with the longest retry interval).
|
||||
/// Any app that has reached it's retry limit, the user is notified that it could not be checked.
|
||||
/// If there are any errors, we recursively call the same function with retry count for the relevant apps decremented (if zero, the user is notified).
|
||||
///
|
||||
/// Once all update checks are complete, the task is run again in install mode.
|
||||
/// In this mode, all pending silent updates are downloaded and installed in the background (serially - one at a time).
|
||||
/// 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.
|
||||
/// In this mode, all pending silent updates are downloaded (in parallel) and installed in the background.
|
||||
/// If there is an error, the user is notified.
|
||||
///
|
||||
@pragma('vm:entry-point')
|
||||
Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
||||
Future<void> bgUpdateCheck(String taskId, Map<String, dynamic>? params) async {
|
||||
// ignore: avoid_print
|
||||
print('Started $taskId: ${params.toString()}');
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await EasyLocalization.ensureInitialized();
|
||||
await AndroidAlarmManager.initialize();
|
||||
await loadTranslations();
|
||||
|
||||
LogsProvider logs = LogsProvider();
|
||||
@@ -1469,11 +1467,20 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
||||
await appsProvider.loadApps();
|
||||
|
||||
int maxAttempts = 4;
|
||||
int maxRetryWaitSeconds = 5;
|
||||
|
||||
var netResult = await (Connectivity().checkConnectivity());
|
||||
if (netResult == ConnectivityResult.none) {
|
||||
logs.add('BG update task: No network.');
|
||||
return;
|
||||
}
|
||||
|
||||
params ??= {};
|
||||
if (params['toCheck'] == null) {
|
||||
appsProvider.settingsProvider.lastBGCheckTime = DateTime.now();
|
||||
}
|
||||
|
||||
bool firstEverUpdateTask = DateTime.fromMillisecondsSinceEpoch(0)
|
||||
.compareTo(appsProvider.settingsProvider.lastCompletedBGCheckTime) ==
|
||||
0;
|
||||
|
||||
List<MapEntry<String, int>> toCheck = <MapEntry<String, int>>[
|
||||
...(params['toCheck']
|
||||
?.map((entry) => MapEntry<String, int>(
|
||||
@@ -1481,6 +1488,11 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
||||
.toList() ??
|
||||
appsProvider
|
||||
.getAppsSortedByUpdateCheckTime(
|
||||
ignoreAppsCheckedAfter: params['toCheck'] == null
|
||||
? firstEverUpdateTask
|
||||
? null
|
||||
: appsProvider.settingsProvider.lastCompletedBGCheckTime
|
||||
: null,
|
||||
onlyCheckInstalledOrTrackOnlyApps: appsProvider
|
||||
.settingsProvider.onlyCheckInstalledOrTrackOnlyApps)
|
||||
.map((e) => MapEntry(e, 0)))
|
||||
@@ -1493,51 +1505,34 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
||||
(<List<MapEntry<String, int>>>[]))
|
||||
];
|
||||
|
||||
var netResult = await (Connectivity().checkConnectivity());
|
||||
|
||||
if (netResult == ConnectivityResult.none) {
|
||||
var networkBasedRetryInterval = 15;
|
||||
var nextRegularCheck = appsProvider.settingsProvider.lastBGCheckTime
|
||||
.add(Duration(minutes: appsProvider.settingsProvider.updateInterval));
|
||||
var potentialNetworkRetryCheck =
|
||||
DateTime.now().add(Duration(minutes: networkBasedRetryInterval));
|
||||
var shouldRetry = potentialNetworkRetryCheck.isBefore(nextRegularCheck);
|
||||
logs.add(
|
||||
'BG update task $taskId: No network. Will ${shouldRetry ? 'retry in $networkBasedRetryInterval minutes' : 'not retry'}.');
|
||||
AndroidAlarmManager.oneShot(
|
||||
const Duration(minutes: 15), taskId + 1, bgUpdateCheck,
|
||||
params: {
|
||||
'toCheck': toCheck
|
||||
.map((entry) => {'key': entry.key, 'value': entry.value})
|
||||
.toList(),
|
||||
'toInstall': toInstall
|
||||
.map((entry) => {'key': entry.key, 'value': entry.value})
|
||||
.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 (toCheck.isNotEmpty) {
|
||||
// Task is either in update mode or install mode
|
||||
// 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)
|
||||
// Then we run the function again in install mode (toCheck is empty)
|
||||
|
||||
var enoughTimePassed = appsProvider.settingsProvider.updateInterval != 0 &&
|
||||
appsProvider.settingsProvider.lastCompletedBGCheckTime
|
||||
.add(
|
||||
Duration(minutes: appsProvider.settingsProvider.updateInterval))
|
||||
.isBefore(DateTime.now());
|
||||
if (!enoughTimePassed) {
|
||||
// ignore: avoid_print
|
||||
print(
|
||||
'BG update task: Too early for another check (last check was ${appsProvider.settingsProvider.lastCompletedBGCheckTime.toIso8601String()}, interval is ${appsProvider.settingsProvider.updateInterval}).');
|
||||
return;
|
||||
}
|
||||
|
||||
logs.add('BG update task: Started (${toCheck.length}).');
|
||||
|
||||
// Init. vars.
|
||||
List<App> updates = []; // All updates found (silent and non-silent)
|
||||
@@ -1545,8 +1540,7 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
||||
[]; // 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)
|
||||
var retryAfterXSeconds = 0;
|
||||
MultiAppMultiError?
|
||||
errors; // All errors including those that will lead to a retry
|
||||
MultiAppMultiError toThrow =
|
||||
@@ -1569,27 +1563,32 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
||||
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()}\'.');
|
||||
'BG update task: 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
|
||||
int minRetryIntervalForThisApp = err is RateLimitError
|
||||
? (err.remainingMinutes * 60)
|
||||
: e is ClientException
|
||||
? (15 * 60)
|
||||
: pow(toCheckApp.value + 1, 2).toInt();
|
||||
: (toCheckApp.value + 1);
|
||||
if (minRetryIntervalForThisApp > maxRetryWaitSeconds) {
|
||||
minRetryIntervalForThisApp = maxRetryWaitSeconds;
|
||||
}
|
||||
if (minRetryIntervalForThisApp > retryAfterXSeconds) {
|
||||
retryAfterXSeconds = minRetryIntervalForThisApp;
|
||||
}
|
||||
} else {
|
||||
toThrow.add(key, err, appName: errors?.appIdNames[key]);
|
||||
if (err is! RateLimitError) {
|
||||
toThrow.add(key, err, appName: errors?.appIdNames[key]);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -1624,37 +1623,32 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
||||
id: Random().nextInt(10000)));
|
||||
}
|
||||
}
|
||||
|
||||
// if there are update checks to retry, schedule a retry task
|
||||
logs.add('BG update task: Done checking for updates.');
|
||||
if (toRetry.isNotEmpty) {
|
||||
logs.add(
|
||||
'BG update task $taskId: Will retry in $retryAfterXSeconds seconds.');
|
||||
AndroidAlarmManager.oneShot(
|
||||
Duration(seconds: retryAfterXSeconds), taskId + 1, bgUpdateCheck,
|
||||
params: {
|
||||
'toCheck': toRetry
|
||||
.map((entry) => {'key': entry.key, 'value': entry.value})
|
||||
.toList(),
|
||||
'toInstall': toInstall
|
||||
.map((entry) => {'key': entry.key, 'value': entry.value})
|
||||
.toList(),
|
||||
});
|
||||
return await bgUpdateCheck(taskId, {
|
||||
'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()
|
||||
});
|
||||
// If there are no more update checks, call the function in install mode
|
||||
logs.add('BG update task: Done checking for updates.');
|
||||
return await bgUpdateCheck(taskId, {
|
||||
'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 you haven't explicitly been given updates to install, grab all available silent updates
|
||||
if (toInstall.isEmpty && !networkRestricted) {
|
||||
var temp = appsProvider.findExistingUpdates(installedOnly: true);
|
||||
for (var i = 0; i < temp.length; i++) {
|
||||
@@ -1664,60 +1658,34 @@ Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
if (toInstall.isNotEmpty) {
|
||||
logs.add('BG install task: Started (${toInstall.length}).');
|
||||
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
|
||||
try {
|
||||
logs.add(
|
||||
'BG install 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;
|
||||
}
|
||||
await appsProvider.downloadAndInstallLatestApps(
|
||||
toInstall.map((e) => e.key).toList(), null,
|
||||
notificationsProvider: notificationsProvider,
|
||||
forceParallelDownloads: 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 install task $taskId: Got error on updating $appId \'${e.toString()}\'.');
|
||||
if (retryCount < maxAttempts) {
|
||||
var remainingSeconds = retryCount;
|
||||
logs.add(
|
||||
'BG install 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));
|
||||
AndroidAlarmManager.oneShot(
|
||||
Duration(seconds: remainingSeconds), taskId + 1, bgUpdateCheck,
|
||||
params: {
|
||||
'toCheck': toCheck
|
||||
.map((entry) => {'key': entry.key, 'value': entry.value})
|
||||
.toList(),
|
||||
'toInstall': remainingToInstall
|
||||
.map((entry) => {'key': entry.key, 'value': entry.value})
|
||||
.toList(),
|
||||
});
|
||||
break;
|
||||
if (e is MultiAppMultiError) {
|
||||
e.idsByErrorString.forEach((key, value) {
|
||||
notificationsProvider.notify(ErrorCheckingUpdatesNotification(
|
||||
e.errorsAppsString(key, value)));
|
||||
});
|
||||
} 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()));
|
||||
// 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 install task: ${e.toString()}');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (didCompleteInstalling || toInstall.isEmpty) {
|
||||
logs.add('BG install task $taskId: Done.');
|
||||
logs.add('BG install task: Done installing updates.');
|
||||
}
|
||||
}
|
||||
appsProvider.settingsProvider.lastCompletedBGCheckTime = DateTime.now();
|
||||
}
|
||||
|
Reference in New Issue
Block a user