diff --git a/lib/app_sources/apkcombo.dart b/lib/app_sources/apkcombo.dart index 3a1ae49..d21d826 100644 --- a/lib/app_sources/apkcombo.dart +++ b/lib/app_sources/apkcombo.dart @@ -45,7 +45,7 @@ class APKCombo extends AppSource { if (res.statusCode != 200) { throw getObtainiumHttpError(res); } - var html = parse(res.body); + var html = parse(res.data); return html .querySelectorAll('#variants-tab > div > ul > li') .map((e) { @@ -96,7 +96,7 @@ class APKCombo extends AppSource { if (preres.statusCode != 200) { throw getObtainiumHttpError(preres); } - var res = parse(preres.body); + var res = parse(preres.data); String? version = res.querySelector('div.version')?.text.trim(); if (version == null) { throw NoVersionError(); diff --git a/lib/app_sources/apkmirror.dart b/lib/app_sources/apkmirror.dart index 236a300..cd742ed 100644 --- a/lib/app_sources/apkmirror.dart +++ b/lib/app_sources/apkmirror.dart @@ -1,8 +1,8 @@ import 'dart:io'; +import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:html/parser.dart'; -import 'package:http/http.dart'; import 'package:obtainium/components/generated_form.dart'; import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/providers/source_provider.dart'; @@ -62,7 +62,7 @@ class APKMirror extends AppSource { : null; Response res = await sourceRequest('$standardUrl/feed', additionalSettings); if (res.statusCode == 200) { - var items = parse(res.body).querySelectorAll('item'); + var items = parse(res.data).querySelectorAll('item'); dynamic targetRelease; for (int i = 0; i < items.length; i++) { if (!fallbackToOlderReleases && i > 0) break; diff --git a/lib/app_sources/apkpure.dart b/lib/app_sources/apkpure.dart index 715a8d3..4ba9395 100644 --- a/lib/app_sources/apkpure.dart +++ b/lib/app_sources/apkpure.dart @@ -61,8 +61,8 @@ class APKPure extends AppSource { var res = await sourceRequest('$standardUrl/download', additionalSettings); var resChangelog = await sourceRequest(standardUrl, additionalSettings); if (res.statusCode == 200 && resChangelog.statusCode == 200) { - var html = parse(res.body); - var htmlChangelog = parse(resChangelog.body); + var html = parse(res.data); + var htmlChangelog = parse(resChangelog.data); String? version = html.querySelector('span.info-sdk span')?.text.trim(); if (version == null) { throw NoVersionError(); diff --git a/lib/app_sources/aptoide.dart b/lib/app_sources/aptoide.dart index 9daf57b..24f403f 100644 --- a/lib/app_sources/aptoide.dart +++ b/lib/app_sources/aptoide.dart @@ -38,10 +38,10 @@ class Aptoide extends AppSource { if (res.statusCode != 200) { throw getObtainiumHttpError(res); } - var idMatch = RegExp('"app":{"id":[0-9]+').firstMatch(res.body); + var idMatch = RegExp('"app":{"id":[0-9]+').firstMatch(res.data); String? id; if (idMatch != null) { - id = res.body.substring(idMatch.start + 12, idMatch.end); + id = res.data.substring(idMatch.start + 12, idMatch.end); } else { throw NoReleasesError(); } @@ -50,7 +50,7 @@ class Aptoide extends AppSource { if (res2.statusCode != 200) { throw getObtainiumHttpError(res); } - return jsonDecode(res2.body)?['nodes']?['meta']?['data']; + return jsonDecode(res2.data)?['nodes']?['meta']?['data']; } @override diff --git a/lib/app_sources/fdroid.dart b/lib/app_sources/fdroid.dart index ac08801..2839c79 100644 --- a/lib/app_sources/fdroid.dart +++ b/lib/app_sources/fdroid.dart @@ -1,8 +1,8 @@ import 'dart:convert'; +import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:html/parser.dart'; -import 'package:http/http.dart'; import 'package:obtainium/app_sources/github.dart'; import 'package:obtainium/app_sources/gitlab.dart'; import 'package:obtainium/components/generated_form.dart'; @@ -82,7 +82,7 @@ class FDroid extends AppSource { var res = await sourceRequest( 'https://gitlab.com/fdroid/fdroiddata/-/raw/master/metadata/$appId.yml', additionalSettings); - var lines = res.body.split('\n'); + var lines = res.data.split('\n'); var authorLines = lines.where((l) => l.startsWith('AuthorName: ')); if (authorLines.isNotEmpty) { details.names.author = @@ -112,7 +112,7 @@ class FDroid extends AppSource { details.changeLog = (await sourceRequest( details.changeLog!.replaceFirst('/blob/', '/raw/'), additionalSettings)) - .body; + .data; } } } catch (e) { @@ -132,7 +132,7 @@ class FDroid extends AppSource { 'https://search.${hosts[0]}/?q=${Uri.encodeQueryComponent(query)}', {}); if (res.statusCode == 200) { Map> urlsWithDescriptions = {}; - parse(res.body).querySelectorAll('.package-header').forEach((e) { + parse(res.data).querySelectorAll('.package-header').forEach((e) { String? url = e.attributes['href']; if (url != null) { try { @@ -172,7 +172,7 @@ class FDroid extends AppSource { ? additionalSettings['apkFilterRegEx'] : null; if (res.statusCode == 200) { - var response = jsonDecode(res.body); + var response = jsonDecode(res.data); List releases = response['packages'] ?? []; if (apkFilterRegEx != null) { releases = releases.where((rel) { diff --git a/lib/app_sources/fdroidrepo.dart b/lib/app_sources/fdroidrepo.dart index 41e92df..e0f6a24 100644 --- a/lib/app_sources/fdroidrepo.dart +++ b/lib/app_sources/fdroidrepo.dart @@ -1,6 +1,6 @@ +import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:html/parser.dart'; -import 'package:http/http.dart'; import 'package:obtainium/components/generated_form.dart'; import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/providers/source_provider.dart'; @@ -61,9 +61,10 @@ class FDroidRepo extends AppSource { throw NoReleasesError(); } url = removeQueryParamsFromUrl(standardizeUrl(url)); - var res = await sourceRequestWithURLVariants(url, {}); + var ress = await sourceRequestWithURLVariants(url, {}); + var res = ress.value; if (res.statusCode == 200) { - var body = parse(res.body); + var body = parse(res.data); Map> results = {}; body.querySelectorAll('application').toList().forEach((app) { String appId = app.attributes['id']!; @@ -74,7 +75,7 @@ class FDroidRepo extends AppSource { appName.contains(query) || appDesc.contains(query)) { results[ - '${res.request!.url.toString().split('/').reversed.toList().sublist(1).reversed.join('/')}?appId=$appId'] = [ + '${ress.value.toString().split('/').reversed.toList().sublist(1).reversed.join('/')}?appId=$appId'] = [ appName, appDesc ]; @@ -107,24 +108,24 @@ class FDroidRepo extends AppSource { return app; } - Future sourceRequestWithURLVariants( + Future> sourceRequestWithURLVariants( String url, Map additionalSettings, ) async { - var res = await sourceRequest( - '$url${url.endsWith('/index.xml') ? '' : '/index.xml'}', - additionalSettings); + var finalUrl = '$url${url.endsWith('/index.xml') ? '' : '/index.xml'}'; + var res = await sourceRequest(finalUrl, additionalSettings); if (res.statusCode != 200) { var base = url.endsWith('/index.xml') ? url.split('/').reversed.toList().sublist(1).reversed.join('/') : url; - res = await sourceRequest('$base/repo/index.xml', additionalSettings); + finalUrl = '$base/repo/index.xml'; + res = await sourceRequest(finalUrl, additionalSettings); if (res.statusCode != 200) { - res = await sourceRequest( - '$base/fdroid/repo/index.xml', additionalSettings); + finalUrl = '$base/fdroid/repo/index.xml'; + res = await sourceRequest(finalUrl, additionalSettings); } } - return res; + return MapEntry(finalUrl, res); } @override @@ -142,10 +143,11 @@ class FDroidRepo extends AppSource { if (appIdOrName == null) { throw NoReleasesError(); } - var res = + var ress = await sourceRequestWithURLVariants(standardUrl, additionalSettings); + var res = ress.value; if (res.statusCode == 200) { - var body = parse(res.body); + var body = parse(res.data); var foundApps = body.querySelectorAll('application').where((element) { return element.attributes['id'] == appIdOrName; }).toList(); @@ -193,7 +195,7 @@ class FDroidRepo extends AppSource { } List apkUrls = latestVersionReleases .map((e) => - '${res.request!.url.toString().split('/').reversed.toList().sublist(1).reversed.join('/')}/${e.querySelector('apkname')!.innerHtml}') + '${ress.value.toString().split('/').reversed.toList().sublist(1).reversed.join('/')}/${e.querySelector('apkname')!.innerHtml}') .toList(); return APKDetails(latestVersion, getApkUrlsFromUrls(apkUrls), AppNames(authorName, appName), diff --git a/lib/app_sources/github.dart b/lib/app_sources/github.dart index f6e2615..a3130a1 100644 --- a/lib/app_sources/github.dart +++ b/lib/app_sources/github.dart @@ -1,8 +1,8 @@ import 'dart:convert'; import 'dart:io'; +import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:http/http.dart'; import 'package:obtainium/app_sources/html.dart'; import 'package:obtainium/components/generated_form.dart'; import 'package:obtainium/custom_errors.dart'; @@ -112,12 +112,12 @@ class GitHub extends AppSource { ]; for (var path in possibleBuildGradleLocations) { try { - var res = await sourceRequest( - '${await convertStandardUrlToAPIUrl(standardUrl, additionalSettings)}/contents/$path', - additionalSettings); + var finalUrl = + '${await convertStandardUrlToAPIUrl(standardUrl, additionalSettings)}/contents/$path'; + var res = await sourceRequest(finalUrl, additionalSettings); if (res.statusCode == 200) { try { - var body = jsonDecode(res.body); + var body = jsonDecode(res.data); var trimmedLines = utf8 .decode(base64 .decode(body['content'].toString().split('\n').join(''))) @@ -143,7 +143,7 @@ class GitHub extends AppSource { } } catch (err) { LogsProvider().add( - 'Error parsing build.gradle from ${res.request!.url.toString()}: ${err.toString()}'); + 'Error parsing build.gradle from $finalUrl: ${err.toString()}'); } } } catch (err) { @@ -256,11 +256,11 @@ class GitHub extends AppSource { } throw getObtainiumHttpError(res); } - latestRelease = jsonDecode(res.body); + latestRelease = jsonDecode(res.data); } Response res = await sourceRequest(requestUrl, additionalSettings); if (res.statusCode == 200) { - var releases = jsonDecode(res.body) as List; + var releases = jsonDecode(res.data) as List; if (latestRelease != null) { var latestTag = latestRelease['tag_name'] ?? latestRelease['name']; if (releases @@ -466,7 +466,7 @@ class GitHub extends AppSource { ? int.parse(querySettings['minStarCount']) : 0; Map> urlsWithDescriptions = {}; - for (var e in (jsonDecode(res.body)[rootProp] as List)) { + for (var e in (jsonDecode(res.data)[rootProp] as List)) { if ((e['stargazers_count'] ?? e['stars_count'] ?? 0) >= minStarCount) { urlsWithDescriptions.addAll({ e['html_url'] as String: [ @@ -500,11 +500,13 @@ class GitHub extends AppSource { } rateLimitErrorCheck(Response res) { - if (res.headers['x-ratelimit-remaining'] == '0') { + String? rateLimitHeader; + if (res.headers.map['x-ratelimit-remaining']?.isNotEmpty == true) { + rateLimitHeader = res.headers.map['x-ratelimit-remaining']![0]; + } + if (rateLimitHeader == '0') { throw RateLimitError( - (int.parse(res.headers['x-ratelimit-reset'] ?? '1800000000') / - 60000000) - .round()); + (int.parse(rateLimitHeader ?? '1800000000') / 60000000).round()); } } } diff --git a/lib/app_sources/gitlab.dart b/lib/app_sources/gitlab.dart index 42a00a0..9845515 100644 --- a/lib/app_sources/gitlab.dart +++ b/lib/app_sources/gitlab.dart @@ -1,8 +1,8 @@ import 'dart:convert'; import 'dart:io'; +import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; -import 'package:http/http.dart'; import 'package:obtainium/app_sources/github.dart'; import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/providers/settings_provider.dart'; @@ -81,7 +81,7 @@ class GitLab extends AppSource { if (res.statusCode != 200) { throw getObtainiumHttpError(res); } - var json = jsonDecode(res.body) as List; + var json = jsonDecode(res.data) as List; Map> results = {}; for (var element in json) { results['https://${hosts[0]}/${element['path_with_namespace']}'] = [ @@ -131,7 +131,7 @@ class GitLab extends AppSource { // Extract .apk details from received data Iterable apkDetailsList = []; - var json = jsonDecode(res.body) as List; + var json = jsonDecode(res.data) as List; apkDetailsList = json.map((e) { var apkUrlsFromAssets = (e['assets']?['links'] as List? ?? []) .map((e) { @@ -152,9 +152,8 @@ class GitLab extends AppSource { var apkUrlsSet = apkUrlsFromAssets.toSet(); apkUrlsSet.addAll(uploadedAPKsFromDescription); var releaseDateString = e['released_at'] ?? e['created_at']; - DateTime? releaseDate = releaseDateString != null - ? DateTime.parse(releaseDateString) - : null; + DateTime? releaseDate = + releaseDateString != null ? DateTime.parse(releaseDateString) : null; return APKDetails( e['tag_name'] ?? e['name'], getApkUrlsFromUrls(apkUrlsSet.toList()), diff --git a/lib/app_sources/html.dart b/lib/app_sources/html.dart index 51f6143..e37bbf9 100644 --- a/lib/app_sources/html.dart +++ b/lib/app_sources/html.dart @@ -1,6 +1,6 @@ +import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:html/parser.dart'; -import 'package:http/http.dart'; import 'package:obtainium/components/generated_form.dart'; import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/providers/apps_provider.dart'; @@ -213,11 +213,11 @@ class HTML extends AppSource { // Given an HTTP response, grab some links according to the common additional settings // (those that apply to intermediate and final steps) Future>> grabLinksCommon( - Response res, Map additionalSettings) async { + Response res, Uri url, Map additionalSettings) async { if (res.statusCode != 200) { throw getObtainiumHttpError(res); } - var html = parse(res.body); + var html = parse(res.data); List> allLinks = html .querySelectorAll('a') .map((element) => MapEntry( @@ -226,13 +226,12 @@ class HTML extends AppSource { ? element.text : (element.attributes['href'] ?? '').split('/').last)) .where((element) => element.key.isNotEmpty) - .map((e) => - MapEntry(ensureAbsoluteUrl(e.key, res.request!.url), e.value)) + .map((e) => MapEntry(ensureAbsoluteUrl(e.key, url), e.value)) .toList(); if (allLinks.isEmpty) { allLinks = RegExp( r'(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?') - .allMatches(res.body) + .allMatches(res.data) .map((match) => MapEntry(match.group(0)!, match.group(0)?.split('/').last ?? '')) .toList(); @@ -285,6 +284,7 @@ class HTML extends AppSource { for (int i = 0; i < (additionalSettings['intermediateLink'].length); i++) { var intLinks = await grabLinksCommon( await sourceRequest(currentUrl, additionalSettings), + Uri.parse(currentUrl), additionalSettings['intermediateLink'][i]); if (intLinks.isEmpty) { throw NoReleasesError(); @@ -298,8 +298,9 @@ class HTML extends AppSource { if (additionalSettings['directAPKLink'] != true) { Response res = await sourceRequest(currentUrl, additionalSettings); versionExtractionWholePageString = - res.body.split('\r\n').join('\n').split('\n').join('\\n'); - links = await grabLinksCommon(res, additionalSettings); + res.data.split('\r\n').join('\n').split('\n').join('\\n'); + links = + await grabLinksCommon(res, Uri.parse(currentUrl), additionalSettings); links = filterApks(links, additionalSettings['apkFilterRegEx'], additionalSettings['invertAPKFilter']); if (links.isEmpty) { diff --git a/lib/app_sources/huaweiappgallery.dart b/lib/app_sources/huaweiappgallery.dart index 2c4d480..750c101 100644 --- a/lib/app_sources/huaweiappgallery.dart +++ b/lib/app_sources/huaweiappgallery.dart @@ -1,5 +1,5 @@ +import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:http/http.dart'; import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/providers/source_provider.dart'; @@ -57,8 +57,8 @@ class HuaweiAppGallery extends AppSource { {Map additionalSettings = const {}}) async { String dlUrl = getDlUrl(standardUrl); Response res = await requestAppdlRedirect(dlUrl, additionalSettings); - return res.headers['location'] != null - ? appIdFromRedirectDlUrl(res.headers['location']!) + return res.headers.map['location']?.isNotEmpty == true + ? appIdFromRedirectDlUrl(res.headers.map['location']![0]) : null; } @@ -72,9 +72,12 @@ class HuaweiAppGallery extends AppSource { if (res.headers['location'] == null) { throw NoReleasesError(); } - String appId = appIdFromRedirectDlUrl(res.headers['location']!); - var relDateStr = - res.headers['location']?.split('?')[0].split('.').reversed.toList()[1]; + String appId = appIdFromRedirectDlUrl(res.headers.map['location']![0]); + var relDateStr = res.headers.map['location']?[0] + .split('?')[0] + .split('.') + .reversed + .toList()[1]; var relDateStrAdj = relDateStr?.split(''); var tempLen = relDateStrAdj?.length ?? 0; var i = 2; diff --git a/lib/app_sources/jenkins.dart b/lib/app_sources/jenkins.dart index 39a4f79..60ecc6b 100644 --- a/lib/app_sources/jenkins.dart +++ b/lib/app_sources/jenkins.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:http/http.dart'; +import 'package:dio/dio.dart'; import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/providers/source_provider.dart'; @@ -33,7 +33,7 @@ class Jenkins extends AppSource { Response res = await sourceRequest( '$standardUrl/lastSuccessfulBuild/api/json', additionalSettings); if (res.statusCode == 200) { - var json = jsonDecode(res.body); + var json = jsonDecode(res.data); var releaseDate = json['timestamp'] == null ? null : DateTime.fromMillisecondsSinceEpoch(json['timestamp'] as int); diff --git a/lib/app_sources/mullvad.dart b/lib/app_sources/mullvad.dart index 3b0439d..04da243 100644 --- a/lib/app_sources/mullvad.dart +++ b/lib/app_sources/mullvad.dart @@ -1,5 +1,5 @@ +import 'package:dio/dio.dart'; import 'package:html/parser.dart'; -import 'package:http/http.dart'; import 'package:obtainium/app_sources/github.dart'; import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/providers/source_provider.dart'; @@ -33,7 +33,7 @@ class Mullvad extends AppSource { Response res = await sourceRequest( '$standardUrl/en/download/android', additionalSettings); if (res.statusCode == 200) { - var versions = parse(res.body) + var versions = parse(res.data) .querySelectorAll('p') .map((e) => e.innerHtml) .where((p) => p.contains('Latest version: ')) diff --git a/lib/app_sources/neutroncode.dart b/lib/app_sources/neutroncode.dart index 0429718..0bf7480 100644 --- a/lib/app_sources/neutroncode.dart +++ b/lib/app_sources/neutroncode.dart @@ -1,5 +1,5 @@ +import 'package:dio/dio.dart'; import 'package:html/parser.dart'; -import 'package:http/http.dart'; import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/providers/source_provider.dart'; @@ -83,7 +83,7 @@ class NeutronCode extends AppSource { ) async { Response res = await sourceRequest(standardUrl, additionalSettings); if (res.statusCode == 200) { - var http = parse(res.body); + var http = parse(res.data); var name = http.querySelector('.pd-title')?.innerHtml; var filename = http.querySelector('.pd-filename .pd-float')?.innerHtml; if (filename == null) { diff --git a/lib/app_sources/signal.dart b/lib/app_sources/signal.dart index 8e91939..13b2958 100644 --- a/lib/app_sources/signal.dart +++ b/lib/app_sources/signal.dart @@ -1,5 +1,5 @@ import 'dart:convert'; -import 'package:http/http.dart'; +import 'package:dio/dio.dart'; import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/providers/source_provider.dart'; @@ -21,7 +21,7 @@ class Signal extends AppSource { Response res = await sourceRequest( 'https://updates.${hosts[0]}/android/latest.json', additionalSettings); if (res.statusCode == 200) { - var json = jsonDecode(res.body); + var json = jsonDecode(res.data); String? apkUrl = json['url']; List apkUrls = apkUrl == null ? [] : [apkUrl]; String? version = json['versionName']; diff --git a/lib/app_sources/sourceforge.dart b/lib/app_sources/sourceforge.dart index caaeea8..2f8b832 100644 --- a/lib/app_sources/sourceforge.dart +++ b/lib/app_sources/sourceforge.dart @@ -1,5 +1,5 @@ +import 'package:dio/dio.dart'; import 'package:html/parser.dart'; -import 'package:http/http.dart'; import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/providers/source_provider.dart'; @@ -49,7 +49,7 @@ class SourceForge extends AppSource { '${standardUri.origin}/${standardUri.pathSegments.sublist(0, 2).join('/')}/rss?path=/', additionalSettings); if (res.statusCode == 200) { - var parsedHtml = parse(res.body); + var parsedHtml = parse(res.data); var allDownloadLinks = parsedHtml .querySelectorAll('guid') .map((e) => e.innerHtml) diff --git a/lib/app_sources/sourcehut.dart b/lib/app_sources/sourcehut.dart index e28294c..e137c26 100644 --- a/lib/app_sources/sourcehut.dart +++ b/lib/app_sources/sourcehut.dart @@ -1,5 +1,5 @@ +import 'package:dio/dio.dart'; import 'package:html/parser.dart'; -import 'package:http/http.dart'; import 'package:obtainium/app_sources/html.dart'; import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/providers/source_provider.dart'; @@ -55,7 +55,7 @@ class SourceHut extends AppSource { Response res = await sourceRequest('$standardUrl/refs/rss.xml', additionalSettings); if (res.statusCode == 200) { - var parsedHtml = parse(res.body); + var parsedHtml = parse(res.data); List apkDetailsList = []; int ind = 0; @@ -85,7 +85,7 @@ class SourceHut extends AppSource { var res2 = await sourceRequest(releasePage, additionalSettings); List> apkUrls = []; if (res2.statusCode == 200) { - apkUrls = getApkUrlsFromUrls(parse(res2.body) + apkUrls = getApkUrlsFromUrls(parse(res2.data) .querySelectorAll('a') .map((e) => e.attributes['href'] ?? '') .where((e) => e.toLowerCase().endsWith('.apk')) diff --git a/lib/app_sources/steammobile.dart b/lib/app_sources/steammobile.dart index cbb28ef..3bbc366 100644 --- a/lib/app_sources/steammobile.dart +++ b/lib/app_sources/steammobile.dart @@ -1,6 +1,6 @@ +import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:html/parser.dart'; -import 'package:http/http.dart'; import 'package:obtainium/components/generated_form.dart'; import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/providers/source_provider.dart'; @@ -38,7 +38,7 @@ class SteamMobile extends AppSource { } String apkInURLRegexPattern = '/$apkNamePrefix-([0-9]+\\.)*[0-9]+\\.apk\$'; - var links = parse(res.body) + var links = parse(res.data) .querySelectorAll('a') .map((e) => e.attributes['href'] ?? '') .where((e) => RegExp('https://.*$apkInURLRegexPattern').hasMatch(e)) diff --git a/lib/app_sources/telegramapp.dart b/lib/app_sources/telegramapp.dart index 651d00f..4c909cb 100644 --- a/lib/app_sources/telegramapp.dart +++ b/lib/app_sources/telegramapp.dart @@ -1,6 +1,6 @@ +import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:html/parser.dart'; -import 'package:http/http.dart'; import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/providers/source_provider.dart'; @@ -23,7 +23,7 @@ class TelegramApp extends AppSource { Response res = await sourceRequest('https://t.me/s/TAndroidAPK', additionalSettings); if (res.statusCode == 200) { - var http = parse(res.body); + var http = parse(res.data); var messages = http.querySelectorAll('.tgme_widget_message_text.js-message_text'); var version = messages.isNotEmpty diff --git a/lib/app_sources/uptodown.dart b/lib/app_sources/uptodown.dart index 00de36c..acf7411 100644 --- a/lib/app_sources/uptodown.dart +++ b/lib/app_sources/uptodown.dart @@ -37,7 +37,7 @@ class Uptodown extends AppSource { if (res.statusCode != 200) { throw getObtainiumHttpError(res); } - var html = parse(res.body); + var html = parse(res.data); String? version = html.querySelector('div.version')?.innerHtml; String? apkUrl = '${standardUrl.split('/').reversed.toList().sublist(1).reversed.join('/')}/post-download'; @@ -94,7 +94,7 @@ class Uptodown extends AppSource { if (res.statusCode != 200) { throw getObtainiumHttpError(res); } - var html = parse(res.body); + var html = parse(res.data); var finalUrlKey = html.querySelector('.post-download')?.attributes['data-url']; if (finalUrlKey == null) { diff --git a/lib/app_sources/vlc.dart b/lib/app_sources/vlc.dart index 7d16870..52f1e6a 100644 --- a/lib/app_sources/vlc.dart +++ b/lib/app_sources/vlc.dart @@ -1,7 +1,8 @@ +import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:html/parser.dart'; -import 'package:http/http.dart'; import 'package:obtainium/custom_errors.dart'; +import 'package:obtainium/main.dart'; import 'package:obtainium/providers/source_provider.dart'; class VLC extends AppSource { @@ -29,7 +30,7 @@ class VLC extends AppSource { String standardUrl, Map additionalSettings) async { Response res = await sourceRequest(dwUrlBase, additionalSettings); if (res.statusCode == 200) { - var dwLinks = parse(res.body) + var dwLinks = parse(res.data) .querySelectorAll('a') .where((element) => element.attributes['href'] != 'last/') .map((e) => e.attributes['href']?.split('/')[0]) @@ -49,11 +50,11 @@ class VLC extends AppSource { String standardUrl, Map additionalSettings, ) async { - Response res = await get( - Uri.parse('https://www.videolan.org/vlc/download-android.html')); + Response res = + await dio.get('https://www.videolan.org/vlc/download-android.html'); if (res.statusCode == 200) { var dwUrlBase = 'get.videolan.org/vlc-android'; - var dwLinks = parse(res.body) + var dwLinks = parse(res.data) .querySelectorAll('a') .where((element) => element.attributes['href']?.contains(dwUrlBase) ?? false) @@ -84,14 +85,14 @@ class VLC extends AppSource { Response res = await sourceRequest(apkUrl, additionalSettings); if (res.statusCode == 200) { String? apkUrl = - parse(res.body).querySelector('#alt_link')?.attributes['href']; + parse(res.data).querySelector('#alt_link')?.attributes['href']; if (apkUrl == null) { throw NoAPKError(); } return apkUrl; } else if (res.statusCode == 500 && - res.body.toLowerCase().indexOf('mirror') > 0) { - var html = parse(res.body); + res.data.toLowerCase().indexOf('mirror') > 0) { + var html = parse(res.data); var err = ''; html.body?.nodes.forEach((element) { if (element.text != null) { diff --git a/lib/app_sources/whatsapp.dart b/lib/app_sources/whatsapp.dart index 94b26d1..be45781 100644 --- a/lib/app_sources/whatsapp.dart +++ b/lib/app_sources/whatsapp.dart @@ -1,5 +1,5 @@ +import 'package:dio/dio.dart'; import 'package:html/parser.dart'; -import 'package:http/http.dart'; import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/providers/source_provider.dart'; @@ -20,7 +20,7 @@ class WhatsApp extends AppSource { Response res = await sourceRequest('$standardUrl/android', additionalSettings); if (res.statusCode == 200) { - var targetLinks = parse(res.body) + var targetLinks = parse(res.data) .querySelectorAll('a') .map((e) => e.attributes['href'] ?? '') .where((e) => e.isNotEmpty) diff --git a/lib/main.dart b/lib/main.dart index 32f2c0b..fef3f00 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -18,6 +18,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/src/easy_localization_controller.dart'; // ignore: implementation_imports import 'package:easy_localization/src/localization.dart'; +import 'package:dio/dio.dart'; List> supportedLocales = const [ MapEntry(Locale('en'), 'English'), @@ -46,6 +47,9 @@ var fdroid = false; final globalNavigatorKey = GlobalKey(); +final dio = Dio(BaseOptions( + responseType: ResponseType.plain, receiveDataWhenStatusError: true)); + Future loadTranslations() async { // See easy_localization/issues/210 await EasyLocalizationController.initEasyLocation(); diff --git a/lib/mass_app_sources/githubstars.dart b/lib/mass_app_sources/githubstars.dart index 4a7ac61..554e32a 100644 --- a/lib/mass_app_sources/githubstars.dart +++ b/lib/mass_app_sources/githubstars.dart @@ -1,9 +1,10 @@ import 'dart:convert'; +import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:http/http.dart'; import 'package:obtainium/app_sources/github.dart'; import 'package:obtainium/custom_errors.dart'; +import 'package:obtainium/main.dart'; import 'package:obtainium/providers/source_provider.dart'; class GitHubStars implements MassAppUrlSource { @@ -15,13 +16,12 @@ class GitHubStars implements MassAppUrlSource { Future>> getOnePageOfUserStarredUrlsWithDescriptions( String username, int page) async { - Response res = await get( - Uri.parse( - 'https://api.github.com/users/$username/starred?per_page=100&page=$page'), - headers: await GitHub().getRequestHeaders({})); + Response res = await dio.get( + 'https://api.github.com/users/$username/starred?per_page=100&page=$page', + options: Options(headers: await GitHub().getRequestHeaders({}))); if (res.statusCode == 200) { Map> urlsWithDescriptions = {}; - for (var e in (jsonDecode(res.body) as List)) { + for (var e in (jsonDecode(res.data) as List)) { urlsWithDescriptions.addAll({ e['html_url'] as String: [ e['full_name'] as String, diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index 4b1d0e6..d5d3afc 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -5,14 +5,15 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:math'; -import 'package:http/http.dart' as http; import 'package:crypto/crypto.dart'; +import 'package:http/http.dart' as http; import 'package:android_intent_plus/flag.dart'; import 'package:android_package_installer/android_package_installer.dart'; import 'package:android_package_manager/android_package_manager.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:device_info_plus/device_info_plus.dart'; +import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -28,7 +29,6 @@ import 'package:provider/provider.dart'; import 'package:path_provider/path_provider.dart'; import 'package:flutter_fgbg/flutter_fgbg.dart'; import 'package:obtainium/providers/source_provider.dart'; -import 'package:http/http.dart'; import 'package:android_intent_plus/android_intent.dart'; import 'package:flutter_archive/flutter_archive.dart'; import 'package:shared_storage/shared_storage.dart' as saf; @@ -146,10 +146,10 @@ Future downloadFileWithRetry( Map? headers, int retries = 3}) async { try { - return await downloadFile(url, fileNameNoExt, onProgress, destDir, + return await downloadApk(url, fileNameNoExt, onProgress, destDir, useExisting: useExisting, headers: headers); } catch (e) { - if (retries > 0 && e is ClientException) { + if (retries > 0 && e is DioException) { await Future.delayed(const Duration(seconds: 5)); return await downloadFileWithRetry( url, fileNameNoExt, onProgress, destDir, @@ -183,9 +183,162 @@ Future checkPartialDownloadHashDynamic(String url, throw NoVersionError(); } +Future downloadApk( + String url, String fileNameNoExt, Function? onProgress, String destDir, + {bool useExisting = true, Map? headers}) async { + var resHeaders = await getHeaders(url, headers: headers); + + String ext = resHeaders['content-disposition']?.split('.').last ?? 'apk'; + if (ext.endsWith('"') || ext.endsWith("other")) { + ext = ext.substring(0, ext.length - 1); + } + if (url.toLowerCase().endsWith('.apk') && ext != 'apk') { + ext = 'apk'; + } + File file = File('$destDir/$fileNameNoExt.$ext'); + + final contentLength = await getContentLengthIfRangeSupported(resHeaders); + + if (useExisting && file.existsSync()) { + var length = file.lengthSync(); + if (contentLength == null) { + return file; + } else { + if (length == contentLength) { + return file; + } + if (length > contentLength) { + useExisting = false; + } + } + } + + double progress = -1; + + try { + if (contentLength == null) { + Response response = await dio.download( + url, + file.path, + options: Options(headers: headers), + onReceiveProgress: (count, total) { + progress = (total > 0 ? count / total * 100 : 30); + if (onProgress != null) { + onProgress(progress); + } + }, + ); + if ((response.statusCode ?? 200) < 200 || + (response.statusCode ?? 200) > 299) { + throw response.statusMessage ?? tr('unexpectedError'); + } + } else { + var targetFileLength = + useExisting && file.existsSync() ? file.lengthSync() : null; + int bufferSize = 1024 * 1024; // 1 Megabyte + final sink = file.openWrite( + mode: useExisting ? FileMode.writeOnlyAppend : FileMode.writeOnly, + ); + int rangeStart = targetFileLength ?? 0; + int rangeEnd = min( + rangeStart + bufferSize - 1, + contentLength - 1, + ); + if (onProgress != null) { + progress = ((rangeStart / contentLength) * 100); + onProgress(progress); + } + while (true) { + var headersCurrent = headers ?? {}; + headersCurrent['range'] = 'bytes=$rangeStart-$rangeEnd'; + Response response = await dio.get( + url, + onReceiveProgress: (count, total) { + if (onProgress != null) { + final newProgress = + (((rangeStart + count) / contentLength) * 100); + if (newProgress != progress) { + progress = newProgress; + onProgress(progress); + } + } + }, + options: Options( + headers: headersCurrent, + responseType: ResponseType.bytes, + ), + ); + + if ((response.statusCode ?? 200) < 200 || + (response.statusCode ?? 200) > 299) { + throw response.statusMessage ?? tr('unexpectedError'); + } + + final Uint8List data = response.data; + sink.add(data); + if (rangeEnd == contentLength - 1) { + break; + } + rangeStart = rangeEnd + 1; + rangeEnd = min( + rangeStart + bufferSize - 1, + contentLength - 1, + ); + } + await sink.flush(); + await sink.close(); + } + } finally { + if (onProgress != null) { + onProgress(null); + } + } + return file; +} + +Future getContentLengthIfRangeSupported( + Map headers) async { + try { + int? contentLength; + { + var contentLengthHeaderValue = headers['content-length']; + if (contentLengthHeaderValue?.isNotEmpty == true) { + contentLength = int.tryParse(contentLengthHeaderValue!); + } + } + bool rangeFeatureEnabled = false; + if (headers['accept-ranges']?.isNotEmpty == true) { + rangeFeatureEnabled = + headers['accept-ranges']!.trim().toLowerCase() == 'bytes'; + } + if (!rangeFeatureEnabled) { + contentLength = null; + } + return contentLength; + } catch (e) { + return null; + } +} + +Future> getHeaders(String url, + {Map? headers}) async { + var req = http.Request('GET', Uri.parse(url)); + if (headers != null) { + req.headers.addAll(headers); + } + var client = http.Client(); + var response = await client.send(req); + if (response.statusCode < 200 || response.statusCode > 299) { + throw ObtainiumError(response.reasonPhrase ?? tr('unexpectedError')); + } + var returnHeaders = response.headers; + client.close(); + return returnHeaders; +} + Future checkPartialDownloadHash(String url, int bytesToGrab, {Map? headers}) async { - var req = Request('GET', Uri.parse(url)); + var req = http.Request('GET', Uri.parse(url)); if (headers != null) { req.headers.addAll(headers); } @@ -199,58 +352,41 @@ Future checkPartialDownloadHash(String url, int bytesToGrab, return hashListOfLists(bytes); } -Future downloadFile( - String url, String fileNameNoExt, Function? onProgress, String destDir, - {bool useExisting = true, Map? headers}) async { - var req = Request('GET', Uri.parse(url)); - if (headers != null) { - req.headers.addAll(headers); - } - var client = http.Client(); - StreamedResponse response = await client.send(req); - String ext = - response.headers['content-disposition']?.split('.').last ?? 'apk'; - if (ext.endsWith('"') || ext.endsWith("other")) { - ext = ext.substring(0, ext.length - 1); - } - if (url.toLowerCase().endsWith('.apk') && ext != 'apk') { - ext = 'apk'; - } - File downloadedFile = File('$destDir/$fileNameNoExt.$ext'); - if (!(downloadedFile.existsSync() && useExisting)) { - File tempDownloadedFile = File('${downloadedFile.path}.part'); - if (tempDownloadedFile.existsSync()) { - tempDownloadedFile.deleteSync(recursive: true); - } - var length = response.contentLength; - var received = 0; - double? progress; - var sink = tempDownloadedFile.openWrite(); - await response.stream.map((s) { - received += s.length; - progress = (length != null ? received / length * 100 : 30); - if (onProgress != null) { - onProgress(progress); - } - return s; - }).pipe(sink); - await sink.close(); - progress = null; - if (onProgress != null) { - onProgress(progress); - } - if (response.statusCode != 200) { - tempDownloadedFile.deleteSync(recursive: true); - throw response.reasonPhrase ?? tr('unexpectedError'); - } - if (tempDownloadedFile.existsSync()) { - tempDownloadedFile.renameSync(downloadedFile.path); - } - } else { - client.close(); - } - return downloadedFile; -} +// Future downloadFile( +// String url, String fileNameNoExt, Function? onProgress, String destDir, +// {bool useExisting = true, Map? headers}) async { +// var resHead = await dio.head(url); +// String ext = +// resHead.headers.map['content-disposition']?[0].split('.').last ?? 'apk'; +// if (ext.endsWith('"') || ext.endsWith("other")) { +// ext = ext.substring(0, ext.length - 1); +// } +// if (url.toLowerCase().endsWith('.apk') && ext != 'apk') { +// ext = 'apk'; +// } +// File downloadedFile = File('$destDir/$fileNameNoExt.$ext'); +// if (!(downloadedFile.existsSync() && useExisting)) { +// double? progress; +// var response = await dio.download( +// url, +// downloadedFile.path, +// options: Options(headers: headers), +// onReceiveProgress: (count, total) { +// progress = (total > 0 ? count / total * 100 : 30); +// if (onProgress != null) { +// onProgress(progress); +// } +// }, +// ); +// if (onProgress != null) { +// onProgress(null); +// } +// if (response.statusCode != 200) { +// throw response.statusMessage ?? tr('unexpectedError'); +// } +// } +// return downloadedFile; +// } Future getInstalledInfo(String? packageName, {bool printErr = true}) async { @@ -1621,7 +1757,7 @@ Future bgUpdateCheck(String taskId, Map? params) async { // Next task interval is based on the error with the longest retry time int minRetryIntervalForThisApp = err is RateLimitError ? (err.remainingMinutes * 60) - : e is ClientException + : e is DioException ? (15 * 60) : (toCheckApp.value + 1); if (minRetryIntervalForThisApp > maxRetryWaitSeconds) { diff --git a/lib/providers/source_provider.dart b/lib/providers/source_provider.dart index d2c34bf..68c2854 100644 --- a/lib/providers/source_provider.dart +++ b/lib/providers/source_provider.dart @@ -4,9 +4,9 @@ import 'dart:convert'; import 'package:device_info_plus/device_info_plus.dart'; +import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:html/dom.dart'; -import 'package:http/http.dart'; import 'package:obtainium/app_sources/apkmirror.dart'; import 'package:obtainium/app_sources/apkpure.dart'; import 'package:obtainium/app_sources/aptoide.dart'; @@ -31,6 +31,7 @@ import 'package:obtainium/app_sources/vlc.dart'; import 'package:obtainium/app_sources/whatsapp.dart'; import 'package:obtainium/components/generated_form.dart'; import 'package:obtainium/custom_errors.dart'; +import 'package:obtainium/main.dart'; import 'package:obtainium/mass_app_sources/githubstars.dart'; import 'package:obtainium/providers/settings_provider.dart'; @@ -442,16 +443,9 @@ abstract class AppSource { String url, Map additionalSettings, {bool followRedirects = true}) async { var requestHeaders = await getRequestHeaders(additionalSettings); - if (requestHeaders != null || followRedirects == false) { - var req = Request('GET', Uri.parse(url)); - req.followRedirects = followRedirects; - if (requestHeaders != null) { - req.headers.addAll(requestHeaders); - } - return Response.fromStream(await Client().send(req)); - } else { - return get(Uri.parse(url)); - } + return await dio.get(url, + options: + Options(headers: requestHeaders, followRedirects: followRedirects)); } String sourceSpecificStandardizeURL(String url) { @@ -618,10 +612,10 @@ abstract class AppSource { } ObtainiumError getObtainiumHttpError(Response res) { - return ObtainiumError((res.reasonPhrase != null && - res.reasonPhrase != null && - res.reasonPhrase!.isNotEmpty) - ? res.reasonPhrase! + return ObtainiumError((res.statusMessage != null && + res.statusMessage != null && + res.statusMessage!.isNotEmpty) + ? res.statusMessage! : tr('errorWithHttpStatusCode', args: [res.statusCode.toString()])); } diff --git a/pubspec.lock b/pubspec.lock index fa08f4a..20c7d99 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -202,6 +202,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + dio: + dependency: "direct main" + description: + name: dio + sha256: "49af28382aefc53562459104f64d16b9dfd1e8ef68c862d5af436cc8356ce5a8" + url: "https://pub.dev" + source: hosted + version: "5.4.1" dynamic_color: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 1a44b56..0a03fc5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -68,6 +68,7 @@ dependencies: crypto: ^3.0.3 app_links: ^3.5.0 background_fetch: ^1.2.1 + dio: ^5.4.1 dev_dependencies: flutter_test: