Various bugfixes + prep for multiple apk support

This commit is contained in:
Imran Remtulla
2022-08-26 21:36:52 -04:00
parent ecb1e7d367
commit a6f290eb59
5 changed files with 66 additions and 42 deletions

View File

@@ -9,7 +9,7 @@ import 'package:provider/provider.dart';
import 'package:workmanager/workmanager.dart';
import 'package:dynamic_color/dynamic_color.dart';
const String CURRENT_RELEASE_TAG =
const String currentReleaseTag =
'v0.1.3-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
@pragma('vm:entry-point')
@@ -101,9 +101,8 @@ class MyApp extends StatelessWidget {
'https://github.com/ImranR98/Obtainium',
'ImranR98',
'Obtainium',
CURRENT_RELEASE_TAG,
CURRENT_RELEASE_TAG,
''));
currentReleaseTag,
currentReleaseTag, []));
}
appsProvider.deleteSavedAPKs();
appsProvider.checkUpdates();

View File

@@ -48,7 +48,7 @@ class _AppPageState extends State<AppPage> {
? () {
appsProvider
.downloadAndInstallLatestApp(
app!.app.id);
app!.app.id, context);
}
: null,
child: Text(app?.app.installedVersion == null

View File

@@ -26,7 +26,7 @@ class _AppsPageState extends State<AppsPage> {
? null
: () {
for (var e in existingUpdateAppIds) {
appsProvider.downloadAndInstallLatestApp(e);
appsProvider.downloadAndInstallLatestApp(e, context);
}
},
icon: const Icon(Icons.update),

View File

@@ -68,12 +68,13 @@ class AppsProvider with ChangeNotifier {
}
// Given a App (assumed valid), initiate an APK download (will trigger install callback when complete)
Future<void> downloadAndInstallLatestApp(String appId) async {
Future<void> downloadAndInstallLatestApp(
String appId, BuildContext context) async {
if (apps[appId] == null) {
throw 'App not found';
}
StreamedResponse response =
await Client().send(Request('GET', Uri.parse(apps[appId]!.app.apkUrl)));
StreamedResponse response = await Client()
.send(Request('GET', Uri.parse(apps[appId]!.app.apkUrls[0])));
File downloadFile =
File('${(await getExternalStorageDirectory())!.path}/$appId.apk');
if (downloadFile.existsSync()) {
@@ -244,14 +245,14 @@ class AppsProvider with ChangeNotifier {
path = exportDir!.path;
}
File export = File(
'${exportDir!.path}/obtainium-export-${DateTime.now().millisecondsSinceEpoch}.json');
'${exportDir.path}/obtainium-export-${DateTime.now().millisecondsSinceEpoch}.json');
export.writeAsStringSync(
jsonEncode(apps.values.map((e) => e.app.toJson()).toList()));
return path;
}
Future<int> importApps(String appsJSON) async {
// FilePickerResult? result = await FilePicker.platform.pickFiles();
// FilePickerResult? result = await FilePicker.platform.pickFiles(); // Does not work on Android 13
// if (result != null) {
// String appsJSON = File(result.files.single.path!).readAsStringSync();

View File

@@ -1,6 +1,9 @@
// Exposes functions related to interacting with App sources and retrieving App info
// Stateless - not a provider
import 'dart:convert';
import 'package:html/dom.dart';
import 'package:http/http.dart';
import 'package:html/parser.dart';
@@ -15,9 +18,9 @@ class AppNames {
class APKDetails {
late String version;
late String downloadUrl;
late List<String> apkUrls;
APKDetails(this.version, this.downloadUrl);
APKDetails(this.version, this.apkUrls);
}
// App Source abstract class (diff. implementations for GitHub, GitLab, etc.)
@@ -35,6 +38,17 @@ escapeRegEx(String s) {
});
}
List<String> getLinksFromParsedHTML(
Document dom, RegExp hrefPattern, String prependToLinks) =>
dom
.querySelectorAll('a')
.where((element) {
if (element.attributes['href'] == null) return false;
return hrefPattern.hasMatch(element.attributes['href']!);
})
.map((e) => '$prependToLinks${e.attributes['href']!}')
.toList();
// App class
class App {
@@ -44,13 +58,13 @@ class App {
late String name;
String? installedVersion;
late String latestVersion;
late String apkUrl;
List<String> apkUrls = [];
App(this.id, this.url, this.author, this.name, this.installedVersion,
this.latestVersion, this.apkUrl);
this.latestVersion, this.apkUrls);
@override
String toString() {
return 'ID: $id URL: $url INSTALLED: $installedVersion LATEST: $latestVersion APK: $apkUrl';
return 'ID: $id URL: $url INSTALLED: $installedVersion LATEST: $latestVersion APK: $apkUrls';
}
factory App.fromJson(Map<String, dynamic> json) => App(
@@ -62,7 +76,7 @@ class App {
? null
: json['installedVersion'] as String,
json['latestVersion'] as String,
json['apkUrl'] as String);
List<String>.from(jsonDecode(json['apkUrls'])));
Map<String, dynamic> toJson() => {
'id': id,
@@ -71,7 +85,7 @@ class App {
'name': name,
'installedVersion': installedVersion,
'latestVersion': latestVersion,
'apkUrl': apkUrl,
'apkUrls': jsonEncode(apkUrls),
};
}
@@ -98,23 +112,31 @@ class GitHub implements AppSource {
if (res.statusCode == 200) {
var standardUri = Uri.parse(standardUrl);
var parsedHtml = parse(res.body);
var apkUrlList = parsedHtml.querySelectorAll('a').where((element) {
if (element.attributes['href'] == null) return false;
return RegExp(
var apkUrlList = getLinksFromParsedHTML(
parsedHtml,
RegExp(
'^${escapeRegEx(standardUri.path)}/releases/download/[^/]+/[^/]+\\.apk\$',
caseSensitive: false)
.hasMatch(element.attributes['href']!);
}).toList();
caseSensitive: false),
standardUri.origin);
if (apkUrlList.isEmpty) {
throw 'No APK found';
}
String getTag(String url) {
List<String> parts = url.split('/');
return parts[parts.length - 2];
}
String latestTag = getTag(apkUrlList[0]);
String? version = parsedHtml
.querySelector('.octicon-tag')
?.nextElementSibling
?.innerHtml
.trim();
if (apkUrlList.isEmpty || version == null) {
throw 'No APK found';
if (version == null) {
throw 'Could not determine latest release version';
}
return APKDetails(
version, '${standardUri.origin}${apkUrlList[0].attributes['href']!}');
return APKDetails(version,
apkUrlList.where((element) => getTag(element) == latestTag).toList());
} else {
throw 'Unable to fetch release info';
}
@@ -152,21 +174,23 @@ class GitLab implements AppSource {
var entry = parsedHtml.querySelector('entry');
var entryContent =
parse(parseFragment(entry!.querySelector('content')!.innerHtml).text);
var apkUrlList = entryContent.querySelectorAll('a').where((element) {
if (element.attributes['href'] == null) return false;
return RegExp(
var apkUrlList = getLinksFromParsedHTML(
entryContent,
RegExp(
'^${escapeRegEx(standardUri.path)}/uploads/[^/]+/[^/]+\\.apk\$',
caseSensitive: false)
.hasMatch(element.attributes['href']!);
}).toList();
caseSensitive: false),
standardUri.origin);
if (apkUrlList.isEmpty) {
throw 'No APK found';
}
var entryId = entry.querySelector('id')?.innerHtml;
var version =
entryId == null ? null : Uri.parse(entryId).pathSegments.last;
if (apkUrlList.isEmpty || version == null) {
throw 'No APK found';
if (version == null) {
throw 'Could not determine latest release version';
}
return APKDetails(
version, '${standardUri.origin}${apkUrlList[0].attributes['href']!}');
return APKDetails(version, apkUrlList);
} else {
throw 'Unable to fetch release info';
}
@@ -203,12 +227,12 @@ class SourceService {
AppNames names = source.getAppNames(standardUrl);
APKDetails apk = await source.getLatestAPKDetails(standardUrl);
return App(
'${names.author}_${names.name}_${source.sourceId}',
'${names.author.toLowerCase()}_${names.name.toLowerCase()}_${source.sourceId}',
standardUrl,
names.author[0].toUpperCase() + names.author.substring(1),
names.name[0].toUpperCase() + names.name.substring(1),
null,
apk.version,
apk.downloadUrl);
apk.apkUrls);
}
}