From 408bca8951e82c5659030156cfa81f84d69f05d4 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Tue, 9 May 2023 00:37:06 -0400 Subject: [PATCH] XAPK bugfixes, HTML default User-Agent --- lib/app_sources/html.dart | 7 ++ lib/pages/app.dart | 4 +- lib/pages/apps.dart | 8 +- lib/providers/apps_provider.dart | 89 +++++++++++++++-------- lib/providers/notifications_provider.dart | 3 +- 5 files changed, 76 insertions(+), 35 deletions(-) diff --git a/lib/app_sources/html.dart b/lib/app_sources/html.dart index d9ec173..c6ec9e2 100644 --- a/lib/app_sources/html.dart +++ b/lib/app_sources/html.dart @@ -89,6 +89,13 @@ class HTML extends AppSource { overrideEligible = true; } + @override + // TODO: implement requestHeaders choice, hardcoded for now + Map? get requestHeaders => { + "User-Agent": + "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/81.0" + }; + @override String sourceSpecificStandardizeURL(String url) { return url; diff --git a/lib/pages/app.dart b/lib/pages/app.dart index 8f851e5..b7585c8 100644 --- a/lib/pages/app.dart +++ b/lib/pages/app.dart @@ -444,7 +444,9 @@ class _AppPageState extends State { Padding( padding: const EdgeInsets.fromLTRB(0, 8, 0, 0), child: LinearProgressIndicator( - value: app!.downloadProgress! / 100)) + value: app!.downloadProgress! >= 0 + ? app.downloadProgress! / 100 + : null)) ], )); diff --git a/lib/pages/apps.dart b/lib/pages/apps.dart index 5fcee6a..37fa60c 100644 --- a/lib/pages/apps.dart +++ b/lib/pages/apps.dart @@ -542,8 +542,12 @@ class AppsPageState extends State { ? SizedBox( width: 110, child: Text(tr('percentProgress', args: [ - listedApps[index].downloadProgress?.toInt().toString() ?? - '100' + listedApps[index].downloadProgress! >= 0 + ? listedApps[index] + .downloadProgress! + .toInt() + .toString() + : tr('pleaseWait') ]))) : trailingRow, onTap: () { diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index 3e6f5ea..65eb1a3 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -132,15 +132,21 @@ class AppsProvider with ChangeNotifier { }(); } - downloadFile(String url, String fileName, Function? onProgress, + Future downloadFile( + String url, String fileNameNoExt, Function? onProgress, {bool useExisting = true, Map? headers}) async { var destDir = (await getExternalCacheDirectories())!.first.path; var req = Request('GET', Uri.parse(url)); if (headers != null) { req.headers.addAll(headers); } - StreamedResponse response = await Client().send(req); - File downloadedFile = File('$destDir/$fileName'); + var client = Client(); + StreamedResponse response = await client.send(req); + var ext = response.headers['content-disposition']!.split('.').last; + if (ext.endsWith('"') || ext.endsWith("other")) { + ext = ext.substring(0, ext.length - 1); + } + File downloadedFile = File('$destDir/$fileNameNoExt.$ext'); if (!(downloadedFile.existsSync() && useExisting)) { File tempDownloadedFile = File('${downloadedFile.path}.part'); if (tempDownloadedFile.existsSync()) { @@ -168,12 +174,14 @@ class AppsProvider with ChangeNotifier { throw response.reasonPhrase ?? tr('unexpectedError'); } tempDownloadedFile.renameSync(downloadedFile.path); + } else { + client.close(); } return downloadedFile; } - handleAPKIDChange(App app, PackageArchiveInfo newInfo, File downloadedFile, - String downloadUrl) async { + Future handleAPKIDChange(App app, PackageArchiveInfo 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) { @@ -184,12 +192,13 @@ class AppsProvider with ChangeNotifier { var originalAppId = app.id; app.id = newInfo.packageName; downloadedFile = downloadedFile.renameSync( - '${downloadedFile.parent.path}/${app.id}-${downloadUrl.hashCode}.apk'); + '${downloadedFile.parent.path}/${app.id}-${downloadUrl.hashCode}.${downloadedFile.path.split('.').last}'); if (apps[originalAppId] != null) { await removeApps([originalAppId]); await saveApps([app], onlyIfExists: !isTempId); } } + return downloadedFile; } Future downloadApp(App app, BuildContext? context) async { @@ -205,11 +214,11 @@ class AppsProvider with ChangeNotifier { .getSource(app.url, overrideSource: app.overrideSource); String downloadUrl = await source.apkUrlPrefetchModifier( app.apkUrls[app.preferredApkIndex].value, app.url); - var fileName = '${app.id}-${downloadUrl.hashCode}.apk'; var notif = DownloadNotification(app.finalName, 100); notificationsProvider?.cancel(notif.id); int? prevProg; - File downloadedFile = await downloadFile(downloadUrl, fileName, + var fileNameNoExt = '${app.id}-${downloadUrl.hashCode}'; + var downloadedFile = await downloadFile(downloadUrl, fileNameNoExt, headers: source.requestHeaders, (double? progress) { int? prog = progress?.ceil(); if (apps[app.id] != null) { @@ -222,18 +231,20 @@ class AppsProvider with ChangeNotifier { } prevProg = prog; }); - PackageArchiveInfo? newInfo; - try { - newInfo = await PackageArchiveInfo.fromPath(downloadedFile.path); - } catch (e) { - // Assume it's an XAPK - fileName = '${app.id}-${downloadUrl.hashCode}.xapk'; - String newPath = '${downloadedFile.parent.path}/$fileName'; - downloadedFile.renameSync(newPath); - downloadedFile = File(newPath); + // Set to 90 for remaining steps, will make null in 'finally' + if (apps[app.id] != null) { + apps[app.id]!.downloadProgress = -1; + notifyListeners(); + notif = DownloadNotification(app.finalName, -1); + notificationsProvider?.notify(notif); } + PackageArchiveInfo? newInfo; + var isAPK = downloadedFile.path.toLowerCase().endsWith('.apk'); Directory? xapkDir; - if (newInfo == null) { + if (isAPK) { + newInfo = await PackageArchiveInfo.fromPath(downloadedFile.path); + } else { + // Assume XAPK String xapkDirPath = '${downloadedFile.path}-dir'; unzipFile(downloadedFile.path, '${downloadedFile.path}-dir'); xapkDir = Directory(xapkDirPath); @@ -243,20 +254,21 @@ class AppsProvider with ChangeNotifier { .toList(); newInfo = await PackageArchiveInfo.fromPath(apks.first.path); } - await handleAPKIDChange(app, newInfo, downloadedFile, downloadUrl); + downloadedFile = + 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; if (fn.startsWith('${app.id}-') && - fn.toLowerCase().endsWith(xapkDir == null ? '.apk' : '.xapk') && - fn != downloadedFile.path.split('/').last) { + FileSystemEntity.isFileSync(file.path) && + file.path != downloadedFile.path) { file.delete(); } } - if (xapkDir != null) { - return DownloadedXApkDir(app.id, downloadedFile, xapkDir); - } else { + if (isAPK) { return DownloadedApk(app.id, downloadedFile); + } else { + return DownloadedXApkDir(app.id, downloadedFile, xapkDir!); } } finally { notificationsProvider?.cancel(notifId); @@ -324,18 +336,23 @@ class AppsProvider with ChangeNotifier { Future installXApkDir(DownloadedXApkDir dir, {bool silent = false}) async { try { + var somethingInstalled = false; for (var apk in dir.extracted .listSync() .where((f) => f is File && f.path.toLowerCase().endsWith('.apk'))) { - await installApk(DownloadedApk(dir.appId, apk as File), silent: silent); + somethingInstalled = somethingInstalled || + await installApk(DownloadedApk(dir.appId, apk as File), + silent: silent); + } + if (somethingInstalled) { + dir.file.delete(); } - dir.file.delete(); } finally { dir.extracted.delete(recursive: true); } } - Future installApk(DownloadedApk file, {bool silent = false}) async { + Future installApk(DownloadedApk file, {bool silent = false}) async { // TODO: Use 'silent' when/if ever possible var newInfo = await PackageArchiveInfo.fromPath(file.file.path); AppInfo? appInfo; @@ -351,14 +368,17 @@ class AppsProvider with ChangeNotifier { } int? code = await AndroidPackageInstaller.installApk(apkFilePath: file.file.path); + bool installed = false; if (code != null && code != 0 && code != 3) { throw InstallError(code); } else if (code == 0) { + installed = true; apps[file.appId]!.app.installedVersion = apps[file.appId]!.app.latestVersion; file.file.delete(); } await saveApps([apps[file.appId]!.app]); + return installed; } void uninstallApp(String appId) async { @@ -503,10 +523,17 @@ class AppsProvider with ChangeNotifier { // ignore: use_build_context_synchronously await waitForUserToReturnToForeground(context); } - if (downloadedFile != null) { - await installApk(downloadedFile, silent: willBeSilent); - } else { - await installXApkDir(downloadedDir!, silent: willBeSilent); + apps[id]?.downloadProgress = -1; + notifyListeners(); + try { + if (downloadedFile != null) { + await installApk(downloadedFile, silent: willBeSilent); + } else { + await installXApkDir(downloadedDir!, silent: willBeSilent); + } + } finally { + apps[id]?.downloadProgress = null; + notifyListeners(); } installedIds.add(id); } catch (e) { diff --git a/lib/providers/notifications_provider.dart b/lib/providers/notifications_provider.dart index f48b09a..1f39cc8 100644 --- a/lib/providers/notifications_provider.dart +++ b/lib/providers/notifications_provider.dart @@ -167,7 +167,8 @@ class NotificationsProvider { progress: progPercent ?? 0, maxProgress: 100, showProgress: progPercent != null, - onlyAlertOnce: onlyAlertOnce))); + onlyAlertOnce: onlyAlertOnce, + indeterminate: progPercent != null && progPercent < 0))); } Future notify(ObtainiumNotification notif,