Initial work on separate download library

This commit is contained in:
Torsten Grote 2022-01-04 16:37:51 -03:00
parent 1573952fbb
commit 3d479b29e5
No known key found for this signature in database
GPG Key ID: 3E5F77D92CF891FF
17 changed files with 628 additions and 120 deletions

View File

@ -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'

View File

@ -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",

View File

@ -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();

View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -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());

1
download/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

90
download/build.gradle Normal file
View File

@ -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
}
}

View File

@ -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"/>

View File

@ -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)
}
}
}

View File

@ -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"
}
}

View File

@ -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,
)

View File

@ -0,0 +1,7 @@
package org.fdroid.download
data class HeadInfo(
val eTagChanged: Boolean,
val contentLength: Long?,
val lastModified: String?,
)

View File

@ -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.

View File

@ -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"/>

View File

@ -1 +1,2 @@
include ':app'
include ':download'