From 85f599866faef4142c1700702af4aa57ee9daaf8 Mon Sep 17 00:00:00 2001 From: =?utf8?q?David=20=E2=80=98Bombe=E2=80=99=20Roden?= Date: Wed, 7 Aug 2019 07:37:10 +0200 Subject: [PATCH] =?utf8?q?=E2=99=BB=EF=B8=8F=20Add=20first=20stab=20at=20a?= =?utf8?q?sync=20freenet=20interface?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 + .../sone/freenet/AsyncFreenetInterface.kt | 27 ++++++++ .../sone/freenet/AsyncFreenetInterfaceTest.kt | 80 ++++++++++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 src/main/kotlin/net/pterodactylus/sone/freenet/AsyncFreenetInterface.kt create mode 100644 src/test/kotlin/net/pterodactylus/sone/freenet/AsyncFreenetInterfaceTest.kt diff --git a/build.gradle b/build.gradle index a0861a6..9505ab6 100644 --- a/build.gradle +++ b/build.gradle @@ -44,6 +44,8 @@ dependencies { provided group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.54' compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8' + compile group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: '1.3.0-RC' + compile group: 'net.pterodactylus', name: 'utils', version: '0.12.4' compile group: 'com.google.inject', name: 'guice', version: '4.2.2' compile group: 'com.google.guava', name: 'guava', version: '27.0.1-android' diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/AsyncFreenetInterface.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/AsyncFreenetInterface.kt new file mode 100644 index 0000000..8219d7e --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/AsyncFreenetInterface.kt @@ -0,0 +1,27 @@ +package net.pterodactylus.sone.freenet + +import freenet.client.* +import freenet.keys.* +import kotlinx.coroutines.* +import net.pterodactylus.sone.core.* + +class AsyncFreenetInterface(private val freenetClient: FreenetClient) { + + suspend fun fetchUri(freenetUri: FreenetURI): Fetched { + var currentUri = freenetUri + var result: FetchResult? = null + while (result == null) { + try { + result = withContext(Dispatchers.Default) { freenetClient.fetch(currentUri) } + } catch (fetchException: FetchException) { + if (fetchException.mode == FetchException.FetchExceptionMode.PERMANENT_REDIRECT) { + currentUri = fetchException.newURI + continue + } else + throw fetchException + } + } + return Fetched(currentUri, result) + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/freenet/AsyncFreenetInterfaceTest.kt b/src/test/kotlin/net/pterodactylus/sone/freenet/AsyncFreenetInterfaceTest.kt new file mode 100644 index 0000000..915a4b5 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/freenet/AsyncFreenetInterfaceTest.kt @@ -0,0 +1,80 @@ +/** + * Sone - AsyncFreenetInterfaceTest.kt - Copyright © 2019 David ‘Bombe’ Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.freenet + +import freenet.client.* +import freenet.keys.* +import freenet.support.io.* +import kotlinx.coroutines.* +import net.pterodactylus.sone.core.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import java.util.concurrent.atomic.* +import kotlin.test.* + +class AsyncFreenetInterfaceTest { + + @Test + fun `returned deferred is completed by success`() = runBlocking { + val result = FetchResult(ClientMetadata(), NullBucket()) + val freenetClient = object : FreenetClient { + override fun fetch(freenetKey: FreenetURI) = result + } + val freenetInterface = AsyncFreenetInterface(freenetClient) + val fetched = async { freenetInterface.fetchUri(FreenetURI("KSK@GPL.txt")) } + + withTimeout(1000) { + assertThat(fetched.await(), equalTo(Fetched(FreenetURI("KSK@GPL.txt"), result))) + } + } + + @Test + fun `permanent redircts are being followed`() = runBlocking { + val result = FetchResult(ClientMetadata(), NullBucket()) + val freenetClient = object : FreenetClient { + val redirected = AtomicBoolean(false) + override fun fetch(freenetKey: FreenetURI) = + if (redirected.compareAndSet(false, true)) + throw FetchException(FetchException.FetchExceptionMode.PERMANENT_REDIRECT, FreenetURI("KSK@GPLv3.txt")) + else result + } + val freenetInterface = AsyncFreenetInterface(freenetClient) + val fetched = async { freenetInterface.fetchUri(FreenetURI("KSK@GPL.txt")) } + + withTimeout(1000) { + assertThat(fetched.await(), equalTo(Fetched(FreenetURI("KSK@GPLv3.txt"), result))) + } + } + + @Test + fun `fetch errors are being re-thrown`() = runBlocking { + val freenetClient = object : FreenetClient { + override fun fetch(freenetKey: FreenetURI) = + throw FetchException(FetchException.FetchExceptionMode.ALL_DATA_NOT_FOUND) + } + val freenetInterface = AsyncFreenetInterface(freenetClient) + val fetched = supervisorScope { async { freenetInterface.fetchUri(FreenetURI("KSK@GPL.txt")) } } + + withTimeout(1000) { + assertFailsWith(FetchException::class) { + fetched.await() + } + } + } + +} -- 2.7.4