From 7c41692d5f1b9ef22ce6815ed510b751be62950c Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sun, 20 Aug 2023 16:03:41 -0400 Subject: [PATCH 01/22] Enable version correction in background (replace plugin) --- android/app/src/main/AndroidManifest.xml | 1 + lib/pages/app.dart | 4 +- lib/pages/apps.dart | 4 +- lib/providers/apps_provider.dart | 162 ++++++++++------------- pubspec.lock | 26 +--- pubspec.yaml | 2 - 6 files changed, 78 insertions(+), 121 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index cfeac66..d48e3f5 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -71,4 +71,5 @@ + \ No newline at end of file diff --git a/lib/pages/app.dart b/lib/pages/app.dart index 0708b02..de64bb3 100644 --- a/lib/pages/app.dart +++ b/lib/pages/app.dart @@ -153,10 +153,10 @@ class _AppPageState extends State { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const SizedBox(height: 125), - app?.installedInfo != null + app?.icon != null ? Row(mainAxisAlignment: MainAxisAlignment.center, children: [ Image.memory( - app!.installedInfo!.icon!, + app!.icon!, height: 150, gaplessPlayback: true, ) diff --git a/lib/pages/apps.dart b/lib/pages/apps.dart index f8ee0e4..516a6a6 100644 --- a/lib/pages/apps.dart +++ b/lib/pages/apps.dart @@ -393,9 +393,9 @@ class AppsPageState extends State { } getAppIcon(int appIndex) { - return listedApps[appIndex].installedInfo != null + return listedApps[appIndex].icon != null ? Image.memory( - listedApps[appIndex].installedInfo!.icon!, + listedApps[appIndex].icon!, gaplessPlayback: true, ) : Row( diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index 4f5b475..3c63a72 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -12,15 +12,12 @@ import 'package:device_info_plus/device_info_plus.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:installed_apps/app_info.dart'; -import 'package:installed_apps/installed_apps.dart'; import 'package:obtainium/components/generated_form.dart'; import 'package:obtainium/components/generated_form_modal.dart'; import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/providers/logs_provider.dart'; import 'package:obtainium/providers/notifications_provider.dart'; import 'package:obtainium/providers/settings_provider.dart'; -import 'package:package_archive_info/package_archive_info.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; import 'package:path_provider/path_provider.dart'; @@ -35,13 +32,14 @@ final pm = AndroidPackageManager(); class AppInMemory { late App app; double? downloadProgress; - AppInfo? installedInfo; + PackageInfo? installedInfo; + Uint8List? icon; - AppInMemory(this.app, this.downloadProgress, this.installedInfo); + AppInMemory(this.app, this.downloadProgress, this.installedInfo, this.icon); AppInMemory deepCopy() => - AppInMemory(app.deepCopy(), downloadProgress, installedInfo); + AppInMemory(app.deepCopy(), downloadProgress, installedInfo, icon); - String get name => app.overrideName ?? installedInfo?.name ?? app.finalName; + String get name => app.overrideName ?? app.finalName; } class DownloadedApk { @@ -218,19 +216,19 @@ class AppsProvider with ChangeNotifier { return downloadedFile; } - Future handleAPKIDChange(App app, PackageArchiveInfo newInfo, + Future handleAPKIDChange(App app, PackageInfo newInfo, File downloadedFile, String downloadUrl) async { // If the APK package ID is different from the App ID, it is either new (using a placeholder ID) or the ID has changed // The former case should be handled (give the App its real ID), the latter is a security issue if (app.id != newInfo.packageName) { var isTempId = SourceProvider().isTempId(app); if (apps[app.id] != null && !isTempId && !app.allowIdChange) { - throw IDChangedError(newInfo.packageName); + throw IDChangedError(newInfo.packageName!); } var idChangeWasAllowed = app.allowIdChange; app.allowIdChange = false; var originalAppId = app.id; - app.id = newInfo.packageName; + app.id = newInfo.packageName!; downloadedFile = downloadedFile.renameSync( '${downloadedFile.parent.path}/${app.id}-${downloadUrl.hashCode}.${downloadedFile.path.split('.').last}'); if (apps[originalAppId] != null) { @@ -279,11 +277,12 @@ class AppsProvider with ChangeNotifier { notif = DownloadNotification(app.finalName, -1); notificationsProvider?.notify(notif); } - PackageArchiveInfo? newInfo; + PackageInfo? newInfo; var isAPK = downloadedFile.path.toLowerCase().endsWith('.apk'); Directory? xapkDir; if (isAPK) { - newInfo = await PackageArchiveInfo.fromPath(downloadedFile.path); + newInfo = await pm.getPackageArchiveInfo( + archiveFilePath: downloadedFile.path); } else { // Assume XAPK String xapkDirPath = '${downloadedFile.path}-dir'; @@ -293,10 +292,11 @@ class AppsProvider with ChangeNotifier { .listSync() .where((e) => e.path.toLowerCase().endsWith('.apk')) .toList(); - newInfo = await PackageArchiveInfo.fromPath(apks.first.path); + newInfo = + await pm.getPackageArchiveInfo(archiveFilePath: apks.first.path); } downloadedFile = - await handleAPKIDChange(app, newInfo, downloadedFile, downloadUrl); + await handleAPKIDChange(app, newInfo!, downloadedFile, downloadUrl); // Delete older versions of the file if any for (var file in downloadedFile.parent.listSync()) { var fn = file.path.split('/').last; @@ -344,14 +344,8 @@ class AppsProvider with ChangeNotifier { // If we did not install the app (or it isn't installed), silent install is not possible return false; } - int? targetSDK; - try { - targetSDK = (await pm.getPackageInfo(packageName: app.id)) - ?.applicationInfo - ?.targetSdkVersion; - } catch (e) { - // Weird if you get here - ignore - } + int? targetSDK = + (await getInstalledInfo(app.id))?.applicationInfo?.targetSdkVersion; // The OS must also be new enough and the APK should target a new enough API return osInfo.version.sdkInt >= 30 && @@ -371,14 +365,8 @@ class AppsProvider with ChangeNotifier { } } - Future canDowngradeApps() async { - try { - await InstalledApps.getAppInfo('com.berdik.letmedowngrade'); - return true; - } catch (e) { - return false; - } - } + Future canDowngradeApps() async => + (await getInstalledInfo('com.berdik.letmedowngrade')) != null; Future unzipFile(String filePath, String destinationPath) async { await ZipFile.extractToDirectory( @@ -419,15 +407,11 @@ class AppsProvider with ChangeNotifier { } Future installApk(DownloadedApk file) async { - var newInfo = await PackageArchiveInfo.fromPath(file.file.path); - AppInfo? appInfo; - try { - appInfo = await InstalledApps.getAppInfo(apps[file.appId]!.app.id); - } catch (e) { - // OK - } + var newInfo = + await pm.getPackageArchiveInfo(archiveFilePath: file.file.path); + PackageInfo? appInfo = await getInstalledInfo(apps[file.appId]!.app.id); if (appInfo != null && - int.parse(newInfo.buildNumber) < appInfo.versionCode! && + newInfo!.versionCode! < appInfo.versionCode! && !(await canDowngradeApps())) { throw DowngradeError(); } @@ -638,27 +622,17 @@ class AppsProvider with ChangeNotifier { return appsDir; } - Future getInstalledInfo(String? packageName) async { + Future getInstalledInfo(String? packageName) async { if (packageName != null) { try { - return await InstalledApps.getAppInfo(packageName); + return await pm.getPackageInfo(packageName: packageName); } catch (e) { - // OK + print(e); // OK } } return null; } - Future doesInstalledAppsPluginWork() async { - bool res = false; - try { - res = (await InstalledApps.getAppInfo(obtainiumId)).versionName != null; - } catch (e) { - // - } - return res; - } - bool isVersionDetectionPossible(AppInMemory? app) { return app?.app.additionalSettings['trackOnly'] != true && app?.app.additionalSettings['versionDetection'] != @@ -672,7 +646,8 @@ class AppsProvider with ChangeNotifier { // Given an App and it's on-device info... // Reconcile unexpected differences between its reported installed version, real installed version, and reported latest version - App? getCorrectedInstallStatusAppIfPossible(App app, AppInfo? installedInfo) { + App? getCorrectedInstallStatusAppIfPossible( + App app, PackageInfo? installedInfo) { var modded = false; var trackOnly = app.additionalSettings['trackOnly'] == true; var noVersionDetection = app.additionalSettings['versionDetection'] != @@ -718,7 +693,8 @@ class AppsProvider with ChangeNotifier { if (installedInfo != null && app.additionalSettings['versionDetection'] == 'standardVersionDetection' && - !isVersionDetectionPossible(AppInMemory(app, null, installedInfo))) { + !isVersionDetectionPossible( + AppInMemory(app, null, installedInfo, null))) { app.additionalSettings['versionDetection'] = 'noVersionDetection'; logs.add('Could not reconcile version formats for: ${app.id}'); modded = true; @@ -802,9 +778,9 @@ class AppsProvider with ChangeNotifier { sp.getSource(app.url, overrideSource: app.overrideSource); apps.update( app.id, - (value) => - AppInMemory(app, value.downloadProgress, value.installedInfo), - ifAbsent: () => AppInMemory(app, null, null)); + (value) => AppInMemory( + app, value.downloadProgress, value.installedInfo, value.icon), + ifAbsent: () => AppInMemory(app, null, null, null)); } catch (e) { errors.add([app.id, app.finalName, e.toString()]); } @@ -817,34 +793,39 @@ class AppsProvider with ChangeNotifier { AppsRemovedNotification(errors.map((e) => [e[1], e[2]]).toList())); } - if (await doesInstalledAppsPluginWork()) { - for (var app in apps.values) { - // Check install status for each App (slow) - apps[app.app.id]?.installedInfo = await getInstalledInfo(app.app.id); - notifyListeners(); + for (var app in apps.values) { + // Get install status and other OS info for each App (slow) + apps[app.app.id]?.installedInfo = await getInstalledInfo(app.app.id); + apps[app.app.id]?.icon = + await apps[app.app.id]?.installedInfo?.applicationInfo?.getAppIcon(); + apps[app.app.id]?.app.name = await (apps[app.app.id] + ?.installedInfo + ?.applicationInfo + ?.getAppLabel()) ?? + app.name; + notifyListeners(); + } + // Reconcile version differences + List modifiedApps = []; + for (var app in apps.values) { + var moddedApp = + getCorrectedInstallStatusAppIfPossible(app.app, app.installedInfo); + if (moddedApp != null) { + modifiedApps.add(moddedApp); } - // Reconcile version differences - List modifiedApps = []; - for (var app in apps.values) { - var moddedApp = - getCorrectedInstallStatusAppIfPossible(app.app, app.installedInfo); - if (moddedApp != null) { - modifiedApps.add(moddedApp); - } - } - if (modifiedApps.isNotEmpty) { - await saveApps(modifiedApps, attemptToCorrectInstallStatus: false); - var removedAppIds = modifiedApps - .where((a) => a.installedVersion == null) - .map((e) => e.id) - .toList(); - // After reconciliation, delete externally uninstalled Apps if needed - if (removedAppIds.isNotEmpty) { - var settingsProvider = SettingsProvider(); - await settingsProvider.initializeSettings(); - if (settingsProvider.removeOnExternalUninstall) { - await removeApps(removedAppIds); - } + } + if (modifiedApps.isNotEmpty) { + await saveApps(modifiedApps, attemptToCorrectInstallStatus: false); + var removedAppIds = modifiedApps + .where((a) => a.installedVersion == null) + .map((e) => e.id) + .toList(); + // After reconciliation, delete externally uninstalled Apps if needed + if (removedAppIds.isNotEmpty) { + var settingsProvider = SettingsProvider(); + await settingsProvider.initializeSettings(); + if (settingsProvider.removeOnExternalUninstall) { + await removeApps(removedAppIds); } } } @@ -856,12 +837,12 @@ class AppsProvider with ChangeNotifier { Future saveApps(List apps, {bool attemptToCorrectInstallStatus = true, bool onlyIfExists = true}) async { - attemptToCorrectInstallStatus = - attemptToCorrectInstallStatus && (await doesInstalledAppsPluginWork()); + attemptToCorrectInstallStatus = attemptToCorrectInstallStatus; for (var a in apps) { var app = a.deepCopy(); - AppInfo? info = await getInstalledInfo(app.id); - app.name = info?.name ?? app.name; + PackageInfo? info = await getInstalledInfo(app.id); + var icon = await info?.applicationInfo?.getAppIcon(); + app.name = await (info?.applicationInfo?.getAppLabel()) ?? app.name; if (attemptToCorrectInstallStatus) { app = getCorrectedInstallStatusAppIfPossible(app, info) ?? app; } @@ -870,9 +851,10 @@ class AppsProvider with ChangeNotifier { .writeAsStringSync(jsonEncode(app.toJson())); } try { - this.apps.update( - app.id, (value) => AppInMemory(app, value.downloadProgress, info), - ifAbsent: onlyIfExists ? null : () => AppInMemory(app, null, info)); + this.apps.update(app.id, + (value) => AppInMemory(app, value.downloadProgress, info, icon), + ifAbsent: + onlyIfExists ? null : () => AppInMemory(app, null, info, icon)); } catch (e) { if (e is! ArgumentError || e.name != 'key') { rethrow; diff --git a/pubspec.lock b/pubspec.lock index d702e92..ffb90fa 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -31,7 +31,7 @@ packages: description: path: "." ref: master - resolved-ref: "6e68991ef9c6232695abce2eef345d3cca2f52ac" + resolved-ref: c7c2f992a9dc452393c94d96cdf2b1f5a5ce7c80 url: "https://github.com/ImranR98/android_package_manager" source: git version: "0.5.4" @@ -375,14 +375,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.17" - installed_apps: - dependency: "direct main" - description: - name: installed_apps - sha256: "145af8eb6e4e7c830e9888d6de0573ae5c139e8e0742a3e67316e1db21ab6fe0" - url: "https://pub.dev" - source: hosted - version: "1.3.1" intl: dependency: transitive description: @@ -463,22 +455,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" - package_archive_info: - dependency: "direct main" - description: - name: package_archive_info - sha256: "8f671a29b79d15f192e5e2b0dab9d0bad66b9ee93fb58d4e0afdb62f91a259be" - url: "https://pub.dev" - source: hosted - version: "0.1.0" - package_info: - dependency: transitive - description: - name: package_info - sha256: "6c07d9d82c69e16afeeeeb6866fe43985a20b3b50df243091bfc4a4ad2b03b75" - url: "https://pub.dev" - source: hosted - version: "2.0.2" path: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 83b618f..551bf12 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -60,8 +60,6 @@ dependencies: url: https://github.com/ImranR98/android_package_manager ref: master share_plus: ^7.0.0 - installed_apps: ^1.3.1 # TODO: Remove when android_package_manager supports getting icon as UInt8List and versionCode - package_archive_info: ^0.1.0 # TODO: Remove when android_package_manager supports getting versionCode android_alarm_manager_plus: ^3.0.0 sqflite: ^2.2.0+3 easy_localization: ^3.0.1 From b59d3e77f9e7eb99fc2175201580ec8734423171 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sun, 20 Aug 2023 22:32:33 -0400 Subject: [PATCH 02/22] Enable experimental BG update support (not well tested) (#25) --- assets/translations/bs.json | 7 ++++ assets/translations/de.json | 7 ++++ assets/translations/en.json | 7 ++++ assets/translations/es.json | 7 ++++ assets/translations/fa.json | 7 ++++ assets/translations/fr.json | 7 ++++ assets/translations/hu.json | 7 ++++ assets/translations/it.json | 7 ++++ assets/translations/ja.json | 7 ++++ assets/translations/pl.json | 7 ++++ assets/translations/ru.json | 7 ++++ assets/translations/zh.json | 7 ++++ lib/main.dart | 43 +++++++++----------- lib/providers/apps_provider.dart | 49 +++++++++++++++++------ lib/providers/notifications_provider.dart | 32 +++++++++++---- 15 files changed, 164 insertions(+), 44 deletions(-) diff --git a/assets/translations/bs.json b/assets/translations/bs.json index 2c3c5d3..ef08037 100644 --- a/assets/translations/bs.json +++ b/assets/translations/bs.json @@ -247,6 +247,9 @@ "sortByFileNamesNotLinks": "Sort by file names instead of full links", "filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression", "customLinkFilterRegex": "Custom Link Filter by Regular Expression (Default '.apk$')", + "appsPossiblyUpdated": "App Updates Attempted", + "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", + "xWasPossiblyUpdatedToY": "An attempt was made to update {} to {}.", "removeAppQuestion": { "one": "Želite li ukloniti aplikaciju?", "other": "Želite li ukloniti aplikacije?" @@ -294,5 +297,9 @@ "xAndNMoreUpdatesInstalled": { "one": "{} i još 1 aplikacija je ažurirana.", "other": "{} i još {} aplikacija je ažurirano." + }, + "xAndNMoreUpdatesPossiblyInstalled": { + "one": "Attempts were made to update {} and 1 more app.", + "other": "Attempts were made to update {} and {} more apps." } } diff --git a/assets/translations/de.json b/assets/translations/de.json index b47e385..ffc08dc 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -247,6 +247,9 @@ "sortByFileNamesNotLinks": "Sortiere nach Dateinamen, anstelle von ganzen Links", "filterReleaseNotesByRegEx": "Versionshinweise nach regulärem Ausdruck filtern", "customLinkFilterRegex": "Benutzerdefinierter Link Filter nach Regulärem Ausdruck (Standard '.apk$')", + "appsPossiblyUpdated": "App Updates Attempted", + "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", + "xWasPossiblyUpdatedToY": "An attempt was made to update {} to {}.", "removeAppQuestion": { "one": "App entfernen?", "other": "Apps entfernen?" @@ -294,5 +297,9 @@ "xAndNMoreUpdatesInstalled": { "one": "{} und 1 weitere Anwendung wurden aktualisiert.", "other": "{} und {} weitere Anwendungen wurden aktualisiert." + }, + "xAndNMoreUpdatesPossiblyInstalled": { + "one": "Attempts were made to update {} and 1 more app.", + "other": "Attempts were made to update {} and {} more apps." } } diff --git a/assets/translations/en.json b/assets/translations/en.json index 397c7a2..8fa9763 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -247,6 +247,9 @@ "sortByFileNamesNotLinks": "Sort by file names instead of full links", "filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression", "customLinkFilterRegex": "Custom Link Filter by Regular Expression (Default '.apk$')", + "appsPossiblyUpdated": "App Updates Attempted", + "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", + "xWasPossiblyUpdatedToY": "An attempt was made to update {} to {}.", "removeAppQuestion": { "one": "Remove App?", "other": "Remove Apps?" @@ -294,5 +297,9 @@ "xAndNMoreUpdatesInstalled": { "one": "{} and 1 more app were updated.", "other": "{} and {} more apps were updated." + }, + "xAndNMoreUpdatesPossiblyInstalled": { + "one": "Attempts were made to update {} and 1 more app.", + "other": "Attempts were made to update {} and {} more apps." } } diff --git a/assets/translations/es.json b/assets/translations/es.json index b5d3d25..bffc0de 100644 --- a/assets/translations/es.json +++ b/assets/translations/es.json @@ -247,6 +247,9 @@ "sortByFileNamesNotLinks": "Sort by file names instead of full links", "filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression", "customLinkFilterRegex": "Custom Link Filter by Regular Expression (Default '.apk$')", + "appsPossiblyUpdated": "App Updates Attempted", + "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", + "xWasPossiblyUpdatedToY": "An attempt was made to update {} to {}.", "removeAppQuestion": { "one": "¿Eliminar Aplicación?", "other": "¿Eliminar Aplicaciones?" @@ -294,5 +297,9 @@ "xAndNMoreUpdatesInstalled": { "one": "{} y 1 aplicación más han sido actualizadas.", "other": "{} y {} aplicaciones más han sido actualizadas." + }, + "xAndNMoreUpdatesPossiblyInstalled": { + "one": "Attempts were made to update {} and 1 more app.", + "other": "Attempts were made to update {} and {} more apps." } } diff --git a/assets/translations/fa.json b/assets/translations/fa.json index 3f98e69..02027bf 100644 --- a/assets/translations/fa.json +++ b/assets/translations/fa.json @@ -247,6 +247,9 @@ "sortByFileNamesNotLinks": "مرتب سازی بر اساس نام فایل به جای پیوندهای کامل", "filterReleaseNotesByRegEx": "یادداشت های انتشار را با بیان منظم فیلتر کنید", "customLinkFilterRegex": "فیلتر پیوند سفارشی بر اساس عبارت منظم (پیش‌فرض '.apk$')", + "appsPossiblyUpdated": "App Updates Attempted", + "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", + "xWasPossiblyUpdatedToY": "An attempt was made to update {} to {}.", "removeAppQuestion": { "one": "برنامه حذف شود؟", "other": "برنامه ها حذف شوند؟" @@ -294,5 +297,9 @@ "xAndNMoreUpdatesInstalled": { "one": "{} و 1 برنامه دیگر به روز شدند.", "other": "{} و {} برنامه دیگر به روز شدند." + }, + "xAndNMoreUpdatesPossiblyInstalled": { + "one": "Attempts were made to update {} and 1 more app.", + "other": "Attempts were made to update {} and {} more apps." } } diff --git a/assets/translations/fr.json b/assets/translations/fr.json index ec680f8..582e286 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -247,6 +247,9 @@ "sortByFileNamesNotLinks": "Sort by file names instead of full links", "filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression", "customLinkFilterRegex": "Custom Link Filter by Regular Expression (Default '.apk$')", + "appsPossiblyUpdated": "App Updates Attempted", + "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", + "xWasPossiblyUpdatedToY": "An attempt was made to update {} to {}.", "removeAppQuestion": { "one": "Supprimer l'application ?", "other": "Supprimer les applications ?" @@ -294,5 +297,9 @@ "xAndNMoreUpdatesInstalled": { "one": "{} et 1 autre application ont été mises à jour.", "other": "{} et {} autres applications ont été mises à jour." + }, + "xAndNMoreUpdatesPossiblyInstalled": { + "one": "Attempts were made to update {} and 1 more app.", + "other": "Attempts were made to update {} and {} more apps." } } diff --git a/assets/translations/hu.json b/assets/translations/hu.json index da0b17a..5164bcd 100644 --- a/assets/translations/hu.json +++ b/assets/translations/hu.json @@ -246,6 +246,9 @@ "sortByFileNamesNotLinks": "Fájlnevek szerinti elrendezés teljes linkek helyett", "filterReleaseNotesByRegEx": "Kiadási megjegyzések szűrése reguláris kifejezéssel", "customLinkFilterRegex": "Custom Link Filter by Regular Expression (Default '.apk$')", + "appsPossiblyUpdated": "App Updates Attempted", + "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", + "xWasPossiblyUpdatedToY": "An attempt was made to update {} to {}.", "removeAppQuestion": { "one": "Eltávolítja az alkalmazást?", "other": "Eltávolítja az alkalmazást?" @@ -293,5 +296,9 @@ "xAndNMoreUpdatesInstalled": { "one": "A(z) {} és 1 további alkalmazás frissítve.", "other": "{} és {} további alkalmazás frissítve." + }, + "xAndNMoreUpdatesPossiblyInstalled": { + "one": "Attempts were made to update {} and 1 more app.", + "other": "Attempts were made to update {} and {} more apps." } } diff --git a/assets/translations/it.json b/assets/translations/it.json index 4dd0383..ed43216 100644 --- a/assets/translations/it.json +++ b/assets/translations/it.json @@ -247,6 +247,9 @@ "sortByFileNamesNotLinks": "Sort by file names instead of full links", "filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression", "customLinkFilterRegex": "Custom Link Filter by Regular Expression (Default '.apk$')", + "appsPossiblyUpdated": "App Updates Attempted", + "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", + "xWasPossiblyUpdatedToY": "An attempt was made to update {} to {}.", "removeAppQuestion": { "one": "Rimuovere l'app?", "other": "Rimuovere le app?" @@ -294,5 +297,9 @@ "xAndNMoreUpdatesInstalled": { "one": "{} e un'altra app sono state aggiornate.", "other": "{} e altre {} app sono state aggiornate." + }, + "xAndNMoreUpdatesPossiblyInstalled": { + "one": "Attempts were made to update {} and 1 more app.", + "other": "Attempts were made to update {} and {} more apps." } } diff --git a/assets/translations/ja.json b/assets/translations/ja.json index 1ade9f2..37edc58 100644 --- a/assets/translations/ja.json +++ b/assets/translations/ja.json @@ -247,6 +247,9 @@ "sortByFileNamesNotLinks": "フルのリンクではなくファイル名でソートする", "filterReleaseNotesByRegEx": "正規表現でリリースノートをフィルタリングする", "customLinkFilterRegex": "正規表現によるカスタムリンクフィルター (デフォルト '.apk$')", + "appsPossiblyUpdated": "App Updates Attempted", + "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", + "xWasPossiblyUpdatedToY": "An attempt was made to update {} to {}.", "removeAppQuestion": { "one": "アプリを削除しますか?", "other": "アプリを削除しますか?" @@ -294,5 +297,9 @@ "xAndNMoreUpdatesInstalled": { "one": "{} とさらに {} 個のアプリがアップデートされました", "other": "{} とさらに {} 個のアプリがアップデートされました" + }, + "xAndNMoreUpdatesPossiblyInstalled": { + "one": "Attempts were made to update {} and 1 more app.", + "other": "Attempts were made to update {} and {} more apps." } } diff --git a/assets/translations/pl.json b/assets/translations/pl.json index b8a1a81..ea8be8a 100644 --- a/assets/translations/pl.json +++ b/assets/translations/pl.json @@ -251,6 +251,9 @@ "sortByFileNamesNotLinks": "Sortuj wg nazw plików zamiast pełnych linków", "filterReleaseNotesByRegEx": "Filtruj informacje o wersji według wyrażenia regularnego", "customLinkFilterRegex": "Niestandardowy filtr linków wg. wyrażenia regularnego (domyślnie \".apk$\")", + "appsPossiblyUpdated": "App Updates Attempted", + "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", + "xWasPossiblyUpdatedToY": "An attempt was made to update {} to {}.", "removeAppQuestion": { "one": "Usunąć aplikację?", "other": "Usunąć aplikacje?" @@ -294,5 +297,9 @@ "xAndNMoreUpdatesInstalled": { "one": "Zaktualizowano {} i jeszcze 1 aplikację.", "other": "Zaktualizowano {} i {} aplik." + }, + "xAndNMoreUpdatesPossiblyInstalled": { + "one": "Attempts were made to update {} and 1 more app.", + "other": "Attempts were made to update {} and {} more apps." } } diff --git a/assets/translations/ru.json b/assets/translations/ru.json index 1f27453..609b5e3 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -247,6 +247,9 @@ "sortByFileNamesNotLinks": "Sort by file names instead of full links", "filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression", "customLinkFilterRegex": "Custom Link Filter by Regular Expression (Default '.apk$')", + "appsPossiblyUpdated": "App Updates Attempted", + "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", + "xWasPossiblyUpdatedToY": "An attempt was made to update {} to {}.", "removeAppQuestion": { "one": "Удалить приложение?", "other": "Удалить приложения?" @@ -294,5 +297,9 @@ "xAndNMoreUpdatesInstalled": { "one": "{} и еще 1 приложение были обновлены.", "other": "{} и еще {} приложений были обновлены." + }, + "xAndNMoreUpdatesPossiblyInstalled": { + "one": "Attempts were made to update {} and 1 more app.", + "other": "Attempts were made to update {} and {} more apps." } } diff --git a/assets/translations/zh.json b/assets/translations/zh.json index 028e8d1..1aa36fc 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -247,6 +247,9 @@ "sortByFileNamesNotLinks": "根据文件名而不是完整链接来排序", "filterReleaseNotesByRegEx": "用正则表达式筛选发布说明", "customLinkFilterRegex": "用正则表达式自定义链接筛选(默认 '.apk$')", + "appsPossiblyUpdated": "App Updates Attempted", + "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", + "xWasPossiblyUpdatedToY": "An attempt was made to update {} to {}.", "removeAppQuestion": { "one": "是否删除应用?", "other": "是否删除应用?" @@ -294,5 +297,9 @@ "xAndNMoreUpdatesInstalled": { "one": "{} 和另外 1 个应用已更新。", "other": "{} 和另外 {} 个应用已更新。" + }, + "xAndNMoreUpdatesPossiblyInstalled": { + "one": "Attempts were made to update {} and 1 more app.", + "other": "Attempts were made to update {} and {} more apps." } } diff --git a/lib/main.dart b/lib/main.dart index 5074433..79bdfcb 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -88,10 +88,8 @@ Future bgUpdateCheck(int taskId, Map? params) async { : null; logs.add(tr('bgUpdateIgnoreAfterIs', args: [ignoreAfter.toString()])); var notificationsProvider = NotificationsProvider(); - await notificationsProvider.notify(checkingUpdatesNotification); try { var appsProvider = AppsProvider(); - await notificationsProvider.cancel(ErrorCheckingUpdatesNotification('').id); await appsProvider.loadApps(); List existingUpdateIds = appsProvider.findExistingUpdates(installedOnly: true); @@ -100,7 +98,9 @@ Future bgUpdateCheck(int taskId, Map? params) async { try { logs.add(tr('startedActualBGUpdateCheck')); await appsProvider.checkUpdates( - ignoreAppsCheckedAfter: ignoreAfter, throwErrorsForRetry: true); + ignoreAppsCheckedAfter: ignoreAfter, + throwErrorsForRetry: true, + notificationsProvider: notificationsProvider); } catch (e) { if (e is RateLimitError || e is ClientException) { var remainingMinutes = e is RateLimitError ? e.remainingMinutes : 15; @@ -124,34 +124,29 @@ Future bgUpdateCheck(int taskId, Map? params) async { .where((id) => !existingUpdateIds.contains(id)) .map((e) => appsProvider.apps[e]!.app) .toList(); - - // TODO: This silent update code doesn't work yet - // List silentlyUpdated = await appsProvider - // .downloadAndInstallLatestApp( - // [...newUpdates.map((e) => e.id), ...existingUpdateIds], null); - // if (silentlyUpdated.isNotEmpty) { - // newUpdates = newUpdates - // .where((element) => !silentlyUpdated.contains(element.id)) - // .toList(); - // notificationsProvider.notify( - // SilentUpdateNotification( - // silentlyUpdated.map((e) => appsProvider.apps[e]!.app).toList()), - // cancelExisting: true); - // } - logs.add( - plural('bgCheckFoundUpdatesWillNotifyIfNeeded', newUpdates.length)); - if (newUpdates.isNotEmpty) { - notificationsProvider.notify(UpdateNotification(newUpdates)); + List nonSilentUpdates = []; + List silentUpdates = []; + for (var a in newUpdates) { + if (await appsProvider.canInstallSilently(a)) { + silentUpdates.add(a); + } else { + nonSilentUpdates.add(a); + } } + if (silentUpdates.isNotEmpty) { + await appsProvider.downloadAndInstallLatestApps( + silentUpdates.map((e) => e.id).toList(), null, + notificationsProvider: notificationsProvider); + } + logs.add(plural( + 'bgCheckFoundUpdatesWillNotifyIfNeeded', nonSilentUpdates.length)); if (err != null) { throw err; } } catch (e) { - notificationsProvider - .notify(ErrorCheckingUpdatesNotification(e.toString())); + logs.add('${tr('errorCheckingUpdates')}: ${e.toString()}'); } finally { logs.add(tr('bgUpdateTaskFinished')); - await notificationsProvider.cancel(checkingUpdatesNotification.id); } } diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index 3c63a72..30f75bd 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -239,9 +239,8 @@ class AppsProvider with ChangeNotifier { return downloadedFile; } - Future downloadApp(App app, BuildContext? context) async { - NotificationsProvider? notificationsProvider = - context?.read(); + Future downloadApp(App app, BuildContext? context, + {NotificationsProvider? notificationsProvider}) async { var notifId = DownloadNotification(app.finalName, 0).id; if (apps[app.id] != null) { apps[app.id]!.downloadProgress = 0; @@ -507,7 +506,10 @@ class AppsProvider with ChangeNotifier { // Returns an array of Ids for Apps that were successfully downloaded, regardless of installation result Future> downloadAndInstallLatestApps( List appIds, BuildContext? context, - {SettingsProvider? settingsProvider}) async { + {SettingsProvider? settingsProvider, + NotificationsProvider? notificationsProvider}) async { + notificationsProvider = + notificationsProvider ?? context?.read(); List appsToInstall = []; List trackOnlyAppsToUpdate = []; // For all specified Apps, filter out those for which: @@ -567,8 +569,10 @@ class AppsProvider with ChangeNotifier { for (var id in appsToInstall) { try { - // ignore: use_build_context_synchronously - var downloadedArtifact = await downloadApp(apps[id]!.app, context); + var downloadedArtifact = + // ignore: use_build_context_synchronously + await downloadApp(apps[id]!.app, context, + notificationsProvider: notificationsProvider); DownloadedApk? downloadedFile; DownloadedXApkDir? downloadedDir; if (downloadedArtifact is DownloadedApk) { @@ -576,8 +580,8 @@ class AppsProvider with ChangeNotifier { } else { downloadedDir = downloadedArtifact as DownloadedXApkDir; } - bool willBeSilent = await canInstallSilently( - apps[downloadedFile?.appId ?? downloadedDir!.appId]!.app); + var appId = downloadedFile?.appId ?? downloadedDir!.appId; + bool willBeSilent = await canInstallSilently(apps[appId]!.app); if (!(await settingsProvider?.getInstallPermission(enforce: false) ?? true)) { throw ObtainiumError(tr('cancelled')); @@ -590,9 +594,24 @@ class AppsProvider with ChangeNotifier { notifyListeners(); try { if (downloadedFile != null) { - await installApk(downloadedFile); + if (willBeSilent) { + // Would await forever - workaround - TODO + installApk(downloadedFile); + } else { + await installApk(downloadedFile); + } } else { - await installXApkDir(downloadedDir!); + if (willBeSilent) { + // Would await forever - workaround - TODO + installXApkDir(downloadedDir!); + } else { + await installXApkDir(downloadedDir!); + } + } + if (willBeSilent) { + notificationsProvider?.notify(SilentUpdateAttemptNotification( + [apps[appId]!.app], + id: appId.hashCode)); } } finally { apps[id]?.downloadProgress = null; @@ -608,8 +627,6 @@ class AppsProvider with ChangeNotifier { throw errors; } - NotificationsProvider().cancel(UpdateNotification([]).id); - return installedIds; } @@ -971,7 +988,8 @@ class AppsProvider with ChangeNotifier { Future> checkUpdates( {DateTime? ignoreAppsCheckedAfter, - bool throwErrorsForRetry = false}) async { + bool throwErrorsForRetry = false, + NotificationsProvider? notificationsProvider}) async { List updates = []; MultiAppMultiError errors = MultiAppMultiError(); if (!gettingUpdates) { @@ -998,9 +1016,14 @@ class AppsProvider with ChangeNotifier { rethrow; } errors.add(appIds[i], e.toString()); + notificationsProvider?.notify(ErrorCheckingUpdatesNotification( + '${appIds[i]}: ${e.toString()}', + id: appIds[i].hashCode)); } if (newApp != null) { updates.add(newApp); + notificationsProvider + ?.notify(UpdateNotification([newApp], id: newApp.id.hashCode)); } } } finally { diff --git a/lib/providers/notifications_provider.dart b/lib/providers/notifications_provider.dart index 1f39cc8..1c2ab23 100644 --- a/lib/providers/notifications_provider.dart +++ b/lib/providers/notifications_provider.dart @@ -22,9 +22,9 @@ class ObtainiumNotification { } class UpdateNotification extends ObtainiumNotification { - UpdateNotification(List updates) + UpdateNotification(List updates, {int? id}) : super( - 2, + id ?? 2, tr('updatesAvailable'), '', 'UPDATES_AVAILABLE', @@ -41,8 +41,8 @@ class UpdateNotification extends ObtainiumNotification { } class SilentUpdateNotification extends ObtainiumNotification { - SilentUpdateNotification(List updates) - : super(3, tr('appsUpdated'), '', 'APPS_UPDATED', tr('appsUpdated'), + SilentUpdateNotification(List updates, {int? id}) + : super(id ?? 3, tr('appsUpdated'), '', 'APPS_UPDATED', tr('appsUpdated'), tr('appsUpdatedNotifDescription'), Importance.defaultImportance) { message = updates.length == 1 ? tr('xWasUpdatedToY', @@ -52,10 +52,28 @@ class SilentUpdateNotification extends ObtainiumNotification { } } -class ErrorCheckingUpdatesNotification extends ObtainiumNotification { - ErrorCheckingUpdatesNotification(String error) +class SilentUpdateAttemptNotification extends ObtainiumNotification { + SilentUpdateAttemptNotification(List updates, {int? id}) : super( - 5, + id ?? 3, + tr('appsPossiblyUpdated'), + '', + 'APPS_POSSIBLY_UPDATED', + tr('appsPossiblyUpdated'), + tr('appsPossiblyUpdatedNotifDescription'), + Importance.defaultImportance) { + message = updates.length == 1 + ? tr('xWasPossiblyUpdatedToY', + args: [updates[0].finalName, updates[0].latestVersion]) + : plural('xAndNMoreUpdatesPossiblyInstalled', updates.length - 1, + args: [updates[0].finalName, (updates.length - 1).toString()]); + } +} + +class ErrorCheckingUpdatesNotification extends ObtainiumNotification { + ErrorCheckingUpdatesNotification(String error, {int? id}) + : super( + id ?? 5, tr('errorCheckingUpdates'), error, 'BG_UPDATE_CHECK_ERROR', From 03bb1ad9a65812fcbad65fd7a6e22d677af8c747 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Mon, 21 Aug 2023 09:48:35 -0400 Subject: [PATCH 03/22] bugfix --- lib/providers/apps_provider.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index 30f75bd..00a6bd0 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -594,21 +594,21 @@ class AppsProvider with ChangeNotifier { notifyListeners(); try { if (downloadedFile != null) { - if (willBeSilent) { + if (willBeSilent && context == null) { // Would await forever - workaround - TODO installApk(downloadedFile); } else { await installApk(downloadedFile); } } else { - if (willBeSilent) { + if (willBeSilent && context == null) { // Would await forever - workaround - TODO installXApkDir(downloadedDir!); } else { await installXApkDir(downloadedDir!); } } - if (willBeSilent) { + if (willBeSilent && context == null) { notificationsProvider?.notify(SilentUpdateAttemptNotification( [apps[appId]!.app], id: appId.hashCode)); From 3eb25c4060258b61ece5331c23bfb9ae6aeb9d41 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Mon, 21 Aug 2023 20:10:30 -0400 Subject: [PATCH 04/22] Switch to per-app BG update tasks --- assets/translations/bs.json | 16 ++-- assets/translations/de.json | 12 +-- assets/translations/en.json | 14 +--- assets/translations/es.json | 12 +-- assets/translations/fa.json | 12 +-- assets/translations/fr.json | 12 +-- assets/translations/hu.json | 12 +-- assets/translations/it.json | 12 +-- assets/translations/ja.json | 12 +-- assets/translations/pl.json | 12 +-- assets/translations/ru.json | 12 +-- assets/translations/zh.json | 12 +-- lib/main.dart | 132 +++++++++++++++---------------- lib/providers/apps_provider.dart | 43 +++++----- 14 files changed, 129 insertions(+), 196 deletions(-) diff --git a/assets/translations/bs.json b/assets/translations/bs.json index ef08037..7c86b74 100644 --- a/assets/translations/bs.json +++ b/assets/translations/bs.json @@ -1,4 +1,4 @@ - { +{ "invalidURLForSource": "Nije važeći URL aplikacije {}", "noReleaseFound": "Nije moguće pronaći odgovarajuće izdanje", "noVersionFound": "Nije moguće odrediti verziju izdanja", @@ -11,12 +11,6 @@ "unexpectedError": "Neočekivana greška", "ok": "Dobro", "and": "i", - "startedBgUpdateTask": "Započeo je pozadinski zadatak provjere ažuriranja", - "bgUpdateIgnoreAfterIs": "ignoreAfter pozadinskog zadataka je {}", - "startedActualBGUpdateCheck": "Započela je stvarna provjera ažuriranja", - "bgUpdateTaskFinished": "Završen zadatak provjere ažuriranja", - "firstRun": "Ovo je prvi put da pokrećete Obtainium", - "settingUpdateCheckIntervalTo": "Podešavanje intervala ažuriranja na {}", "githubPATLabel": "GitHub token za lični pristup (eng. PAT, povećava ograničenje stope)", "githubPATHint": "PAT mora biti u ovom formatu: korisničko_ime:token", "githubPATFormat": "korisničko_ime:token", @@ -249,7 +243,7 @@ "customLinkFilterRegex": "Custom Link Filter by Regular Expression (Default '.apk$')", "appsPossiblyUpdated": "App Updates Attempted", "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", - "xWasPossiblyUpdatedToY": "An attempt was made to update {} to {}.", + "xWasPossiblyUpdatedToY": "{} may have been updated to {}.", "removeAppQuestion": { "one": "Želite li ukloniti aplikaciju?", "other": "Želite li ukloniti aplikacije?" @@ -299,7 +293,7 @@ "other": "{} i još {} aplikacija je ažurirano." }, "xAndNMoreUpdatesPossiblyInstalled": { - "one": "Attempts were made to update {} and 1 more app.", - "other": "Attempts were made to update {} and {} more apps." + "one": "{} and 1 more app may have been updated.", + "other": "{} and {} more apps may have been updated." } -} +} \ No newline at end of file diff --git a/assets/translations/de.json b/assets/translations/de.json index ffc08dc..973c229 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -11,12 +11,6 @@ "unexpectedError": "Unerwarteter Fehler", "ok": "Okay", "and": "und", - "startedBgUpdateTask": "Hintergrundaktualisierungsprüfung gestartet", - "bgUpdateIgnoreAfterIs": "Hintergrundaktualisierung 'ignoreAfter' ist {}", - "startedActualBGUpdateCheck": "Überprüfung der Hintergrundaktualisierung gestartet", - "bgUpdateTaskFinished": "Hintergrundaktualisierungsprüfung abgeschlossen", - "firstRun": "Dies ist der erste Start von Obtainium überhaupt", - "settingUpdateCheckIntervalTo": "Aktualisierungsintervall auf {} stellen", "githubPATLabel": "GitHub Personal Access Token (Erhöht das Ratenlimit)", "githubPATHint": "PAT muss in diesem Format sein: Benutzername:Token", "githubPATFormat": "Benutzername:Token", @@ -249,7 +243,7 @@ "customLinkFilterRegex": "Benutzerdefinierter Link Filter nach Regulärem Ausdruck (Standard '.apk$')", "appsPossiblyUpdated": "App Updates Attempted", "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", - "xWasPossiblyUpdatedToY": "An attempt was made to update {} to {}.", + "xWasPossiblyUpdatedToY": "{} may have been updated to {}.", "removeAppQuestion": { "one": "App entfernen?", "other": "Apps entfernen?" @@ -299,7 +293,7 @@ "other": "{} und {} weitere Anwendungen wurden aktualisiert." }, "xAndNMoreUpdatesPossiblyInstalled": { - "one": "Attempts were made to update {} and 1 more app.", - "other": "Attempts were made to update {} and {} more apps." + "one": "{} and 1 more app may have been updated.", + "other": "{} and {} more apps may have been updated." } } diff --git a/assets/translations/en.json b/assets/translations/en.json index 8fa9763..7a76230 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -11,12 +11,6 @@ "unexpectedError": "Unexpected Error", "ok": "Okay", "and": "and", - "startedBgUpdateTask": "Started BG update check task", - "bgUpdateIgnoreAfterIs": "Bg update ignoreAfter is {}", - "startedActualBGUpdateCheck": "Started actual BG update checking", - "bgUpdateTaskFinished": "Finished BG update check task", - "firstRun": "This is the first ever run of Obtainium", - "settingUpdateCheckIntervalTo": "Setting update interval to {}", "githubPATLabel": "GitHub Personal Access Token (Increases Rate Limit)", "githubPATHint": "PAT must be in this format: username:token", "githubPATFormat": "username:token", @@ -249,7 +243,7 @@ "customLinkFilterRegex": "Custom Link Filter by Regular Expression (Default '.apk$')", "appsPossiblyUpdated": "App Updates Attempted", "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", - "xWasPossiblyUpdatedToY": "An attempt was made to update {} to {}.", + "xWasPossiblyUpdatedToY": "{} may have been updated to {}.", "removeAppQuestion": { "one": "Remove App?", "other": "Remove Apps?" @@ -295,11 +289,11 @@ "other": "{} and {} more apps have updates." }, "xAndNMoreUpdatesInstalled": { - "one": "{} and 1 more app were updated.", + "one": "{} and 1 more app was updated.", "other": "{} and {} more apps were updated." }, "xAndNMoreUpdatesPossiblyInstalled": { - "one": "Attempts were made to update {} and 1 more app.", - "other": "Attempts were made to update {} and {} more apps." + "one": "{} and 1 more app may have been updated.", + "other": "{} and {} more apps may have been updated." } } diff --git a/assets/translations/es.json b/assets/translations/es.json index bffc0de..63f9dc1 100644 --- a/assets/translations/es.json +++ b/assets/translations/es.json @@ -11,12 +11,6 @@ "unexpectedError": "Error Inesperado", "ok": "Correcto", "and": "y", - "startedBgUpdateTask": "Empezada la tarea de comprobación de actualizaciones en segundo plano", - "bgUpdateIgnoreAfterIs": "El parámetro ignoreAfter de la actualización en segundo plano es {}", - "startedActualBGUpdateCheck": "Ha comenzado la comprobación de actualizaciones en segundo plano", - "bgUpdateTaskFinished": "Ha finalizado la comprobación de actualizaciones en segundo plano", - "firstRun": "Esta es la primera ejecución de Obtainium", - "settingUpdateCheckIntervalTo": "Cambiando intervalo de actualización a {}", "githubPATLabel": "Token de Acceso Personal de GitHub (Reduce tiempos de espera)", "githubPATHint": "El TAP debe tener este formato: nombre_de_usuario:token", "githubPATFormat": "nombre_de_usuario:token", @@ -249,7 +243,7 @@ "customLinkFilterRegex": "Custom Link Filter by Regular Expression (Default '.apk$')", "appsPossiblyUpdated": "App Updates Attempted", "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", - "xWasPossiblyUpdatedToY": "An attempt was made to update {} to {}.", + "xWasPossiblyUpdatedToY": "{} may have been updated to {}.", "removeAppQuestion": { "one": "¿Eliminar Aplicación?", "other": "¿Eliminar Aplicaciones?" @@ -299,7 +293,7 @@ "other": "{} y {} aplicaciones más han sido actualizadas." }, "xAndNMoreUpdatesPossiblyInstalled": { - "one": "Attempts were made to update {} and 1 more app.", - "other": "Attempts were made to update {} and {} more apps." + "one": "{} and 1 more app may have been updated.", + "other": "{} and {} more apps may have been updated." } } diff --git a/assets/translations/fa.json b/assets/translations/fa.json index 02027bf..4342244 100644 --- a/assets/translations/fa.json +++ b/assets/translations/fa.json @@ -11,12 +11,6 @@ "unexpectedError": "خطای غیرمنتظره", "ok": "باشه", "and": "و", - "startedBgUpdateTask": "شروع بررسی بروزرسانی BG", - "bgUpdateIgnoreAfterIs": "نادیده گرفتن بروزرسانی BG بعد از {} است", - "startedActualBGUpdateCheck": "بررسی به‌روزرسانی واقعی BG آغاز شد", - "bgUpdateTaskFinished": "کار بررسی به‌روزرسانی BG تمام شد", - "firstRun": "این اولین اجرای Obtainium است", - "settingUpdateCheckIntervalTo": "تنظیم فاصله به‌روزرسانی روی {}", "githubPATLabel": "توکن دسترسی شخصی گیت هاب(محدودیت نرخ را افزایش میدهد)", "githubPATHint": "PAT باید در این قالب باشد: username:token", "githubPATFormat": "username:token", @@ -249,7 +243,7 @@ "customLinkFilterRegex": "فیلتر پیوند سفارشی بر اساس عبارت منظم (پیش‌فرض '.apk$')", "appsPossiblyUpdated": "App Updates Attempted", "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", - "xWasPossiblyUpdatedToY": "An attempt was made to update {} to {}.", + "xWasPossiblyUpdatedToY": "{} may have been updated to {}.", "removeAppQuestion": { "one": "برنامه حذف شود؟", "other": "برنامه ها حذف شوند؟" @@ -299,7 +293,7 @@ "other": "{} و {} برنامه دیگر به روز شدند." }, "xAndNMoreUpdatesPossiblyInstalled": { - "one": "Attempts were made to update {} and 1 more app.", - "other": "Attempts were made to update {} and {} more apps." + "one": "{} and 1 more app may have been updated.", + "other": "{} and {} more apps may have been updated." } } diff --git a/assets/translations/fr.json b/assets/translations/fr.json index 582e286..1bc1a6e 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -11,12 +11,6 @@ "unexpectedError": "Erreur inattendue", "ok": "Okay", "and": "et", - "startedBgUpdateTask": "Démarrage de la tâche de vérification de mise à jour en arrière-plan", - "bgUpdateIgnoreAfterIs": "Mise à jour en arrière-plan est ignoré après {}", - "startedActualBGUpdateCheck": "Démarrage de la vérification de la mise à jour en arrière-plan", - "bgUpdateTaskFinished": "Tâche de vérification de la mise à jour en arrière-plan terminée", - "firstRun": "Il s'agit de la toute première exécution d'Obtainium", - "settingUpdateCheckIntervalTo": "Définition de l'intervalle de mise à jour sur {}", "githubPATLabel": "Jeton d'Accès Personnel GitHub (Augmente la limite de débit)", "githubPATHint": "Le JAP doit être dans ce format : username:token", "githubPATFormat": "username:token", @@ -249,7 +243,7 @@ "customLinkFilterRegex": "Custom Link Filter by Regular Expression (Default '.apk$')", "appsPossiblyUpdated": "App Updates Attempted", "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", - "xWasPossiblyUpdatedToY": "An attempt was made to update {} to {}.", + "xWasPossiblyUpdatedToY": "{} may have been updated to {}.", "removeAppQuestion": { "one": "Supprimer l'application ?", "other": "Supprimer les applications ?" @@ -299,7 +293,7 @@ "other": "{} et {} autres applications ont été mises à jour." }, "xAndNMoreUpdatesPossiblyInstalled": { - "one": "Attempts were made to update {} and 1 more app.", - "other": "Attempts were made to update {} and {} more apps." + "one": "{} and 1 more app may have been updated.", + "other": "{} and {} more apps may have been updated." } } diff --git a/assets/translations/hu.json b/assets/translations/hu.json index 5164bcd..117bd54 100644 --- a/assets/translations/hu.json +++ b/assets/translations/hu.json @@ -11,12 +11,6 @@ "unexpectedError": "Váratlan hiba", "ok": "Oké", "and": "és", - "startedBgUpdateTask": "Háttérfrissítés ellenőrzési feladat elindítva", - "bgUpdateIgnoreAfterIs": "Háttérfrissítés ignoreAfter a következő: {}", - "startedActualBGUpdateCheck": "Elkezdődött a tényleges háttérfrissítés ellenőrzése", - "bgUpdateTaskFinished": "A háttérfrissítés ellenőrzési feladat befejeződött", - "firstRun": "Ez az Obtainium első futása", - "settingUpdateCheckIntervalTo": "A frissítési intervallum beállítása erre: {}", "githubPATLabel": "GitHub Personal Access Token (megnöveli a díjkorlátot)", "githubPATHint": "A PAT-nak a következő formátumban kell lennie: felhasználónév:token", "githubPATFormat": "felhasználónév:token", @@ -248,7 +242,7 @@ "customLinkFilterRegex": "Custom Link Filter by Regular Expression (Default '.apk$')", "appsPossiblyUpdated": "App Updates Attempted", "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", - "xWasPossiblyUpdatedToY": "An attempt was made to update {} to {}.", + "xWasPossiblyUpdatedToY": "{} may have been updated to {}.", "removeAppQuestion": { "one": "Eltávolítja az alkalmazást?", "other": "Eltávolítja az alkalmazást?" @@ -298,7 +292,7 @@ "other": "{} és {} további alkalmazás frissítve." }, "xAndNMoreUpdatesPossiblyInstalled": { - "one": "Attempts were made to update {} and 1 more app.", - "other": "Attempts were made to update {} and {} more apps." + "one": "{} and 1 more app may have been updated.", + "other": "{} and {} more apps may have been updated." } } diff --git a/assets/translations/it.json b/assets/translations/it.json index ed43216..e3a5211 100644 --- a/assets/translations/it.json +++ b/assets/translations/it.json @@ -11,12 +11,6 @@ "unexpectedError": "Errore imprevisto", "ok": "Va bene", "and": "e", - "startedBgUpdateTask": "Avviata l'attività di controllo degli aggiornamenti in secondo piano", - "bgUpdateIgnoreAfterIs": "Il parametro di agg. in secondo piano 'ignoreAfter' è {}", - "startedActualBGUpdateCheck": "Avviato il controllo effettivo degli aggiornamenti in secondo piano", - "bgUpdateTaskFinished": "Terminata l'attività di controllo degli aggiornamenti in secondo piano", - "firstRun": "Questo è il primo avvio di sempre di Obtainium", - "settingUpdateCheckIntervalTo": "Fissato intervallo di aggiornamento a {}", "githubPATLabel": "GitHub Personal Access Token (diminuisce limite di traffico)", "githubPATHint": "PAT deve seguire questo formato: nomeutente:token", "githubPATFormat": "nomeutente:token", @@ -249,7 +243,7 @@ "customLinkFilterRegex": "Custom Link Filter by Regular Expression (Default '.apk$')", "appsPossiblyUpdated": "App Updates Attempted", "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", - "xWasPossiblyUpdatedToY": "An attempt was made to update {} to {}.", + "xWasPossiblyUpdatedToY": "{} may have been updated to {}.", "removeAppQuestion": { "one": "Rimuovere l'app?", "other": "Rimuovere le app?" @@ -299,7 +293,7 @@ "other": "{} e altre {} app sono state aggiornate." }, "xAndNMoreUpdatesPossiblyInstalled": { - "one": "Attempts were made to update {} and 1 more app.", - "other": "Attempts were made to update {} and {} more apps." + "one": "{} and 1 more app may have been updated.", + "other": "{} and {} more apps may have been updated." } } diff --git a/assets/translations/ja.json b/assets/translations/ja.json index 37edc58..f228582 100644 --- a/assets/translations/ja.json +++ b/assets/translations/ja.json @@ -11,12 +11,6 @@ "unexpectedError": "予期せぬエラーが発生しました", "ok": "OK", "and": "と", - "startedBgUpdateTask": "バックグラウンドのアップデート確認タスクを開始", - "bgUpdateIgnoreAfterIs": "Bg update ignoreAfter is {}", - "startedActualBGUpdateCheck": "実際のバックグラウンドのアップデート確認を開始", - "bgUpdateTaskFinished": "バックグラウンドのアップデート確認タスクを終了", - "firstRun": "これがObtainiumの最初の実行です", - "settingUpdateCheckIntervalTo": "確認間隔を{}に設定する", "githubPATLabel": "GitHub パーソナルアクセストークン (レート制限の引き上げ)", "githubPATHint": "PATは次の形式でなければなりません: ユーザー名:トークン", "githubPATFormat": "ユーザー名:トークン", @@ -249,7 +243,7 @@ "customLinkFilterRegex": "正規表現によるカスタムリンクフィルター (デフォルト '.apk$')", "appsPossiblyUpdated": "App Updates Attempted", "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", - "xWasPossiblyUpdatedToY": "An attempt was made to update {} to {}.", + "xWasPossiblyUpdatedToY": "{} may have been updated to {}.", "removeAppQuestion": { "one": "アプリを削除しますか?", "other": "アプリを削除しますか?" @@ -299,7 +293,7 @@ "other": "{} とさらに {} 個のアプリがアップデートされました" }, "xAndNMoreUpdatesPossiblyInstalled": { - "one": "Attempts were made to update {} and 1 more app.", - "other": "Attempts were made to update {} and {} more apps." + "one": "{} and 1 more app may have been updated.", + "other": "{} and {} more apps may have been updated." } } diff --git a/assets/translations/pl.json b/assets/translations/pl.json index ea8be8a..1b5809c 100644 --- a/assets/translations/pl.json +++ b/assets/translations/pl.json @@ -19,12 +19,6 @@ "unexpectedError": "Nieoczekiwany błąd", "ok": "Okej", "and": "i", - "startedBgUpdateTask": "Rozpoczęto zadanie sprawdzania aktualizacji w tle", - "bgUpdateIgnoreAfterIs": "Parametr ignoreAfter aktualizacji w tle to {}", - "startedActualBGUpdateCheck": "Rozpoczęto sprawdzanie aktualizacji w tle", - "bgUpdateTaskFinished": "Zakończono zadanie sprawdzania aktualizacji w tle", - "firstRun": "Jest to pierwsze uruchomienie Obtainium", - "settingUpdateCheckIntervalTo": "Ustawianie interwału aktualizacji na {}", "githubPATLabel": "Osobisty token dostępu GitHub (zwiększa limit zapytań)", "githubPATHint": "Wymagany format: użytkownik:token", "githubPATFormat": "użytkownik:token", @@ -253,7 +247,7 @@ "customLinkFilterRegex": "Niestandardowy filtr linków wg. wyrażenia regularnego (domyślnie \".apk$\")", "appsPossiblyUpdated": "App Updates Attempted", "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", - "xWasPossiblyUpdatedToY": "An attempt was made to update {} to {}.", + "xWasPossiblyUpdatedToY": "{} may have been updated to {}.", "removeAppQuestion": { "one": "Usunąć aplikację?", "other": "Usunąć aplikacje?" @@ -299,7 +293,7 @@ "other": "Zaktualizowano {} i {} aplik." }, "xAndNMoreUpdatesPossiblyInstalled": { - "one": "Attempts were made to update {} and 1 more app.", - "other": "Attempts were made to update {} and {} more apps." + "one": "{} and 1 more app may have been updated.", + "other": "{} and {} more apps may have been updated." } } diff --git a/assets/translations/ru.json b/assets/translations/ru.json index 609b5e3..798793a 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -11,12 +11,6 @@ "unexpectedError": "Неожиданная ошибка", "ok": "Окей", "and": "и", - "startedBgUpdateTask": "Запущена задача фоновой проверки обновлений", - "bgUpdateIgnoreAfterIs": "Параметр игнорирования фоновых обновлений: {}", - "startedActualBGUpdateCheck": "Запущена фактическая проверка фоновых обновлений", - "bgUpdateTaskFinished": "Завершена задача фоновой проверки обновлений", - "firstRun": "Это первый запуск Obtainium", - "settingUpdateCheckIntervalTo": "Установка интервала проверки обновлений: {}", "githubPATLabel": "Персональный токен доступа GitHub (увеличивает лимит запросов)", "githubPATHint": "Токен доступа должен быть в формате: имя_пользователя:токен", "githubPATFormat": "имя_пользователя:токен", @@ -249,7 +243,7 @@ "customLinkFilterRegex": "Custom Link Filter by Regular Expression (Default '.apk$')", "appsPossiblyUpdated": "App Updates Attempted", "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", - "xWasPossiblyUpdatedToY": "An attempt was made to update {} to {}.", + "xWasPossiblyUpdatedToY": "{} may have been updated to {}.", "removeAppQuestion": { "one": "Удалить приложение?", "other": "Удалить приложения?" @@ -299,7 +293,7 @@ "other": "{} и еще {} приложений были обновлены." }, "xAndNMoreUpdatesPossiblyInstalled": { - "one": "Attempts were made to update {} and 1 more app.", - "other": "Attempts were made to update {} and {} more apps." + "one": "{} and 1 more app may have been updated.", + "other": "{} and {} more apps may have been updated." } } diff --git a/assets/translations/zh.json b/assets/translations/zh.json index 1aa36fc..05f3bc3 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -11,12 +11,6 @@ "unexpectedError": "意外错误", "ok": "好的", "and": "和", - "startedBgUpdateTask": "后台更新检查任务已启动", - "bgUpdateIgnoreAfterIs": "后台更新检查间隔为 {}", - "startedActualBGUpdateCheck": "开始后台更新检查", - "bgUpdateTaskFinished": "后台更新检查任务已完成", - "firstRun": "这是 Obtainium 首次启动", - "settingUpdateCheckIntervalTo": "更新检查间隔设置为 {}", "githubPATLabel": "GitHub 个人访问令牌(提升 API 请求限额)", "githubPATHint": "个人访问令牌必须为“username:token”的格式", "githubPATFormat": "username:token", @@ -249,7 +243,7 @@ "customLinkFilterRegex": "用正则表达式自定义链接筛选(默认 '.apk$')", "appsPossiblyUpdated": "App Updates Attempted", "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", - "xWasPossiblyUpdatedToY": "An attempt was made to update {} to {}.", + "xWasPossiblyUpdatedToY": "{} may have been updated to {}.", "removeAppQuestion": { "one": "是否删除应用?", "other": "是否删除应用?" @@ -299,7 +293,7 @@ "other": "{} 和另外 {} 个应用已更新。" }, "xAndNMoreUpdatesPossiblyInstalled": { - "one": "Attempts were made to update {} and 1 more app.", - "other": "Attempts were made to update {} and {} more apps." + "one": "{} and 1 more app may have been updated.", + "other": "{} and {} more apps may have been updated." } } diff --git a/lib/main.dart b/lib/main.dart index 79bdfcb..4506787 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,4 @@ import 'dart:io'; -import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -73,80 +72,80 @@ Future loadTranslations() async { } @pragma('vm:entry-point') -Future bgUpdateCheck(int taskId, Map? params) async { +Future bgUpdateCheckApps(int taskId, Map? params) async { WidgetsFlutterBinding.ensureInitialized(); await EasyLocalization.ensureInitialized(); - + await AndroidAlarmManager.initialize(); await loadTranslations(); LogsProvider logs = LogsProvider(); - logs.add(tr('startedBgUpdateTask')); - int? ignoreAfterMicroseconds = params?['ignoreAfterMicroseconds']; + AppsProvider appsProvider = AppsProvider(); + await appsProvider.loadApps(); + + logs.add('BG update master task started.'); + var appIds = appsProvider.getAppsSortedByUpdateCheckTime(); + for (var id in appIds) { + AndroidAlarmManager.oneShot( + const Duration(minutes: 0), id.hashCode, bgUpdateCheckApp, + params: {'appId': id}); + await Future.delayed(const Duration(seconds: 1)); + } + logs.add('BG update master task - all $appIds child tasks started.'); +} + +@pragma('vm:entry-point') +Future bgUpdateCheckApp(int taskId, Map? params) async { + WidgetsFlutterBinding.ensureInitialized(); + await EasyLocalization.ensureInitialized(); await AndroidAlarmManager.initialize(); - DateTime? ignoreAfter = ignoreAfterMicroseconds != null - ? DateTime.fromMicrosecondsSinceEpoch(ignoreAfterMicroseconds) - : null; - logs.add(tr('bgUpdateIgnoreAfterIs', args: [ignoreAfter.toString()])); - var notificationsProvider = NotificationsProvider(); + await loadTranslations(); + + LogsProvider logs = LogsProvider(); + NotificationsProvider notificationsProvider = NotificationsProvider(); + AppsProvider appsProvider = AppsProvider(); + + String appId = params!['appId']; try { - var appsProvider = AppsProvider(); - await appsProvider.loadApps(); - List existingUpdateIds = - appsProvider.findExistingUpdates(installedOnly: true); - DateTime nextIgnoreAfter = DateTime.now(); - String? err; - try { - logs.add(tr('startedActualBGUpdateCheck')); - await appsProvider.checkUpdates( - ignoreAppsCheckedAfter: ignoreAfter, - throwErrorsForRetry: true, - notificationsProvider: notificationsProvider); - } catch (e) { - if (e is RateLimitError || e is ClientException) { - var remainingMinutes = e is RateLimitError ? e.remainingMinutes : 15; - logs.add( - plural('bgUpdateGotErrorRetryInMinutes', remainingMinutes, args: [ - e is ClientException - ? '${(e).message}, ${e.uri?.path}' - : e.toString(), - remainingMinutes.toString() - ])); - AndroidAlarmManager.oneShot(Duration(minutes: remainingMinutes), - Random().nextInt(pow(2, 31) as int), bgUpdateCheck, params: { - 'ignoreAfterMicroseconds': nextIgnoreAfter.microsecondsSinceEpoch - }); - } else { - err = e.toString(); + await appsProvider.loadApps(singleId: appId); + AppInMemory app = appsProvider.apps[appId]!; + App? newApp; + if (app.app.installedVersion == app.app.latestVersion && + app.app.installedVersion != null) { + try { + notificationsProvider.notify(checkingUpdatesNotification, + cancelExisting: true); + newApp = await appsProvider.checkUpdate(appId); + } catch (e) { + logs.add('BG update check for $appId got error \'${e.toString()}\'.'); + if (e is RateLimitError || e is ClientException) { + var remainingMinutes = e is RateLimitError ? e.remainingMinutes : 15; + logs.add( + 'BG update check for $appId will be retried in $remainingMinutes minutes.'); + AndroidAlarmManager.oneShot( + Duration(minutes: remainingMinutes), taskId, bgUpdateCheckApp, + params: params); + } else { + rethrow; + } + } finally { + notificationsProvider.cancel(checkingUpdatesNotification.id); } } - List newUpdates = appsProvider - .findExistingUpdates(installedOnly: true) - .where((id) => !existingUpdateIds.contains(id)) - .map((e) => appsProvider.apps[e]!.app) - .toList(); - List nonSilentUpdates = []; - List silentUpdates = []; - for (var a in newUpdates) { - if (await appsProvider.canInstallSilently(a)) { - silentUpdates.add(a); + if (newApp != null) { + var canInstallSilently = await appsProvider.canInstallSilently(app.app); + if (!canInstallSilently) { + notificationsProvider + .notify(UpdateNotification([newApp], id: newApp.id.hashCode * 10)); } else { - nonSilentUpdates.add(a); + logs.add('Attempting to update $appId in the background.'); + await appsProvider.downloadAndInstallLatestApps([appId], null, + notificationsProvider: notificationsProvider); } } - if (silentUpdates.isNotEmpty) { - await appsProvider.downloadAndInstallLatestApps( - silentUpdates.map((e) => e.id).toList(), null, - notificationsProvider: notificationsProvider); - } - logs.add(plural( - 'bgCheckFoundUpdatesWillNotifyIfNeeded', nonSilentUpdates.length)); - if (err != null) { - throw err; - } } catch (e) { - logs.add('${tr('errorCheckingUpdates')}: ${e.toString()}'); - } finally { - logs.add(tr('bgUpdateTaskFinished')); + notificationsProvider.notify(ErrorCheckingUpdatesNotification( + '$appId: ${e.toString()}', + id: appId.hashCode * 20)); } } @@ -207,7 +206,7 @@ class _ObtainiumState extends State { } else { bool isFirstRun = settingsProvider.checkAndFlipFirstRun(); if (isFirstRun) { - logs.add(tr('firstRun')); + logs.add('This is the first ever run of Obtainium.'); // If this is the first run, ask for notification permissions and add Obtainium to the Apps list Permission.notification.request(); appsProvider.saveApps([ @@ -236,8 +235,8 @@ class _ObtainiumState extends State { // Register the background update task according to the user's setting if (existingUpdateInterval != settingsProvider.updateInterval) { if (existingUpdateInterval != -1) { - logs.add(tr('settingUpdateCheckIntervalTo', - args: [settingsProvider.updateInterval.toString()])); + logs.add( + 'Setting update interval to ${settingsProvider.updateInterval.toString()}'); } existingUpdateInterval = settingsProvider.updateInterval; if (existingUpdateInterval == 0) { @@ -246,7 +245,8 @@ class _ObtainiumState extends State { AndroidAlarmManager.periodic( Duration(minutes: existingUpdateInterval), bgUpdateCheckAlarmId, - bgUpdateCheck, + bgUpdateCheckApps, + allowWhileIdle: true, rescheduleOnReboot: true, wakeup: true); } diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index 00a6bd0..be3af8b 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -765,7 +765,7 @@ class AppsProvider with ChangeNotifier { : false; } - Future loadApps() async { + Future loadApps({String? singleId}) async { while (loadingApps) { await Future.delayed(const Duration(microseconds: 1)); } @@ -776,6 +776,10 @@ class AppsProvider with ChangeNotifier { List newApps = (await getAppsDir()) // Parse Apps from JSON .listSync() .where((item) => item.path.toLowerCase().endsWith('.json')) + .where((item) => + singleId == null || + item.path.split('/').last.toLowerCase() == + '${singleId.toLowerCase()}.json') .map((e) { try { return App.fromJson(jsonDecode(File(e.path).readAsStringSync())); @@ -986,26 +990,32 @@ class AppsProvider with ChangeNotifier { return newApp.latestVersion != currentApp.latestVersion ? newApp : null; } + List getAppsSortedByUpdateCheckTime( + {DateTime? ignoreAppsCheckedAfter}) { + List appIds = apps.values + .where((app) => + app.app.lastUpdateCheck == null || + ignoreAppsCheckedAfter == null || + app.app.lastUpdateCheck!.isBefore(ignoreAppsCheckedAfter)) + .map((e) => e.app.id) + .toList(); + appIds.sort((a, b) => + (apps[a]!.app.lastUpdateCheck ?? DateTime.fromMicrosecondsSinceEpoch(0)) + .compareTo(apps[b]!.app.lastUpdateCheck ?? + DateTime.fromMicrosecondsSinceEpoch(0))); + return appIds; + } + Future> checkUpdates( {DateTime? ignoreAppsCheckedAfter, - bool throwErrorsForRetry = false, - NotificationsProvider? notificationsProvider}) async { + bool throwErrorsForRetry = false}) async { List updates = []; MultiAppMultiError errors = MultiAppMultiError(); if (!gettingUpdates) { gettingUpdates = true; try { - List appIds = apps.values - .where((app) => - app.app.lastUpdateCheck == null || - ignoreAppsCheckedAfter == null || - app.app.lastUpdateCheck!.isBefore(ignoreAppsCheckedAfter)) - .map((e) => e.app.id) - .toList(); - appIds.sort((a, b) => (apps[a]!.app.lastUpdateCheck ?? - DateTime.fromMicrosecondsSinceEpoch(0)) - .compareTo(apps[b]!.app.lastUpdateCheck ?? - DateTime.fromMicrosecondsSinceEpoch(0))); + List appIds = getAppsSortedByUpdateCheckTime( + ignoreAppsCheckedAfter: ignoreAppsCheckedAfter); for (int i = 0; i < appIds.length; i++) { App? newApp; try { @@ -1016,14 +1026,9 @@ class AppsProvider with ChangeNotifier { rethrow; } errors.add(appIds[i], e.toString()); - notificationsProvider?.notify(ErrorCheckingUpdatesNotification( - '${appIds[i]}: ${e.toString()}', - id: appIds[i].hashCode)); } if (newApp != null) { updates.add(newApp); - notificationsProvider - ?.notify(UpdateNotification([newApp], id: newApp.id.hashCode)); } } } finally { From f5479ec82f3497cd7d248a1231eb144f7c8ebe8a Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Mon, 21 Aug 2023 20:15:57 -0400 Subject: [PATCH 05/22] Max 5 attempts for bg check fail due to rate/net --- lib/main.dart | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/main.dart b/lib/main.dart index 4506787..1b59a7f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -105,6 +105,12 @@ Future bgUpdateCheckApp(int taskId, Map? params) async { AppsProvider appsProvider = AppsProvider(); String appId = params!['appId']; + params['attemptCount'] = (params['attemptCount'] ?? 0) + 1; + int maxAttempts = 5; + if (params['attemptCount'] > 1) { + logs.add( + 'BG update check for $appId: Note this is attempt #${params['attemptCount']} of $maxAttempts'); + } try { await appsProvider.loadApps(singleId: appId); AppInMemory app = appsProvider.apps[appId]!; @@ -117,7 +123,8 @@ Future bgUpdateCheckApp(int taskId, Map? params) async { newApp = await appsProvider.checkUpdate(appId); } catch (e) { logs.add('BG update check for $appId got error \'${e.toString()}\'.'); - if (e is RateLimitError || e is ClientException) { + if (e is RateLimitError || + e is ClientException && params['attemptCount'] < maxAttempts) { var remainingMinutes = e is RateLimitError ? e.remainingMinutes : 15; logs.add( 'BG update check for $appId will be retried in $remainingMinutes minutes.'); From d08ff3fbae774d5c30b8c3862bc908064252cbd0 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Mon, 21 Aug 2023 20:32:41 -0400 Subject: [PATCH 06/22] Add BG update toggle --- assets/translations/bs.json | 2 ++ assets/translations/de.json | 2 ++ assets/translations/en.json | 3 ++ assets/translations/es.json | 2 ++ assets/translations/fa.json | 2 ++ assets/translations/fr.json | 2 ++ assets/translations/hu.json | 2 ++ assets/translations/it.json | 2 ++ assets/translations/ja.json | 2 ++ assets/translations/pl.json | 2 ++ assets/translations/ru.json | 2 ++ assets/translations/zh.json | 2 ++ lib/pages/settings.dart | 46 ++++++++++++++++++++++++++++ lib/providers/settings_provider.dart | 9 ++++++ 14 files changed, 80 insertions(+) diff --git a/assets/translations/bs.json b/assets/translations/bs.json index 7c86b74..cd14a19 100644 --- a/assets/translations/bs.json +++ b/assets/translations/bs.json @@ -244,6 +244,8 @@ "appsPossiblyUpdated": "App Updates Attempted", "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", "xWasPossiblyUpdatedToY": "{} may have been updated to {}.", + "backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.", + "backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.", "removeAppQuestion": { "one": "Želite li ukloniti aplikaciju?", "other": "Želite li ukloniti aplikacije?" diff --git a/assets/translations/de.json b/assets/translations/de.json index 973c229..9227e9f 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -244,6 +244,8 @@ "appsPossiblyUpdated": "App Updates Attempted", "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", "xWasPossiblyUpdatedToY": "{} may have been updated to {}.", + "backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.", + "backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.", "removeAppQuestion": { "one": "App entfernen?", "other": "Apps entfernen?" diff --git a/assets/translations/en.json b/assets/translations/en.json index 7a76230..165b4ff 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -244,6 +244,9 @@ "appsPossiblyUpdated": "App Updates Attempted", "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", "xWasPossiblyUpdatedToY": "{} may have been updated to {}.", + "enableBackgroundUpdates": "Enable background updates", + "backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.", + "backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.", "removeAppQuestion": { "one": "Remove App?", "other": "Remove Apps?" diff --git a/assets/translations/es.json b/assets/translations/es.json index 63f9dc1..bbed509 100644 --- a/assets/translations/es.json +++ b/assets/translations/es.json @@ -244,6 +244,8 @@ "appsPossiblyUpdated": "App Updates Attempted", "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", "xWasPossiblyUpdatedToY": "{} may have been updated to {}.", + "backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.", + "backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.", "removeAppQuestion": { "one": "¿Eliminar Aplicación?", "other": "¿Eliminar Aplicaciones?" diff --git a/assets/translations/fa.json b/assets/translations/fa.json index 4342244..93caa25 100644 --- a/assets/translations/fa.json +++ b/assets/translations/fa.json @@ -244,6 +244,8 @@ "appsPossiblyUpdated": "App Updates Attempted", "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", "xWasPossiblyUpdatedToY": "{} may have been updated to {}.", + "backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.", + "backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.", "removeAppQuestion": { "one": "برنامه حذف شود؟", "other": "برنامه ها حذف شوند؟" diff --git a/assets/translations/fr.json b/assets/translations/fr.json index 1bc1a6e..bfbbfcc 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -244,6 +244,8 @@ "appsPossiblyUpdated": "App Updates Attempted", "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", "xWasPossiblyUpdatedToY": "{} may have been updated to {}.", + "backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.", + "backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.", "removeAppQuestion": { "one": "Supprimer l'application ?", "other": "Supprimer les applications ?" diff --git a/assets/translations/hu.json b/assets/translations/hu.json index 117bd54..5c5fd49 100644 --- a/assets/translations/hu.json +++ b/assets/translations/hu.json @@ -243,6 +243,8 @@ "appsPossiblyUpdated": "App Updates Attempted", "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", "xWasPossiblyUpdatedToY": "{} may have been updated to {}.", + "backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.", + "backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.", "removeAppQuestion": { "one": "Eltávolítja az alkalmazást?", "other": "Eltávolítja az alkalmazást?" diff --git a/assets/translations/it.json b/assets/translations/it.json index e3a5211..14586d4 100644 --- a/assets/translations/it.json +++ b/assets/translations/it.json @@ -244,6 +244,8 @@ "appsPossiblyUpdated": "App Updates Attempted", "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", "xWasPossiblyUpdatedToY": "{} may have been updated to {}.", + "backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.", + "backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.", "removeAppQuestion": { "one": "Rimuovere l'app?", "other": "Rimuovere le app?" diff --git a/assets/translations/ja.json b/assets/translations/ja.json index f228582..386a512 100644 --- a/assets/translations/ja.json +++ b/assets/translations/ja.json @@ -244,6 +244,8 @@ "appsPossiblyUpdated": "App Updates Attempted", "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", "xWasPossiblyUpdatedToY": "{} may have been updated to {}.", + "backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.", + "backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.", "removeAppQuestion": { "one": "アプリを削除しますか?", "other": "アプリを削除しますか?" diff --git a/assets/translations/pl.json b/assets/translations/pl.json index 1b5809c..327e720 100644 --- a/assets/translations/pl.json +++ b/assets/translations/pl.json @@ -248,6 +248,8 @@ "appsPossiblyUpdated": "App Updates Attempted", "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", "xWasPossiblyUpdatedToY": "{} may have been updated to {}.", + "backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.", + "backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.", "removeAppQuestion": { "one": "Usunąć aplikację?", "other": "Usunąć aplikacje?" diff --git a/assets/translations/ru.json b/assets/translations/ru.json index 798793a..e8a2507 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -244,6 +244,8 @@ "appsPossiblyUpdated": "App Updates Attempted", "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", "xWasPossiblyUpdatedToY": "{} may have been updated to {}.", + "backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.", + "backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.", "removeAppQuestion": { "one": "Удалить приложение?", "other": "Удалить приложения?" diff --git a/assets/translations/zh.json b/assets/translations/zh.json index 05f3bc3..eac5e3e 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -244,6 +244,8 @@ "appsPossiblyUpdated": "App Updates Attempted", "appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background", "xWasPossiblyUpdatedToY": "{} may have been updated to {}.", + "backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.", + "backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.", "removeAppQuestion": { "one": "是否删除应用?", "other": "是否删除应用?" diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index 2ea391e..f98d8a0 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -1,3 +1,4 @@ +import 'package:device_info_plus/device_info_plus.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:obtainium/components/custom_app_bar.dart'; @@ -184,6 +185,10 @@ class _SettingsPageState extends State { } }); + const height8 = SizedBox( + height: 8, + ); + const height16 = SizedBox( height: 16, ); @@ -211,6 +216,47 @@ class _SettingsPageState extends State { color: Theme.of(context).colorScheme.primary), ), intervalDropdown, + FutureBuilder( + builder: (ctx, val) { + return (val.data?.version.sdkInt ?? 0) >= 30 + ? Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + height16, + Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + Flexible( + child: Text(tr( + 'enableBackgroundUpdates'))), + Switch( + value: settingsProvider + .enableBackgroundUpdates, + onChanged: (value) { + settingsProvider + .enableBackgroundUpdates = + value; + }) + ], + ), + height8, + Text(tr('backgroundUpdateReqsExplanation'), + style: Theme.of(context) + .textTheme + .labelSmall), + Text(tr('backgroundUpdateLimitsExplanation'), + style: Theme.of(context) + .textTheme + .labelSmall), + height8 + ], + ) + : const SizedBox.shrink(); + }, + future: DeviceInfoPlugin().androidInfo), height16, Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index ea79e38..f5aba32 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -309,4 +309,13 @@ class SettingsProvider with ChangeNotifier { prefs?.setBool('reversePageTransitions', show); notifyListeners(); } + + bool get enableBackgroundUpdates { + return prefs?.getBool('enableBackgroundUpdates') ?? true; + } + + set enableBackgroundUpdates(bool val) { + prefs?.setBool('enableBackgroundUpdates', val); + notifyListeners(); + } } From bb4f34317b6a8a6d08c7dc0a5b550321ca0c3f24 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Mon, 21 Aug 2023 23:55:54 -0400 Subject: [PATCH 07/22] Bugfix + version increment + update packages --- lib/main.dart | 17 +++++++++-------- pubspec.lock | 34 +++++++++++++++++++++------------- pubspec.yaml | 2 +- 3 files changed, 31 insertions(+), 22 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 1b59a7f..a6130f0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -21,7 +21,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart'; // ignore: implementation_imports import 'package:easy_localization/src/localization.dart'; -const String currentVersion = '0.13.26'; +const String currentVersion = '0.13.27'; const String currentReleaseTag = 'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES @@ -82,7 +82,7 @@ Future bgUpdateCheckApps(int taskId, Map? params) async { AppsProvider appsProvider = AppsProvider(); await appsProvider.loadApps(); - logs.add('BG update master task started.'); + logs.add('BG update parent task started.'); var appIds = appsProvider.getAppsSortedByUpdateCheckTime(); for (var id in appIds) { AndroidAlarmManager.oneShot( @@ -90,7 +90,8 @@ Future bgUpdateCheckApps(int taskId, Map? params) async { params: {'appId': id}); await Future.delayed(const Duration(seconds: 1)); } - logs.add('BG update master task - all $appIds child tasks started.'); + logs.add( + 'BG update parent task ended (${appIds.length} child task(s) started).'); } @pragma('vm:entry-point') @@ -107,10 +108,8 @@ Future bgUpdateCheckApp(int taskId, Map? params) async { String appId = params!['appId']; params['attemptCount'] = (params['attemptCount'] ?? 0) + 1; int maxAttempts = 5; - if (params['attemptCount'] > 1) { - logs.add( - 'BG update check for $appId: Note this is attempt #${params['attemptCount']} of $maxAttempts'); - } + logs.add( + 'BG update task for $appId started (attempt #${params['attemptCount']}).'); try { await appsProvider.loadApps(singleId: appId); AppInMemory app = appsProvider.apps[appId]!; @@ -153,6 +152,9 @@ Future bgUpdateCheckApp(int taskId, Map? params) async { notificationsProvider.notify(ErrorCheckingUpdatesNotification( '$appId: ${e.toString()}', id: appId.hashCode * 20)); + } finally { + logs.add( + 'BG update task for $appId ended (attempt #${params['attemptCount']}).'); } } @@ -253,7 +255,6 @@ class _ObtainiumState extends State { Duration(minutes: existingUpdateInterval), bgUpdateCheckAlarmId, bgUpdateCheckApps, - allowWhileIdle: true, rescheduleOnReboot: true, wakeup: true); } diff --git a/pubspec.lock b/pubspec.lock index ffb90fa..fd01871 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -111,10 +111,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" convert: dependency: transitive description: @@ -379,10 +379,10 @@ packages: dependency: transitive description: name: intl - sha256: a3715e3bc90294e971cb7dc063fbf3cd9ee0ebf8604ffeafabd9e6f16abbdbe6 + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" url: "https://pub.dev" source: hosted - version: "0.18.0" + version: "0.18.1" js: dependency: transitive description: @@ -419,18 +419,18 @@ packages: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -672,10 +672,10 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" sqflite: dependency: "direct main" description: @@ -736,10 +736,10 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.0" timezone: dependency: transitive description: @@ -836,6 +836,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" webview_flutter: dependency: "direct main" description: @@ -909,5 +917,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" flutter: ">=3.10.0" diff --git a/pubspec.yaml b/pubspec.yaml index 551bf12..f107cb0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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 # 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. -version: 0.13.26+190 # When changing this, update the tag in main() accordingly +version: 0.13.27+191 # When changing this, update the tag in main() accordingly environment: sdk: '>=2.18.2 <3.0.0' From e956ee92544fb4150502ada174afa0f43dbd40ee Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Tue, 22 Aug 2023 12:51:55 -0400 Subject: [PATCH 08/22] Trying a new recursive BG update task due2 mem limits --- lib/main.dart | 113 ++++++++++++++++--------------- lib/providers/apps_provider.dart | 14 +--- 2 files changed, 63 insertions(+), 64 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index a6130f0..1a98726 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -71,31 +71,23 @@ Future loadTranslations() async { fallbackTranslations: controller.fallbackTranslations); } -@pragma('vm:entry-point') -Future bgUpdateCheckApps(int taskId, Map? params) async { - WidgetsFlutterBinding.ensureInitialized(); - await EasyLocalization.ensureInitialized(); - await AndroidAlarmManager.initialize(); - await loadTranslations(); - - LogsProvider logs = LogsProvider(); - AppsProvider appsProvider = AppsProvider(); - await appsProvider.loadApps(); - - logs.add('BG update parent task started.'); - var appIds = appsProvider.getAppsSortedByUpdateCheckTime(); - for (var id in appIds) { - AndroidAlarmManager.oneShot( - const Duration(minutes: 0), id.hashCode, bgUpdateCheckApp, - params: {'appId': id}); - await Future.delayed(const Duration(seconds: 1)); +moveStrToEnd(List arr, String str, {String? strB}) { + String? temp; + arr.removeWhere((element) { + bool res = element == str || element == strB; + if (res) { + temp = element; + } + return res; + }); + if (temp != null) { + arr = [...arr, temp!]; } - logs.add( - 'BG update parent task ended (${appIds.length} child task(s) started).'); + return arr; } @pragma('vm:entry-point') -Future bgUpdateCheckApp(int taskId, Map? params) async { +Future bgUpdateCheck(int taskId, Map? params) async { WidgetsFlutterBinding.ensureInitialized(); await EasyLocalization.ensureInitialized(); await AndroidAlarmManager.initialize(); @@ -104,31 +96,50 @@ Future bgUpdateCheckApp(int taskId, Map? params) async { LogsProvider logs = LogsProvider(); NotificationsProvider notificationsProvider = NotificationsProvider(); AppsProvider appsProvider = AppsProvider(); + await appsProvider.loadApps(); - String appId = params!['appId']; - params['attemptCount'] = (params['attemptCount'] ?? 0) + 1; int maxAttempts = 5; + + params ??= {}; + params['attemptCount'] = (params['attemptCount'] ?? 0) + 1; + params['toCheck'] = + params['toCheck'] ?? appsProvider.getAppsSortedByUpdateCheckTime(); + params['toInstall'] = params['toInstall'] ?? []; + + List toCheck = params['toCheck']; + List toInstall = params['toCheck']; + logs.add( - 'BG update task for $appId started (attempt #${params['attemptCount']}).'); - try { - await appsProvider.loadApps(singleId: appId); - AppInMemory app = appsProvider.apps[appId]!; - App? newApp; - if (app.app.installedVersion == app.app.latestVersion && - app.app.installedVersion != null) { + 'BG update task $taskId started - ${toCheck.length} to check and ${toInstall.length} to install${params['attemptCount'] > 1 ? ' (attempt #${params['attemptCount']})' : ''}.'); + + if (toCheck.isNotEmpty) { + String appId = toCheck.removeAt(0); + AppInMemory? app = appsProvider.apps[appId]; + if (app?.app.installedVersion != null) { try { notificationsProvider.notify(checkingUpdatesNotification, cancelExisting: true); - newApp = await appsProvider.checkUpdate(appId); + App? newApp = await appsProvider.checkUpdate(appId); + if (newApp != null) { + if (!(await appsProvider.canInstallSilently(app!.app))) { + notificationsProvider.notify( + UpdateNotification([newApp], id: newApp.id.hashCode * 10)); + } else { + toInstall.add(appId); + } + } } catch (e) { - logs.add('BG update check for $appId got error \'${e.toString()}\'.'); + logs.add( + 'BG update check got error on checking for $appId \'${e.toString()}\'.'); if (e is RateLimitError || e is ClientException && params['attemptCount'] < maxAttempts) { var remainingMinutes = e is RateLimitError ? e.remainingMinutes : 15; logs.add( - 'BG update check for $appId will be retried in $remainingMinutes minutes.'); + 'BG update task $taskId will be retried in $remainingMinutes minutes (with $appId moved to the end of the line).'); + toCheck = toInstall = []; // So the next task will not start + params['toCheck'] = moveStrToEnd(params['toCheck'], appId); AndroidAlarmManager.oneShot( - Duration(minutes: remainingMinutes), taskId, bgUpdateCheckApp, + Duration(minutes: remainingMinutes), taskId + 1, bgUpdateCheck, params: params); } else { rethrow; @@ -137,24 +148,20 @@ Future bgUpdateCheckApp(int taskId, Map? params) async { notificationsProvider.cancel(checkingUpdatesNotification.id); } } - if (newApp != null) { - var canInstallSilently = await appsProvider.canInstallSilently(app.app); - if (!canInstallSilently) { - notificationsProvider - .notify(UpdateNotification([newApp], id: newApp.id.hashCode * 10)); - } else { - logs.add('Attempting to update $appId in the background.'); - await appsProvider.downloadAndInstallLatestApps([appId], null, - notificationsProvider: notificationsProvider); - } - } - } catch (e) { - notificationsProvider.notify(ErrorCheckingUpdatesNotification( - '$appId: ${e.toString()}', - id: appId.hashCode * 20)); - } finally { - logs.add( - 'BG update task for $appId ended (attempt #${params['attemptCount']}).'); + } else if (toInstall.isNotEmpty) { + toInstall = moveStrToEnd(toInstall, obtainiumId); + String appId = toInstall.removeAt(0); + logs.add('Attempting to update $appId in the background.'); + await appsProvider.downloadAndInstallLatestApps([appId], null, + notificationsProvider: notificationsProvider); + } + + logs.add('BG update task $taskId ended.'); + + if (toCheck.isNotEmpty || toInstall.isNotEmpty) { + AndroidAlarmManager.oneShot(Duration(seconds: toCheck.isNotEmpty ? 1 : 5), + taskId + 1, bgUpdateCheck, + params: {'toCheck': toCheck, 'toInstall': toInstall}); } } @@ -254,7 +261,7 @@ class _ObtainiumState extends State { AndroidAlarmManager.periodic( Duration(minutes: existingUpdateInterval), bgUpdateCheckAlarmId, - bgUpdateCheckApps, + bgUpdateCheck, rescheduleOnReboot: true, wakeup: true); } diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index be3af8b..b9f29a7 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -15,6 +15,7 @@ import 'package:flutter/services.dart'; import 'package:obtainium/components/generated_form.dart'; import 'package:obtainium/components/generated_form_modal.dart'; import 'package:obtainium/custom_errors.dart'; +import 'package:obtainium/main.dart'; import 'package:obtainium/providers/logs_provider.dart'; import 'package:obtainium/providers/notifications_provider.dart'; import 'package:obtainium/providers/settings_provider.dart'; @@ -555,17 +556,8 @@ class AppsProvider with ChangeNotifier { List installedIds = []; // Move Obtainium to the end of the line (let all other apps update first) - String? temp; - appsToInstall.removeWhere((element) { - bool res = element == obtainiumId || element == obtainiumTempId; - if (res) { - temp = element; - } - return res; - }); - if (temp != null) { - appsToInstall = [...appsToInstall, temp!]; - } + appsToInstall = + moveStrToEnd(appsToInstall, obtainiumId, strB: obtainiumTempId); for (var id in appsToInstall) { try { From 82e08150ab312d92c90f35cfb772ee0dee8a5aab Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Tue, 22 Aug 2023 16:28:22 -0400 Subject: [PATCH 09/22] bugs --- lib/main.dart | 26 ++++++++++++++------------ lib/providers/apps_provider.dart | 11 ----------- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 1a98726..25ad603 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -104,13 +104,13 @@ Future bgUpdateCheck(int taskId, Map? params) async { params['attemptCount'] = (params['attemptCount'] ?? 0) + 1; params['toCheck'] = params['toCheck'] ?? appsProvider.getAppsSortedByUpdateCheckTime(); - params['toInstall'] = params['toInstall'] ?? []; + params['toInstall'] = params['toInstall'] ?? ([]); - List toCheck = params['toCheck']; - List toInstall = params['toCheck']; + List toCheck = [...params['toCheck']]; + List toInstall = [...params['toInstall']]; logs.add( - 'BG update task $taskId started - ${toCheck.length} to check and ${toInstall.length} to install${params['attemptCount'] > 1 ? ' (attempt #${params['attemptCount']})' : ''}.'); + 'BG update task $taskId: Started [${toCheck.length},${toInstall.length}]${params['attemptCount'] > 1 ? '. ${params['attemptCount'] - 1} consecutive fail(s)' : ''}.'); if (toCheck.isNotEmpty) { String appId = toCheck.removeAt(0); @@ -123,19 +123,19 @@ Future bgUpdateCheck(int taskId, Map? params) async { if (newApp != null) { if (!(await appsProvider.canInstallSilently(app!.app))) { notificationsProvider.notify( - UpdateNotification([newApp], id: newApp.id.hashCode * 10)); + UpdateNotification([newApp], id: newApp.id.hashCode - 1)); } else { toInstall.add(appId); } } } catch (e) { logs.add( - 'BG update check got error on checking for $appId \'${e.toString()}\'.'); + 'BG update task $taskId: Got error on checking for $appId \'${e.toString()}\'.'); if (e is RateLimitError || e is ClientException && params['attemptCount'] < maxAttempts) { var remainingMinutes = e is RateLimitError ? e.remainingMinutes : 15; logs.add( - 'BG update task $taskId will be retried in $remainingMinutes minutes (with $appId moved to the end of the line).'); + 'BG update task $taskId: Next task will start in $remainingMinutes minutes (with $appId moved to the end of the line).'); toCheck = toInstall = []; // So the next task will not start params['toCheck'] = moveStrToEnd(params['toCheck'], appId); AndroidAlarmManager.oneShot( @@ -151,17 +151,19 @@ Future bgUpdateCheck(int taskId, Map? params) async { } else if (toInstall.isNotEmpty) { toInstall = moveStrToEnd(toInstall, obtainiumId); String appId = toInstall.removeAt(0); - logs.add('Attempting to update $appId in the background.'); + logs.add( + 'BG update task $taskId: Attempting to update $appId in the background.'); await appsProvider.downloadAndInstallLatestApps([appId], null, notificationsProvider: notificationsProvider); } - logs.add('BG update task $taskId ended.'); - if (toCheck.isNotEmpty || toInstall.isNotEmpty) { - AndroidAlarmManager.oneShot(Duration(seconds: toCheck.isNotEmpty ? 1 : 5), - taskId + 1, bgUpdateCheck, + logs.add('BG update task $taskId: Ended. Next task will start soon.'); + AndroidAlarmManager.oneShot( + const Duration(seconds: 0), taskId + 1, bgUpdateCheck, params: {'toCheck': toCheck, 'toInstall': toInstall}); + } else { + logs.add('BG update task $taskId: Ended.'); } } diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index b9f29a7..20cbbe9 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -708,17 +708,6 @@ class AppsProvider with ChangeNotifier { logs.add('Could not reconcile version formats for: ${app.id}'); modded = true; } - // if (app.installedVersion != null && - // app.additionalSettings['versionDetection'] == - // 'standardVersionDetection') { - // var correctedInstalledVersion = - // reconcileVersionDifferences(app.installedVersion!, app.latestVersion); - // if (correctedInstalledVersion == null) { - // app.additionalSettings['versionDetection'] = 'noVersionDetection'; - // logs.add('Could not reconcile version formats for: ${app.id}'); - // modded = true; - // } - // } return modded ? app : null; } From 9eb32ae55a287405d24f15545127a1f852346d37 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Tue, 22 Aug 2023 17:13:15 -0400 Subject: [PATCH 10/22] Don't reschedule bg checks if app is restarted --- lib/main.dart | 37 ++++++++++++++++++---------- lib/providers/settings_provider.dart | 12 +++++++++ 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 25ad603..165d6b1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -97,10 +97,15 @@ Future bgUpdateCheck(int taskId, Map? params) async { NotificationsProvider notificationsProvider = NotificationsProvider(); AppsProvider appsProvider = AppsProvider(); await appsProvider.loadApps(); + var settingsProvider = SettingsProvider(); + await settingsProvider.initializeSettings(); int maxAttempts = 5; params ??= {}; + if (params['toCheck'] == null) { + settingsProvider.lastBGCheckTime = DateTime.now(); + } params['attemptCount'] = (params['attemptCount'] ?? 0) + 1; params['toCheck'] = params['toCheck'] ?? appsProvider.getAppsSortedByUpdateCheckTime(); @@ -251,22 +256,28 @@ class _ObtainiumState extends State { settingsProvider.resetLocaleSafe(context); } // Register the background update task according to the user's setting - if (existingUpdateInterval != settingsProvider.updateInterval) { - if (existingUpdateInterval != -1) { - logs.add( - 'Setting update interval to ${settingsProvider.updateInterval.toString()}'); - } - existingUpdateInterval = settingsProvider.updateInterval; - if (existingUpdateInterval == 0) { + var actualUpdateInterval = settingsProvider.updateInterval; + if (existingUpdateInterval != actualUpdateInterval) { + if (actualUpdateInterval == 0) { AndroidAlarmManager.cancel(bgUpdateCheckAlarmId); } else { - AndroidAlarmManager.periodic( - Duration(minutes: existingUpdateInterval), - bgUpdateCheckAlarmId, - bgUpdateCheck, - rescheduleOnReboot: true, - wakeup: true); + var settingChanged = existingUpdateInterval != -1; + var lastCheckWasTooLongAgo = actualUpdateInterval != 0 && + settingsProvider.lastBGCheckTime + .add(Duration(seconds: actualUpdateInterval + 60)) + .isBefore(DateTime.now()); + if (settingChanged || lastCheckWasTooLongAgo) { + logs.add( + 'Update interval was set to ${actualUpdateInterval.toString()} (reason: ${settingChanged ? 'setting changed' : 'last check was too long ago or never'}).'); + AndroidAlarmManager.periodic( + Duration(minutes: actualUpdateInterval), + bgUpdateCheckAlarmId, + bgUpdateCheck, + rescheduleOnReboot: true, + wakeup: true); + } } + existingUpdateInterval = actualUpdateInterval; } } diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index f5aba32..386b267 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -318,4 +318,16 @@ class SettingsProvider with ChangeNotifier { prefs?.setBool('enableBackgroundUpdates', val); notifyListeners(); } + + DateTime get lastBGCheckTime { + int? temp = prefs?.getInt('lastBGCheckTime'); + return temp != null + ? DateTime.fromMillisecondsSinceEpoch(temp) + : DateTime.fromMillisecondsSinceEpoch(0); + } + + set lastBGCheckTime(DateTime val) { + prefs?.setInt('lastBGCheckTime', val.millisecondsSinceEpoch); + notifyListeners(); + } } From 5cfd80e510cf8691fe49518720c4f6f84627ac6a Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Tue, 22 Aug 2023 17:36:13 -0400 Subject: [PATCH 11/22] Added debug menu with on-demand bg task --- lib/pages/settings.dart | 40 +++++++++++++++++++++++++++- lib/providers/settings_provider.dart | 9 +++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index f98d8a0..cd5ce5a 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -1,3 +1,4 @@ +import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -508,7 +509,44 @@ class _SettingsPageState extends State { label: Text(tr('appLogs'))), ], ), - height16, + const Divider( + height: 32, + ), + Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), + child: Column(children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Flexible(child: Text('Debug Menu')), + Switch( + value: settingsProvider.showDebugOpts, + onChanged: (value) { + settingsProvider.showDebugOpts = value; + }) + ], + ), + if (settingsProvider.showDebugOpts) + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + height16, + TextButton( + onPressed: () { + AndroidAlarmManager.oneShot( + const Duration(seconds: 0), + bgUpdateCheckAlarmId + 200, + bgUpdateCheck); + showError( + 'Background task started - check logs.', + context); + }, + child: + const Text('Run Background Update Check Now')) + ], + ), + ]), + ), ], ), ) diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index 386b267..7495c9b 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -330,4 +330,13 @@ class SettingsProvider with ChangeNotifier { prefs?.setInt('lastBGCheckTime', val.millisecondsSinceEpoch); notifyListeners(); } + + bool get showDebugOpts { + return prefs?.getBool('showDebugOpts') ?? false; + } + + set showDebugOpts(bool val) { + prefs?.setBool('showDebugOpts', val); + notifyListeners(); + } } From 3f6d96289d8b30d34e2a0bf515e60dd839cb60f3 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Tue, 22 Aug 2023 18:13:28 -0400 Subject: [PATCH 12/22] Bugfix, logging --- lib/main.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 165d6b1..a670d8f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -157,9 +157,11 @@ Future bgUpdateCheck(int taskId, Map? params) async { toInstall = moveStrToEnd(toInstall, obtainiumId); String appId = toInstall.removeAt(0); logs.add( - 'BG update task $taskId: Attempting to update $appId in the background.'); + 'BG update task $taskId: Attempting to download $appId in the background.'); await appsProvider.downloadAndInstallLatestApps([appId], null, notificationsProvider: notificationsProvider); + logs.add( + 'BG update task $taskId: Attempting to update $appId in the background.'); } if (toCheck.isNotEmpty || toInstall.isNotEmpty) { @@ -264,11 +266,11 @@ class _ObtainiumState extends State { var settingChanged = existingUpdateInterval != -1; var lastCheckWasTooLongAgo = actualUpdateInterval != 0 && settingsProvider.lastBGCheckTime - .add(Duration(seconds: actualUpdateInterval + 60)) + .add(Duration(minutes: actualUpdateInterval + 60)) .isBefore(DateTime.now()); if (settingChanged || lastCheckWasTooLongAgo) { logs.add( - 'Update interval was set to ${actualUpdateInterval.toString()} (reason: ${settingChanged ? 'setting changed' : 'last check was too long ago or never'}).'); + 'Update interval was set to ${actualUpdateInterval.toString()} (reason: ${settingChanged ? 'setting changed' : 'last check was ${settingsProvider.lastBGCheckTime.toLocal().toString()}'}).'); AndroidAlarmManager.periodic( Duration(minutes: actualUpdateInterval), bgUpdateCheckAlarmId, From 788c4c79175e321857afb5560aa11e9594f8681b Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Tue, 22 Aug 2023 19:14:56 -0400 Subject: [PATCH 13/22] Try less messy bg update method --- lib/main.dart | 161 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 106 insertions(+), 55 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index a670d8f..ce8b3bf 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -86,6 +86,24 @@ moveStrToEnd(List arr, String str, {String? strB}) { return arr; } +/// Background updater function +/// +/// @param List? toCheck: The appIds to check for updates (default to all apps sorted by last update check time) +/// +/// @param List? toInstall: The appIds to attempt to update (defaults to an empty array) +/// +/// @param int? attemptCount: The number of times the function has failed up to this point (defaults to 0) +/// +/// 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. +/// If an update is available, the appId is either added to toInstall (if a background update is possible) or the user is notified. +/// If there is an error, the function tries to continue after a few minutes (duration depends on the error), up to a maximum of 5 tries. +/// +/// Once all update checks are complete, the function is called again in install mode. +/// In this mode, all apps in toInstall are downloaded and installed in the background (install result is unknown). +/// If there is an error, the function tries to continue after a few minutes (duration depends on the error), up to a maximum of 5 tries. +/// +/// In either mode, if the function fails after the maximum number of tries, the user is notified. @pragma('vm:entry-point') Future bgUpdateCheck(int taskId, Map? params) async { WidgetsFlutterBinding.ensureInitialized(); @@ -106,71 +124,104 @@ Future bgUpdateCheck(int taskId, Map? params) async { if (params['toCheck'] == null) { settingsProvider.lastBGCheckTime = DateTime.now(); } - params['attemptCount'] = (params['attemptCount'] ?? 0) + 1; - params['toCheck'] = - params['toCheck'] ?? appsProvider.getAppsSortedByUpdateCheckTime(); - params['toInstall'] = params['toInstall'] ?? ([]); + int attemptCount = (params['attemptCount'] ?? 0) + 1; + List toCheck = [ + ...(params['toCheck'] ?? appsProvider.getAppsSortedByUpdateCheckTime()) + ]; + List toInstall = [...(params['toInstall'] ?? ([]))]; - List toCheck = [...params['toCheck']]; - List toInstall = [...params['toInstall']]; + bool installMode = toCheck.isEmpty && toInstall.isNotEmpty; logs.add( - 'BG update task $taskId: Started [${toCheck.length},${toInstall.length}]${params['attemptCount'] > 1 ? '. ${params['attemptCount'] - 1} consecutive fail(s)' : ''}.'); + 'BG update task $taskId: Started in ${installMode ? 'install' : 'update'} mode${attemptCount > 1 ? '. ${attemptCount - 1} consecutive fail(s)' : ''}.'); - if (toCheck.isNotEmpty) { - String appId = toCheck.removeAt(0); - AppInMemory? app = appsProvider.apps[appId]; - if (app?.app.installedVersion != null) { - try { - notificationsProvider.notify(checkingUpdatesNotification, - cancelExisting: true); - App? newApp = await appsProvider.checkUpdate(appId); - if (newApp != null) { - if (!(await appsProvider.canInstallSilently(app!.app))) { - notificationsProvider.notify( - UpdateNotification([newApp], id: newApp.id.hashCode - 1)); - } else { - toInstall.add(appId); + if (!installMode) { + var didCompleteChecking = false; + for (int i = 0; i < toCheck.length; i++) { + var appId = toCheck[i]; + AppInMemory? app = appsProvider.apps[appId]; + if (app?.app.installedVersion != null) { + try { + logs.add('BG update task $taskId: Checking for updates for $appId.'); + notificationsProvider.notify(checkingUpdatesNotification, + cancelExisting: true); + App? newApp = await appsProvider.checkUpdate(appId); + if (newApp != null) { + if (!(await appsProvider.canInstallSilently(app!.app))) { + notificationsProvider.notify( + UpdateNotification([newApp], id: newApp.id.hashCode - 1)); + } else { + toInstall.add(appId); + } } - } - } catch (e) { - logs.add( - 'BG update task $taskId: Got error on checking for $appId \'${e.toString()}\'.'); - if (e is RateLimitError || - e is ClientException && params['attemptCount'] < maxAttempts) { - var remainingMinutes = e is RateLimitError ? e.remainingMinutes : 15; + if (i == (toCheck.length - 1)) { + didCompleteChecking = true; + } + } catch (e) { logs.add( - 'BG update task $taskId: Next task will start in $remainingMinutes minutes (with $appId moved to the end of the line).'); - toCheck = toInstall = []; // So the next task will not start - params['toCheck'] = moveStrToEnd(params['toCheck'], appId); - AndroidAlarmManager.oneShot( - Duration(minutes: remainingMinutes), taskId + 1, bgUpdateCheck, - params: params); - } else { - rethrow; + 'BG update task $taskId: Got error on checking for $appId \'${e.toString()}\'.'); + if (attemptCount < maxAttempts) { + var remainingSeconds = e is RateLimitError + ? (e.remainingMinutes * 60) + : e is ClientException + ? (15 * 60) + : 1; + logs.add( + 'BG update task $taskId: Will continue in $remainingSeconds seconds (with $appId moved to the end of the line).'); + var remainingToCheck = moveStrToEnd(toCheck.sublist(i), appId); + AndroidAlarmManager.oneShot( + Duration(seconds: remainingSeconds), taskId, bgUpdateCheck, + params: { + 'toCheck': remainingToCheck, + 'toInstall': toInstall, + 'attemptCount': attemptCount + }); + break; + } else { + notificationsProvider + .notify(ErrorCheckingUpdatesNotification(e.toString())); + } + } finally { + notificationsProvider.cancel(checkingUpdatesNotification.id); } - } finally { - notificationsProvider.cancel(checkingUpdatesNotification.id); } } - } else if (toInstall.isNotEmpty) { - toInstall = moveStrToEnd(toInstall, obtainiumId); - String appId = toInstall.removeAt(0); - logs.add( - 'BG update task $taskId: Attempting to download $appId in the background.'); - await appsProvider.downloadAndInstallLatestApps([appId], null, - notificationsProvider: notificationsProvider); - logs.add( - 'BG update task $taskId: Attempting to update $appId in the background.'); - } - - if (toCheck.isNotEmpty || toInstall.isNotEmpty) { - logs.add('BG update task $taskId: Ended. Next task will start soon.'); - AndroidAlarmManager.oneShot( - const Duration(seconds: 0), taskId + 1, bgUpdateCheck, - params: {'toCheck': toCheck, 'toInstall': toInstall}); + if (didCompleteChecking && toInstall.isNotEmpty) { + AndroidAlarmManager.oneShot( + const Duration(minutes: 0), taskId + 1, bgUpdateCheck, + params: {'toCheck': [], 'toInstall': toInstall}); + } } else { - logs.add('BG update task $taskId: Ended.'); + toInstall = moveStrToEnd(toInstall, obtainiumId); + for (var i = 0; i < toInstall.length; i++) { + String appId = toInstall[i]; + try { + logs.add( + 'BG update task $taskId: Attempting to update $appId in the background.'); + await appsProvider.downloadAndInstallLatestApps([appId], null, + notificationsProvider: notificationsProvider); + } catch (e) { + logs.add( + 'BG update task $taskId: Got error on updating $appId \'${e.toString()}\'.'); + if (attemptCount < maxAttempts) { + var remainingSeconds = 1; + logs.add( + 'BG update task $taskId: Will continue in $remainingSeconds seconds (with $appId moved to the end of the line).'); + var remainingToInstall = moveStrToEnd(toInstall.sublist(i), appId); + AndroidAlarmManager.oneShot( + Duration(seconds: remainingSeconds), taskId, bgUpdateCheck, + params: { + 'toCheck': toCheck, + 'toInstall': remainingToInstall, + 'attemptCount': attemptCount + }); + break; + } else { + notificationsProvider + .notify(ErrorCheckingUpdatesNotification(e.toString())); + } + } + } } } From 5307fd0901ca493bacf882e053e807d734832ce8 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Tue, 22 Aug 2023 19:43:23 -0400 Subject: [PATCH 14/22] bug --- lib/main.dart | 2 +- lib/providers/apps_provider.dart | 28 ++++++++++++++++------------ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index ce8b3bf..a138880 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -113,7 +113,7 @@ Future bgUpdateCheck(int taskId, Map? params) async { LogsProvider logs = LogsProvider(); NotificationsProvider notificationsProvider = NotificationsProvider(); - AppsProvider appsProvider = AppsProvider(); + AppsProvider appsProvider = AppsProvider(isBg: true); await appsProvider.loadApps(); var settingsProvider = SettingsProvider(); await settingsProvider.initializeSettings(); diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index 20cbbe9..1da2213 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -114,7 +114,7 @@ class AppsProvider with ChangeNotifier { Iterable getAppValues() => apps.values.map((a) => a.deepCopy()); - AppsProvider() { + AppsProvider({isBg = false}) { // Subscribe to changes in the app foreground status foregroundStream = FGBGEvents.stream.asBroadcastStream(); foregroundSubscription = foregroundStream?.listen((event) async { @@ -132,17 +132,21 @@ class AppsProvider with ChangeNotifier { APKDir.createSync(); } } - // Load Apps into memory (in background, this is done later instead of in the constructor) - await loadApps(); - // Delete any partial APKs - var cutoff = DateTime.now().subtract(const Duration(days: 7)); - APKDir.listSync() - .where((element) => - element.path.endsWith('.part') || - element.statSync().modified.isBefore(cutoff)) - .forEach((partialApk) { - partialApk.delete(recursive: true); - }); + if (!isBg) { + // Load Apps into memory (in background processes, this is done later instead of in the constructor) + await loadApps(); + // Delete any partial APKs (if safe to do so) + var cutoff = DateTime.now().subtract(const Duration(days: 7)); + APKDir.listSync() + .where((element) => + element.path.endsWith('.part') || + element.statSync().modified.isBefore(cutoff)) + .forEach((partialApk) { + if (!areDownloadsRunning()) { + partialApk.delete(recursive: true); + } + }); + } }(); } From 6785708661173dd72e799b448acbbb72a2644991 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Tue, 22 Aug 2023 19:57:42 -0400 Subject: [PATCH 15/22] BG update toggle has an effect --- lib/main.dart | 6 ++++-- lib/pages/app.dart | 3 ++- lib/pages/apps.dart | 8 ++++---- lib/providers/apps_provider.dart | 22 +++++++++++++--------- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index a138880..1cd8d55 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -147,7 +147,8 @@ Future bgUpdateCheck(int taskId, Map? params) async { cancelExisting: true); App? newApp = await appsProvider.checkUpdate(appId); if (newApp != null) { - if (!(await appsProvider.canInstallSilently(app!.app))) { + if (!(await appsProvider.canInstallSilently( + app!.app, settingsProvider))) { notificationsProvider.notify( UpdateNotification([newApp], id: newApp.id.hashCode - 1)); } else { @@ -198,7 +199,8 @@ Future bgUpdateCheck(int taskId, Map? params) async { try { logs.add( 'BG update task $taskId: Attempting to update $appId in the background.'); - await appsProvider.downloadAndInstallLatestApps([appId], null, + await appsProvider.downloadAndInstallLatestApps( + [appId], null, settingsProvider, notificationsProvider: notificationsProvider); } catch (e) { logs.add( diff --git a/lib/pages/app.dart b/lib/pages/app.dart index de64bb3..710c3fb 100644 --- a/lib/pages/app.dart +++ b/lib/pages/app.dart @@ -339,7 +339,8 @@ class _AppPageState extends State { HapticFeedback.heavyImpact(); var res = await appsProvider.downloadAndInstallLatestApps( app?.app.id != null ? [app!.app.id] : [], - globalNavigatorKey.currentContext); + globalNavigatorKey.currentContext, + settingsProvider); if (res.isNotEmpty && mounted) { Navigator.of(context).pop(); } diff --git a/lib/pages/apps.dart b/lib/pages/apps.dart index 516a6a6..41c0f7d 100644 --- a/lib/pages/apps.dart +++ b/lib/pages/apps.dart @@ -381,7 +381,8 @@ class AppsPageState extends State { : () { appsProvider.downloadAndInstallLatestApps( [listedApps[appIndex].app.id], - globalNavigatorKey.currentContext).catchError((e) { + globalNavigatorKey.currentContext, + settingsProvider).catchError((e) { showError(e, context); return []; }); @@ -683,9 +684,8 @@ class AppsPageState extends State { toInstall.addAll(trackOnlyUpdateIdsAllOrSelected); } appsProvider - .downloadAndInstallLatestApps( - toInstall, globalNavigatorKey.currentContext, - settingsProvider: settingsProvider) + .downloadAndInstallLatestApps(toInstall, + globalNavigatorKey.currentContext, settingsProvider) .catchError((e) { showError(e, context); return []; diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index 1da2213..1ea70ce 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -328,7 +328,11 @@ class AppsProvider with ChangeNotifier { .where((element) => element.downloadProgress != null) .isNotEmpty; - Future canInstallSilently(App app) async { + Future canInstallSilently( + App app, SettingsProvider settingsProvider) async { + if (!settingsProvider.enableBackgroundUpdates) { + return false; + } if (app.apkUrls.length > 1) { // Manual API selection means silent install is not possible return false; @@ -509,10 +513,9 @@ class AppsProvider with ChangeNotifier { // If no BuildContext is provided, apps that require user interaction are ignored // If user input is needed and the App is in the background, a notification is sent to get the user's attention // Returns an array of Ids for Apps that were successfully downloaded, regardless of installation result - Future> downloadAndInstallLatestApps( - List appIds, BuildContext? context, - {SettingsProvider? settingsProvider, - NotificationsProvider? notificationsProvider}) async { + Future> downloadAndInstallLatestApps(List appIds, + BuildContext? context, SettingsProvider settingsProvider, + {NotificationsProvider? notificationsProvider}) async { notificationsProvider = notificationsProvider ?? context?.read(); List appsToInstall = []; @@ -540,7 +543,8 @@ class AppsProvider with ChangeNotifier { apps[id]!.app.preferredApkIndex = urlInd; await saveApps([apps[id]!.app]); } - if (context != null || await canInstallSilently(apps[id]!.app)) { + if (context != null || + await canInstallSilently(apps[id]!.app, settingsProvider)) { appsToInstall.add(id); } } @@ -577,9 +581,9 @@ class AppsProvider with ChangeNotifier { downloadedDir = downloadedArtifact as DownloadedXApkDir; } var appId = downloadedFile?.appId ?? downloadedDir!.appId; - bool willBeSilent = await canInstallSilently(apps[appId]!.app); - if (!(await settingsProvider?.getInstallPermission(enforce: false) ?? - true)) { + bool willBeSilent = + await canInstallSilently(apps[appId]!.app, settingsProvider); + if (!(await settingsProvider.getInstallPermission(enforce: false))) { throw ObtainiumError(tr('cancelled')); } if (!willBeSilent && context != null) { From 0deab8296fa72e7f9f37d2a2b4709ce8d32a75c0 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Wed, 23 Aug 2023 18:28:16 -0400 Subject: [PATCH 16/22] bug --- lib/main.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 1cd8d55..38668c2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -171,7 +171,7 @@ Future bgUpdateCheck(int taskId, Map? params) async { 'BG update task $taskId: Will continue in $remainingSeconds seconds (with $appId moved to the end of the line).'); var remainingToCheck = moveStrToEnd(toCheck.sublist(i), appId); AndroidAlarmManager.oneShot( - Duration(seconds: remainingSeconds), taskId, bgUpdateCheck, + Duration(seconds: remainingSeconds), taskId + 1, bgUpdateCheck, params: { 'toCheck': remainingToCheck, 'toInstall': toInstall, @@ -211,7 +211,7 @@ Future bgUpdateCheck(int taskId, Map? params) async { 'BG update task $taskId: Will continue in $remainingSeconds seconds (with $appId moved to the end of the line).'); var remainingToInstall = moveStrToEnd(toInstall.sublist(i), appId); AndroidAlarmManager.oneShot( - Duration(seconds: remainingSeconds), taskId, bgUpdateCheck, + Duration(seconds: remainingSeconds), taskId + 1, bgUpdateCheck, params: { 'toCheck': toCheck, 'toInstall': remainingToInstall, From 5eac851f8014621e18a5e7dfc440d15c97791a1d Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Wed, 23 Aug 2023 19:36:24 -0400 Subject: [PATCH 17/22] Added convenience build script --- build.sh | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 build.sh diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..8193949 --- /dev/null +++ b/build.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# Convenience script + +git fetch && git merge origin/main && git push; # Typically run after a PR to main, so bring dev up to date +rm ./build/app/outputs/flutter-apk/* 2>/dev/null; # Get rid of older builds if any +flutter build apk && flutter build apk --split-per-abi # Build (both split and combined APKs) +for file in ./build/app/outputs/flutter-apk/*.sha1; do gpg --sign --detach-sig "$file"; done # Generate PGP signatures +rsync -r ./build/app/outputs/flutter-apk/ ~/Downloads/Obtainium-build/ # Dropoff in Downloads to allow for drag-drop into Flatpak Firefox \ No newline at end of file From 862bb2b2761db3ccab27cce622130129d7c1ef5f Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Wed, 23 Aug 2023 19:39:23 -0400 Subject: [PATCH 18/22] chmod +x build.sh --- build.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 build.sh diff --git a/build.sh b/build.sh old mode 100644 new mode 100755 From be0b57ac007ced3151f48c0294e03f3899679fb2 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Wed, 23 Aug 2023 19:51:52 -0400 Subject: [PATCH 19/22] Added logs, BG install cooldown --- lib/main.dart | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 38668c2..297b664 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -133,7 +133,7 @@ Future bgUpdateCheck(int taskId, Map? params) async { bool installMode = toCheck.isEmpty && toInstall.isNotEmpty; logs.add( - 'BG update task $taskId: Started in ${installMode ? 'install' : 'update'} mode${attemptCount > 1 ? '. ${attemptCount - 1} consecutive fail(s)' : ''}.'); + 'BG ${installMode ? 'install' : 'update'} task $taskId: Started${attemptCount > 1 ? '. ${attemptCount - 1} consecutive fail(s)' : ''}.'); if (!installMode) { var didCompleteChecking = false; @@ -188,6 +188,8 @@ Future bgUpdateCheck(int taskId, Map? params) async { } } if (didCompleteChecking && toInstall.isNotEmpty) { + logs.add( + 'BG update task $taskId: Scheduling install task to run immediately.'); AndroidAlarmManager.oneShot( const Duration(minutes: 0), taskId + 1, bgUpdateCheck, params: {'toCheck': [], 'toInstall': toInstall}); @@ -198,17 +200,20 @@ Future bgUpdateCheck(int taskId, Map? params) async { String appId = toInstall[i]; try { logs.add( - 'BG update task $taskId: Attempting to update $appId in the background.'); + 'BG install task $taskId: Attempting to update $appId in the background.'); await appsProvider.downloadAndInstallLatestApps( [appId], null, settingsProvider, notificationsProvider: notificationsProvider); + await Future.delayed(const Duration( + seconds: + 5)); // Just in case task ending causes install fail (not clear) } catch (e) { logs.add( - 'BG update task $taskId: Got error on updating $appId \'${e.toString()}\'.'); + 'BG install task $taskId: Got error on updating $appId \'${e.toString()}\'.'); if (attemptCount < maxAttempts) { var remainingSeconds = 1; logs.add( - 'BG update task $taskId: Will continue in $remainingSeconds seconds (with $appId moved to the end of the line).'); + 'BG install task $taskId: Will continue in $remainingSeconds seconds (with $appId moved to the end of the line).'); var remainingToInstall = moveStrToEnd(toInstall.sublist(i), appId); AndroidAlarmManager.oneShot( Duration(seconds: remainingSeconds), taskId + 1, bgUpdateCheck, @@ -225,6 +230,7 @@ Future bgUpdateCheck(int taskId, Map? params) async { } } } + logs.add('BG ${installMode ? 'install' : 'update'} task $taskId: Done.'); } void main() async { From 57d44c972ff8a66b5cfd12e9ff314b23cabf8971 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Wed, 23 Aug 2023 20:05:08 -0400 Subject: [PATCH 20/22] Update build script to create zips --- build.sh | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/build.sh b/build.sh index 8193949..0ad2412 100755 --- a/build.sh +++ b/build.sh @@ -1,8 +1,18 @@ #!/bin/bash # Convenience script -git fetch && git merge origin/main && git push; # Typically run after a PR to main, so bring dev up to date -rm ./build/app/outputs/flutter-apk/* 2>/dev/null; # Get rid of older builds if any -flutter build apk && flutter build apk --split-per-abi # Build (both split and combined APKs) +CURR_DIR="$(pwd)" +trap "cd "$CURR_DIR"" EXIT + +git fetch && git merge origin/main && git push # Typically run after a PR to main, so bring dev up to date +rm ./build/app/outputs/flutter-apk/* 2>/dev/null # Get rid of older builds if any +flutter build apk && flutter build apk --split-per-abi # Build (both split and combined APKs) for file in ./build/app/outputs/flutter-apk/*.sha1; do gpg --sign --detach-sig "$file"; done # Generate PGP signatures -rsync -r ./build/app/outputs/flutter-apk/ ~/Downloads/Obtainium-build/ # Dropoff in Downloads to allow for drag-drop into Flatpak Firefox \ No newline at end of file +rsync -r ./build/app/outputs/flutter-apk/ ~/Downloads/Obtainium-build/ # Dropoff in Downloads to allow for drag-drop into Flatpak Firefox +cd ~/Downloads/Obtainium-build/ # Make zips just in case (for in-comment uploads) +for apk in *.apk; do + PREFIX="$(echo "$apk" | head -c -5)" + zip "$PREFIX" "$PREFIX"* +done +mkdir -p zips +mv *.zip zips/ From 5e41d5762b0c901a3f2dc384e0ebf47a91b5bad0 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Thu, 24 Aug 2023 10:46:11 -0400 Subject: [PATCH 21/22] BG task: notif, retry, log tweaks --- lib/main.dart | 27 ++++++++++++++++------- lib/providers/notifications_provider.dart | 19 +++++++++------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 297b664..ff27e29 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -118,7 +118,7 @@ Future bgUpdateCheck(int taskId, Map? params) async { var settingsProvider = SettingsProvider(); await settingsProvider.initializeSettings(); - int maxAttempts = 5; + int maxAttempts = 4; params ??= {}; if (params['toCheck'] == null) { @@ -137,13 +137,14 @@ Future bgUpdateCheck(int taskId, Map? params) async { if (!installMode) { var didCompleteChecking = false; + CheckingUpdatesNotification? notif; for (int i = 0; i < toCheck.length; i++) { var appId = toCheck[i]; AppInMemory? app = appsProvider.apps[appId]; if (app?.app.installedVersion != null) { try { - logs.add('BG update task $taskId: Checking for updates for $appId.'); - notificationsProvider.notify(checkingUpdatesNotification, + notificationsProvider.notify( + notif = CheckingUpdatesNotification(app?.name ?? appId), cancelExisting: true); App? newApp = await appsProvider.checkUpdate(appId); if (newApp != null) { @@ -166,7 +167,7 @@ Future bgUpdateCheck(int taskId, Map? params) async { ? (e.remainingMinutes * 60) : e is ClientException ? (15 * 60) - : 1; + : (attemptCount ^ 2); logs.add( 'BG update task $taskId: Will continue in $remainingSeconds seconds (with $appId moved to the end of the line).'); var remainingToCheck = moveStrToEnd(toCheck.sublist(i), appId); @@ -183,18 +184,23 @@ Future bgUpdateCheck(int taskId, Map? params) async { .notify(ErrorCheckingUpdatesNotification(e.toString())); } } finally { - notificationsProvider.cancel(checkingUpdatesNotification.id); + if (notif != null) { + notificationsProvider.cancel(notif.id); + } } } } if (didCompleteChecking && toInstall.isNotEmpty) { logs.add( - 'BG update task $taskId: Scheduling install task to run immediately.'); + 'BG update task $taskId: Done. Scheduling install task to run immediately.'); AndroidAlarmManager.oneShot( const Duration(minutes: 0), taskId + 1, bgUpdateCheck, params: {'toCheck': [], 'toInstall': toInstall}); + } else if (didCompleteChecking) { + logs.add('BG install task $taskId: Done.'); } } else { + var didCompleteInstalling = false; toInstall = moveStrToEnd(toInstall, obtainiumId); for (var i = 0; i < toInstall.length; i++) { String appId = toInstall[i]; @@ -207,11 +213,14 @@ Future bgUpdateCheck(int taskId, Map? params) async { 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) { logs.add( 'BG install task $taskId: Got error on updating $appId \'${e.toString()}\'.'); if (attemptCount < maxAttempts) { - var remainingSeconds = 1; + var remainingSeconds = attemptCount; logs.add( 'BG install task $taskId: Will continue in $remainingSeconds seconds (with $appId moved to the end of the line).'); var remainingToInstall = moveStrToEnd(toInstall.sublist(i), appId); @@ -228,9 +237,11 @@ Future bgUpdateCheck(int taskId, Map? params) async { .notify(ErrorCheckingUpdatesNotification(e.toString())); } } + if (didCompleteInstalling) { + logs.add('BG install task $taskId: Done.'); + } } } - logs.add('BG ${installMode ? 'install' : 'update'} task $taskId: Done.'); } void main() async { diff --git a/lib/providers/notifications_provider.dart b/lib/providers/notifications_provider.dart index 1c2ab23..452512a 100644 --- a/lib/providers/notifications_provider.dart +++ b/lib/providers/notifications_provider.dart @@ -117,14 +117,17 @@ final completeInstallationNotification = ObtainiumNotification( tr('completeAppInstallationNotifDescription'), Importance.max); -final checkingUpdatesNotification = ObtainiumNotification( - 4, - tr('checkingForUpdates'), - '', - 'BG_UPDATE_CHECK', - tr('checkingForUpdates'), - tr('checkingForUpdatesNotifDescription'), - Importance.min); +class CheckingUpdatesNotification extends ObtainiumNotification { + CheckingUpdatesNotification(String appName) + : super( + 4, + tr('checkingForUpdates'), + appName, + 'BG_UPDATE_CHECK', + tr('checkingForUpdates'), + tr('checkingForUpdatesNotifDescription'), + Importance.min); +} class NotificationsProvider { FlutterLocalNotificationsPlugin notifications = From 2504ae24fcfdbbc7cf1f4da32290b4be845f350d Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Thu, 24 Aug 2023 15:05:07 -0400 Subject: [PATCH 22/22] Fix bgcheck error reporting (per-app), move code around --- lib/main.dart | 175 ----------------------- lib/providers/apps_provider.dart | 236 +++++++++++++++++++++++++++++++ 2 files changed, 236 insertions(+), 175 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index ff27e29..f70dcff 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,8 +2,6 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:http/http.dart'; -import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/pages/home.dart'; import 'package:obtainium/providers/apps_provider.dart'; import 'package:obtainium/providers/logs_provider.dart'; @@ -71,179 +69,6 @@ Future loadTranslations() async { fallbackTranslations: controller.fallbackTranslations); } -moveStrToEnd(List arr, String str, {String? strB}) { - String? temp; - arr.removeWhere((element) { - bool res = element == str || element == strB; - if (res) { - temp = element; - } - return res; - }); - if (temp != null) { - arr = [...arr, temp!]; - } - return arr; -} - -/// Background updater function -/// -/// @param List? toCheck: The appIds to check for updates (default to all apps sorted by last update check time) -/// -/// @param List? toInstall: The appIds to attempt to update (defaults to an empty array) -/// -/// @param int? attemptCount: The number of times the function has failed up to this point (defaults to 0) -/// -/// 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. -/// If an update is available, the appId is either added to toInstall (if a background update is possible) or the user is notified. -/// If there is an error, the function tries to continue after a few minutes (duration depends on the error), up to a maximum of 5 tries. -/// -/// Once all update checks are complete, the function is called again in install mode. -/// In this mode, all apps in toInstall are downloaded and installed in the background (install result is unknown). -/// If there is an error, the function tries to continue after a few minutes (duration depends on the error), up to a maximum of 5 tries. -/// -/// In either mode, if the function fails after the maximum number of tries, the user is notified. -@pragma('vm:entry-point') -Future bgUpdateCheck(int taskId, Map? params) async { - WidgetsFlutterBinding.ensureInitialized(); - await EasyLocalization.ensureInitialized(); - await AndroidAlarmManager.initialize(); - await loadTranslations(); - - LogsProvider logs = LogsProvider(); - NotificationsProvider notificationsProvider = NotificationsProvider(); - AppsProvider appsProvider = AppsProvider(isBg: true); - await appsProvider.loadApps(); - var settingsProvider = SettingsProvider(); - await settingsProvider.initializeSettings(); - - int maxAttempts = 4; - - params ??= {}; - if (params['toCheck'] == null) { - settingsProvider.lastBGCheckTime = DateTime.now(); - } - int attemptCount = (params['attemptCount'] ?? 0) + 1; - List toCheck = [ - ...(params['toCheck'] ?? appsProvider.getAppsSortedByUpdateCheckTime()) - ]; - List toInstall = [...(params['toInstall'] ?? ([]))]; - - bool installMode = toCheck.isEmpty && toInstall.isNotEmpty; - - logs.add( - 'BG ${installMode ? 'install' : 'update'} task $taskId: Started${attemptCount > 1 ? '. ${attemptCount - 1} consecutive fail(s)' : ''}.'); - - if (!installMode) { - var didCompleteChecking = false; - CheckingUpdatesNotification? notif; - for (int i = 0; i < toCheck.length; i++) { - var appId = toCheck[i]; - AppInMemory? app = appsProvider.apps[appId]; - if (app?.app.installedVersion != null) { - try { - notificationsProvider.notify( - notif = CheckingUpdatesNotification(app?.name ?? appId), - cancelExisting: true); - App? newApp = await appsProvider.checkUpdate(appId); - if (newApp != null) { - if (!(await appsProvider.canInstallSilently( - app!.app, settingsProvider))) { - notificationsProvider.notify( - UpdateNotification([newApp], id: newApp.id.hashCode - 1)); - } else { - toInstall.add(appId); - } - } - if (i == (toCheck.length - 1)) { - didCompleteChecking = true; - } - } catch (e) { - logs.add( - 'BG update task $taskId: Got error on checking for $appId \'${e.toString()}\'.'); - if (attemptCount < maxAttempts) { - var remainingSeconds = e is RateLimitError - ? (e.remainingMinutes * 60) - : e is ClientException - ? (15 * 60) - : (attemptCount ^ 2); - logs.add( - 'BG update task $taskId: Will continue in $remainingSeconds seconds (with $appId moved to the end of the line).'); - var remainingToCheck = moveStrToEnd(toCheck.sublist(i), appId); - AndroidAlarmManager.oneShot( - Duration(seconds: remainingSeconds), taskId + 1, bgUpdateCheck, - params: { - 'toCheck': remainingToCheck, - 'toInstall': toInstall, - 'attemptCount': attemptCount - }); - break; - } else { - notificationsProvider - .notify(ErrorCheckingUpdatesNotification(e.toString())); - } - } finally { - if (notif != null) { - notificationsProvider.cancel(notif.id); - } - } - } - } - if (didCompleteChecking && toInstall.isNotEmpty) { - 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}); - } else if (didCompleteChecking) { - logs.add('BG install task $taskId: Done.'); - } - } else { - var didCompleteInstalling = false; - toInstall = moveStrToEnd(toInstall, obtainiumId); - for (var i = 0; i < toInstall.length; i++) { - String appId = toInstall[i]; - try { - logs.add( - 'BG install task $taskId: Attempting to update $appId in the background.'); - await appsProvider.downloadAndInstallLatestApps( - [appId], null, settingsProvider, - 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) { - logs.add( - 'BG install task $taskId: Got error on updating $appId \'${e.toString()}\'.'); - if (attemptCount < maxAttempts) { - var remainingSeconds = attemptCount; - logs.add( - 'BG install task $taskId: Will continue in $remainingSeconds seconds (with $appId moved to the end of the line).'); - var remainingToInstall = moveStrToEnd(toInstall.sublist(i), appId); - AndroidAlarmManager.oneShot( - Duration(seconds: remainingSeconds), taskId + 1, bgUpdateCheck, - params: { - 'toCheck': toCheck, - 'toInstall': remainingToInstall, - 'attemptCount': attemptCount - }); - break; - } else { - notificationsProvider - .notify(ErrorCheckingUpdatesNotification(e.toString())); - } - } - if (didCompleteInstalling) { - logs.add('BG install task $taskId: Done.'); - } - } - } -} - void main() async { WidgetsFlutterBinding.ensureInitialized(); try { diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index 1ea70ce..5b673b6 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +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'; @@ -99,6 +100,38 @@ Set findStandardFormatsForVersion(String version, bool strict) { return results; } +moveStrToEnd(List arr, String str, {String? strB}) { + String? temp; + arr.removeWhere((element) { + bool res = element == str || element == strB; + if (res) { + temp = element; + } + return res; + }); + if (temp != null) { + arr = [...arr, temp!]; + } + return arr; +} + +moveStrToEndMapEntryWithCount( + List> arr, MapEntry str, + {MapEntry? strB}) { + MapEntry? temp; + arr.removeWhere((element) { + bool res = element.key == str.key || element.key == strB?.key; + if (res) { + temp = element; + } + return res; + }); + if (temp != null) { + arr = [...arr, temp!]; + } + return arr; +} + class AppsProvider with ChangeNotifier { // In memory App state (should always be kept in sync with local storage versions) Map apps = {}; @@ -1221,3 +1254,206 @@ class _APKOriginWarningDialogState extends State { ); } } + +/// Background updater function +/// +/// @param List? toCheck: The appIds to check for updates (default to all apps sorted by last update check time) +/// +/// @param List? toInstall: The appIds to attempt to update (defaults to an empty array) +/// +/// @param int? attemptCount: The number of times the function has failed up to this point (defaults to 0) +/// +/// 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. +/// If an update is available, the appId is either added to toInstall (if a background update is possible) or the user is notified. +/// If there is an error, the function tries to continue after a few minutes (duration depends on the error), up to a maximum of 5 tries. +/// +/// Once all update checks are complete, the function is called again in install mode. +/// In this mode, all apps in toInstall are downloaded and installed in the background (install result is unknown). +/// If there is an error, the function tries to continue after a few minutes (duration depends on the error), up to a maximum of 5 tries. +/// +/// In either mode, if the function fails after the maximum number of tries, the user is notified. +@pragma('vm:entry-point') +Future bgUpdateCheck(int taskId, Map? params) async { + WidgetsFlutterBinding.ensureInitialized(); + await EasyLocalization.ensureInitialized(); + await AndroidAlarmManager.initialize(); + await loadTranslations(); + + LogsProvider logs = LogsProvider(); + NotificationsProvider notificationsProvider = NotificationsProvider(); + AppsProvider appsProvider = AppsProvider(isBg: true); + await appsProvider.loadApps(); + var settingsProvider = SettingsProvider(); + await settingsProvider.initializeSettings(); + + int maxAttempts = 4; + + params ??= {}; + if (params['toCheck'] == null) { + settingsProvider.lastBGCheckTime = DateTime.now(); + } + List> toCheck = >[ + ...(params['toCheck'] + ?.map((entry) => MapEntry( + entry['key'] as String, entry['value'] as int)) + .toList() ?? + appsProvider + .getAppsSortedByUpdateCheckTime() + .map((e) => MapEntry(e, 0))) + ]; + List> toInstall = >[ + ...(params['toInstall'] + ?.map((entry) => MapEntry( + entry['key'] as String, entry['value'] as int)) + .toList() ?? + (>>[])) + ]; + + bool installMode = toCheck.isEmpty && + toInstall.isNotEmpty; // 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... + var didCompleteChecking = false; + CheckingUpdatesNotification? notif; + // Loop through all updates and check each + for (int i = 0; i < toCheck.length; i++) { + var appId = toCheck[i].key; + var retryCount = toCheck[i].value; + AppInMemory? app = appsProvider.apps[appId]; + if (app?.app.installedVersion != null) { + try { + notificationsProvider.notify( + notif = CheckingUpdatesNotification(app?.name ?? appId), + cancelExisting: true); + App? newApp = await appsProvider.checkUpdate(appId); + if (newApp != null) { + if (!(await appsProvider.canInstallSilently( + app!.app, settingsProvider))) { + notificationsProvider.notify( + UpdateNotification([newApp], id: newApp.id.hashCode - 1)); + } else { + toInstall.add(MapEntry(appId, 0)); + } + } + if (i == (toCheck.length - 1)) { + didCompleteChecking = 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 checking shortly + logs.add( + 'BG update task $taskId: Got error on checking for $appId \'${e.toString()}\'.'); + if (retryCount < maxAttempts) { + var remainingSeconds = e is RateLimitError + ? (i == 0 ? (e.remainingMinutes * 60) : (5 * 60)) + : e is ClientException + ? (15 * 60) + : (retryCount ^ 2); + logs.add( + 'BG update task $taskId: Will continue in $remainingSeconds seconds (with $appId moved to the end of the line).'); + var remainingToCheck = moveStrToEndMapEntryWithCount( + toCheck.sublist(i), MapEntry(appId, retryCount + 1)); + AndroidAlarmManager.oneShot( + Duration(seconds: remainingSeconds), taskId + 1, bgUpdateCheck, + params: { + 'toCheck': remainingToCheck + .map((entry) => {'key': entry.key, 'value': entry.value}) + .toList(), + 'toInstall': toInstall + .map((entry) => {'key': entry.key, 'value': 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) + toCheck.removeAt(i); + i--; + notificationsProvider + .notify(ErrorCheckingUpdatesNotification(e.toString())); + } + } finally { + if (notif != null) { + notificationsProvider.cancel(notif.id); + } + } + } + } + // If you're done checking and found some silently installable updates, schedule another task which will run in install mode + if (didCompleteChecking && toInstall.isNotEmpty) { + 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 if (didCompleteChecking) { + logs.add('BG install task $taskId: Done.'); + } + } else { + // If in install mode... + 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 install task $taskId: Attempting to update $appId in the background.'); + await appsProvider.downloadAndInstallLatestApps( + [appId], null, settingsProvider, + 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 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; + } 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) { + logs.add('BG install task $taskId: Done.'); + } + } + } +}