XAPK bugfixes, HTML default User-Agent

This commit is contained in:
Imran Remtulla
2023-05-09 00:37:06 -04:00
parent 219b04aedb
commit 408bca8951
5 changed files with 76 additions and 35 deletions

View File

@@ -89,6 +89,13 @@ class HTML extends AppSource {
overrideEligible = true; overrideEligible = true;
} }
@override
// TODO: implement requestHeaders choice, hardcoded for now
Map<String, String>? get requestHeaders => {
"User-Agent":
"Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/81.0"
};
@override @override
String sourceSpecificStandardizeURL(String url) { String sourceSpecificStandardizeURL(String url) {
return url; return url;

View File

@@ -444,7 +444,9 @@ class _AppPageState extends State<AppPage> {
Padding( Padding(
padding: const EdgeInsets.fromLTRB(0, 8, 0, 0), padding: const EdgeInsets.fromLTRB(0, 8, 0, 0),
child: LinearProgressIndicator( child: LinearProgressIndicator(
value: app!.downloadProgress! / 100)) value: app!.downloadProgress! >= 0
? app.downloadProgress! / 100
: null))
], ],
)); ));

View File

@@ -542,8 +542,12 @@ class AppsPageState extends State<AppsPage> {
? SizedBox( ? SizedBox(
width: 110, width: 110,
child: Text(tr('percentProgress', args: [ child: Text(tr('percentProgress', args: [
listedApps[index].downloadProgress?.toInt().toString() ?? listedApps[index].downloadProgress! >= 0
'100' ? listedApps[index]
.downloadProgress!
.toInt()
.toString()
: tr('pleaseWait')
]))) ])))
: trailingRow, : trailingRow,
onTap: () { onTap: () {

View File

@@ -132,15 +132,21 @@ class AppsProvider with ChangeNotifier {
}(); }();
} }
downloadFile(String url, String fileName, Function? onProgress, Future<File> downloadFile(
String url, String fileNameNoExt, Function? onProgress,
{bool useExisting = true, Map<String, String>? headers}) async { {bool useExisting = true, Map<String, String>? headers}) async {
var destDir = (await getExternalCacheDirectories())!.first.path; var destDir = (await getExternalCacheDirectories())!.first.path;
var req = Request('GET', Uri.parse(url)); var req = Request('GET', Uri.parse(url));
if (headers != null) { if (headers != null) {
req.headers.addAll(headers); req.headers.addAll(headers);
} }
StreamedResponse response = await Client().send(req); var client = Client();
File downloadedFile = File('$destDir/$fileName'); 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)) { if (!(downloadedFile.existsSync() && useExisting)) {
File tempDownloadedFile = File('${downloadedFile.path}.part'); File tempDownloadedFile = File('${downloadedFile.path}.part');
if (tempDownloadedFile.existsSync()) { if (tempDownloadedFile.existsSync()) {
@@ -168,12 +174,14 @@ class AppsProvider with ChangeNotifier {
throw response.reasonPhrase ?? tr('unexpectedError'); throw response.reasonPhrase ?? tr('unexpectedError');
} }
tempDownloadedFile.renameSync(downloadedFile.path); tempDownloadedFile.renameSync(downloadedFile.path);
} else {
client.close();
} }
return downloadedFile; return downloadedFile;
} }
handleAPKIDChange(App app, PackageArchiveInfo newInfo, File downloadedFile, Future<File> handleAPKIDChange(App app, PackageArchiveInfo newInfo,
String downloadUrl) async { 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 // 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 // The former case should be handled (give the App its real ID), the latter is a security issue
if (app.id != newInfo.packageName) { if (app.id != newInfo.packageName) {
@@ -184,12 +192,13 @@ class AppsProvider with ChangeNotifier {
var originalAppId = app.id; var originalAppId = app.id;
app.id = newInfo.packageName; app.id = newInfo.packageName;
downloadedFile = downloadedFile.renameSync( 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) { if (apps[originalAppId] != null) {
await removeApps([originalAppId]); await removeApps([originalAppId]);
await saveApps([app], onlyIfExists: !isTempId); await saveApps([app], onlyIfExists: !isTempId);
} }
} }
return downloadedFile;
} }
Future<Object> downloadApp(App app, BuildContext? context) async { Future<Object> downloadApp(App app, BuildContext? context) async {
@@ -205,11 +214,11 @@ class AppsProvider with ChangeNotifier {
.getSource(app.url, overrideSource: app.overrideSource); .getSource(app.url, overrideSource: app.overrideSource);
String downloadUrl = await source.apkUrlPrefetchModifier( String downloadUrl = await source.apkUrlPrefetchModifier(
app.apkUrls[app.preferredApkIndex].value, app.url); app.apkUrls[app.preferredApkIndex].value, app.url);
var fileName = '${app.id}-${downloadUrl.hashCode}.apk';
var notif = DownloadNotification(app.finalName, 100); var notif = DownloadNotification(app.finalName, 100);
notificationsProvider?.cancel(notif.id); notificationsProvider?.cancel(notif.id);
int? prevProg; 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) { headers: source.requestHeaders, (double? progress) {
int? prog = progress?.ceil(); int? prog = progress?.ceil();
if (apps[app.id] != null) { if (apps[app.id] != null) {
@@ -222,18 +231,20 @@ class AppsProvider with ChangeNotifier {
} }
prevProg = prog; prevProg = prog;
}); });
PackageArchiveInfo? newInfo; // Set to 90 for remaining steps, will make null in 'finally'
try { if (apps[app.id] != null) {
newInfo = await PackageArchiveInfo.fromPath(downloadedFile.path); apps[app.id]!.downloadProgress = -1;
} catch (e) { notifyListeners();
// Assume it's an XAPK notif = DownloadNotification(app.finalName, -1);
fileName = '${app.id}-${downloadUrl.hashCode}.xapk'; notificationsProvider?.notify(notif);
String newPath = '${downloadedFile.parent.path}/$fileName';
downloadedFile.renameSync(newPath);
downloadedFile = File(newPath);
} }
PackageArchiveInfo? newInfo;
var isAPK = downloadedFile.path.toLowerCase().endsWith('.apk');
Directory? xapkDir; Directory? xapkDir;
if (newInfo == null) { if (isAPK) {
newInfo = await PackageArchiveInfo.fromPath(downloadedFile.path);
} else {
// Assume XAPK
String xapkDirPath = '${downloadedFile.path}-dir'; String xapkDirPath = '${downloadedFile.path}-dir';
unzipFile(downloadedFile.path, '${downloadedFile.path}-dir'); unzipFile(downloadedFile.path, '${downloadedFile.path}-dir');
xapkDir = Directory(xapkDirPath); xapkDir = Directory(xapkDirPath);
@@ -243,20 +254,21 @@ class AppsProvider with ChangeNotifier {
.toList(); .toList();
newInfo = await PackageArchiveInfo.fromPath(apks.first.path); 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 // Delete older versions of the file if any
for (var file in downloadedFile.parent.listSync()) { for (var file in downloadedFile.parent.listSync()) {
var fn = file.path.split('/').last; var fn = file.path.split('/').last;
if (fn.startsWith('${app.id}-') && if (fn.startsWith('${app.id}-') &&
fn.toLowerCase().endsWith(xapkDir == null ? '.apk' : '.xapk') && FileSystemEntity.isFileSync(file.path) &&
fn != downloadedFile.path.split('/').last) { file.path != downloadedFile.path) {
file.delete(); file.delete();
} }
} }
if (xapkDir != null) { if (isAPK) {
return DownloadedXApkDir(app.id, downloadedFile, xapkDir);
} else {
return DownloadedApk(app.id, downloadedFile); return DownloadedApk(app.id, downloadedFile);
} else {
return DownloadedXApkDir(app.id, downloadedFile, xapkDir!);
} }
} finally { } finally {
notificationsProvider?.cancel(notifId); notificationsProvider?.cancel(notifId);
@@ -324,18 +336,23 @@ class AppsProvider with ChangeNotifier {
Future<void> installXApkDir(DownloadedXApkDir dir, Future<void> installXApkDir(DownloadedXApkDir dir,
{bool silent = false}) async { {bool silent = false}) async {
try { try {
var somethingInstalled = false;
for (var apk in dir.extracted for (var apk in dir.extracted
.listSync() .listSync()
.where((f) => f is File && f.path.toLowerCase().endsWith('.apk'))) { .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 { } finally {
dir.extracted.delete(recursive: true); dir.extracted.delete(recursive: true);
} }
} }
Future<void> installApk(DownloadedApk file, {bool silent = false}) async { Future<bool> installApk(DownloadedApk file, {bool silent = false}) async {
// TODO: Use 'silent' when/if ever possible // TODO: Use 'silent' when/if ever possible
var newInfo = await PackageArchiveInfo.fromPath(file.file.path); var newInfo = await PackageArchiveInfo.fromPath(file.file.path);
AppInfo? appInfo; AppInfo? appInfo;
@@ -351,14 +368,17 @@ class AppsProvider with ChangeNotifier {
} }
int? code = int? code =
await AndroidPackageInstaller.installApk(apkFilePath: file.file.path); await AndroidPackageInstaller.installApk(apkFilePath: file.file.path);
bool installed = false;
if (code != null && code != 0 && code != 3) { if (code != null && code != 0 && code != 3) {
throw InstallError(code); throw InstallError(code);
} else if (code == 0) { } else if (code == 0) {
installed = true;
apps[file.appId]!.app.installedVersion = apps[file.appId]!.app.installedVersion =
apps[file.appId]!.app.latestVersion; apps[file.appId]!.app.latestVersion;
file.file.delete(); file.file.delete();
} }
await saveApps([apps[file.appId]!.app]); await saveApps([apps[file.appId]!.app]);
return installed;
} }
void uninstallApp(String appId) async { void uninstallApp(String appId) async {
@@ -503,10 +523,17 @@ class AppsProvider with ChangeNotifier {
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
await waitForUserToReturnToForeground(context); await waitForUserToReturnToForeground(context);
} }
if (downloadedFile != null) { apps[id]?.downloadProgress = -1;
await installApk(downloadedFile, silent: willBeSilent); notifyListeners();
} else { try {
await installXApkDir(downloadedDir!, silent: willBeSilent); if (downloadedFile != null) {
await installApk(downloadedFile, silent: willBeSilent);
} else {
await installXApkDir(downloadedDir!, silent: willBeSilent);
}
} finally {
apps[id]?.downloadProgress = null;
notifyListeners();
} }
installedIds.add(id); installedIds.add(id);
} catch (e) { } catch (e) {

View File

@@ -167,7 +167,8 @@ class NotificationsProvider {
progress: progPercent ?? 0, progress: progPercent ?? 0,
maxProgress: 100, maxProgress: 100,
showProgress: progPercent != null, showProgress: progPercent != null,
onlyAlertOnce: onlyAlertOnce))); onlyAlertOnce: onlyAlertOnce,
indeterminate: progPercent != null && progPercent < 0)));
} }
Future<void> notify(ObtainiumNotification notif, Future<void> notify(ObtainiumNotification notif,