|
|
|
@@ -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 {
|
|
|
|
|
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<File> 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<Object> 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<void> 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<void> installApk(DownloadedApk file, {bool silent = false}) async {
|
|
|
|
|
Future<bool> 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) {
|
|
|
|
|