Initial work on separate download library
This commit is contained in:
parent
1573952fbb
commit
3d479b29e5
|
@ -149,6 +149,7 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(":download")
|
||||
implementation 'androidx.appcompat:appcompat:1.3.0'
|
||||
implementation 'androidx.preference:preference:1.1.1'
|
||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||
|
|
|
@ -28,7 +28,7 @@ public class HttpDownloaderTest {
|
|||
ArrayList<String> tempUrls = new ArrayList<>(Arrays.asList(
|
||||
"https://f-droid.org/repo/index-v1.jar",
|
||||
// sites that use SNI for HTTPS
|
||||
"https://mirrors.kernel.org/debian/dists/stable/Release",
|
||||
"https://mirrors.edge.kernel.org/debian/dists/stable/Release",
|
||||
"https://fdroid.tetaneutral.net/fdroid/repo/index-v1.jar",
|
||||
"https://ftp.fau.de/fdroid/repo/index-v1.jar",
|
||||
//"https://microg.org/fdroid/repo/index-v1.jar",
|
||||
|
|
|
@ -64,7 +64,6 @@ import org.fdroid.fdroid.nearby.SDCardScannerService;
|
|||
import org.fdroid.fdroid.nearby.WifiStateChangeService;
|
||||
import org.fdroid.fdroid.net.ConnectivityMonitorService;
|
||||
import org.fdroid.fdroid.net.Downloader;
|
||||
import org.fdroid.fdroid.net.HttpDownloader;
|
||||
import org.fdroid.fdroid.panic.HidingManager;
|
||||
import org.fdroid.fdroid.work.CleanCacheWorker;
|
||||
|
||||
|
@ -126,6 +125,9 @@ public class FDroidApp extends Application implements androidx.work.Configuratio
|
|||
|
||||
public static final SubnetUtils.SubnetInfo UNSET_SUBNET_INFO = new SubnetUtils("0.0.0.0/32").getInfo();
|
||||
|
||||
@Nullable
|
||||
public static volatile String queryString;
|
||||
|
||||
private static volatile LongSparseArray<String> lastWorkingMirrorArray = new LongSparseArray<>(1);
|
||||
private static volatile int numTries = Integer.MAX_VALUE;
|
||||
private static volatile int timeout = Downloader.DEFAULT_TIMEOUT;
|
||||
|
@ -468,8 +470,8 @@ public class FDroidApp extends Application implements androidx.work.Configuratio
|
|||
|
||||
final String queryStringKey = "http-downloader-query-string";
|
||||
if (preferences.sendVersionAndUUIDToServers()) {
|
||||
HttpDownloader.queryString = atStartTime.getString(queryStringKey, null);
|
||||
if (HttpDownloader.queryString == null) {
|
||||
queryString = atStartTime.getString(queryStringKey, null);
|
||||
if (queryString == null) {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE / Byte.SIZE * 2);
|
||||
buffer.putLong(uuid.getMostSignificantBits());
|
||||
|
@ -481,10 +483,8 @@ public class FDroidApp extends Application implements androidx.work.Configuratio
|
|||
if (versionName != null) {
|
||||
builder.append("&client_version=").append(versionName);
|
||||
}
|
||||
HttpDownloader.queryString = builder.toString();
|
||||
}
|
||||
if (!atStartTime.contains(queryStringKey)) {
|
||||
atStartTime.edit().putString(queryStringKey, HttpDownloader.queryString).apply();
|
||||
queryString = builder.toString();
|
||||
atStartTime.edit().putString(queryStringKey, queryString).apply();
|
||||
}
|
||||
} else {
|
||||
atStartTime.edit().remove(queryStringKey).apply();
|
||||
|
|
|
@ -24,24 +24,22 @@ package org.fdroid.fdroid.net;
|
|||
import android.annotation.TargetApi;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Base64;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.fdroid.download.DownloadRequest;
|
||||
import org.fdroid.download.HeadInfo;
|
||||
import org.fdroid.download.JvmDownloadManager;
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URL;
|
||||
|
||||
import info.guardianproject.netcipher.NetCipher;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Download files over HTTP, with support for proxies, {@code .onion} addresses,
|
||||
|
@ -53,22 +51,16 @@ import info.guardianproject.netcipher.NetCipher;
|
|||
public class HttpDownloader extends Downloader {
|
||||
private static final String TAG = "HttpDownloader";
|
||||
|
||||
public static final String HEADER_FIELD_ETAG = "ETag";
|
||||
|
||||
private final JvmDownloadManager downloadManager =
|
||||
new JvmDownloadManager(Utils.getUserAgent(), FDroidApp.queryString);
|
||||
private final String username;
|
||||
private final String password;
|
||||
private URL sourceUrl;
|
||||
private HttpURLConnection connection;
|
||||
private final URL sourceUrl;
|
||||
|
||||
private boolean newFileAvailableOnServer;
|
||||
|
||||
private long fileFullSize = -1L;
|
||||
/**
|
||||
* String to append to all HTTP downloads, created in {@link FDroidApp#onCreate()}
|
||||
*/
|
||||
public static String queryString;
|
||||
|
||||
HttpDownloader(Uri uri, File destFile)
|
||||
throws FileNotFoundException, MalformedURLException {
|
||||
HttpDownloader(Uri uri, File destFile) throws MalformedURLException {
|
||||
this(uri, destFile, null, null);
|
||||
}
|
||||
|
||||
|
@ -83,7 +75,7 @@ public class HttpDownloader extends Downloader {
|
|||
* @throws MalformedURLException
|
||||
*/
|
||||
HttpDownloader(Uri uri, File destFile, String username, String password)
|
||||
throws FileNotFoundException, MalformedURLException {
|
||||
throws MalformedURLException {
|
||||
super(uri, destFile);
|
||||
this.sourceUrl = new URL(urlString);
|
||||
this.username = username;
|
||||
|
@ -92,8 +84,10 @@ public class HttpDownloader extends Downloader {
|
|||
|
||||
@Override
|
||||
protected InputStream getDownloadersInputStream() throws IOException {
|
||||
setupConnection(false);
|
||||
return new BufferedInputStream(connection.getInputStream());
|
||||
List<String> mirrors = Collections.singletonList(""); // TODO get real mirrors here
|
||||
DownloadRequest request = new DownloadRequest(urlString, mirrors, username, password, isSwapUrl(sourceUrl));
|
||||
// TODO why do we need to wrap this in a BufferedInputStream here?
|
||||
return new BufferedInputStream(downloadManager.getBlocking(request));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -130,115 +124,47 @@ public class HttpDownloader extends Downloader {
|
|||
*/
|
||||
@Override
|
||||
public void download() throws IOException, InterruptedException {
|
||||
// get the file size from the server
|
||||
HttpURLConnection tmpConn = getConnection();
|
||||
tmpConn.setRequestMethod("HEAD");
|
||||
|
||||
int contentLength = -1;
|
||||
int statusCode = tmpConn.getResponseCode();
|
||||
tmpConn.disconnect();
|
||||
newFileAvailableOnServer = false;
|
||||
switch (statusCode) {
|
||||
case HttpURLConnection.HTTP_OK:
|
||||
String headETag = tmpConn.getHeaderField(HEADER_FIELD_ETAG);
|
||||
contentLength = tmpConn.getContentLength();
|
||||
fileFullSize = contentLength;
|
||||
if (!TextUtils.isEmpty(cacheTag)) {
|
||||
if (cacheTag.equals(headETag)) {
|
||||
Utils.debugLog(TAG, urlString + " cached, not downloading: " + headETag);
|
||||
return;
|
||||
} else {
|
||||
String calcedETag = String.format("\"%x-%x\"",
|
||||
tmpConn.getLastModified() / 1000, contentLength);
|
||||
if (cacheTag.equals(calcedETag)) {
|
||||
Utils.debugLog(TAG, urlString + " cached based on calced ETag, not downloading: " +
|
||||
calcedETag);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
newFileAvailableOnServer = true;
|
||||
break;
|
||||
case HttpURLConnection.HTTP_NOT_FOUND:
|
||||
notFound = true;
|
||||
return;
|
||||
default:
|
||||
Utils.debugLog(TAG, "HEAD check of " + urlString + " returned " + statusCode + ": "
|
||||
+ tmpConn.getResponseMessage());
|
||||
boolean isSwap = isSwapUrl(sourceUrl);
|
||||
DownloadRequest request =
|
||||
new DownloadRequest(urlString, Collections.singletonList(""), username, password, isSwap);
|
||||
HeadInfo headInfo = downloadManager.headBlocking(request, cacheTag);
|
||||
fileFullSize = headInfo.getContentLength() == null ? -1 : headInfo.getContentLength();
|
||||
if (!headInfo.getETagChanged()) {
|
||||
// ETag has not changed, don't download again
|
||||
Utils.debugLog(TAG, urlString + " cached, not downloading.");
|
||||
newFileAvailableOnServer = false;
|
||||
return;
|
||||
}
|
||||
newFileAvailableOnServer = true;
|
||||
|
||||
boolean resumable = false;
|
||||
long fileLength = outputFile.length();
|
||||
if (fileLength > contentLength) {
|
||||
if (fileLength > fileFullSize) {
|
||||
FileUtils.deleteQuietly(outputFile);
|
||||
} else if (fileLength == contentLength && outputFile.isFile()) {
|
||||
} else if (fileLength == fileFullSize && outputFile.isFile()) {
|
||||
Utils.debugLog(TAG, "Already have outputFile, not download. " + outputFile.getAbsolutePath());
|
||||
return; // already have it!
|
||||
} else if (fileLength > 0) {
|
||||
resumable = true;
|
||||
}
|
||||
setupConnection(resumable);
|
||||
Utils.debugLog(TAG, "downloading " + urlString + " (is resumable: " + resumable + ")");
|
||||
downloadFromStream(resumable);
|
||||
cacheTag = connection.getHeaderField(HEADER_FIELD_ETAG);
|
||||
}
|
||||
|
||||
public static boolean isSwapUrl(Uri uri) {
|
||||
return isSwapUrl(uri.getHost(), uri.getPort());
|
||||
}
|
||||
|
||||
public static boolean isSwapUrl(URL url) {
|
||||
static boolean isSwapUrl(URL url) {
|
||||
return isSwapUrl(url.getHost(), url.getPort());
|
||||
}
|
||||
|
||||
public static boolean isSwapUrl(String host, int port) {
|
||||
static boolean isSwapUrl(String host, int port) {
|
||||
return port > 1023 // only root can use <= 1023, so never a swap repo
|
||||
&& host.matches("[0-9.]+") // host must be an IP address
|
||||
&& FDroidApp.subnetInfo.isInRange(host); // on the same subnet as we are
|
||||
}
|
||||
|
||||
HttpURLConnection getConnection() throws SocketTimeoutException, IOException {
|
||||
HttpURLConnection connection;
|
||||
if (isSwapUrl(sourceUrl)) {
|
||||
// swap never works with a proxy, its unrouted IP on the same subnet
|
||||
connection = (HttpURLConnection) sourceUrl.openConnection();
|
||||
connection.setRequestProperty("Connection", "Close"); // avoid keep-alive
|
||||
} else {
|
||||
if (queryString != null) {
|
||||
connection = NetCipher.getHttpURLConnection(new URL(urlString + "?" + queryString));
|
||||
} else {
|
||||
connection = NetCipher.getHttpURLConnection(sourceUrl);
|
||||
}
|
||||
}
|
||||
|
||||
connection.setRequestProperty("User-Agent", Utils.getUserAgent());
|
||||
connection.setConnectTimeout(getTimeout());
|
||||
connection.setReadTimeout(getTimeout());
|
||||
|
||||
if (Build.VERSION.SDK_INT < 19) { // gzip encoding can be troublesome on old Androids
|
||||
connection.setRequestProperty("Accept-Encoding", "identity");
|
||||
}
|
||||
|
||||
if (username != null && password != null) {
|
||||
// add authorization header from username / password if set
|
||||
String authString = username + ":" + password;
|
||||
connection.setRequestProperty("Authorization", "Basic "
|
||||
+ Base64.encodeToString(authString.getBytes(), Base64.NO_WRAP));
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
|
||||
private void setupConnection(boolean resumable) throws IOException {
|
||||
if (connection != null) {
|
||||
return;
|
||||
}
|
||||
connection = getConnection();
|
||||
|
||||
if (resumable) {
|
||||
// partial file exists, resume the download
|
||||
connection.setRequestProperty("Range", "bytes=" + outputFile.length() + "-");
|
||||
}
|
||||
}
|
||||
|
||||
// Testing in the emulator for me, showed that figuring out the
|
||||
// filesize took about 1 to 1.5 seconds.
|
||||
// To put this in context, downloading a repo of:
|
||||
|
@ -264,8 +190,6 @@ public class HttpDownloader extends Downloader {
|
|||
|
||||
@Override
|
||||
public void close() {
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
}
|
||||
// TODO abort ongoing download somehow
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,9 @@ package org.fdroid.fdroid.net;
|
|||
|
||||
import android.net.Uri;
|
||||
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
|
@ -10,6 +13,9 @@ import java.io.OutputStream;
|
|||
import java.io.OutputStreamWriter;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
import info.guardianproject.netcipher.NetCipher;
|
||||
|
||||
/**
|
||||
* HTTP POST a JSON string to the URL configured in the constructor.
|
||||
|
@ -44,4 +50,15 @@ public class HttpPoster extends HttpDownloader {
|
|||
throw new IOException("HTTP POST failed with " + statusCode + " " + connection.getResponseMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private HttpURLConnection getConnection() throws IOException {
|
||||
HttpURLConnection connection;
|
||||
if (FDroidApp.queryString != null) {
|
||||
connection = NetCipher.getHttpURLConnection(new URL(urlString + "?" + FDroidApp.queryString));
|
||||
} else {
|
||||
connection = NetCipher.getHttpURLConnection(new URL(urlString));
|
||||
}
|
||||
connection.setRequestProperty("User-Agent", Utils.getUserAgent());
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,6 +75,8 @@ import static org.junit.Assert.assertTrue;
|
|||
@RunWith(RobolectricTestRunner.class)
|
||||
public class LocalHTTPDTest {
|
||||
|
||||
private static final String HEADER_FIELD_ETAG = "ETag";
|
||||
|
||||
private static ClassLoader classLoader;
|
||||
private static LocalHTTPD localHttpd;
|
||||
private static Thread serverStartThread;
|
||||
|
@ -217,7 +219,7 @@ public class LocalHTTPDTest {
|
|||
assertEquals(indexFile.length(), connection.getContentLength());
|
||||
assertNotEquals(0, connection.getContentLength());
|
||||
|
||||
String etag = connection.getHeaderField(HttpDownloader.HEADER_FIELD_ETAG);
|
||||
String etag = connection.getHeaderField(HEADER_FIELD_ETAG);
|
||||
assertFalse(TextUtils.isEmpty(etag));
|
||||
|
||||
assertEquals(200, connection.getResponseCode());
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
/build
|
|
@ -0,0 +1,90 @@
|
|||
plugins {
|
||||
id 'org.jetbrains.kotlin.multiplatform'
|
||||
id 'com.android.library'
|
||||
}
|
||||
|
||||
group = 'org.fdroid'
|
||||
version = '0.1'
|
||||
|
||||
kotlin {
|
||||
jvm {
|
||||
compilations.all {
|
||||
kotlinOptions.jvmTarget = '1.8'
|
||||
}
|
||||
testRuns["test"].executionTask.configure {
|
||||
useJUnit()
|
||||
}
|
||||
}
|
||||
jvmToolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(8))
|
||||
}
|
||||
def hostOs = System.getProperty("os.name")
|
||||
def isMingwX64 = hostOs.startsWith("Windows")
|
||||
def nativeTarget
|
||||
if (hostOs == "Mac OS X") nativeTarget = macosX64('native')
|
||||
else if (hostOs == "Linux") nativeTarget = linuxX64("native")
|
||||
else if (isMingwX64) nativeTarget = mingwX64("native")
|
||||
else throw new GradleException("Host OS is not supported in Kotlin/Native.")
|
||||
|
||||
ext {
|
||||
ktor_version = "2.0.0-beta-1"
|
||||
}
|
||||
|
||||
android()
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
implementation "io.ktor:ktor-client-core:$ktor_version"
|
||||
}
|
||||
}
|
||||
commonTest {
|
||||
dependencies {
|
||||
implementation kotlin('test')
|
||||
}
|
||||
}
|
||||
jvmMain {
|
||||
kotlin.srcDir('src/commonJvmAndroid/kotlin')
|
||||
dependencies {
|
||||
implementation "io.ktor:ktor-client-cio:$ktor_version"
|
||||
}
|
||||
}
|
||||
jvmTest {
|
||||
|
||||
}
|
||||
androidMain {
|
||||
kotlin.srcDir('src/commonJvmAndroid/kotlin')
|
||||
dependencies {
|
||||
implementation "io.ktor:ktor-client-okhttp:$ktor_version"
|
||||
}
|
||||
}
|
||||
androidTest {
|
||||
dependencies {
|
||||
implementation 'junit:junit:4.13.2'
|
||||
}
|
||||
}
|
||||
nativeMain {
|
||||
dependencies {
|
||||
implementation "io.ktor:ktor-client-curl:$ktor_version"
|
||||
}
|
||||
}
|
||||
nativeTest {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
sourceSets.main.manifest.srcFile('src/androidMain/AndroidManifest.xml')
|
||||
defaultConfig {
|
||||
minSdkVersion 22
|
||||
targetSdkVersion 25
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
lintOptions {
|
||||
disable "InvalidPackage" // FIXME remove when Ktor 2.0 has been released
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.fdroid.download"/>
|
|
@ -0,0 +1,51 @@
|
|||
package org.fdroid.download
|
||||
|
||||
import io.ktor.client.plugins.ResponseException
|
||||
import io.ktor.utils.io.jvm.javaio.toInputStream
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.util.Date
|
||||
|
||||
public class JvmDownloadManager(
|
||||
userAgent: String,
|
||||
queryString: String?,
|
||||
) : DownloadManager(userAgent, queryString) {
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun headBlocking(request: DownloadRequest, eTag: String?): HeadInfo = runBlocking {
|
||||
val headInfo = head(request, eTag) ?: throw IOException()
|
||||
if (eTag == null) return@runBlocking headInfo
|
||||
// If the ETag does not match, it could be because the file is on a mirror
|
||||
// running a different webserver, e.g. Apache vs Nginx.
|
||||
// Content-Length and Last-Modified could be used as well.
|
||||
// Nginx and Apache 2.4 defaults use only those two values to generate the ETag.
|
||||
// Unfortunately, other webservers and CDNs have totally different methods.
|
||||
// And mirrors that are syncing using a method other than rsync
|
||||
// could easily have different Last-Modified times on the exact same file.
|
||||
// On top of that, some services like GitHub's and GitLab's raw file support
|
||||
// do not set the header at all.
|
||||
val lastModified = try {
|
||||
// this method is not available multi-platform, so for now only done in JVM
|
||||
Date.parse(headInfo.lastModified) / 1000
|
||||
} catch (e: Exception) {
|
||||
0L
|
||||
}
|
||||
val calculatedEtag: String =
|
||||
String.format("\"%x-%x\"", lastModified, headInfo.contentLength)
|
||||
if (calculatedEtag == eTag) headInfo.copy(eTagChanged = false)
|
||||
else headInfo
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
@Throws(IOException::class)
|
||||
fun getBlocking(request: DownloadRequest, skipFirstBytes: Long? = null): InputStream =
|
||||
runBlocking {
|
||||
try {
|
||||
get(request, skipFirstBytes).toInputStream()
|
||||
} catch (e: ResponseException) {
|
||||
throw IOException(e)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
package org.fdroid.download
|
||||
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.plugins.ResponseException
|
||||
import io.ktor.client.plugins.UserAgent
|
||||
import io.ktor.client.plugins.onDownload
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.request.head
|
||||
import io.ktor.client.request.header
|
||||
import io.ktor.client.request.parameter
|
||||
import io.ktor.client.statement.bodyAsChannel
|
||||
import io.ktor.http.HttpHeaders.Authorization
|
||||
import io.ktor.http.HttpHeaders.Connection
|
||||
import io.ktor.http.HttpHeaders.ETag
|
||||
import io.ktor.http.HttpHeaders.LastModified
|
||||
import io.ktor.http.contentLength
|
||||
import io.ktor.util.encodeBase64
|
||||
import io.ktor.utils.io.ByteReadChannel
|
||||
import io.ktor.utils.io.charsets.Charsets
|
||||
import io.ktor.utils.io.core.toByteArray
|
||||
import kotlin.jvm.JvmOverloads
|
||||
|
||||
public open class DownloadManager(
|
||||
private val userAgent: String,
|
||||
queryString: String? = null,
|
||||
) {
|
||||
|
||||
private val httpClient by lazy {
|
||||
HttpClient {
|
||||
followRedirects = false
|
||||
expectSuccess = true
|
||||
developmentMode = true // TODO remove
|
||||
engine {
|
||||
proxy = null // TODO use proxy except when swap
|
||||
threadsCount = 4
|
||||
pipelining = true
|
||||
}
|
||||
install(UserAgent) {
|
||||
agent = userAgent
|
||||
}
|
||||
}
|
||||
}
|
||||
private val parameters = queryString?.split('&')?.map { p ->
|
||||
val (key, value) = p.split('=')
|
||||
Pair(key, value)
|
||||
}
|
||||
|
||||
// TODO try to force onion addresses over proxy like NetCipher.getHttpURLConnection()
|
||||
|
||||
/**
|
||||
* Performs a HEAD request and returns [HeadInfo].
|
||||
*
|
||||
* This is useful for checking if the repository index has changed before downloading it again.
|
||||
* However, due to non-standard ETags on mirrors, change detection is unreliable.
|
||||
*/
|
||||
suspend fun head(request: DownloadRequest, eTag: String?): HeadInfo? {
|
||||
val authString = constructBasicAuthValue(request)
|
||||
val response = try {
|
||||
httpClient.head(request.url) {
|
||||
// add authorization header from username / password if set
|
||||
if (authString != null) header(Authorization, authString)
|
||||
}
|
||||
} catch (e: ResponseException) {
|
||||
println(e)
|
||||
return null
|
||||
}
|
||||
val contentLength = response.contentLength()
|
||||
val lastModified = response.headers[LastModified]
|
||||
if (eTag != null && response.headers[ETag] == eTag) {
|
||||
return HeadInfo(false, contentLength, lastModified)
|
||||
}
|
||||
return HeadInfo(true, contentLength, lastModified)
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
suspend fun get(request: DownloadRequest, skipFirstBytes: Long? = null): ByteReadChannel {
|
||||
val authString = constructBasicAuthValue(request)
|
||||
val response = httpClient.get(request.url) {
|
||||
// add query string parameters if existing
|
||||
parameters?.forEach { (key, value) ->
|
||||
parameter(key, value)
|
||||
}
|
||||
// add authorization header from username / password if set
|
||||
if (authString != null) header(Authorization, authString)
|
||||
// add range header if set
|
||||
if (skipFirstBytes != null) header("Range", "bytes=${skipFirstBytes}-")
|
||||
// avoid keep-alive for swap due to strange errors observed in the past
|
||||
if (request.isSwap) header(Connection, "Close")
|
||||
|
||||
onDownload { bytesSentTotal, contentLength ->
|
||||
println("Received $bytesSentTotal bytes from $contentLength")
|
||||
}
|
||||
}
|
||||
return response.bodyAsChannel()
|
||||
}
|
||||
|
||||
private fun constructBasicAuthValue(request: DownloadRequest): String? {
|
||||
if (request.username == null || request.password == null) return null
|
||||
val authString = "${request.username}:${request.password}"
|
||||
val authBuf = authString.toByteArray(Charsets.UTF_8).encodeBase64()
|
||||
return "Basic $authBuf"
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package org.fdroid.download
|
||||
|
||||
import kotlin.jvm.JvmOverloads
|
||||
|
||||
data class DownloadRequest @JvmOverloads constructor(
|
||||
val url: String,
|
||||
val mirrors: List<String>,
|
||||
val username: String? = null,
|
||||
val password: String? = null,
|
||||
val isSwap: Boolean = false,
|
||||
)
|
|
@ -0,0 +1,7 @@
|
|||
package org.fdroid.download
|
||||
|
||||
data class HeadInfo(
|
||||
val eTagChanged: Boolean,
|
||||
val contentLength: Long?,
|
||||
val lastModified: String?,
|
||||
)
|
|
@ -1,3 +1,8 @@
|
|||
org.gradle.jvmargs=-Xms1g -Xmx2g -XX:MaxPermSize=2g
|
||||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
|
||||
kotlin.code.style=official
|
||||
kotlin.mpp.stability.nowarn=true
|
||||
kotlin.mpp.enableGranularSourceSetsMetadata=true
|
||||
kotlin.native.enableDependencyPropagation=false
|
||||
|
|
Binary file not shown.
|
@ -3,6 +3,10 @@
|
|||
<configuration>
|
||||
<verify-metadata>false</verify-metadata>
|
||||
<verify-signatures>true</verify-signatures>
|
||||
<trusted-artifacts>
|
||||
<trust file=".*-javadoc[.]jar" regex="true"/>
|
||||
<trust file=".*-sources[.]jar" regex="true"/>
|
||||
</trusted-artifacts>
|
||||
<trusted-keys>
|
||||
<trusted-key id="0394681addddb4f6388a64d295123567c1886c47" group="ch.acra" name="acra" version="4.9.1"/>
|
||||
<trusted-key id="042b29e928995b9db963c636c7ca19b7b620d787" group="org.apache.maven" name="maven-ant-tasks" version="2.1.3"/>
|
||||
|
@ -37,8 +41,12 @@
|
|||
<trusted-key id="31bae2e51d95e0f8ad9b7bcc40a3c4432bd7308c" group="com.googlecode.juniversalchardet" name="juniversalchardet" version="1.0.3"/>
|
||||
<trusted-key id="3288b8be8512d6c0ca185268c51e6cbc7ff46f0b" group="com.google.auto.service" name="auto-service" version="1.0-rc4"/>
|
||||
<trusted-key id="3872ed7d5904493d23d78fa2c4c8cb73b1435348" group="com.android.tools.build" name="transform-api" version="2.0.0-deprecated-use-gradle-api"/>
|
||||
<trusted-key id="394cb436c56916fc01eea4a77c30f7b1329dba87" group="io.ktor"/>
|
||||
<trusted-key id="3d11126ea77e4e07fbabb38614a84c976d265b25" group="com.google.protobuf"/>
|
||||
<trusted-key id="475f3b8e59e6e63aa78067482c7b12f2a511e325" group="org.slf4j"/>
|
||||
<trusted-key id="475f3b8e59e6e63aa78067482c7b12f2a511e325">
|
||||
<trusting group="org.slf4j"/>
|
||||
<trusting group="org.slf4j" name="slf4j-api"/>
|
||||
</trusted-key>
|
||||
<trusted-key id="49977dad0140e24894f9b955354214e5e508c045" group="com.hannesdorfmann" name="adapterdelegates3" version="3.0.1"/>
|
||||
<trusted-key id="4cf4b443734c0aed8dc93a1f6132aae95d8e9fe0" group="org.nanohttpd"/>
|
||||
<trusted-key id="4db1a49729b053caf015cee9a6adfc93ef34893e" group="org.hamcrest"/>
|
||||
|
@ -78,6 +86,7 @@
|
|||
<trusting group="org.jetbrains.kotlinx"/>
|
||||
<trusting group="^org[.]jetbrains($|([.].*))" regex="true"/>
|
||||
</trusted-key>
|
||||
<trusted-key id="8e3a02905a1ae67e7b0f9acd3967d4eda591b991" group="io.ktor"/>
|
||||
<trusted-key id="90ee19787a7bcf6fd37a1e9180c08b1c29100955">
|
||||
<trusting group="com.squareup" name="javawriter"/>
|
||||
<trusting group="com.squareup" name="javawriter" version="2.1.1"/>
|
||||
|
@ -88,7 +97,10 @@
|
|||
<trusted-key id="984460dfd8f76a226f7dede2e483332711b8c7d6" group="com.ashokvarma.android" name="bottom-navigation-bar" version="2.0.5"/>
|
||||
<trusted-key id="998af0e2b935996f5cebd56b9b1fda9f3c062231" group="^org[.]apache($|([.].*))" regex="true"/>
|
||||
<trusted-key id="9ffed7a118d45a44e4a1e47130e6f80434a72a7f" group="^org[.]apache[.]maven($|([.].*))" regex="true"/>
|
||||
<trusted-key id="a6d6c97108b8585f91b158748671a8df71296252" group="com.squareup" name="javapoet" version="1.10.0"/>
|
||||
<trusted-key id="a6d6c97108b8585f91b158748671a8df71296252">
|
||||
<trusting group="com.squareup" name="javapoet" version="1.10.0"/>
|
||||
<trusting group="^com[.]squareup($|([.].*))" regex="true"/>
|
||||
</trusted-key>
|
||||
<trusted-key id="a7892505cf1a58076453e52d7999befba1039e8b" group="net.bytebuddy"/>
|
||||
<trusted-key id="ae2b18e836c5f30687f37efdcc6346f2ce3872d9" group="com.google.protobuf" name="protobuf-java" version="2.6.1"/>
|
||||
<trusted-key id="afcc4c7594d09e2182c60e0f7a01b0f236e5430f">
|
||||
|
@ -133,16 +145,25 @@
|
|||
<trusted-key id="dddee87612e9fb95f5c8d91e411063a3a0ffd119" group="commons-beanutils" name="commons-beanutils" version="1.9.3"/>
|
||||
<trusted-key id="e3a9f95079e84ce201f7cf60bede11eaf1164480" group="org.hamcrest" name="hamcrest" version="2.2"/>
|
||||
<trusted-key id="e77417ac194160a3fabd04969a259c7ee636c5ed" group="com.google.errorprone"/>
|
||||
<trusted-key id="e7dc75fc24fb3c8dfe8086ad3d5839a2262cbbfb" group="org.jetbrains.kotlinx"/>
|
||||
<trusted-key id="e85aed155021af8a6c6b7a4a7c7d8456294423ba" group="org.objenesis"/>
|
||||
<trusted-key id="f254b35617dc255d9344bcfa873a8e86b4372146">
|
||||
<trusting group="org.codehaus.mojo"/>
|
||||
<trusting group="org.codehaus.mojo" name="animal-sniffer-annotations"/>
|
||||
</trusted-key>
|
||||
<trusted-key id="fa7929f83ad44c4590f6cc6815c71c0a4e0b8edd" group="net.java.dev.jna"/>
|
||||
<trusted-key id="ff6e2c001948c5f2f38b0cc385911f425ec61b51" group="junit" name="junit" version="4.13"/>
|
||||
<trusted-key id="ff6e2c001948c5f2f38b0cc385911f425ec61b51">
|
||||
<trusting group="junit" name="junit" version="4.13"/>
|
||||
<trusting group="junit" name="junit"/>
|
||||
</trusted-key>
|
||||
</trusted-keys>
|
||||
</configuration>
|
||||
<components>
|
||||
<component group="" name="kotlin-native-prebuilt-linux-x86_64" version="1.6.10">
|
||||
<artifact name="kotlin-native-prebuilt-linux-x86_64-1.6.10.tar.gz">
|
||||
<sha256 value="3c2e070412785620690054943cabfba7b0f3465b544e22f76a5449a329ad8cd6" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.activity" name="activity" version="1.1.0">
|
||||
<artifact name="activity-1.1.0.aar">
|
||||
<sha256 value="4f2b35916768032f7d0c20e250e28b29037ed4ce9ebf3da4fcd51bcb0c6067ef" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
|
@ -1949,6 +1970,11 @@
|
|||
<sha256 value="e1abd7f1116cf5e0c59947693e2189208ec94296b2a3394c959e3511d399a7b0" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.squareup.okio" name="okio" version="2.6.0">
|
||||
<artifact name="okio-jvm-2.6.0.jar">
|
||||
<sha256 value="4d84ef686277b58eb05691ac19cd3befa3429a27274982ee65ea0f07044bcc00" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.sun.activation" name="all" version="1.2.0">
|
||||
<artifact name="all-1.2.0.pom">
|
||||
<pgp value="4f7e32d440ef90a83011a8fc6425559c47cc79c4"/>
|
||||
|
@ -2109,6 +2135,239 @@
|
|||
<sha256 value="e2b8153dd1bd760c1a8521cd500205dbf36b6fe89526a190e1ced65193cd51d3" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-client-android" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-client-android-metadata-2.0.0-beta-1-all.jar">
|
||||
<sha256 value="4a84b904c161615270d5a9c0936fbff9cdad7e19a144f9ae5542e6cc0f37665f" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-client-android-jvm" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-client-android-jvm-2.0.0-beta-1.jar">
|
||||
<sha256 value="8de743555689f6c08c008c187fb080d5f3202a79f8ac0502b1a5aadb47d5bf90" origin="Generated by Gradle because a key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-client-auth" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-client-auth-metadata-2.0.0-beta-1-all.jar">
|
||||
<sha256 value="5e09db671a7d44b28a007a45c08b9cdbce19a37d9c55212a0102b162c8d2db2d" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-client-auth-linuxx64" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-client-auth.klib">
|
||||
<sha256 value="9cc27068ca97250e7af938f264692acdb556d6f8f96cf8b6a69e497ad2fe2882" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-client-cio" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-client-cio-metadata-2.0.0-beta-1-all.jar">
|
||||
<sha256 value="08e95f2424ba28ca226d6cacd90f6056de2d621b32006752c083ed09aa3a5da0" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-client-cio-jvm" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-client-cio-jvm-2.0.0-beta-1.jar">
|
||||
<sha256 value="619b1c787fd6e732886f0551b2155616db8622ba2c79612836e10d7c1c992626" origin="Generated by Gradle because a key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-client-core" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-client-core-2.0.0-beta-1.jar">
|
||||
<sha256 value="58ad1ef2e783e40b5508a1eb5ddc73c04d133a84d038251497b7e0555541e0a1" origin="Generated by Gradle because a key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
<artifact name="ktor-client-core-metadata-2.0.0-beta-1-all.jar">
|
||||
<sha256 value="54818603b3246f97501ae2eed4366e169d86654c81907215f47d218430db7fd5" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-client-core-jvm" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-client-core-jvm-2.0.0-beta-1.jar">
|
||||
<sha256 value="c565624e44b4f83713a6259f118e6ee828af360eccaa1c57917d5c48c0bd620e" origin="Generated by Gradle because a key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-client-core-linuxx64" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-client-core.klib">
|
||||
<sha256 value="9b3ce3264d1f8312980966dc06425ece07b0896f961138de44ad8491be427a6f" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-client-curl" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-client-curl-metadata-2.0.0-beta-1-all.jar">
|
||||
<sha256 value="289779c28934d36bf7e764b6d92852af1b301ba199aa66f839814458cbe17c6a" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-client-curl-linuxx64" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-client-curl-cinterop-libcurl.klib">
|
||||
<sha256 value="30441407384ec1f3ee5b538cf91b0a643e1c6963c6ab12b65cdb42aa2ba989f9" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
<artifact name="ktor-client-curl.klib">
|
||||
<sha256 value="73a3ecd442223d0dcaaa5055f9d7985a66a674162bf14643b4b9ea181674187c" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-client-okhttp" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-client-okhttp-metadata-2.0.0-beta-1-all.jar">
|
||||
<sha256 value="012c653e21af55ae42b70796fb31927c65a4fdb478190c5fe1c4300654a7932b" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-client-okhttp-jvm" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-client-okhttp-jvm-2.0.0-beta-1.jar">
|
||||
<pgp value="8e3a02905a1ae67e7b0f9acd3967d4eda591b991"/>
|
||||
<sha256 value="0f7e222a8386cc52893fdfe35c9e3ce9346a6451d63307dce6288e8cd05efe31"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-events" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-events-2.0.0-beta-1.jar">
|
||||
<sha256 value="361269a6cb488ac30621941c879e05751ab9414ef4753f716bc7378c9d2d7de2" origin="Generated by Gradle because a key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
<artifact name="ktor-events-metadata-2.0.0-beta-1-all.jar">
|
||||
<sha256 value="12c26a3a28a304ac7cdc1da37c318716b92ce20080728d40d8ca8636a4e185b9" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-events-jvm" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-events-jvm-2.0.0-beta-1.jar">
|
||||
<sha256 value="55803053a94674f772091816537e80da2c85646748bbd68d8cdb5fd86f6d82a8" origin="Generated by Gradle because a key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-events-linuxx64" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-events.klib">
|
||||
<sha256 value="b70c3750b7e93dbf4405943596f709c42b08752d0ac7233f140e5fab1caeb924" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-http" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-http-2.0.0-beta-1.jar">
|
||||
<sha256 value="15080bebbbf861cad7d18e05e1202c5a3125521e93efb43d48002d46432ee6b4" origin="Generated by Gradle because a key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
<artifact name="ktor-http-metadata-2.0.0-beta-1-all.jar">
|
||||
<sha256 value="ee0bebd9086bba2cabc5b58b59286f62d1ac961ffc3c71f6ac99a862d03f7869" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-http-cio" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-http-cio-metadata-2.0.0-beta-1-all.jar">
|
||||
<sha256 value="270d03b08ede003f90b0234d4deb970c727a1ac9958624873c3e10d968730caa" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-http-cio-jvm" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-http-cio-jvm-2.0.0-beta-1.jar">
|
||||
<sha256 value="b9c84414f7166c3f1a66a1d56525467045ca6bc0125f72ed0a1331986157b69c" origin="Generated by Gradle because a key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-http-cio-linuxx64" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-http-cio.klib">
|
||||
<sha256 value="989f37aa36b8d0950f47176fad569ac2a2b99283df9cb3fcf3ed1b4942657a2d" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-http-jvm" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-http-jvm-2.0.0-beta-1.jar">
|
||||
<sha256 value="f8aed40449e782ffd8e5032be1aceefd09739df9f85e934cf3cc36b6c330ad98" origin="Generated by Gradle because a key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-http-linuxx64" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-http.klib">
|
||||
<sha256 value="c99cf7e029915a436a2c281d4b19e409d1d63eb5289f437ecc152b5e79b69016" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-io" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-io-2.0.0-beta-1.jar">
|
||||
<sha256 value="6d67c9aaaf2f20a8fc6f7c9ab6a13e06c57d8dabd7a64e4a697dc43ef3b632d8" origin="Generated by Gradle because a key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
<artifact name="ktor-io-metadata-2.0.0-beta-1-all.jar">
|
||||
<sha256 value="590d2caa2377edaf431a7332235be6ac1794ab4dbf63ac6e867a92bc2c400e25" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-io-jvm" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-io-jvm-2.0.0-beta-1.jar">
|
||||
<sha256 value="27a18b9e78aca8cfff41156fb54ba02943b450b33e505029630f7a728f3dff22" origin="Generated by Gradle because a key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-io-linuxx64" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-io.klib">
|
||||
<sha256 value="d0202e82eb0320d45546b396bb31a17c330fe114b7f37aaaf07abc6510afbfaf" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-network" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-network-metadata-2.0.0-beta-1-all.jar">
|
||||
<sha256 value="41425690a6e3ac1097b062b5f3e89510c27460c8281cc4a78bae3532e11033cd" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-network-jvm" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-network-jvm-2.0.0-beta-1.jar">
|
||||
<sha256 value="34594eda913ecb0a47dcf380d9c3168a8bdcca9fcff3e2e408069ff18341d2e9" origin="Generated by Gradle because a key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-network-tls" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-network-tls-metadata-2.0.0-beta-1-all.jar">
|
||||
<sha256 value="8148fc9cefdeb1aca9332db0cde028fa4caf073ca7bac59937fabc277485e896" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-network-tls-jvm" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-network-tls-jvm-2.0.0-beta-1.jar">
|
||||
<sha256 value="870f52f530ab0b521a9bdb5d7ac46815cfed9ae3aae3e8d7c4d6375d65361647" origin="Generated by Gradle because a key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-serialization" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-serialization-2.0.0-beta-1.jar">
|
||||
<sha256 value="e611383645944b3154908ccb59470e88c720a0dd9b0931dfbbb3764bfc0fe8fa" origin="Generated by Gradle because a key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
<artifact name="ktor-serialization-metadata-2.0.0-beta-1-all.jar">
|
||||
<sha256 value="03fd54642e919d9109f6d99928111b72dde1cc2b1fd4be41ed1a3df037a7cf43" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-serialization-jvm" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-serialization-jvm-2.0.0-beta-1.jar">
|
||||
<sha256 value="cb67983240248443a290b82a458192fea84b6a93efb4a19b9c47f7b6c8927568" origin="Generated by Gradle because a key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-serialization-linuxx64" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-serialization.klib">
|
||||
<sha256 value="8f04142ef2234b718b590316c5d126fd9dcd6781e44b3499d17840a1813d2c47" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-utils" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-utils-2.0.0-beta-1.jar">
|
||||
<sha256 value="3b2c97807bd8557b3c5ba6721c341fc2a928e711c1824f3f248a42bef73ebfb1" origin="Generated by Gradle because a key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
<artifact name="ktor-utils-metadata-2.0.0-beta-1-all.jar">
|
||||
<sha256 value="5ad913497029bb639f5f9402e0b1f2d0327c11df4e287302deef8c5c336bb114" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-utils-jvm" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-utils-jvm-2.0.0-beta-1.jar">
|
||||
<sha256 value="a261885c15bcfd57c7f7f87910ba47df8aaf3d461536f674a598379140105bfa" origin="Generated by Gradle because a key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-utils-linuxx64" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-utils.klib">
|
||||
<sha256 value="c94c118cc5fd747635dd97f1cf0ab56378f3d2fb490eaa73f35de56720146aa3" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-websocket-serialization" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-websocket-serialization-2.0.0-beta-1.jar">
|
||||
<sha256 value="1019a86df4a0d73be0c3c6dceaf9e88903571b140166af25687995e8522b931a" origin="Generated by Gradle because a key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
<artifact name="ktor-websocket-serialization-metadata-2.0.0-beta-1-all.jar">
|
||||
<sha256 value="0a4f5aed043969b999b0aa1a87dbd4cd89b6dc3c4f81dc366825dfbe281d12e6" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-websocket-serialization-jvm" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-websocket-serialization-jvm-2.0.0-beta-1.jar">
|
||||
<sha256 value="9e2b4ece9c8a3799d30f6c92478d23bad9ba8166a8701c08b494af01c51aae8e" origin="Generated by Gradle because a key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-websocket-serialization-linuxx64" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-websocket-serialization.klib">
|
||||
<sha256 value="49bdb43d612b15b88fd1a57a810a8c3d5aa65afd2bb7b8151d509d421c792688" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-websockets" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-websockets-2.0.0-beta-1.jar">
|
||||
<sha256 value="38466e9b58f7ae9372f84c463fc28dfaf08a2f0b59c1596d8e48b4594bbe255f" origin="Generated by Gradle because a key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
<artifact name="ktor-websockets-metadata-2.0.0-beta-1-all.jar">
|
||||
<sha256 value="cb46e25c9f7c96f3eec42bd6e0ea77031dde0add2f9c7383a164213b2d282d98" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-websockets-jvm" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-websockets-jvm-2.0.0-beta-1.jar">
|
||||
<sha256 value="f0dabbae5a420838cdbff168e3bb75b88c8cf27991e5621b19ebed72f3ea146f" origin="Generated by Gradle because a key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.ktor" name="ktor-websockets-linuxx64" version="2.0.0-beta-1">
|
||||
<artifact name="ktor-websockets.klib">
|
||||
<sha256 value="ed80b79d2426ec0f1fb900691dc61de64d83f649d0d0ad9f545413d6435cf9ff" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="io.reactivex" name="rxjava" version="1.1.0">
|
||||
<artifact name="rxjava-1.1.0.jar">
|
||||
<pgp value="1d9aa7f9e1e2824728b8cd1794b291aef984a085"/>
|
||||
|
@ -3002,6 +3261,11 @@
|
|||
<sha256 value="db14015976f9d4f3579935d1639ec857b48322c2d3b4144f513e508ad7f69f7e" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.jetbrains.kotlin" name="kotlin-native-prebuilt-linux-x86_64" version="1.6.10">
|
||||
<artifact name="kotlin-kotlin-native-prebuilt-linux-x86_64-1.6.10.jar">
|
||||
<sha256 value="a0b6d712414de6e1213727c145d35fa7ea5a554f2e3df18ebd8690f9df9019a0" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.jetbrains.kotlin" name="kotlin-native-utils" version="1.6.10">
|
||||
<artifact name="kotlin-native-utils-1.6.10.jar">
|
||||
<sha256 value="a0b6d712414de6e1213727c145d35fa7ea5a554f2e3df18ebd8690f9df9019a0" origin="Generated by Gradle"/>
|
||||
|
@ -3087,6 +3351,19 @@
|
|||
<sha256 value="12a572e6608423e9e71363008165bb2f11aa2dd61e2ee0cea722068dfa938c30" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.jetbrains.kotlinx" name="atomicfu" version="0.16.3">
|
||||
<artifact name="atomicfu-metadata-0.16.3-all.jar">
|
||||
<sha256 value="c3c0546dd33e0ef75782734d9703eb3583a28414b1d4464fcbde56d70a7d5a6d" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.jetbrains.kotlinx" name="atomicfu-linuxx64" version="0.16.3">
|
||||
<artifact name="atomicfu-cinterop-interop.klib">
|
||||
<sha256 value="7a28fdb9f981f2802ed1a8a630c81ccaa0abe75b62cb14c4e323400866c5b286" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
<artifact name="atomicfu.klib">
|
||||
<sha256 value="a00c0e198a955725082452b7767053e25c599761484fd6f08a11098eebfb0cbd" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-android" version="1.3.4">
|
||||
<artifact name="kotlinx-coroutines-android-1.3.4.jar">
|
||||
<sha256 value="f36ea75c31934bfad0682cfc435cce922e28b3bffa5af26cf86f07db13008f8a" origin="Generated by Gradle"/>
|
||||
|
@ -3097,12 +3374,22 @@
|
|||
<sha256 value="17bec6112d93f5fcb11c27ecc8a14b48e30a5689ccf42c95025b89ba2210c28f" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-core" version="1.5.2-native-mt">
|
||||
<artifact name="kotlinx-coroutines-core-metadata-1.5.2-native-mt-all.jar">
|
||||
<sha256 value="540250c45b0c88bcd139d537924bfba138f50932503a3aa333e81a9c07a2527b" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-core-jvm" version="1.5.0">
|
||||
<artifact name="kotlinx-coroutines-core-jvm-1.5.0.jar">
|
||||
<pgp value="e7dc75fc24fb3c8dfe8086ad3d5839a2262cbbfb"/>
|
||||
<sha256 value="78d6cc7135f84d692ff3752fcfd1fa1bbe0940d7df70652e4f1eaeec0c78afbb" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-core-linuxx64" version="1.5.2-native-mt">
|
||||
<artifact name="kotlinx-coroutines-core.klib">
|
||||
<sha256 value="bd5e3639e853cc1e8dca4db696ab29f95924453da93db96e20f30979e9463ef2" origin="Generated by Gradle because artifact wasn't signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.jetbrains.trove4j" name="trove4j" version="20160824">
|
||||
<artifact name="trove4j-20160824.jar">
|
||||
<pgp value="33fd4bfd33554634053d73c0c2148900bcd3c2af"/>
|
||||
|
@ -3350,6 +3637,11 @@
|
|||
<sha256 value="7cd9d7a0b5d93dfd461a148891b43509cf403a9c7f9fb49060d3554df1c81e1e" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.slf4j" name="slf4j-api" version="1.7.30">
|
||||
<artifact name="slf4j-api-1.7.30.jar">
|
||||
<pgp value="475f3b8e59e6e63aa78067482c7b12f2a511e325"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.slf4j" name="slf4j-parent" version="1.7.25">
|
||||
<artifact name="slf4j-parent-1.7.25.pom">
|
||||
<sha256 value="18f5c52120db036e88d6136f8839c832d074bdda95c756c6f429249d2db54ac6" origin="Generated by Gradle"/>
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
include ':app'
|
||||
include ':download'
|
||||
|
|
Loading…
Reference in New Issue