Add 'ETag header' option for HTML and direct APK links (#2221) - needs testing

This commit is contained in:
Imran Remtulla
2025-04-05 14:32:26 -04:00
parent 2aa91ed535
commit 94ab83ff75
3 changed files with 52 additions and 17 deletions

View File

@@ -1,5 +1,6 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:obtainium/app_sources/html.dart'; import 'package:obtainium/app_sources/html.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';
@@ -8,12 +9,23 @@ class DirectAPKLink extends AppSource {
DirectAPKLink() { DirectAPKLink() {
name = tr('directAPKLink'); name = tr('directAPKLink');
additionalSourceAppSpecificSettingFormItems = html additionalSourceAppSpecificSettingFormItems = [
.additionalSourceAppSpecificSettingFormItems ...html.additionalSourceAppSpecificSettingFormItems
.where((element) => element .where((element) => element
.where((element) => element.key == 'requestHeader') .where((element) => element.key == 'requestHeader')
.isNotEmpty) .isNotEmpty)
.toList(); .toList(),
[
GeneratedFormDropdown(
'defaultPseudoVersioningMethod',
[
MapEntry('partialAPKHash', tr('partialAPKHash')),
MapEntry('ETag', 'ETag')
],
label: tr('defaultPseudoVersioningMethod'),
defaultValue: 'partialAPKHash')
]
];
excludeCommonSettingKeys = [ excludeCommonSettingKeys = [
'versionExtractionRegEx', 'versionExtractionRegEx',
'matchGroupToUse', 'matchGroupToUse',
@@ -57,9 +69,8 @@ class DirectAPKLink extends AppSource {
additionalSettingsNew[s] = additionalSettings[s]; additionalSettingsNew[s] = additionalSettings[s];
} }
} }
additionalSettingsNew['defaultPseudoVersioningMethod'] = 'partialAPKHash';
additionalSettingsNew['directAPKLink'] = true; additionalSettingsNew['directAPKLink'] = true;
additionalSettings['versionDetection'] = false; additionalSettingsNew['versionDetection'] = false;
return html.getLatestAPKDetails(standardUrl, additionalSettingsNew); return html.getLatestAPKDetails(standardUrl, additionalSettingsNew);
} }
} }

View File

@@ -263,7 +263,8 @@ class HTML extends AppSource {
'defaultPseudoVersioningMethod', 'defaultPseudoVersioningMethod',
[ [
MapEntry('partialAPKHash', tr('partialAPKHash')), MapEntry('partialAPKHash', tr('partialAPKHash')),
MapEntry('APKLinkHash', tr('APKLinkHash')) MapEntry('APKLinkHash', tr('APKLinkHash')),
MapEntry('ETag', 'ETag')
], ],
label: tr('defaultPseudoVersioningMethod'), label: tr('defaultPseudoVersioningMethod'),
defaultValue: 'partialAPKHash') defaultValue: 'partialAPKHash')
@@ -356,14 +357,24 @@ class HTML extends AppSource {
additionalSettings['versionExtractWholePage'] == true additionalSettings['versionExtractWholePage'] == true
? versionExtractionWholePageString ? versionExtractionWholePageString
: relDecoded); : relDecoded);
version ??= additionalSettings['defaultPseudoVersioningMethod'] == var apkReqHeaders =
'APKLinkHash' await getRequestHeaders(additionalSettings, forAPKDownload: true);
? rel.hashCode.toString() if (version == null &&
: (await checkPartialDownloadHashDynamic(rel, additionalSettings['defaultPseudoVersioningMethod'] == 'ETag') {
headers: await getRequestHeaders(additionalSettings, version = await checkETagHeader(rel,
forAPKDownload: true), headers: apkReqHeaders,
allowInsecure: additionalSettings['allowInsecure'] == true)) allowInsecure: additionalSettings['allowInsecure'] == true);
.toString(); if (version == null) {
throw NoVersionError();
}
}
version ??=
additionalSettings['defaultPseudoVersioningMethod'] == 'APKLinkHash'
? rel.hashCode.toString()
: (await checkPartialDownloadHashDynamic(rel,
headers: apkReqHeaders,
allowInsecure: additionalSettings['allowInsecure'] == true))
.toString();
return APKDetails( return APKDetails(
version, version,
[rel].map((e) { [rel].map((e) {

View File

@@ -220,6 +220,19 @@ Future<String> checkPartialDownloadHash(String url, int bytesToGrab,
return hashListOfLists(bytes); return hashListOfLists(bytes);
} }
Future<String?> checkETagHeader(String url,
{Map<String, String>? headers, bool allowInsecure = false}) async {
// Send the initial request but cancel it as soon as you have the headers
var reqHeaders = headers ?? {};
var req = Request('GET', Uri.parse(url));
req.headers.addAll(reqHeaders);
var client = IOClient(createHttpClient(allowInsecure));
StreamedResponse response = await client.send(req);
var resHeaders = response.headers;
client.close();
return resHeaders[HttpHeaders.etagHeader];
}
Future<File> downloadFile(String url, String fileName, bool fileNameHasExt, Future<File> downloadFile(String url, String fileName, bool fileNameHasExt,
Function? onProgress, String destDir, Function? onProgress, String destDir,
{bool useExisting = true, {bool useExisting = true,