mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-10-21 10:03:44 +02:00
Add support for zipped APKs - GitHub/HTML only for now (#983)
This commit is contained in:
@@ -18,6 +18,7 @@ class GitHub extends AppSource {
|
||||
appIdInferIsOptional = true;
|
||||
showReleaseDateAsVersionToggle = true;
|
||||
this.hostChanged = hostChanged;
|
||||
allowIncludeZips = true;
|
||||
|
||||
sourceConfigSettingFormItems = [
|
||||
GeneratedFormTextField(
|
||||
@@ -370,6 +371,7 @@ class GitHub extends AppSource {
|
||||
additionalSettings['useLatestAssetDateAsReleaseDate'] == true;
|
||||
String sortMethod =
|
||||
additionalSettings['sortMethodChoice'] ?? 'smartname-datefallback';
|
||||
bool includeZips = additionalSettings['includeZips'] == true;
|
||||
dynamic latestRelease;
|
||||
if (verifyLatestTag) {
|
||||
var temp = requestUrl.split('?');
|
||||
@@ -402,7 +404,8 @@ class GitHub extends AppSource {
|
||||
|
||||
findReleaseAssetUrls(dynamic release) =>
|
||||
(release['assets'] as List<dynamic>?)?.map((e) {
|
||||
var url = !e['name'].toString().toLowerCase().endsWith('.apk')
|
||||
var ext = e['name'].toString().toLowerCase().split('.').last;
|
||||
var url = !(ext == 'apk' || (includeZips && ext == 'zip'))
|
||||
? (e['browser_download_url'] ?? e['url'])
|
||||
: (e['url'] ?? e['browser_download_url']);
|
||||
url = undoGHProxyMod(url, sourceConfigSettingValues);
|
||||
@@ -542,14 +545,13 @@ class GitHub extends AppSource {
|
||||
List<MapEntry<String, String>> allAssetUrls = allAssetsWithUrls
|
||||
.map((e) => e['final_url'] as MapEntry<String, String>)
|
||||
.toList();
|
||||
var apkAssetsWithUrls = allAssetsWithUrls
|
||||
.where(
|
||||
(element) => (element['final_url'] as MapEntry<String, String>)
|
||||
.key
|
||||
.toLowerCase()
|
||||
.endsWith('.apk'),
|
||||
)
|
||||
.toList();
|
||||
var apkAssetsWithUrls = allAssetsWithUrls.where((element) {
|
||||
var ext = (element['final_url'] as MapEntry<String, String>).key
|
||||
.toLowerCase()
|
||||
.split('.')
|
||||
.last;
|
||||
return ext == 'apk' || (includeZips && ext == 'zip');
|
||||
}).toList();
|
||||
|
||||
var filteredApkUrls = filterApks(
|
||||
apkAssetsWithUrls
|
||||
|
@@ -203,11 +203,11 @@ class AddAppPageState extends State<AddAppPage> {
|
||||
notificationsProvider: notificationsProvider,
|
||||
);
|
||||
DownloadedApk? downloadedFile;
|
||||
DownloadedXApkDir? downloadedDir;
|
||||
DownloadedDir? downloadedDir;
|
||||
if (downloadedArtifact is DownloadedApk) {
|
||||
downloadedFile = downloadedArtifact;
|
||||
} else {
|
||||
downloadedDir = downloadedArtifact as DownloadedXApkDir;
|
||||
downloadedDir = downloadedArtifact as DownloadedDir;
|
||||
}
|
||||
app.id = downloadedFile?.appId ?? downloadedDir!.appId;
|
||||
}
|
||||
|
@@ -62,11 +62,14 @@ class DownloadedApk {
|
||||
DownloadedApk(this.appId, this.file);
|
||||
}
|
||||
|
||||
class DownloadedXApkDir {
|
||||
enum DownloadedDirType { XAPK, ZIP }
|
||||
|
||||
class DownloadedDir {
|
||||
String appId;
|
||||
File file;
|
||||
Directory extracted;
|
||||
DownloadedXApkDir(this.appId, this.file, this.extracted);
|
||||
DownloadedDirType type;
|
||||
DownloadedDir(this.appId, this.file, this.extracted, this.type);
|
||||
}
|
||||
|
||||
List<String> generateStandardVersionRegExStrings() {
|
||||
@@ -664,17 +667,18 @@ class AppsProvider with ChangeNotifier {
|
||||
}
|
||||
PackageInfo? newInfo;
|
||||
var isAPK = downloadedFile.path.toLowerCase().endsWith('.apk');
|
||||
Directory? xapkDir;
|
||||
var isXAPK = downloadedFile.path.toLowerCase().endsWith('.xapk');
|
||||
Directory? apkDir;
|
||||
if (isAPK) {
|
||||
newInfo = await pm.getPackageArchiveInfo(
|
||||
archiveFilePath: downloadedFile.path,
|
||||
);
|
||||
} else {
|
||||
// Assume XAPK
|
||||
String xapkDirPath = '${downloadedFile.path}-dir';
|
||||
// Assume XAPK or ZIP
|
||||
String apkDirPath = '${downloadedFile.path}-dir';
|
||||
await unzipFile(downloadedFile.path, '${downloadedFile.path}-dir');
|
||||
xapkDir = Directory(xapkDirPath);
|
||||
var apks = xapkDir
|
||||
apkDir = Directory(apkDirPath);
|
||||
var apks = apkDir
|
||||
.listSync()
|
||||
.where((e) => e.path.toLowerCase().endsWith('.apk'))
|
||||
.toList();
|
||||
@@ -691,6 +695,22 @@ class AppsProvider with ChangeNotifier {
|
||||
apks = [temp!, ...apks];
|
||||
}
|
||||
|
||||
if (app.additionalSettings['zippedApkFilterRegEx']?.isNotEmpty ==
|
||||
true) {
|
||||
var reg = RegExp(app.additionalSettings['zippedApkFilterRegEx']);
|
||||
apks.removeWhere((apk) {
|
||||
var shouldDelete = !reg.hasMatch(apk.uri.pathSegments.last);
|
||||
if (shouldDelete) {
|
||||
apk.delete();
|
||||
}
|
||||
return shouldDelete;
|
||||
});
|
||||
}
|
||||
|
||||
if (apks.isEmpty) {
|
||||
throw NoAPKError();
|
||||
}
|
||||
|
||||
for (var i = 0; i < apks.length; i++) {
|
||||
try {
|
||||
newInfo = await pm.getPackageArchiveInfo(
|
||||
@@ -728,7 +748,12 @@ class AppsProvider with ChangeNotifier {
|
||||
if (isAPK) {
|
||||
return DownloadedApk(app.id, downloadedFile);
|
||||
} else {
|
||||
return DownloadedXApkDir(app.id, downloadedFile, xapkDir!);
|
||||
return DownloadedDir(
|
||||
app.id,
|
||||
downloadedFile,
|
||||
apkDir!,
|
||||
isXAPK ? DownloadedDirType.XAPK : DownloadedDirType.ZIP,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
notificationsProvider?.cancel(notifId);
|
||||
@@ -826,15 +851,16 @@ class AppsProvider with ChangeNotifier {
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> installXApkDir(
|
||||
DownloadedXApkDir dir,
|
||||
Future<bool> installApkDir(
|
||||
DownloadedDir dir,
|
||||
BuildContext? firstTimeWithContext, {
|
||||
bool needsBGWorkaround = false,
|
||||
bool shizukuPretendToBeGooglePlay = false,
|
||||
}) async {
|
||||
// We don't know which APKs in an XAPK are supported by the user's device
|
||||
// We don't know which APKs in an XAPK or ZIP are supported by the user's device
|
||||
// So we try installing all of them and assume success if at least one installed
|
||||
// If 0 APKs installed, throw the first install error encountered
|
||||
// Obviously this approach is naive and is undesirable in many cases, needs to be improved
|
||||
var somethingInstalled = false;
|
||||
try {
|
||||
MultiAppMultiError errors = MultiAppMultiError();
|
||||
@@ -863,7 +889,7 @@ class AppsProvider with ChangeNotifier {
|
||||
}
|
||||
|
||||
try {
|
||||
await installApk(
|
||||
var wasInstalled = await installApk(
|
||||
DownloadedApk(dir.appId, APKFiles[0]),
|
||||
firstTimeWithContext,
|
||||
needsBGWorkaround: needsBGWorkaround,
|
||||
@@ -872,10 +898,10 @@ class AppsProvider with ChangeNotifier {
|
||||
1,
|
||||
).map((a) => DownloadedApk(dir.appId, a)).toList(),
|
||||
);
|
||||
somethingInstalled = true;
|
||||
somethingInstalled = somethingInstalled || wasInstalled;
|
||||
dir.file.delete(recursive: true);
|
||||
} catch (e) {
|
||||
logs.add('Could not install APKs from XAPK: ${e.toString()}');
|
||||
logs.add('Could not install APKs from ${dir.type}: ${e.toString()}');
|
||||
errors.add(dir.appId, e, appName: apps[dir.appId]?.name);
|
||||
}
|
||||
if (errors.idsByErrorString.isNotEmpty) {
|
||||
@@ -1148,7 +1174,7 @@ class AppsProvider with ChangeNotifier {
|
||||
String id,
|
||||
bool willBeSilent,
|
||||
DownloadedApk? downloadedFile,
|
||||
DownloadedXApkDir? downloadedDir,
|
||||
DownloadedDir? downloadedDir,
|
||||
) async {
|
||||
apps[id]?.downloadProgress = -1;
|
||||
notifyListeners();
|
||||
@@ -1183,14 +1209,14 @@ class AppsProvider with ChangeNotifier {
|
||||
} else {
|
||||
if (needBGWorkaround) {
|
||||
// ignore: use_build_context_synchronously
|
||||
installXApkDir(
|
||||
installApkDir(
|
||||
downloadedDir!,
|
||||
contextIfNewInstall,
|
||||
needsBGWorkaround: true,
|
||||
);
|
||||
} else {
|
||||
// ignore: use_build_context_synchronously
|
||||
sayInstalled = await installXApkDir(
|
||||
sayInstalled = await installApkDir(
|
||||
downloadedDir!,
|
||||
contextIfNewInstall,
|
||||
shizukuPretendToBeGooglePlay: shizukuPretendToBeGooglePlay,
|
||||
@@ -1227,7 +1253,7 @@ class AppsProvider with ChangeNotifier {
|
||||
}) async {
|
||||
bool willBeSilent = false;
|
||||
DownloadedApk? downloadedFile;
|
||||
DownloadedXApkDir? downloadedDir;
|
||||
DownloadedDir? downloadedDir;
|
||||
try {
|
||||
var downloadedArtifact =
|
||||
// ignore: use_build_context_synchronously
|
||||
@@ -1240,7 +1266,7 @@ class AppsProvider with ChangeNotifier {
|
||||
if (downloadedArtifact is DownloadedApk) {
|
||||
downloadedFile = downloadedArtifact;
|
||||
} else {
|
||||
downloadedDir = downloadedArtifact as DownloadedXApkDir;
|
||||
downloadedDir = downloadedArtifact as DownloadedDir;
|
||||
}
|
||||
id = downloadedFile?.appId ?? downloadedDir!.appId;
|
||||
willBeSilent = await canInstallSilently(apps[id]!.app);
|
||||
@@ -1292,7 +1318,7 @@ class AppsProvider with ChangeNotifier {
|
||||
res['id'] as String,
|
||||
res['willBeSilent'] as bool,
|
||||
res['downloadedFile'] as DownloadedApk?,
|
||||
res['downloadedDir'] as DownloadedXApkDir?,
|
||||
res['downloadedDir'] as DownloadedDir?,
|
||||
);
|
||||
} catch (e) {
|
||||
var id = res['id'] as String;
|
||||
|
@@ -636,6 +636,7 @@ abstract class AppSource {
|
||||
bool versionDetectionDisallowed = false;
|
||||
List<String> excludeCommonSettingKeys = [];
|
||||
bool urlsAlwaysHaveExtension = false;
|
||||
bool allowIncludeZips = false;
|
||||
|
||||
AppSource() {
|
||||
name = runtimeType.toString();
|
||||
@@ -834,7 +835,7 @@ abstract class AppSource {
|
||||
],
|
||||
];
|
||||
|
||||
// Previous 2 variables combined into one at runtime for convenient usage
|
||||
// Previous 2 variables combined into one at runtime for convenient usage + additional processing
|
||||
List<List<GeneratedFormItem>> get combinedAppSpecificSettingFormItems {
|
||||
if (showReleaseDateAsVersionToggle == true) {
|
||||
if (additionalAppSpecificSourceAgnosticSettingFormItemsNeverUseDirectly
|
||||
@@ -878,6 +879,32 @@ abstract class AppSource {
|
||||
)
|
||||
.where((e) => e.isNotEmpty)
|
||||
.toList();
|
||||
|
||||
var moreConditionalItems = [];
|
||||
if (allowIncludeZips) {
|
||||
moreConditionalItems.addAll([
|
||||
[
|
||||
GeneratedFormSwitch(
|
||||
'includeZips',
|
||||
label: tr('includeZips'),
|
||||
defaultValue: false,
|
||||
),
|
||||
],
|
||||
[
|
||||
GeneratedFormTextField(
|
||||
'zippedApkFilterRegEx',
|
||||
label: tr('zippedApkFilterRegEx'),
|
||||
required: false,
|
||||
additionalValidators: [
|
||||
(value) {
|
||||
return regExValidator(value);
|
||||
},
|
||||
],
|
||||
),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
if (versionDetectionDisallowed) {
|
||||
overrideAdditionalAppSpecificSourceAgnosticSettingSwitch(
|
||||
'versionDetection',
|
||||
@@ -893,6 +920,7 @@ abstract class AppSource {
|
||||
return [
|
||||
...additionalSourceAppSpecificSettingFormItems,
|
||||
...additionalAppSpecificSourceAgnosticSettingFormItemsNeverUseDirectly,
|
||||
...moreConditionalItems,
|
||||
];
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user