From 054d9c07efed3c70c9ef12b3d996f69f1080f4b3 Mon Sep 17 00:00:00 2001 From: =?utf8?q?David=20=E2=80=98Bombe=E2=80=99=20Roden?= Date: Wed, 7 Aug 2019 14:13:05 +0200 Subject: [PATCH] =?utf8?q?=E2=99=BB=EF=B8=8F=20Replace=20UpdateChecker=20w?= =?utf8?q?ith=20Kotlin=20version?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../net/pterodactylus/sone/core/UpdateChecker.java | 246 --------------------- .../net/pterodactylus/sone/core/UpdateChecker.kt | 123 +++++++++++ .../kotlin/net/pterodactylus/sone/utils/Objects.kt | 6 + .../net/pterodactylus/sone/utils/ObjectsTest.kt | 36 ++- 4 files changed, 161 insertions(+), 250 deletions(-) delete mode 100644 src/main/java/net/pterodactylus/sone/core/UpdateChecker.java create mode 100644 src/main/kotlin/net/pterodactylus/sone/core/UpdateChecker.kt diff --git a/src/main/java/net/pterodactylus/sone/core/UpdateChecker.java b/src/main/java/net/pterodactylus/sone/core/UpdateChecker.java deleted file mode 100644 index 9f6963f..0000000 --- a/src/main/java/net/pterodactylus/sone/core/UpdateChecker.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Sone - UpdateChecker.java - Copyright © 2011–2019 David 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.core; - -import static java.util.logging.Logger.getLogger; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.MalformedURLException; -import java.util.Date; -import java.util.Properties; -import java.util.logging.Level; -import java.util.logging.Logger; - -import javax.inject.Singleton; - -import net.pterodactylus.sone.core.event.UpdateFoundEvent; -import net.pterodactylus.sone.main.PluginHomepage; -import net.pterodactylus.sone.main.SonePlugin; -import net.pterodactylus.util.io.Closer; -import net.pterodactylus.util.version.Version; - -import com.google.common.eventbus.EventBus; -import com.google.inject.Inject; - -import freenet.keys.FreenetURI; -import freenet.support.api.Bucket; - -/** - * Watches the official Sone homepage for new releases. - */ -@Singleton -public class UpdateChecker { - - /** The logger. */ - private static final Logger logger = getLogger(UpdateChecker.class.getName()); - - /** The event bus. */ - private final EventBus eventBus; - - /** The Freenet interface. */ - private final FreenetInterface freenetInterface; - - /** The current URI of the homepage. */ - private FreenetURI currentUri; - - /** The latest known edition. */ - private long latestEdition = SonePlugin.getLatestEdition(); - - /** The current latest known version. */ - private Version currentLatestVersion; - private final Version currentRunningVersion; - - /** The release date of the latest version. */ - private long latestVersionDate; - - private final PluginHomepage pluginHomepage; - - /** - * Creates a new update checker. - * - * @param eventBus - * The event bus - * @param freenetInterface - * The freenet interface to use - */ - @Inject - public UpdateChecker(EventBus eventBus, FreenetInterface freenetInterface, Version currentVersion, PluginHomepage pluginHomepage) { - this.eventBus = eventBus; - this.freenetInterface = freenetInterface; - this.currentRunningVersion = currentVersion; - this.currentLatestVersion = currentVersion; - this.pluginHomepage = pluginHomepage; - } - - // - // ACCESSORS - // - - /** - * Returns whether a version that is later than the currently running - * version has been found. - * - * @return {@code true} if a new version was found - */ - public boolean hasLatestVersion() { - return currentLatestVersion.compareTo(currentRunningVersion) > 0; - } - - /** - * Returns the latest version. If no new latest version has been found, the - * current version is returned. - * - * @return The latest known version - */ - public Version getLatestVersion() { - return currentLatestVersion; - } - - /** - * Returns the release time of the latest version. If no new latest version - * has been found, the returned value is undefined. - * - * @return The release time of the latest version, if a new version was - * found - */ - public long getLatestVersionDate() { - return latestVersionDate; - } - - /** - * Returns the latest known edition of the Sone homepage. - * - * @return The latest edition of the Sone homepage - */ - public long getLatestEdition() { - return latestEdition; - } - - // - // ACTIONS - // - - /** - * Starts the update checker. - */ - public void start() { - try { - currentUri = new FreenetURI(pluginHomepage.getHomepage()); - } catch (MalformedURLException mue1) { - /* this can not really happen unless I screw up. */ - logger.log(Level.SEVERE, "Sone Homepage URI invalid!", mue1); - } - freenetInterface.registerUsk(currentUri, new FreenetInterface.Callback() { - - @Override - @SuppressWarnings("synthetic-access") - public void editionFound(FreenetURI uri, long edition, boolean newKnownGood, boolean newSlot) { - logger.log(Level.FINEST, String.format("Found update for %s: %d, %s, %s", uri, edition, newKnownGood, newSlot)); - if (newKnownGood || newSlot) { - Fetched uriResult = freenetInterface.fetchUri(uri.setMetaString(new String[] { "sone.properties" })); - if (uriResult == null) { - logger.log(Level.WARNING, String.format("Could not fetch properties of latest homepage: %s", uri)); - return; - } - Bucket resultBucket = uriResult.getFetchResult().asBucket(); - try { - parseProperties(resultBucket.getInputStream(), edition); - latestEdition = edition; - } catch (IOException ioe1) { - logger.log(Level.WARNING, String.format("Could not parse sone.properties of %s!", uri), ioe1); - } finally { - resultBucket.free(); - } - } - } - }); - } - - /** - * Stops the update checker. - */ - public void stop() { - freenetInterface.unregisterUsk(currentUri); - } - - // - // PRIVATE ACTIONS - // - - /** - * Parses the properties of the latest version and fires events, if - * necessary. - * - * @see UpdateFoundEvent - * @param propertiesInputStream - * The input stream to parse - * @param edition - * The latest edition of the Sone homepage - * @throws IOException - * if an I/O error occured - */ - private void parseProperties(InputStream propertiesInputStream, long edition) throws IOException { - Properties properties = new Properties(); - InputStreamReader inputStreamReader = null; - try { - inputStreamReader = new InputStreamReader(propertiesInputStream, "UTF-8"); - properties.load(inputStreamReader); - } finally { - Closer.close(inputStreamReader); - } - String versionString = properties.getProperty("CurrentVersion/Version"); - String releaseTimeString = properties.getProperty("CurrentVersion/ReleaseTime"); - if ((versionString == null) || (releaseTimeString == null)) { - logger.log(Level.INFO, "Invalid data parsed from properties."); - return; - } - Version version = Version.parse(versionString); - long releaseTime = 0; - try { - releaseTime = Long.parseLong(releaseTimeString); - } catch (NumberFormatException nfe1) { - /* ignore. */ - } - if ((version == null) || (releaseTime == 0)) { - logger.log(Level.INFO, "Could not parse data from properties."); - return; - } - if (version.compareTo(currentLatestVersion) > 0) { - currentLatestVersion = version; - latestVersionDate = releaseTime; - boolean disruptive = disruptiveVersionBetweenCurrentAndFound(properties); - logger.log(Level.INFO, String.format("Found new version: %s (%tc%s)", version, new Date(releaseTime), disruptive ? ", disruptive" : "")); - eventBus.post(new UpdateFoundEvent(version, releaseTime, edition, disruptive)); - } - } - - private boolean disruptiveVersionBetweenCurrentAndFound(Properties properties) { - for (String key : properties.stringPropertyNames()) { - if (key.startsWith("DisruptiveVersion/")) { - Version disruptiveVersion = Version.parse(key.substring("DisruptiveVersion/".length())); - if (disruptiveVersion.compareTo(currentRunningVersion) > 0) { - return true; - } - } - } - return false; - } - -} diff --git a/src/main/kotlin/net/pterodactylus/sone/core/UpdateChecker.kt b/src/main/kotlin/net/pterodactylus/sone/core/UpdateChecker.kt new file mode 100644 index 0000000..025c7e7 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/core/UpdateChecker.kt @@ -0,0 +1,123 @@ +/* + * Sone - UpdateChecker.java - Copyright © 2011–2019 David 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.core + +import com.google.common.eventbus.* +import com.google.common.primitives.* +import com.google.inject.Inject +import freenet.keys.* +import net.pterodactylus.sone.core.event.* +import net.pterodactylus.sone.main.* +import net.pterodactylus.sone.utils.* +import net.pterodactylus.util.version.Version +import java.io.* +import java.util.* +import java.util.logging.* +import java.util.logging.Logger.* +import javax.inject.Singleton + +/** + * Watches the official Sone homepage for new releases. + */ +@Singleton +class UpdateChecker @Inject constructor( + private val eventBus: EventBus, + private val freenetInterface: FreenetInterface, + private val currentRunningVersion: Version, + pluginHomepage: PluginHomepage) { + + private val logger: Logger = getLogger(UpdateChecker::class.java.name) + + private val currentUri by lazy { FreenetURI(pluginHomepage.homepage) } + + var latestEdition = SonePlugin.getLatestEdition() + private set + + var latestVersion: Version = currentRunningVersion + private set + + var latestVersionDate: Long = 0 + private set + + fun hasLatestVersion() = + latestVersion > currentRunningVersion + + fun start() { + freenetInterface.registerUsk(currentUri) { uri, edition, newKnownGood, newSlot -> + logger.log(Level.FINEST, String.format("Found update for %s: %d, %s, %s", uri, edition, newKnownGood, newSlot)) + if (newKnownGood || newSlot) { + try { + freenetInterface.fetchUri(uri.setMetaString(arrayOf("sone.properties"))) + ?.onNull { + logger.log(Level.WARNING, String.format("Could not fetch properties of latest homepage: %s", uri)) + }?.fetchResult + ?.asBucket()?.use { resultBucket -> + resultBucket.inputStream + .let { parseProperties(it) } + .let { extractCurrentVersion(it) } + .onNull { logger.log(Level.INFO, "Invalid data parsed from properties.") } + ?.takeIf { it.version > latestVersion } + ?.also { updateVersionInformation(it, edition) } + ?.also { logger.info { "Found new version: %s (%tc%s)".format(it.version, it.time, if (it.disruptive) ", disruptive" else "") } } + ?.also { eventBus.post(UpdateFoundEvent(it.version, it.time, edition, it.disruptive)) } + } + } catch (ioe1: IOException) { + logger.log(Level.WARNING, String.format("Could not parse sone.properties of %s!", uri), ioe1) + } + } + } + } + + fun stop() { + freenetInterface.unregisterUsk(currentUri) + } + + private fun updateVersionInformation(versionInformation: VersionInformation, edition: Long) { + latestVersion = versionInformation.version + latestVersionDate = versionInformation.time + latestEdition = edition + } + + private fun parseProperties(propertiesInputStream: InputStream) = + Properties().apply { + InputStreamReader(propertiesInputStream, "UTF-8").use { inputStreamReader -> + load(inputStreamReader) + } + } + + private fun extractCurrentVersion(properties: Properties) = + properties.getProperty("CurrentVersion/Version") + ?.let { Version.parse(it) } + ?.let { version -> + properties.getProperty("CurrentVersion/ReleaseTime") + ?.let { Longs.tryParse(it) } + ?.let { time -> + VersionInformation(version, time, disruptiveVersionBetweenCurrentAndFound(properties)) + } + } + + private fun disruptiveVersionBetweenCurrentAndFound(properties: Properties) = + properties.stringPropertyNames() + .filter { it.startsWith("DisruptiveVersion/") } + .map { it.removePrefix("DisruptiveVersion/") } + .map { Version.parse(it) } + .any { it > currentRunningVersion } + +} + +private data class VersionInformation(val version: Version, val time: Long, val disruptive: Boolean) diff --git a/src/main/kotlin/net/pterodactylus/sone/utils/Objects.kt b/src/main/kotlin/net/pterodactylus/sone/utils/Objects.kt index 6d1d413..d9ec162 100644 --- a/src/main/kotlin/net/pterodactylus/sone/utils/Objects.kt +++ b/src/main/kotlin/net/pterodactylus/sone/utils/Objects.kt @@ -5,3 +5,9 @@ val Any?.unit get() = Unit fun T?.throwOnNullIf(throwCondition: Boolean, exception: () -> Throwable) = if (this == null && throwCondition) throw exception() else this + +fun T?.onNull(block: () -> Unit) = this.also { + if (this == null) { + block() + } +} diff --git a/src/test/kotlin/net/pterodactylus/sone/utils/ObjectsTest.kt b/src/test/kotlin/net/pterodactylus/sone/utils/ObjectsTest.kt index 9e96bfe..76cc723 100644 --- a/src/test/kotlin/net/pterodactylus/sone/utils/ObjectsTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/utils/ObjectsTest.kt @@ -1,8 +1,9 @@ package net.pterodactylus.sone.utils -import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.MatcherAssert.* import org.hamcrest.Matchers.* -import org.junit.Test +import java.util.concurrent.* +import kotlin.test.* /** * Unit test for Object utils. @@ -19,9 +20,11 @@ class ObjectsTest { assertThat(null.asList(), empty()) } - @Test(expected = IllegalArgumentException::class) + @Test fun `exception is thrown for null and true condition`() { - null.throwOnNullIf(true) { IllegalArgumentException() } + assertFailsWith(IllegalArgumentException::class) { + null.throwOnNullIf(true) { IllegalArgumentException() } + } } @Test @@ -41,4 +44,29 @@ class ObjectsTest { assertThat(any.throwOnNullIf(false) { IllegalArgumentException() }, equalTo(any)) } + @Test + fun `onNull is executed on null`() { + val called = CountDownLatch(1) + null.onNull { called.countDown() } + assertThat(called.count, equalTo(0L)) + } + + @Test + fun `onNull returns null when called on null`() { + assertThat(null.onNull {}, nullValue()) + } + + @Test + fun `onNull is not executed on non-null`() { + val called = CountDownLatch(1) + Any().onNull { called.countDown() } + assertThat(called.count, equalTo(1L)) + } + + @Test + fun `onNull returns object when called on non-null`() { + val any = Any() + assertThat(any.onNull {}, sameInstance(any)) + } + } -- 2.7.4