🔀 Merge next add-audio-player
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Thu, 21 Nov 2019 06:20:05 +0000 (07:20 +0100)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Thu, 21 Nov 2019 06:22:57 +0000 (07:22 +0100)
159 files changed:
build.gradle
src/main/java/net/pterodactylus/sone/core/Core.java
src/main/java/net/pterodactylus/sone/core/FreenetInterface.java
src/main/java/net/pterodactylus/sone/core/SoneModificationDetector.java
src/main/java/net/pterodactylus/sone/core/SoneParser.java
src/main/java/net/pterodactylus/sone/core/SoneRescuer.java
src/main/java/net/pterodactylus/sone/data/impl/AlbumImpl.java
src/main/java/net/pterodactylus/sone/data/impl/ImageImpl.java
src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.kt
src/main/java/net/pterodactylus/sone/freenet/Key.java [deleted file]
src/main/java/net/pterodactylus/sone/freenet/L10nFilter.java [deleted file]
src/main/java/net/pterodactylus/sone/freenet/plugin/PluginConnector.java [deleted file]
src/main/java/net/pterodactylus/sone/freenet/plugin/PluginException.java [deleted file]
src/main/java/net/pterodactylus/sone/freenet/plugin/event/ReceivedReplyEvent.java [deleted file]
src/main/java/net/pterodactylus/sone/freenet/wot/Context.java [deleted file]
src/main/java/net/pterodactylus/sone/freenet/wot/DefaultIdentity.java [deleted file]
src/main/java/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentity.java [deleted file]
src/main/java/net/pterodactylus/sone/freenet/wot/Identity.java
src/main/java/net/pterodactylus/sone/freenet/wot/IdentityChangeDetector.java [deleted file]
src/main/java/net/pterodactylus/sone/freenet/wot/IdentityChangeDetector.kt [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/freenet/wot/IdentityChangeEventSender.java [deleted file]
src/main/java/net/pterodactylus/sone/freenet/wot/IdentityLoader.java [deleted file]
src/main/java/net/pterodactylus/sone/freenet/wot/IdentityManager.java [deleted file]
src/main/java/net/pterodactylus/sone/freenet/wot/IdentityManagerImpl.java [deleted file]
src/main/java/net/pterodactylus/sone/freenet/wot/Trust.java [deleted file]
src/main/java/net/pterodactylus/sone/freenet/wot/WebOfTrustConnector.java [deleted file]
src/main/java/net/pterodactylus/sone/freenet/wot/WebOfTrustException.java [deleted file]
src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityAddedEvent.java [deleted file]
src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityEvent.java [deleted file]
src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityRemovedEvent.java [deleted file]
src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityUpdatedEvent.java [deleted file]
src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityAddedEvent.java [deleted file]
src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityEvent.java [deleted file]
src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityRemovedEvent.java [deleted file]
src/main/java/net/pterodactylus/sone/notify/ListNotification.java [deleted file]
src/main/java/net/pterodactylus/sone/template/UnknownDateFilter.java
src/main/java/net/pterodactylus/sone/web/WebInterface.java
src/main/java/net/pterodactylus/sone/web/page/PageToadlet.java
src/main/kotlin/net/pterodactylus/sone/freenet/BaseL10nTranslation.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/freenet/FreenetURIs.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/freenet/L10nFilter.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/freenet/Translation.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/freenet/plugin/FredPluginConnector.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/freenet/plugin/PluginConnector.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/freenet/plugin/PluginException.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/freenet/plugin/PluginRespiratorFacade.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/freenet/wot/Context.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/freenet/wot/DefaultIdentity.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentity.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/freenet/wot/IdentityChangeEventSender.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/freenet/wot/IdentityLoader.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/freenet/wot/IdentityManager.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/freenet/wot/IdentityManagerImpl.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/freenet/wot/PluginWebOfTrustConnector.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/freenet/wot/Trust.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/freenet/wot/WebOfTrustConnector.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/freenet/wot/WebOfTrustException.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/IdentityAddedEvent.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/IdentityRemovedEvent.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/IdentityUpdatedEvent.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/OwnIdentityAddedEvent.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/OwnIdentityRemovedEvent.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/main/FreenetModule.kt
src/main/kotlin/net/pterodactylus/sone/main/SoneModule.kt
src/main/kotlin/net/pterodactylus/sone/notify/ListNotification.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/template/HistogramRenderer.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/utils/Booleans.kt
src/main/kotlin/net/pterodactylus/sone/web/WebInterfaceModule.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/GetTranslationAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/page/FreenetRequest.kt
src/main/kotlin/net/pterodactylus/sone/web/page/SoneRequest.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/MetricsPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/RescuePage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/SoneTemplatePage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/UploadImagePage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/ViewSonePage.kt
src/main/resources/i18n/sone.de.properties
src/main/resources/i18n/sone.en.properties
src/main/resources/i18n/sone.es.properties
src/main/resources/i18n/sone.fr.properties
src/main/resources/i18n/sone.ja.properties
src/main/resources/i18n/sone.no.properties
src/main/resources/i18n/sone.pl.properties
src/main/resources/i18n/sone.ru.properties
src/main/resources/static/css/sone.css
src/main/resources/templates/include/viewPost.html
src/main/resources/templates/include/viewReply.html
src/main/resources/templates/metrics.html
src/main/resources/templates/rescue.html
src/test/java/net/pterodactylus/sone/core/SoneRescuerTest.java [new file with mode: 0644]
src/test/java/net/pterodactylus/sone/freenet/KeyTest.java [deleted file]
src/test/java/net/pterodactylus/sone/freenet/wot/DefaultIdentityTest.java [deleted file]
src/test/java/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentityTest.java [deleted file]
src/test/java/net/pterodactylus/sone/freenet/wot/Identities.java [deleted file]
src/test/java/net/pterodactylus/sone/freenet/wot/IdentityChangeDetectorTest.java [deleted file]
src/test/java/net/pterodactylus/sone/freenet/wot/IdentityChangeEventSenderTest.java [deleted file]
src/test/java/net/pterodactylus/sone/freenet/wot/IdentityLoaderTest.java [deleted file]
src/test/java/net/pterodactylus/sone/freenet/wot/IdentityManagerTest.java [deleted file]
src/test/java/net/pterodactylus/sone/freenet/wot/IdentityManagerTest.kt [new file with mode: 0644]
src/test/java/net/pterodactylus/sone/freenet/wot/event/IdentityEventTest.java [deleted file]
src/test/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityEventTest.java [deleted file]
src/test/java/net/pterodactylus/sone/main/DebugLoadersTest.java
src/test/java/net/pterodactylus/sone/main/DefaultLoadersTest.java
src/test/java/net/pterodactylus/sone/notify/ListNotificationTest.java [deleted file]
src/test/java/net/pterodactylus/sone/test/Matchers.java
src/test/kotlin/net/pterodactylus/sone/core/ConfigurationSoneParserTest.kt
src/test/kotlin/net/pterodactylus/sone/core/DefaultElementLoaderTest.kt
src/test/kotlin/net/pterodactylus/sone/core/FreenetInterfaceTest.kt
src/test/kotlin/net/pterodactylus/sone/core/ImageInserterTest.kt
src/test/kotlin/net/pterodactylus/sone/core/SoneInserterTest.kt
src/test/kotlin/net/pterodactylus/sone/core/SoneParserTest.kt
src/test/kotlin/net/pterodactylus/sone/core/SoneRescuerTest.kt [deleted file]
src/test/kotlin/net/pterodactylus/sone/database/memory/MemoryDatabaseTest.kt
src/test/kotlin/net/pterodactylus/sone/freenet/BaseL10nTranslationTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/freenet/FreenetURIsTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/freenet/L10nFilterTest.kt
src/test/kotlin/net/pterodactylus/sone/freenet/plugin/FredPluginConnectorTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/freenet/plugin/PluginRespiratorFacadeTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/freenet/wot/DefaultIdentityTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentityTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/freenet/wot/Identities.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/freenet/wot/IdentityChangeDetectorTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/freenet/wot/IdentityChangeEventSenderTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/freenet/wot/IdentityLoaderTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/freenet/wot/PluginWebOfTrustConnectorTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/main/FreenetModuleTest.kt
src/test/kotlin/net/pterodactylus/sone/main/SoneModuleTest.kt
src/test/kotlin/net/pterodactylus/sone/notify/ListNotificationTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/template/HistogramRendererTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/template/LinkedElementsFilterTest.kt
src/test/kotlin/net/pterodactylus/sone/template/ParserFilterTest.kt
src/test/kotlin/net/pterodactylus/sone/template/ProfileAccessorTest.kt
src/test/kotlin/net/pterodactylus/sone/template/ShortenFilterTest.kt
src/test/kotlin/net/pterodactylus/sone/template/UnknownDateFilterTest.kt
src/test/kotlin/net/pterodactylus/sone/test/Matchers.kt
src/test/kotlin/net/pterodactylus/sone/test/Mockotlin.kt
src/test/kotlin/net/pterodactylus/sone/text/FreenetLinkPartTest.kt
src/test/kotlin/net/pterodactylus/sone/text/LinkPartTest.kt
src/test/kotlin/net/pterodactylus/sone/text/SonePartTest.kt
src/test/kotlin/net/pterodactylus/sone/utils/BooleansTest.kt
src/test/kotlin/net/pterodactylus/sone/web/WebInterfaceModuleTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/TestObjects.kt
src/test/kotlin/net/pterodactylus/sone/web/page/FreenetRequestTest.kt
src/test/kotlin/net/pterodactylus/sone/web/page/SoneRequestTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteSonePageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/DismissNotificationPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/DistrustPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/EditAlbumPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/EditImagePageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/EditProfileFieldPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/EditProfilePageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/FollowSonePageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/ImageBrowserPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/IndexPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/KnownSonesPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/MetricsPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/RescuePageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/SoneTemplatePageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/WebPageTest.kt

index a9e11ca..bff4143 100644 (file)
@@ -1,18 +1,13 @@
-group = 'net.pterodactylus'
-version = '80'
 
-buildscript {
-    ext.kotlinVersion = '1.3.41'
-    repositories {
-        mavenCentral()
-    }
-    dependencies {
-        classpath group: 'info.solidsoft.gradle.pitest', name: 'gradle-pitest-plugin', version: '1.4.0'
-        classpath group: 'org.jetbrains.kotlin', name: 'kotlin-gradle-plugin', version: kotlinVersion
-        classpath group: 'org.jetbrains.kotlin', name: 'kotlin-noarg', version: kotlinVersion
-    }
+plugins {
+    id 'org.jetbrains.kotlin.jvm' version '1.3.50'
+    id 'org.jetbrains.kotlin.plugin.noarg' version '1.3.50'
+    id 'info.solidsoft.pitest' version '1.4.5'
 }
 
+group = 'net.pterodactylus'
+version = '80'
+
 repositories {
      mavenCentral()
      maven { url "https://maven.pterodactylus.net/" }
@@ -27,8 +22,6 @@ tasks.withType(JavaCompile) {
        options.encoding = 'UTF-8'
 }
 
-apply plugin: 'kotlin'
-
 configurations {
     provided {
         dependencies.all { dep ->
@@ -95,9 +88,8 @@ jacoco {
 
 jacocoTestReport.dependsOn test
 
-apply plugin: 'info.solidsoft.pitest'
-
 pitest {
+    pitestVersion = '1.4.10'
     outputFormats = ['HTML', 'XML']
     timestampedReports = false
     timeoutFactor = 3.0
@@ -129,8 +121,6 @@ task countLines {
     dependsOn tasks.countLinesTest
 }
 
-apply plugin: 'kotlin-noarg'
-
 noArg {
     annotation('net.pterodactylus.sone.main.NoArg')
 }
index c3af217..a8d753e 100644 (file)
@@ -88,6 +88,7 @@ import net.pterodactylus.util.service.AbstractService;
 import net.pterodactylus.util.thread.NamedThreadFactory;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Stopwatch;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.HashMultimap;
 import com.google.common.collect.Multimap;
@@ -176,6 +177,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
        private volatile long lastConfigurationUpdate;
 
        private final MetricRegistry metricRegistry;
+       private final Histogram configurationSaveTimeHistogram;
 
        /**
         * Creates a new core.
@@ -207,6 +209,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                this.database = database;
                this.metricRegistry = metricRegistry;
                preferences = new Preferences(eventBus);
+               this.configurationSaveTimeHistogram = metricRegistry.histogram("configuration.save.duration", () -> new Histogram(new ExponentiallyDecayingReservoir(3000, 0)));
        }
 
        //
@@ -631,6 +634,10 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                loadSone(sone);
                database.storeSone(sone);
                sone.setStatus(SoneStatus.idle);
+               if (sone.getPosts().isEmpty() && sone.getReplies().isEmpty()) {
+                       // dirty hack
+                       lockSone(sone);
+               }
                soneInserter.start();
                return sone;
        }
@@ -1363,7 +1370,8 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                synchronized (soneInserters) {
                        for (Entry<Sone, SoneInserter> soneInserter : soneInserters.entrySet()) {
                                soneInserter.getValue().stop();
-                               saveSone(soneInserter.getKey());
+                               Sone latestSone = getLocalSone(soneInserter.getKey().getId());
+                               saveSone(latestSone);
                        }
                }
                synchronized (soneRescuers) {
@@ -1542,7 +1550,9 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        database.save();
 
                        /* now save it. */
+                       Stopwatch stopwatch = Stopwatch.createStarted();
                        configuration.save();
+                       configurationSaveTimeHistogram.update(stopwatch.elapsed(TimeUnit.MICROSECONDS));
 
                } catch (ConfigurationException ce1) {
                        logger.log(Level.SEVERE, "Could not store configuration!", ce1);
@@ -1582,7 +1592,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         */
        @Subscribe
        public void ownIdentityAdded(OwnIdentityAddedEvent ownIdentityAddedEvent) {
-               OwnIdentity ownIdentity = ownIdentityAddedEvent.ownIdentity();
+               OwnIdentity ownIdentity = ownIdentityAddedEvent.getOwnIdentity();
                logger.log(Level.FINEST, String.format("Adding OwnIdentity: %s", ownIdentity));
                if (ownIdentity.hasContext("Sone")) {
                        addLocalSone(ownIdentity);
@@ -1597,7 +1607,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         */
        @Subscribe
        public void ownIdentityRemoved(OwnIdentityRemovedEvent ownIdentityRemovedEvent) {
-               OwnIdentity ownIdentity = ownIdentityRemovedEvent.ownIdentity();
+               OwnIdentity ownIdentity = ownIdentityRemovedEvent.getOwnIdentity();
                logger.log(Level.FINEST, String.format("Removing OwnIdentity: %s", ownIdentity));
                trustedIdentities.removeAll(ownIdentity);
        }
@@ -1610,9 +1620,9 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         */
        @Subscribe
        public void identityAdded(IdentityAddedEvent identityAddedEvent) {
-               Identity identity = identityAddedEvent.identity();
+               Identity identity = identityAddedEvent.getIdentity();
                logger.log(Level.FINEST, String.format("Adding Identity: %s", identity));
-               trustedIdentities.put(identityAddedEvent.ownIdentity(), identity);
+               trustedIdentities.put(identityAddedEvent.getOwnIdentity(), identity);
                addRemoteSone(identity);
        }
 
@@ -1624,7 +1634,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         */
        @Subscribe
        public void identityUpdated(IdentityUpdatedEvent identityUpdatedEvent) {
-               Identity identity = identityUpdatedEvent.identity();
+               Identity identity = identityUpdatedEvent.getIdentity();
                final Sone sone = getRemoteSone(identity.getId());
                if (sone.isLocal()) {
                        return;
@@ -1648,8 +1658,8 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         */
        @Subscribe
        public void identityRemoved(IdentityRemovedEvent identityRemovedEvent) {
-               OwnIdentity ownIdentity = identityRemovedEvent.ownIdentity();
-               Identity identity = identityRemovedEvent.identity();
+               OwnIdentity ownIdentity = identityRemovedEvent.getOwnIdentity();
+               Identity identity = identityRemovedEvent.getIdentity();
                trustedIdentities.remove(ownIdentity, identity);
                for (Entry<OwnIdentity, Collection<Identity>> trustedIdentity : trustedIdentities.asMap().entrySet()) {
                        if (trustedIdentity.getKey().equals(ownIdentity)) {
index b3b32b3..28c8e22 100644 (file)
@@ -21,7 +21,6 @@ import static freenet.keys.USK.create;
 import static java.lang.String.format;
 import static java.util.logging.Level.WARNING;
 import static java.util.logging.Logger.getLogger;
-import static net.pterodactylus.sone.freenet.Key.routingKey;
 
 import java.io.IOException;
 import java.net.MalformedURLException;
@@ -75,6 +74,7 @@ import freenet.support.api.Bucket;
 import freenet.support.api.RandomAccessBucket;
 import freenet.support.io.ArrayBucket;
 import freenet.support.io.ResumeFailedException;
+import net.pterodactylus.sone.freenet.*;
 
 /**
  * Contains all necessary functionality for interacting with the Freenet node.
@@ -255,7 +255,7 @@ public class FreenetInterface {
        public void registerActiveUsk(FreenetURI requestUri,
                        USKCallback uskCallback) {
                try {
-                       soneUskCallbacks.put(routingKey(requestUri), uskCallback);
+                       soneUskCallbacks.put(FreenetURIsKt.getRoutingKeyString(requestUri), uskCallback);
                        node.clientCore.uskManager.subscribe(create(requestUri),
                                        uskCallback, true, requestClient);
                } catch (MalformedURLException mue1) {
@@ -267,7 +267,7 @@ public class FreenetInterface {
        public void registerPassiveUsk(FreenetURI requestUri,
                        USKCallback uskCallback) {
                try {
-                       soneUskCallbacks.put(routingKey(requestUri), uskCallback);
+                       soneUskCallbacks.put(FreenetURIsKt.getRoutingKeyString(requestUri), uskCallback);
                        node.clientCore
                                        .uskManager
                                        .subscribe(create(requestUri), uskCallback, false, requestClient);
index 92fa3dc..e5e20d9 100644 (file)
@@ -24,7 +24,7 @@ class SoneModificationDetector {
        private final Ticker ticker;
        private final LockableFingerprintProvider lockableFingerprintProvider;
        private final AtomicInteger insertionDelay;
-       private Optional<Long> lastModificationTime;
+       private Long lastModificationTime;
        private String lastInsertFingerprint;
        private String lastCheckFingerprint;
 
@@ -42,18 +42,18 @@ class SoneModificationDetector {
 
        public boolean isEligibleForInsert() {
                if (lockableFingerprintProvider.isLocked()) {
-                       lastModificationTime = absent();
+                       lastModificationTime = null;
                        lastCheckFingerprint = "";
                        return false;
                }
                String fingerprint = lockableFingerprintProvider.getFingerprint();
                if (fingerprint.equals(lastInsertFingerprint)) {
-                       lastModificationTime = absent();
+                       lastModificationTime = null;
                        lastCheckFingerprint = fingerprint;
                        return false;
                }
                if (!Objects.equal(lastCheckFingerprint, fingerprint)) {
-                       lastModificationTime = of(ticker.read());
+                       lastModificationTime = ticker.read();
                        lastCheckFingerprint = fingerprint;
                        return false;
                }
@@ -67,11 +67,11 @@ class SoneModificationDetector {
        public void setFingerprint(String fingerprint) {
                lastInsertFingerprint = fingerprint;
                lastCheckFingerprint = lastInsertFingerprint;
-               lastModificationTime = absent();
+               lastModificationTime = null;
        }
 
        private boolean insertionDelayHasPassed() {
-               return NANOSECONDS.toSeconds(ticker.read() - lastModificationTime.get()) >= insertionDelay.get();
+               return NANOSECONDS.toSeconds(ticker.read() - lastModificationTime) >= insertionDelay.get();
        }
 
        public boolean isModified() {
index d82dab5..e1fd9ff 100644 (file)
@@ -33,7 +33,7 @@ public class SoneParser {
        @Inject
        public SoneParser(Database database, MetricRegistry metricRegistry) {
                this.database = database;
-               this.soneParsingDurationHistogram = metricRegistry.histogram("sone.parsing.duration", () -> new Histogram(new ExponentiallyDecayingReservoir(3000, 0)));
+               this.soneParsingDurationHistogram = metricRegistry.histogram("sone.parse.duration", () -> new Histogram(new ExponentiallyDecayingReservoir(3000, 0)));
        }
 
        @Nullable
index adc0925..2246ac9 100644 (file)
@@ -108,6 +108,18 @@ public class SoneRescuer extends AbstractService {
        }
 
        /**
+        * Sets the edition to rescue.
+        *
+        * @param edition
+        *            The edition to rescue
+        * @return This Sone rescuer
+        */
+       public SoneRescuer setEdition(long edition) {
+               currentEdition = edition;
+               return this;
+       }
+
+       /**
         * Sets whether the last fetch was successful.
         *
         * @return {@code true} if the last fetch was successful, {@code false}
@@ -123,7 +135,9 @@ public class SoneRescuer extends AbstractService {
        //
 
        /**
-        * Starts the next fetch.
+        * Starts the next fetch. If you want to fetch a different edition than “the
+        * next older one,” remember to call {@link #setEdition(long)} before
+        * calling this method.
         */
        public void startNextFetch() {
                fetching = true;
@@ -145,14 +159,13 @@ public class SoneRescuer extends AbstractService {
                        }
                        if (fetching) {
                                core.lockSone(sone);
-                               FreenetURI soneUri = sone.getRequestUri().setKeyType("SSK").setDocName("Sone-" + getNextEdition()).setMetaString(new String[] { "sone.xml" });
+                               FreenetURI soneUri = sone.getRequestUri().setKeyType("SSK").setDocName("Sone-" + currentEdition).setMetaString(new String[] { "sone.xml" });
                                System.out.println("URI: " + soneUri);
                                Sone fetchedSone = soneDownloader.fetchSone(sone, soneUri, true);
                                System.out.println("Sone: " + fetchedSone);
                                lastFetchSuccessful = (fetchedSone != null);
                                if (lastFetchSuccessful) {
                                        core.updateSone(fetchedSone, true);
-                                       currentEdition = getNextEdition();
                                }
                                fetching = false;
                        }
index 3ec362f..a8768e1 100644 (file)
 
 package net.pterodactylus.sone.data.impl;
 
-import static com.google.common.base.Optional.absent;
-import static com.google.common.base.Optional.fromNullable;
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-import net.pterodactylus.sone.data.Album;
-import net.pterodactylus.sone.data.Image;
-import net.pterodactylus.sone.data.Sone;
-
-import com.google.common.base.Function;
-import com.google.common.base.Optional;
-import com.google.common.base.Predicates;
-import com.google.common.collect.Collections2;
-import com.google.common.hash.Hasher;
+import java.util.*;
+import javax.annotation.*;
+
+import com.google.common.base.*;
+import com.google.common.collect.*;
 import com.google.common.hash.Hashing;
+import com.google.common.hash.*;
+import net.pterodactylus.sone.data.*;
+
+import static com.google.common.base.Preconditions.*;
+import static java.nio.charset.StandardCharsets.*;
 
 /**
  * Container for images that can also contain nested {@link AlbumImpl}s.
@@ -258,32 +247,33 @@ public class AlbumImpl implements Album {
        public Modifier modify() throws IllegalStateException {
                // TODO: reenable check for local Sones
                return new Modifier() {
-                       private Optional<String> title = absent();
-
-                       private Optional<String> description = absent();
+                       @Nullable
+                       private String title;
+                       @Nullable
+                       private String description;
 
                        @Override
                        public Modifier setTitle(String title) {
-                               this.title = fromNullable(title);
+                               this.title = title;
                                return this;
                        }
 
                        @Override
                        public Modifier setDescription(String description) {
-                               this.description = fromNullable(description);
+                               this.description = description;
                                return this;
                        }
 
                        @Override
                        public Album update() throws IllegalStateException {
-                               if (title.isPresent() && title.get().trim().isEmpty()) {
+                               if (title != null && title.trim().isEmpty()) {
                                        throw new AlbumTitleMustNotBeEmpty();
                                }
-                               if (title.isPresent()) {
-                                       AlbumImpl.this.title = title.get();
+                               if (title != null) {
+                                       AlbumImpl.this.title = title;
                                }
-                               if (description.isPresent()) {
-                                       AlbumImpl.this.description = description.get();
+                               if (description != null) {
+                                       AlbumImpl.this.description = description;
                                }
                                return AlbumImpl.this;
                        }
index a54a8de..b357b6a 100644 (file)
  */
 package net.pterodactylus.sone.data.impl;
 
-import static com.google.common.base.Optional.absent;
-import static com.google.common.base.Optional.fromNullable;
-import static com.google.common.base.Optional.of;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-import static java.nio.charset.StandardCharsets.UTF_8;
+import java.util.*;
+import javax.annotation.*;
 
-import java.util.UUID;
+import com.google.common.hash.*;
+import net.pterodactylus.sone.data.*;
 
-import net.pterodactylus.sone.data.Album;
-import net.pterodactylus.sone.data.Image;
-import net.pterodactylus.sone.data.Sone;
-
-import com.google.common.base.Optional;
-import com.google.common.hash.Hasher;
-import com.google.common.hash.Hashing;
+import static com.google.common.base.Preconditions.*;
+import static java.nio.charset.StandardCharsets.*;
 
 /**
  * Container for image metadata.
@@ -146,93 +138,94 @@ public class ImageImpl implements Image {
        public Modifier modify() throws IllegalStateException {
                // TODO: reenable check for local images
                return new Modifier() {
-                       private Optional<Sone> sone = absent();
-
-                       private Optional<Long> creationTime = absent();
-
-                       private Optional<String> key = absent();
-
-                       private Optional<String> title = absent();
-
-                       private Optional<String> description = absent();
-
-                       private Optional<Integer> width = absent();
-
-                       private Optional<Integer> height = absent();
+                       @Nullable
+                       private Sone sone;
+                       @Nullable
+                       private Long creationTime;
+                       @Nullable
+                       private String key;
+                       @Nullable
+                       private String title;
+                       @Nullable
+                       private String description;
+                       @Nullable
+                       private Integer width;
+                       @Nullable
+                       private Integer height;
 
                        @Override
                        public Modifier setSone(Sone sone) {
-                               this.sone = fromNullable(sone);
+                               this.sone = sone;
                                return this;
                        }
 
                        @Override
                        public Modifier setCreationTime(long creationTime) {
-                               this.creationTime = of(creationTime);
+                               this.creationTime = creationTime;
                                return this;
                        }
 
                        @Override
                        public Modifier setKey(String key) {
-                               this.key = fromNullable(key);
+                               this.key = key;
                                return this;
                        }
 
                        @Override
                        public Modifier setTitle(String title) {
-                               this.title = fromNullable(title);
+                               this.title = title;
                                return this;
                        }
 
                        @Override
                        public Modifier setDescription(String description) {
-                               this.description = fromNullable(description);
+                               this.description = description;
                                return this;
                        }
 
                        @Override
                        public Modifier setWidth(int width) {
-                               this.width = of(width);
+                               this.width = width;
                                return this;
                        }
 
                        @Override
                        public Modifier setHeight(int height) {
-                               this.height = of(height);
+                               this.height = height;
                                return this;
                        }
 
                        @Override
                        public Image update() throws IllegalStateException {
-                               checkState(!sone.isPresent() || (ImageImpl.this.sone == null) || sone.get().equals(ImageImpl.this.sone), "can not change Sone once set");
-                               checkState(!creationTime.isPresent() || ((ImageImpl.this.creationTime == 0) || (ImageImpl.this.creationTime == creationTime.get())), "can not change creation time once set");
-                               checkState(!key.isPresent() || (ImageImpl.this.key == null) || key.get().equals(ImageImpl.this.key), "can not change key once set");
-                               if (title.isPresent() && title.get().trim().isEmpty()) {
+                               checkState(sone == null || (ImageImpl.this.sone == null) || sone.equals(ImageImpl.this.sone), "can not change Sone once set");
+                               checkState(creationTime == null || ((ImageImpl.this.creationTime == 0) || (ImageImpl.this.creationTime == creationTime)), "can not change creation time once set");
+                               checkState(key == null || (ImageImpl.this.key == null) || key.equals(ImageImpl.this.key), "can not change key once set");
+                               if (title != null && title.trim().isEmpty()) {
                                        throw new ImageTitleMustNotBeEmpty();
                                }
-                               checkState(!width.isPresent() || (ImageImpl.this.width == 0) || width.get().equals(ImageImpl.this.width), "can not change width once set");
-                               checkState(!height.isPresent() || (ImageImpl.this.height == 0) || height.get().equals(ImageImpl.this.height), "can not change height once set");
+                               checkState(width == null || (ImageImpl.this.width == 0) || width.equals(ImageImpl.this.width), "can not change width once set");
+                               checkState(height == null || (ImageImpl.this.height == 0) || height.equals(ImageImpl.this.height), "can not change height once set");
 
-                               if (sone.isPresent()) {
-                                       ImageImpl.this.sone = sone.get();
+                               if (sone != null) {
+                                       ImageImpl.this.sone = sone;
                                }
-                               if (creationTime.isPresent()) {
-                                       ImageImpl.this.creationTime = creationTime.get();
+                               if (creationTime != null) {
+                                       ImageImpl.this.creationTime = creationTime;
                                }
-                               if (key.isPresent()) {
-                                       ImageImpl.this.key = key.get();
+                               if (key != null) {
+                                       ImageImpl.this.key = key;
                                }
-                               if (title.isPresent()) {
-                                       ImageImpl.this.title = title.get();
+                               if (title != null) {
+                                       ImageImpl.this.title = title;
                                }
-                               if (description.isPresent()) {
-                                       ImageImpl.this.description = description.get();
+                               if (description != null) {
+                                       ImageImpl.this.description = description;
                                }
-                               if (width.isPresent()) {
-                                       ImageImpl.this.width = width.get();
+                               if (width != null) {
+                                       ImageImpl.this.width = width;
                                }
-                               if (height.isPresent()) {
-                                       ImageImpl.this.height = height.get();
+                               if (height != null) {
+                                       ImageImpl.this.height = height;
                                }
 
                                return ImageImpl.this;
index a9459e8..857560e 100644 (file)
@@ -41,7 +41,7 @@ import net.pterodactylus.sone.database.ImageBuilder
 import net.pterodactylus.sone.database.PostBuilder
 import net.pterodactylus.sone.database.PostDatabase
 import net.pterodactylus.sone.database.PostReplyBuilder
-import net.pterodactylus.sone.utils.unit
+import net.pterodactylus.sone.utils.*
 import net.pterodactylus.util.config.Configuration
 import net.pterodactylus.util.config.ConfigurationException
 import java.util.concurrent.locks.ReentrantReadWriteLock
@@ -71,6 +71,8 @@ class MemoryDatabase @Inject constructor(private val configuration: Configuratio
        private val memoryBookmarkDatabase = MemoryBookmarkDatabase(this, configurationLoader)
        private val memoryFriendDatabase = MemoryFriendDatabase(configurationLoader)
        private val saveRateLimiter: RateLimiter = RateLimiter.create(1.0)
+       private val saveKnownPostsRateLimiter: RateLimiter = RateLimiter.create(1.0)
+       private val saveKnownPostRepliesRateLimiter: RateLimiter = RateLimiter.create(1.0)
 
        override val soneLoader get() = this::getSone
 
@@ -314,15 +316,17 @@ class MemoryDatabase @Inject constructor(private val configuration: Configuratio
                                        }
 
        private fun saveKnownPosts() =
-                       try {
-                               readLock.withLock {
-                                       knownPosts.forEachIndexed { index, knownPostId ->
-                                               configuration.getStringValue("KnownPosts/$index/ID").value = knownPostId
+                       saveKnownPostsRateLimiter.tryAcquire().ifTrue {
+                               try {
+                                       readLock.withLock {
+                                               knownPosts.forEachIndexed { index, knownPostId ->
+                                                       configuration.getStringValue("KnownPosts/$index/ID").value = knownPostId
+                                               }
+                                               configuration.getStringValue("KnownPosts/${knownPosts.size}/ID").value = null
                                        }
-                                       configuration.getStringValue("KnownPosts/${knownPosts.size}/ID").value = null
+                               } catch (ce1: ConfigurationException) {
+                                       throw DatabaseException("Could not save database.", ce1)
                                }
-                       } catch (ce1: ConfigurationException) {
-                               throw DatabaseException("Could not save database.", ce1)
                        }
 
        private fun loadKnownPostReplies(): Unit =
@@ -334,15 +338,17 @@ class MemoryDatabase @Inject constructor(private val configuration: Configuratio
                        }
 
        private fun saveKnownPostReplies() =
-                       try {
-                               readLock.withLock {
-                                       knownPostReplies.forEachIndexed { index, knownPostReply ->
-                                               configuration.getStringValue("KnownReplies/$index/ID").value = knownPostReply
+                       saveKnownPostRepliesRateLimiter.tryAcquire().ifTrue {
+                               try {
+                                       readLock.withLock {
+                                               knownPostReplies.forEachIndexed { index, knownPostReply ->
+                                                       configuration.getStringValue("KnownReplies/$index/ID").value = knownPostReply
+                                               }
+                                               configuration.getStringValue("KnownReplies/${knownPostReplies.size}/ID").value = null
                                        }
-                                       configuration.getStringValue("KnownReplies/${knownPostReplies.size}/ID").value = null
+                               } catch (ce1: ConfigurationException) {
+                                       throw DatabaseException("Could not save database.", ce1)
                                }
-                       } catch (ce1: ConfigurationException) {
-                               throw DatabaseException("Could not save database.", ce1)
                        }
 
 }
diff --git a/src/main/java/net/pterodactylus/sone/freenet/Key.java b/src/main/java/net/pterodactylus/sone/freenet/Key.java
deleted file mode 100644 (file)
index 6811642..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-package net.pterodactylus.sone.freenet;
-
-import static freenet.support.Base64.encode;
-import static java.lang.String.format;
-
-import freenet.keys.FreenetURI;
-
-import com.google.common.annotations.VisibleForTesting;
-
-/**
- * Encapsulates the parts of a {@link FreenetURI} that do not change while
- * being converted from SSK to USK and/or back.
- */
-public class Key {
-
-       private final byte[] routingKey;
-       private final byte[] cryptoKey;
-       private final byte[] extra;
-
-       private Key(byte[] routingKey, byte[] cryptoKey, byte[] extra) {
-               this.routingKey = routingKey;
-               this.cryptoKey = cryptoKey;
-               this.extra = extra;
-       }
-
-       @VisibleForTesting
-       public String getRoutingKey() {
-               return encode(routingKey);
-       }
-
-       @VisibleForTesting
-       public String getCryptoKey() {
-               return encode(cryptoKey);
-       }
-
-       @VisibleForTesting
-       public String getExtra() {
-               return encode(extra);
-       }
-
-       public FreenetURI toUsk(String docName, long edition, String... paths) {
-               return new FreenetURI("USK", docName, paths, routingKey, cryptoKey,
-                               extra, edition);
-       }
-
-       public FreenetURI toSsk(String docName, String... paths) {
-               return new FreenetURI("SSK", docName, paths, routingKey, cryptoKey,
-                               extra);
-       }
-
-       public FreenetURI toSsk(String docName, long edition, String... paths) {
-               return new FreenetURI("SSK", format("%s-%d", docName, edition), paths,
-                               routingKey, cryptoKey, extra, edition);
-       }
-
-       public static Key from(FreenetURI freenetURI) {
-               return new Key(freenetURI.getRoutingKey(), freenetURI.getCryptoKey(),
-                               freenetURI.getExtra());
-       }
-
-       public static String routingKey(FreenetURI freenetURI) {
-               return from(freenetURI).getRoutingKey();
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/freenet/L10nFilter.java b/src/main/java/net/pterodactylus/sone/freenet/L10nFilter.java
deleted file mode 100644 (file)
index d667811..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Sone - L10nFilter.java - Copyright © 2010–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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.freenet;
-
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-
-import javax.annotation.Nonnull;
-
-import net.pterodactylus.util.template.Filter;
-import net.pterodactylus.util.template.TemplateContext;
-
-import freenet.l10n.BaseL10n;
-
-/**
- * {@link Filter} implementation replaces {@link String} values with their
- * translated equivalents.
- */
-public class L10nFilter implements Filter {
-
-       private final BaseL10n l10n;
-
-       public L10nFilter(BaseL10n l10n) {
-               this.l10n = l10n;
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public String format(TemplateContext templateContext, Object data, Map<String, Object> parameters) {
-               List<Object> parameterValues = getParameters(data, parameters);
-               String text = getText(data);
-               if (parameterValues.isEmpty()) {
-                       return l10n.getString(text);
-               }
-               return new MessageFormat(l10n.getString(text), new Locale(l10n.getSelectedLanguage().shortCode)).format(parameterValues.toArray());
-       }
-
-       @Nonnull
-       private String getText(Object data) {
-               return (data instanceof L10nText) ? ((L10nText) data).getText() : String.valueOf(data);
-       }
-
-       @Nonnull
-       private List<Object> getParameters(Object data, Map<String, Object> parameters) {
-               if (data instanceof L10nText) {
-                       return ((L10nText) data).getParameters();
-               }
-               List<Object> parameterValues = new ArrayList<>();
-               int parameterIndex = 0;
-               while (parameters.containsKey(String.valueOf(parameterIndex))) {
-                       Object value = parameters.get(String.valueOf(parameterIndex));
-                       parameterValues.add(value);
-                       ++parameterIndex;
-               }
-               return parameterValues;
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/freenet/plugin/PluginConnector.java b/src/main/java/net/pterodactylus/sone/freenet/plugin/PluginConnector.java
deleted file mode 100644 (file)
index fbaabb3..0000000
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Sone - PluginConnector.java - Copyright © 2010–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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.freenet.plugin;
-
-import net.pterodactylus.sone.freenet.plugin.event.ReceivedReplyEvent;
-
-import com.google.common.eventbus.EventBus;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-
-import freenet.pluginmanager.FredPluginTalker;
-import freenet.pluginmanager.PluginNotFoundException;
-import freenet.pluginmanager.PluginRespirator;
-import freenet.pluginmanager.PluginTalker;
-import freenet.support.SimpleFieldSet;
-import freenet.support.api.Bucket;
-
-/**
- * Interface for talking to other plugins. Other plugins are identified by their
- * name and a unique connection identifier.
- */
-@Singleton
-public class PluginConnector implements FredPluginTalker {
-
-       /** The event bus. */
-       private final EventBus eventBus;
-
-       /** The plugin respirator. */
-       private final PluginRespirator pluginRespirator;
-
-       /**
-        * Creates a new plugin connector.
-        *
-        * @param eventBus
-        *            The event bus
-        * @param pluginRespirator
-        *            The plugin respirator
-        */
-       @Inject
-       public PluginConnector(EventBus eventBus, PluginRespirator pluginRespirator) {
-               this.eventBus = eventBus;
-               this.pluginRespirator = pluginRespirator;
-       }
-
-       //
-       // ACTIONS
-       //
-
-       /**
-        * Sends a request to the given plugin.
-        *
-        * @param pluginName
-        *            The name of the plugin
-        * @param identifier
-        *            The identifier of the connection
-        * @param fields
-        *            The fields of the message
-        * @throws PluginException
-        *             if the plugin can not be found
-        */
-       public void sendRequest(String pluginName, String identifier, SimpleFieldSet fields) throws PluginException {
-               sendRequest(pluginName, identifier, fields, null);
-       }
-
-       /**
-        * Sends a request to the given plugin.
-        *
-        * @param pluginName
-        *            The name of the plugin
-        * @param identifier
-        *            The identifier of the connection
-        * @param fields
-        *            The fields of the message
-        * @param data
-        *            The payload of the message (may be null)
-        * @throws PluginException
-        *             if the plugin can not be found
-        */
-       public void sendRequest(String pluginName, String identifier, SimpleFieldSet fields, Bucket data) throws PluginException {
-               getPluginTalker(pluginName, identifier).send(fields, data);
-       }
-
-       //
-       // PRIVATE METHODS
-       //
-
-       /**
-        * Returns the plugin talker for the given plugin connection.
-        *
-        * @param pluginName
-        *            The name of the plugin
-        * @param identifier
-        *            The identifier of the connection
-        * @return The plugin talker
-        * @throws PluginException
-        *             if the plugin can not be found
-        */
-       private PluginTalker getPluginTalker(String pluginName, String identifier) throws PluginException {
-               try {
-                       return pluginRespirator.getPluginTalker(this, pluginName, identifier);
-               } catch (PluginNotFoundException pnfe1) {
-                       throw new PluginException(pnfe1);
-               }
-       }
-
-       //
-       // INTERFACE FredPluginTalker
-       //
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public void onReply(String pluginName, String identifier, SimpleFieldSet params, Bucket data) {
-               eventBus.post(new ReceivedReplyEvent(this, pluginName, identifier, params, data));
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/freenet/plugin/PluginException.java b/src/main/java/net/pterodactylus/sone/freenet/plugin/PluginException.java
deleted file mode 100644 (file)
index e263a60..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Sone - PluginException.java - Copyright © 2010–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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.freenet.plugin;
-
-import net.pterodactylus.sone.freenet.wot.WebOfTrustException;
-
-/**
- * Exception that signals an error when communicating with a plugin.
- */
-public class PluginException extends WebOfTrustException {
-
-       /**
-        * Creates a new plugin exception.
-        */
-       public PluginException() {
-               super();
-       }
-
-       /**
-        * Creates a new plugin exception.
-        *
-        * @param message
-        *            The message of the exception
-        */
-       public PluginException(String message) {
-               super(message);
-       }
-
-       /**
-        * Creates a new plugin exception.
-        *
-        * @param cause
-        *            The cause of the exception
-        */
-       public PluginException(Throwable cause) {
-               super(cause);
-       }
-
-       /**
-        * Creates a new plugin exception.
-        *
-        * @param message
-        *            The message of the exception
-        * @param cause
-        *            The cause of the exception
-        */
-       public PluginException(String message, Throwable cause) {
-               super(message, cause);
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/freenet/plugin/event/ReceivedReplyEvent.java b/src/main/java/net/pterodactylus/sone/freenet/plugin/event/ReceivedReplyEvent.java
deleted file mode 100644 (file)
index ce2ba7f..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Sone - ReceivedReplyEvent.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.freenet.plugin.event;
-
-import net.pterodactylus.sone.freenet.plugin.PluginConnector;
-import freenet.support.SimpleFieldSet;
-import freenet.support.api.Bucket;
-
-/**
- * Event that signals that a plugin reply was received.
- */
-public class ReceivedReplyEvent {
-
-       /** The connector that received the reply. */
-       private final PluginConnector pluginConnector;
-
-       /** The name of the plugin that sent the reply. */
-       private final String pluginName;
-
-       /** The identifier of the initial request. */
-       private final String identifier;
-
-       /** The fields containing the reply. */
-       private final SimpleFieldSet fieldSet;
-
-       /** The optional reply data. */
-       private final Bucket data;
-
-       /**
-        * Creates a new “reply received” event.
-        *
-        * @param pluginConnector
-        *            The connector that received the event
-        * @param pluginName
-        *            The name of the plugin that sent the reply
-        * @param identifier
-        *            The identifier of the initial request
-        * @param fieldSet
-        *            The fields containing the reply
-        * @param data
-        *            The optional data of the reply
-        */
-       public ReceivedReplyEvent(PluginConnector pluginConnector, String pluginName, String identifier, SimpleFieldSet fieldSet, Bucket data) {
-               this.pluginConnector = pluginConnector;
-               this.pluginName = pluginName;
-               this.identifier = identifier;
-               this.fieldSet = fieldSet;
-               this.data = data;
-       }
-
-       //
-       // ACCESSORS
-       //
-
-       /**
-        * Returns the plugin connector that received the reply.
-        *
-        * @return The plugin connector that received the reply
-        */
-       public PluginConnector pluginConnector() {
-               return pluginConnector;
-       }
-
-       /**
-        * Returns the name of the plugin that sent the reply.
-        *
-        * @return The name of the plugin that sent the reply
-        */
-       public String pluginName() {
-               return pluginName;
-       }
-
-       /**
-        * Returns the identifier of the initial request.
-        *
-        * @return The identifier of the initial request
-        */
-       public String identifier() {
-               return identifier;
-       }
-
-       /**
-        * Returns the fields containing the reply.
-        *
-        * @return The fields containing the reply
-        */
-       public SimpleFieldSet fieldSet() {
-               return fieldSet;
-       }
-
-       /**
-        * Returns the optional data of the reply.
-        *
-        * @return The optional data of the reply (may be {@code null})
-        */
-       public Bucket data() {
-               return data;
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/Context.java b/src/main/java/net/pterodactylus/sone/freenet/wot/Context.java
deleted file mode 100644 (file)
index b386bdb..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Sone - Context.java - Copyright © 2014–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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.freenet.wot;
-
-import javax.annotation.Nullable;
-
-import com.google.common.base.Function;
-
-/**
- * Custom container for the Web of Trust context. This allows easier
- * configuration of dependency injection.
- */
-public class Context {
-
-       public static final Function<Context, String> extractContext = new Function<Context, String>() {
-               @Nullable
-               @Override
-               public String apply(@Nullable Context context) {
-                       return (context == null) ? null : context.getContext();
-               }
-       };
-
-       private final String context;
-
-       public Context(String context) {
-               this.context = context;
-       }
-
-       public String getContext() {
-               return context;
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/DefaultIdentity.java b/src/main/java/net/pterodactylus/sone/freenet/wot/DefaultIdentity.java
deleted file mode 100644 (file)
index 0d92d61..0000000
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Sone - DefaultIdentity.java - Copyright © 2010–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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.freenet.wot;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * A Web of Trust identity.
- */
-public class DefaultIdentity implements Identity {
-
-       /** The ID of the identity. */
-       private final String id;
-
-       /** The nickname of the identity. */
-       private final String nickname;
-
-       /** The request URI of the identity. */
-       private final String requestUri;
-
-       /** The contexts of the identity. */
-       private final Set<String> contexts = Collections.synchronizedSet(new HashSet<String>());
-
-       /** The properties of the identity. */
-       private final Map<String, String> properties = Collections.synchronizedMap(new HashMap<String, String>());
-
-       /** Cached trust. */
-       private final Map<OwnIdentity, Trust> trustCache = Collections.synchronizedMap(new HashMap<OwnIdentity, Trust>());
-
-       /**
-        * Creates a new identity.
-        *
-        * @param id
-        *            The ID of the identity
-        * @param nickname
-        *            The nickname of the identity
-        * @param requestUri
-        *            The request URI of the identity
-        */
-       public DefaultIdentity(String id, String nickname, String requestUri) {
-               this.id = id;
-               this.nickname = nickname;
-               this.requestUri = requestUri;
-       }
-
-       //
-       // ACCESSORS
-       //
-
-       @Override
-       public String getId() {
-               return id;
-       }
-
-       @Override
-       public String getNickname() {
-               return nickname;
-       }
-
-       @Override
-       public String getRequestUri() {
-               return requestUri;
-       }
-
-       @Override
-       public Set<String> getContexts() {
-               return Collections.unmodifiableSet(contexts);
-       }
-
-       @Override
-       public boolean hasContext(String context) {
-               return contexts.contains(context);
-       }
-
-       @Override
-       public void setContexts(Collection<String> contexts) {
-               this.contexts.clear();
-               this.contexts.addAll(contexts);
-       }
-
-       @Override
-       public Identity addContext(String context) {
-               contexts.add(context);
-               return this;
-       }
-
-       @Override
-       public Identity removeContext(String context) {
-               contexts.remove(context);
-               return this;
-       }
-
-       @Override
-       public Map<String, String> getProperties() {
-               return Collections.unmodifiableMap(properties);
-       }
-
-       @Override
-       public void setProperties(Map<String, String> properties) {
-               this.properties.clear();
-               this.properties.putAll(properties);
-       }
-
-       @Override
-       public String getProperty(String name) {
-               return properties.get(name);
-       }
-
-       @Override
-       public Identity setProperty(String name, String value) {
-               properties.put(name, value);
-               return this;
-       }
-
-       @Override
-       public Identity removeProperty(String name) {
-               properties.remove(name);
-               return this;
-       }
-
-       @Override
-       public Trust getTrust(OwnIdentity ownIdentity) {
-               return trustCache.get(ownIdentity);
-       }
-
-       @Override
-       public Identity setTrust(OwnIdentity ownIdentity, Trust trust) {
-               trustCache.put(ownIdentity, trust);
-               return this;
-       }
-
-       @Override
-       public Identity removeTrust(OwnIdentity ownIdentity) {
-               trustCache.remove(ownIdentity);
-               return this;
-       }
-
-       //
-       // OBJECT METHODS
-       //
-
-       @Override
-       public int hashCode() {
-               return getId().hashCode();
-       }
-
-       @Override
-       public boolean equals(Object object) {
-               if (!(object instanceof Identity)) {
-                       return false;
-               }
-               Identity identity = (Identity) object;
-               return identity.getId().equals(getId());
-       }
-
-       @Override
-       public String toString() {
-               return getClass().getSimpleName() + "[id=" + id + ",nickname=" + nickname + ",contexts=" + contexts + ",properties=" + properties + "]";
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentity.java b/src/main/java/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentity.java
deleted file mode 100644 (file)
index e2e3a74..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Sone - DefaultOwnIdentity.java - Copyright © 2010–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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.freenet.wot;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * An own identity is an identity that the owner of the node has full control
- * over.
- */
-public class DefaultOwnIdentity extends DefaultIdentity implements OwnIdentity {
-
-       /** The insert URI of the identity. */
-       private final String insertUri;
-
-       /**
-        * Creates a new own identity.
-        *
-        * @param id
-        *            The ID of the identity
-        * @param nickname
-        *            The nickname of the identity
-        * @param requestUri
-        *            The request URI of the identity
-        * @param insertUri
-        *            The insert URI of the identity
-        */
-       public DefaultOwnIdentity(String id, String nickname, String requestUri, String insertUri) {
-               super(id, nickname, requestUri);
-               this.insertUri = checkNotNull(insertUri);
-       }
-
-       //
-       // ACCESSORS
-       //
-
-       @Override
-       public String getInsertUri() {
-               return insertUri;
-       }
-
-       @Override
-       public OwnIdentity addContext(String context) {
-               return (OwnIdentity) super.addContext(context);
-       }
-
-       @Override
-       public OwnIdentity removeContext(String context) {
-               return (OwnIdentity) super.removeContext(context);
-       }
-
-       @Override
-       public OwnIdentity setProperty(String name, String value) {
-               return (OwnIdentity) super.setProperty(name, value);
-       }
-
-       @Override
-       public OwnIdentity removeProperty(String name) {
-               return (OwnIdentity) super.removeProperty(name);
-       }
-
-       //
-       // OBJECT METHODS
-       //
-
-       @Override
-       public int hashCode() {
-               return super.hashCode();
-       }
-
-       @Override
-       public boolean equals(Object object) {
-               return super.equals(object);
-       }
-
-}
index a99aac0..3460516 100644 (file)
@@ -31,20 +31,6 @@ import com.google.common.base.Function;
  */
 public interface Identity {
 
-       public static final Function<Identity, Set<String>> TO_CONTEXTS = new Function<Identity, Set<String>>() {
-               @Override
-               public Set<String> apply(Identity identity) {
-                       return (identity == null) ? Collections.<String>emptySet() : identity.getContexts();
-               }
-       };
-
-       public static final Function<Identity, Map<String, String>> TO_PROPERTIES = new Function<Identity, Map<String, String>>() {
-               @Override
-               public Map<String, String> apply(Identity input) {
-                       return (input == null) ? Collections.<String, String>emptyMap() : input.getProperties();
-               }
-       };
-
        /**
         * Returns the ID of the identity.
         *
@@ -97,7 +83,7 @@ public interface Identity {
         * @param contexts
         *            All contexts of the identity
         */
-       public void setContexts(Collection<String> contexts);
+       public void setContexts(Set<String> contexts);
 
        /**
         * Removes the given context from this identity.
diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityChangeDetector.java b/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityChangeDetector.java
deleted file mode 100644 (file)
index 8b28011..0000000
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Sone - IdentityChangeDetector.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.freenet.wot;
-
-import static com.google.common.base.Optional.absent;
-import static com.google.common.base.Optional.fromNullable;
-import static com.google.common.base.Predicates.not;
-import static com.google.common.collect.FluentIterable.from;
-import static net.pterodactylus.sone.freenet.wot.Identity.TO_CONTEXTS;
-import static net.pterodactylus.sone.freenet.wot.Identity.TO_PROPERTIES;
-
-import java.util.Collection;
-import java.util.Map;
-import java.util.Map.Entry;
-
-import com.google.common.base.Optional;
-import com.google.common.base.Predicate;
-import com.google.common.collect.FluentIterable;
-import com.google.common.collect.ImmutableMap;
-
-/**
- * Detects changes between two lists of {@link Identity}s. The detector can find
- * added and removed identities, and for identities that exist in both list
- * their contexts and properties are checked for added, removed, or (in case of
- * properties) changed values.
- */
-public class IdentityChangeDetector {
-
-       private final Map<String, Identity> oldIdentities;
-       private Optional<IdentityProcessor> onNewIdentity = absent();
-       private Optional<IdentityProcessor> onRemovedIdentity = absent();
-       private Optional<IdentityProcessor> onChangedIdentity = absent();
-       private Optional<IdentityProcessor> onUnchangedIdentity = absent();
-
-       public IdentityChangeDetector(Collection<? extends Identity> oldIdentities) {
-               this.oldIdentities = convertToMap(oldIdentities);
-       }
-
-       public void onNewIdentity(IdentityProcessor onNewIdentity) {
-               this.onNewIdentity = fromNullable(onNewIdentity);
-       }
-
-       public void onRemovedIdentity(IdentityProcessor onRemovedIdentity) {
-               this.onRemovedIdentity = fromNullable(onRemovedIdentity);
-       }
-
-       public void onChangedIdentity(IdentityProcessor onChangedIdentity) {
-               this.onChangedIdentity = fromNullable(onChangedIdentity);
-       }
-
-       public void onUnchangedIdentity(IdentityProcessor onUnchangedIdentity) {
-               this.onUnchangedIdentity = fromNullable(onUnchangedIdentity);
-       }
-
-       public void detectChanges(final Collection<? extends Identity> newIdentities) {
-               notifyForRemovedIdentities(from(oldIdentities.values()).filter(notContainedIn(newIdentities)));
-               notifyForNewIdentities(from(newIdentities).filter(notContainedIn(oldIdentities.values())));
-               notifyForChangedIdentities(from(newIdentities).filter(containedIn(oldIdentities)).filter(hasChanged(oldIdentities)));
-               notifyForUnchangedIdentities(from(newIdentities).filter(containedIn(oldIdentities)).filter(not(hasChanged(oldIdentities))));
-       }
-
-       private void notifyForRemovedIdentities(Iterable<Identity> identities) {
-               notify(onRemovedIdentity, identities);
-       }
-
-       private void notifyForNewIdentities(FluentIterable<? extends Identity> newIdentities) {
-               notify(onNewIdentity, newIdentities);
-       }
-
-       private void notifyForChangedIdentities(FluentIterable<? extends Identity> identities) {
-               notify(onChangedIdentity, identities);
-       }
-
-       private void notifyForUnchangedIdentities(FluentIterable<? extends Identity> identities) {
-               notify(onUnchangedIdentity, identities);
-       }
-
-       private void notify(Optional<IdentityProcessor> identityProcessor, Iterable<? extends Identity> identities) {
-               if (!identityProcessor.isPresent()) {
-                       return;
-               }
-               for (Identity identity : identities) {
-                       identityProcessor.get().processIdentity(identity);
-               }
-       }
-
-       private static Predicate<Identity> hasChanged(final Map<String, Identity> oldIdentities) {
-               return new Predicate<Identity>() {
-                       @Override
-                       public boolean apply(Identity identity) {
-                               return (identity != null) && identityHasChanged(oldIdentities.get(identity.getId()), identity);
-                       }
-               };
-       }
-
-       private static boolean identityHasChanged(Identity oldIdentity, Identity newIdentity) {
-               return identityHasNewContexts(oldIdentity, newIdentity)
-                               || identityHasRemovedContexts(oldIdentity, newIdentity)
-                               || identityHasNewProperties(oldIdentity, newIdentity)
-                               || identityHasRemovedProperties(oldIdentity, newIdentity)
-                               || identityHasChangedProperties(oldIdentity, newIdentity);
-       }
-
-       private static boolean identityHasNewContexts(Identity oldIdentity, Identity newIdentity) {
-               return from(TO_CONTEXTS.apply(newIdentity)).anyMatch(notAContextOf(oldIdentity));
-       }
-
-       private static boolean identityHasRemovedContexts(Identity oldIdentity, Identity newIdentity) {
-               return from(TO_CONTEXTS.apply(oldIdentity)).anyMatch(notAContextOf(newIdentity));
-       }
-
-       private static boolean identityHasNewProperties(Identity oldIdentity, Identity newIdentity) {
-               return from(TO_PROPERTIES.apply(newIdentity).entrySet()).anyMatch(notAPropertyOf(oldIdentity));
-       }
-
-       private static boolean identityHasRemovedProperties(Identity oldIdentity, Identity newIdentity) {
-               return from(TO_PROPERTIES.apply(oldIdentity).entrySet()).anyMatch(notAPropertyOf(newIdentity));
-       }
-
-       private static boolean identityHasChangedProperties(Identity oldIdentity, Identity newIdentity) {
-               return from(TO_PROPERTIES.apply(oldIdentity).entrySet()).anyMatch(hasADifferentValueThanIn(newIdentity));
-       }
-
-       private static Predicate<Identity> containedIn(final Map<String, Identity> identities) {
-               return new Predicate<Identity>() {
-                       @Override
-                       public boolean apply(Identity identity) {
-                               return (identity != null) && identities.containsKey(identity.getId());
-                       }
-               };
-       }
-
-       private static Predicate<String> notAContextOf(final Identity identity) {
-               return new Predicate<String>() {
-                       @Override
-                       public boolean apply(String context) {
-                               return (identity != null) && !identity.getContexts().contains(context);
-                       }
-               };
-       }
-
-       private static Predicate<Identity> notContainedIn(final Collection<? extends Identity> newIdentities) {
-               return new Predicate<Identity>() {
-                       @Override
-                       public boolean apply(Identity identity) {
-                               return (identity != null) && !newIdentities.contains(identity);
-                       }
-               };
-       }
-
-       private static Predicate<Entry<String, String>> notAPropertyOf(final Identity identity) {
-               return new Predicate<Entry<String, String>>() {
-                       @Override
-                       public boolean apply(Entry<String, String> property) {
-                               return (property != null) && !identity.getProperties().containsKey(property.getKey());
-                       }
-               };
-       }
-
-       private static Predicate<Entry<String, String>> hasADifferentValueThanIn(final Identity newIdentity) {
-               return new Predicate<Entry<String, String>>() {
-                       @Override
-                       public boolean apply(Entry<String, String> property) {
-                               return (property != null) && !newIdentity.getProperty(property.getKey()).equals(property.getValue());
-                       }
-               };
-       }
-
-       private static Map<String, Identity> convertToMap(Collection<? extends Identity> identities) {
-               ImmutableMap.Builder<String, Identity> mapBuilder = ImmutableMap.builder();
-               for (Identity identity : identities) {
-                       mapBuilder.put(identity.getId(), identity);
-               }
-               return mapBuilder.build();
-       }
-
-       public interface IdentityProcessor {
-
-               void processIdentity(Identity identity);
-
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityChangeDetector.kt b/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityChangeDetector.kt
new file mode 100644 (file)
index 0000000..deb35b1
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Sone - IdentityChangeDetector.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.wot
+
+/**
+ * Detects changes between two lists of [Identity]s. The detector can find
+ * added and removed identities, and for identities that exist in both list
+ * their contexts and properties are checked for added, removed, or (in case of
+ * properties) changed values.
+ */
+class IdentityChangeDetector(oldIdentities: Collection<Identity>) {
+
+       private val oldIdentities: Map<String, Identity> = oldIdentities.associateBy { it.id }
+       var onNewIdentity: IdentityProcessor? = null
+       var onRemovedIdentity: IdentityProcessor? = null
+       var onChangedIdentity: IdentityProcessor? = null
+       var onUnchangedIdentity: IdentityProcessor? = null
+
+       fun detectChanges(newIdentities: Collection<Identity>) {
+               onRemovedIdentity.notify(oldIdentities.values.filter { it !in newIdentities })
+               onNewIdentity.notify(newIdentities.filter { it !in oldIdentities.values })
+               onChangedIdentity.notify(newIdentities.filter { it.id in oldIdentities }.filter { identityHasChanged(oldIdentities[it.id]!!, it) })
+               onUnchangedIdentity.notify(newIdentities.filter { it.id in oldIdentities }.filterNot { identityHasChanged(oldIdentities[it.id]!!, it) })
+       }
+
+       private fun identityHasChanged(oldIdentity: Identity, newIdentity: Identity?) =
+                       identityHasNewContexts(oldIdentity, newIdentity!!)
+                                       || identityHasRemovedContexts(oldIdentity, newIdentity)
+                                       || identityHasNewProperties(oldIdentity, newIdentity)
+                                       || identityHasRemovedProperties(oldIdentity, newIdentity)
+                                       || identityHasChangedProperties(oldIdentity, newIdentity)
+
+       private fun identityHasNewContexts(oldIdentity: Identity, newIdentity: Identity) =
+                       newIdentity.contexts.any { it !in oldIdentity.contexts }
+
+       private fun identityHasRemovedContexts(oldIdentity: Identity, newIdentity: Identity) =
+                       oldIdentity.contexts.any { it !in newIdentity.contexts }
+
+       private fun identityHasNewProperties(oldIdentity: Identity, newIdentity: Identity) =
+                       newIdentity.properties.keys.any { it !in oldIdentity.properties }
+
+       private fun identityHasRemovedProperties(oldIdentity: Identity, newIdentity: Identity) =
+                       oldIdentity.properties.keys.any { it !in newIdentity.properties }
+
+       private fun identityHasChangedProperties(oldIdentity: Identity, newIdentity: Identity) =
+                       oldIdentity.properties.entries.any { newIdentity.properties[it.key] != it.value }
+
+}
+
+typealias IdentityProcessor = (Identity) -> Unit
+
+private fun IdentityProcessor?.notify(identities: Iterable<Identity>) =
+               this?.let { identities.forEach(this::invoke) }
diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityChangeEventSender.java b/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityChangeEventSender.java
deleted file mode 100644 (file)
index fd57c38..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Sone - IdentityChangeEventSender.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.freenet.wot;
-
-import java.util.Collection;
-import java.util.Map;
-
-import net.pterodactylus.sone.freenet.wot.IdentityChangeDetector.IdentityProcessor;
-import net.pterodactylus.sone.freenet.wot.event.IdentityAddedEvent;
-import net.pterodactylus.sone.freenet.wot.event.IdentityRemovedEvent;
-import net.pterodactylus.sone.freenet.wot.event.IdentityUpdatedEvent;
-import net.pterodactylus.sone.freenet.wot.event.OwnIdentityAddedEvent;
-import net.pterodactylus.sone.freenet.wot.event.OwnIdentityRemovedEvent;
-
-import com.google.common.eventbus.EventBus;
-
-/**
- * Detects changes in {@link Identity}s trusted my multiple {@link
- * OwnIdentity}s.
- *
- * @see IdentityChangeDetector
- */
-public class IdentityChangeEventSender {
-
-       private final EventBus eventBus;
-       private final Map<OwnIdentity, Collection<Identity>> oldIdentities;
-
-       public IdentityChangeEventSender(EventBus eventBus, Map<OwnIdentity, Collection<Identity>> oldIdentities) {
-               this.eventBus = eventBus;
-               this.oldIdentities = oldIdentities;
-       }
-
-       public void detectChanges(Map<OwnIdentity, Collection<Identity>> identities) {
-               IdentityChangeDetector identityChangeDetector = new IdentityChangeDetector(oldIdentities.keySet());
-               identityChangeDetector.onNewIdentity(addNewOwnIdentityAndItsTrustedIdentities(identities));
-               identityChangeDetector.onRemovedIdentity(removeOwnIdentityAndItsTrustedIdentities(oldIdentities));
-               identityChangeDetector.onUnchangedIdentity(detectChangesInTrustedIdentities(identities, oldIdentities));
-               identityChangeDetector.detectChanges(identities.keySet());
-       }
-
-       private IdentityProcessor addNewOwnIdentityAndItsTrustedIdentities(final Map<OwnIdentity, Collection<Identity>> newIdentities) {
-               return new IdentityProcessor() {
-                       @Override
-                       public void processIdentity(Identity identity) {
-                               eventBus.post(new OwnIdentityAddedEvent((OwnIdentity) identity));
-                               for (Identity newIdentity : newIdentities.get((OwnIdentity) identity)) {
-                                       eventBus.post(new IdentityAddedEvent((OwnIdentity) identity, newIdentity));
-                               }
-                       }
-               };
-       }
-
-       private IdentityProcessor removeOwnIdentityAndItsTrustedIdentities(final Map<OwnIdentity, Collection<Identity>> oldIdentities) {
-               return new IdentityProcessor() {
-                       @Override
-                       public void processIdentity(Identity identity) {
-                               eventBus.post(new OwnIdentityRemovedEvent((OwnIdentity) identity));
-                               for (Identity removedIdentity : oldIdentities.get((OwnIdentity) identity)) {
-                                       eventBus.post(new IdentityRemovedEvent((OwnIdentity) identity, removedIdentity));
-                               }
-                       }
-               };
-       }
-
-       private IdentityProcessor detectChangesInTrustedIdentities(Map<OwnIdentity, Collection<Identity>> newIdentities, Map<OwnIdentity, Collection<Identity>> oldIdentities) {
-               return new DefaultIdentityProcessor(oldIdentities, newIdentities);
-       }
-
-       private class DefaultIdentityProcessor implements IdentityProcessor {
-
-               private final Map<OwnIdentity, Collection<Identity>> oldIdentities;
-               private final Map<OwnIdentity, Collection<Identity>> newIdentities;
-
-               public DefaultIdentityProcessor(Map<OwnIdentity, Collection<Identity>> oldIdentities, Map<OwnIdentity, Collection<Identity>> newIdentities) {
-                       this.oldIdentities = oldIdentities;
-                       this.newIdentities = newIdentities;
-               }
-
-               @Override
-               public void processIdentity(Identity ownIdentity) {
-                       IdentityChangeDetector identityChangeDetector = new IdentityChangeDetector(oldIdentities.get((OwnIdentity) ownIdentity));
-                       identityChangeDetector.onNewIdentity(notifyForAddedIdentities((OwnIdentity) ownIdentity));
-                       identityChangeDetector.onRemovedIdentity(notifyForRemovedIdentities((OwnIdentity) ownIdentity));
-                       identityChangeDetector.onChangedIdentity(notifyForChangedIdentities((OwnIdentity) ownIdentity));
-                       identityChangeDetector.detectChanges(newIdentities.get((OwnIdentity) ownIdentity));
-               }
-
-               private IdentityProcessor notifyForChangedIdentities(final OwnIdentity ownIdentity) {
-                       return new IdentityProcessor() {
-                               @Override
-                               public void processIdentity(Identity identity) {
-                                       eventBus.post(new IdentityUpdatedEvent(ownIdentity, identity));
-                               }
-                       };
-               }
-
-               private IdentityProcessor notifyForRemovedIdentities(final OwnIdentity ownIdentity) {
-                       return new IdentityProcessor() {
-                               @Override
-                               public void processIdentity(Identity identity) {
-                                       eventBus.post(new IdentityRemovedEvent(ownIdentity, identity));
-                               }
-                       };
-               }
-
-               private IdentityProcessor notifyForAddedIdentities(final OwnIdentity ownIdentity) {
-                       return new IdentityProcessor() {
-                               @Override
-                               public void processIdentity(Identity identity) {
-                                       eventBus.post(new IdentityAddedEvent(ownIdentity, identity));
-                               }
-                       };
-               }
-
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityLoader.java b/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityLoader.java
deleted file mode 100644 (file)
index 0917130..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Sone - IdentityLoader.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.freenet.wot;
-
-import static java.util.concurrent.TimeUnit.*;
-import static net.pterodactylus.sone.freenet.wot.Context.*;
-
-import java.util.*;
-import java.util.logging.*;
-
-import net.pterodactylus.sone.freenet.plugin.*;
-
-import com.google.common.base.Optional;
-import com.google.common.base.*;
-import com.google.inject.*;
-
-/**
- * Loads {@link OwnIdentity}s and the {@link Identity}s they trust.
- */
-public class IdentityLoader {
-
-       private final Logger logger = Logger.getLogger(IdentityLoader.class.getName());
-       private final WebOfTrustConnector webOfTrustConnector;
-       private final Optional<Context> context;
-
-       public IdentityLoader(WebOfTrustConnector webOfTrustConnector) {
-               this(webOfTrustConnector, Optional.<Context>absent());
-       }
-
-       @Inject
-       public IdentityLoader(WebOfTrustConnector webOfTrustConnector, Optional<Context> context) {
-               this.webOfTrustConnector = webOfTrustConnector;
-               this.context = context;
-       }
-
-       public Map<OwnIdentity, Collection<Identity>> loadIdentities() throws WebOfTrustException {
-               Stopwatch stopwatch = Stopwatch.createStarted();
-               Collection<OwnIdentity> currentOwnIdentities = webOfTrustConnector.loadAllOwnIdentities();
-               logger.fine("Loaded " + currentOwnIdentities.size() + " own identities in " + (stopwatch.elapsed(MILLISECONDS) / 1000.0) + "s.");
-               return loadTrustedIdentitiesForOwnIdentities(currentOwnIdentities);
-       }
-
-       private Map<OwnIdentity, Collection<Identity>> loadTrustedIdentitiesForOwnIdentities(Collection<OwnIdentity> ownIdentities) throws PluginException {
-               Map<OwnIdentity, Collection<Identity>> currentIdentities = new HashMap<>();
-
-               for (OwnIdentity ownIdentity : ownIdentities) {
-                       if (identityDoesNotHaveTheCorrectContext(ownIdentity)) {
-                               currentIdentities.put(ownIdentity, Collections.<Identity>emptySet());
-                               continue;
-                       }
-
-                       Stopwatch stopwatch = Stopwatch.createStarted();
-                       Set<Identity> trustedIdentities = webOfTrustConnector.loadTrustedIdentities(ownIdentity, context.transform(extractContext));
-                       logger.fine("Loaded " + trustedIdentities.size() + " identities for " + ownIdentity.getNickname() + " in " + (stopwatch.elapsed(MILLISECONDS) / 1000.0) + "s.");
-                       currentIdentities.put(ownIdentity, trustedIdentities);
-               }
-
-               return currentIdentities;
-       }
-
-       private boolean identityDoesNotHaveTheCorrectContext(OwnIdentity ownIdentity) {
-               return context.isPresent() && !ownIdentity.hasContext(context.transform(extractContext).get());
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityManager.java b/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityManager.java
deleted file mode 100644 (file)
index c0f6f1b..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-package net.pterodactylus.sone.freenet.wot;
-
-import java.util.Set;
-
-import net.pterodactylus.util.service.Service;
-
-import com.google.common.eventbus.EventBus;
-import com.google.inject.ImplementedBy;
-
-/**
- * Connects to a {@link WebOfTrustConnector} and sends identity events to an
- * {@link EventBus}.
- */
-@ImplementedBy(IdentityManagerImpl.class)
-public interface IdentityManager extends Service {
-
-       boolean isConnected();
-       Set<OwnIdentity> getAllOwnIdentities();
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityManagerImpl.java b/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityManagerImpl.java
deleted file mode 100644 (file)
index 6f46465..0000000
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Sone - IdentityManagerImpl.java - Copyright © 2010–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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.freenet.wot;
-
-import static java.util.logging.Logger.getLogger;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import net.pterodactylus.sone.freenet.plugin.PluginException;
-import net.pterodactylus.util.service.AbstractService;
-
-import com.google.common.collect.Sets;
-import com.google.common.eventbus.EventBus;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-
-/**
- * The identity manager takes care of loading and storing identities, their
- * contexts, and properties. It does so in a way that does not expose errors via
- * exceptions but it only logs them and tries to return sensible defaults.
- * <p>
- * It is also responsible for polling identities from the Web of Trust plugin
- * and sending events to the {@link EventBus} when {@link Identity}s and
- * {@link OwnIdentity}s are discovered or disappearing.
- */
-@Singleton
-public class IdentityManagerImpl extends AbstractService implements IdentityManager {
-
-       /** The logger. */
-       private static final Logger logger = getLogger(IdentityManagerImpl.class.getName());
-
-       /** The event bus. */
-       private final EventBus eventBus;
-
-       private final IdentityLoader identityLoader;
-
-       /** The Web of Trust connector. */
-       private final WebOfTrustConnector webOfTrustConnector;
-
-       /** The currently known own identities. */
-       private final Set<OwnIdentity> currentOwnIdentities = Sets.newHashSet();
-
-       /**
-        * Creates a new identity manager.
-        *
-        * @param eventBus
-        *            The event bus
-        * @param webOfTrustConnector
-        *            The Web of Trust connector
-        */
-       @Inject
-       public IdentityManagerImpl(EventBus eventBus, WebOfTrustConnector webOfTrustConnector, IdentityLoader identityLoader) {
-               super("Sone Identity Manager", false);
-               this.eventBus = eventBus;
-               this.webOfTrustConnector = webOfTrustConnector;
-               this.identityLoader = identityLoader;
-       }
-
-       //
-       // ACCESSORS
-       //
-
-       /**
-        * Returns whether the Web of Trust plugin could be reached during the last
-        * try.
-        *
-        * @return {@code true} if the Web of Trust plugin is connected,
-        *         {@code false} otherwise
-        */
-       @Override
-       public boolean isConnected() {
-               try {
-                       webOfTrustConnector.ping();
-                       return true;
-               } catch (PluginException pe1) {
-                       /* not connected, ignore. */
-                       return false;
-               }
-       }
-
-       /**
-        * Returns all own identities.
-        *
-        * @return All own identities
-        */
-       @Override
-       public Set<OwnIdentity> getAllOwnIdentities() {
-               synchronized (currentOwnIdentities) {
-                       return new HashSet<>(currentOwnIdentities);
-               }
-       }
-
-       //
-       // SERVICE METHODS
-       //
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       protected void serviceRun() {
-               Map<OwnIdentity, Collection<Identity>> oldIdentities = new HashMap<>();
-
-               while (!shouldStop()) {
-                       try {
-                               Map<OwnIdentity, Collection<Identity>> currentIdentities = identityLoader.loadIdentities();
-
-                               IdentityChangeEventSender identityChangeEventSender = new IdentityChangeEventSender(eventBus, oldIdentities);
-                               identityChangeEventSender.detectChanges(currentIdentities);
-
-                               oldIdentities = currentIdentities;
-
-                               synchronized (currentOwnIdentities) {
-                                       currentOwnIdentities.clear();
-                                       currentOwnIdentities.addAll(currentIdentities.keySet());
-                               }
-                       } catch (WebOfTrustException wote1) {
-                               logger.log(Level.WARNING, "WoT has disappeared!", wote1);
-                       }
-
-                       /* wait a minute before checking again. */
-                       sleep(60 * 1000);
-               }
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/Trust.java b/src/main/java/net/pterodactylus/sone/freenet/wot/Trust.java
deleted file mode 100644 (file)
index 2da00f6..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Sone - Trust.java - Copyright © 2010–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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.freenet.wot;
-
-import static com.google.common.base.Objects.equal;
-
-import com.google.common.base.Objects;
-
-/**
- * Container class for trust in the web of trust.
- */
-public class Trust {
-
-       /** Explicitely assigned trust. */
-       private final Integer explicit;
-
-       /** Implicitely calculated trust. */
-       private final Integer implicit;
-
-       /** The distance from the owner of the trust tree. */
-       private final Integer distance;
-
-       /**
-        * Creates a new trust container.
-        *
-        * @param explicit
-        *            The explicit trust
-        * @param implicit
-        *            The implicit trust
-        * @param distance
-        *            The distance
-        */
-       public Trust(Integer explicit, Integer implicit, Integer distance) {
-               this.explicit = explicit;
-               this.implicit = implicit;
-               this.distance = distance;
-       }
-
-       /**
-        * Returns the trust explicitely assigned to an identity.
-        *
-        * @return The explicitely assigned trust, or {@code null} if the identity
-        *         is not in the own identity’s trust tree
-        */
-       public Integer getExplicit() {
-               return explicit;
-       }
-
-       /**
-        * Returns the implicitely assigned trust, or the calculated trust.
-        *
-        * @return The calculated trust, or {@code null} if the identity is not in
-        *         the own identity’s trust tree
-        */
-       public Integer getImplicit() {
-               return implicit;
-       }
-
-       /**
-        * Returns the distance of the trusted identity from the trusting identity.
-        *
-        * @return The distance from the own identity, or {@code null} if the
-        *         identity is not in the own identity’s trust tree
-        */
-       public Integer getDistance() {
-               return distance;
-       }
-
-       @Override
-       public boolean equals(Object object) {
-               if (!(object instanceof Trust)) {
-                       return false;
-               }
-               Trust trust = (Trust) object;
-               return equal(getExplicit(), trust.getExplicit()) && equal(getImplicit(), trust.getImplicit()) && equal(getDistance(), trust.getDistance());
-       }
-
-       @Override
-       public int hashCode() {
-               return Objects.hashCode(explicit, implicit, distance);
-       }
-
-       /** {@inheritDoc} */
-       @Override
-       public String toString() {
-               return getClass().getName() + "[explicit=" + explicit + ",implicit=" + implicit + ",distance=" + distance + "]";
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/WebOfTrustConnector.java b/src/main/java/net/pterodactylus/sone/freenet/wot/WebOfTrustConnector.java
deleted file mode 100644 (file)
index c1dbef9..0000000
+++ /dev/null
@@ -1,623 +0,0 @@
-/*
- * Sone - WebOfTrustConnector.java - Copyright © 2010–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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.freenet.wot;
-
-import static java.util.logging.Logger.getLogger;
-import static net.pterodactylus.sone.utils.NumberParsers.parseInt;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import net.pterodactylus.sone.freenet.plugin.PluginConnector;
-import net.pterodactylus.sone.freenet.plugin.PluginException;
-import net.pterodactylus.sone.freenet.plugin.event.ReceivedReplyEvent;
-
-import com.google.common.base.Optional;
-import com.google.common.collect.MapMaker;
-import com.google.common.eventbus.Subscribe;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-
-import freenet.support.SimpleFieldSet;
-import freenet.support.api.Bucket;
-
-/**
- * Connector for the Web of Trust plugin.
- */
-@Singleton
-public class WebOfTrustConnector {
-
-       /** The logger. */
-       private static final Logger logger = getLogger(WebOfTrustConnector.class.getName());
-
-       /** The name of the WoT plugin. */
-       private static final String WOT_PLUGIN_NAME = "plugins.WebOfTrust.WebOfTrust";
-
-       /** Counter for connection identifiers. */
-       private final AtomicLong counter = new AtomicLong();
-
-       /** The plugin connector. */
-       private final PluginConnector pluginConnector;
-
-       /** Map for replies. */
-       private final Map<PluginIdentifier, Reply> replies = new MapMaker().makeMap();
-
-       /**
-        * Creates a new Web of Trust connector that uses the given plugin
-        * connector.
-        *
-        * @param pluginConnector
-        *            The plugin connector
-        */
-       @Inject
-       public WebOfTrustConnector(PluginConnector pluginConnector) {
-               this.pluginConnector = pluginConnector;
-       }
-
-       //
-       // ACTIONS
-       //
-
-       /**
-        * Stops the web of trust connector.
-        */
-       public void stop() {
-               /* does nothing. */
-       }
-
-       /**
-        * Loads all own identities from the Web of Trust plugin.
-        *
-        * @return All own identity
-        * @throws WebOfTrustException
-        *             if the own identities can not be loaded
-        */
-       public Set<OwnIdentity> loadAllOwnIdentities() throws WebOfTrustException {
-               Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetOwnIdentities").get());
-               SimpleFieldSet fields = reply.getFields();
-               int ownIdentityCounter = -1;
-               Set<OwnIdentity> ownIdentities = new HashSet<>();
-               while (true) {
-                       String id = fields.get("Identity" + ++ownIdentityCounter);
-                       if (id == null) {
-                               break;
-                       }
-                       String requestUri = fields.get("RequestURI" + ownIdentityCounter);
-                       String insertUri = fields.get("InsertURI" + ownIdentityCounter);
-                       String nickname = fields.get("Nickname" + ownIdentityCounter);
-                       DefaultOwnIdentity ownIdentity = new DefaultOwnIdentity(id, nickname, requestUri, insertUri);
-                       ownIdentity.setContexts(parseContexts("Contexts" + ownIdentityCounter + ".", fields));
-                       ownIdentity.setProperties(parseProperties("Properties" + ownIdentityCounter + ".", fields));
-                       ownIdentities.add(ownIdentity);
-               }
-               return ownIdentities;
-       }
-
-       /**
-        * Loads all identities that the given identities trusts with a score of
-        * more than 0.
-        *
-        * @param ownIdentity
-        *            The own identity
-        * @return All trusted identities
-        * @throws PluginException
-        *             if an error occured talking to the Web of Trust plugin
-        */
-       public Set<Identity> loadTrustedIdentities(OwnIdentity ownIdentity) throws PluginException {
-               return loadTrustedIdentities(ownIdentity, Optional.<String>absent());
-       }
-
-       /**
-        * Loads all identities that the given identities trusts with a score of
-        * more than 0 and the (optional) given context.
-        *
-        * @param ownIdentity
-        *            The own identity
-        * @param context
-        *            The context to filter, or {@code null}
-        * @return All trusted identities
-        * @throws PluginException
-        *             if an error occured talking to the Web of Trust plugin
-        */
-       public Set<Identity> loadTrustedIdentities(OwnIdentity ownIdentity, Optional<String> context) throws PluginException {
-               Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetIdentitiesByScore").put("Truster", ownIdentity.getId()).put("Selection", "+").put("Context", context.or("")).put("WantTrustValues", "true").get());
-               SimpleFieldSet fields = reply.getFields();
-               Set<Identity> identities = new HashSet<>();
-               int identityCounter = -1;
-               while (true) {
-                       String id = fields.get("Identity" + ++identityCounter);
-                       if (id == null) {
-                               break;
-                       }
-                       String nickname = fields.get("Nickname" + identityCounter);
-                       String requestUri = fields.get("RequestURI" + identityCounter);
-                       DefaultIdentity identity = new DefaultIdentity(id, nickname, requestUri);
-                       identity.setContexts(parseContexts("Contexts" + identityCounter + ".", fields));
-                       identity.setProperties(parseProperties("Properties" + identityCounter + ".", fields));
-                       Integer trust = parseInt(fields.get("Trust" + identityCounter), null);
-                       int score = parseInt(fields.get("Score" + identityCounter), 0);
-                       int rank = parseInt(fields.get("Rank" + identityCounter), 0);
-                       identity.setTrust(ownIdentity, new Trust(trust, score, rank));
-                       identities.add(identity);
-               }
-               return identities;
-       }
-
-       /**
-        * Adds the given context to the given identity.
-        *
-        * @param ownIdentity
-        *            The identity to add the context to
-        * @param context
-        *            The context to add
-        * @throws PluginException
-        *             if an error occured talking to the Web of Trust plugin
-        */
-       public void addContext(OwnIdentity ownIdentity, String context) throws PluginException {
-               performRequest(SimpleFieldSetConstructor.create().put("Message", "AddContext").put("Identity", ownIdentity.getId()).put("Context", context).get());
-       }
-
-       /**
-        * Removes the given context from the given identity.
-        *
-        * @param ownIdentity
-        *            The identity to remove the context from
-        * @param context
-        *            The context to remove
-        * @throws PluginException
-        *             if an error occured talking to the Web of Trust plugin
-        */
-       public void removeContext(OwnIdentity ownIdentity, String context) throws PluginException {
-               performRequest(SimpleFieldSetConstructor.create().put("Message", "RemoveContext").put("Identity", ownIdentity.getId()).put("Context", context).get());
-       }
-
-       /**
-        * Returns the value of the property with the given name.
-        *
-        * @param identity
-        *            The identity whose properties to check
-        * @param name
-        *            The name of the property to return
-        * @return The value of the property, or {@code null} if there is no value
-        * @throws PluginException
-        *             if an error occured talking to the Web of Trust plugin
-        */
-       public String getProperty(Identity identity, String name) throws PluginException {
-               Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetProperty").put("Identity", identity.getId()).put("Property", name).get());
-               return reply.getFields().get("Property");
-       }
-
-       /**
-        * Sets the property with the given name to the given value.
-        *
-        * @param ownIdentity
-        *            The identity to set the property on
-        * @param name
-        *            The name of the property to set
-        * @param value
-        *            The value to set
-        * @throws PluginException
-        *             if an error occured talking to the Web of Trust plugin
-        */
-       public void setProperty(OwnIdentity ownIdentity, String name, String value) throws PluginException {
-               performRequest(SimpleFieldSetConstructor.create().put("Message", "SetProperty").put("Identity", ownIdentity.getId()).put("Property", name).put("Value", value).get());
-       }
-
-       /**
-        * Removes the property with the given name.
-        *
-        * @param ownIdentity
-        *            The identity to remove the property from
-        * @param name
-        *            The name of the property to remove
-        * @throws PluginException
-        *             if an error occured talking to the Web of Trust plugin
-        */
-       public void removeProperty(OwnIdentity ownIdentity, String name) throws PluginException {
-               performRequest(SimpleFieldSetConstructor.create().put("Message", "RemoveProperty").put("Identity", ownIdentity.getId()).put("Property", name).get());
-       }
-
-       /**
-        * Returns the trust for the given identity assigned to it by the given own
-        * identity.
-        *
-        * @param ownIdentity
-        *            The own identity
-        * @param identity
-        *            The identity to get the trust for
-        * @return The trust for the given identity
-        * @throws PluginException
-        *             if an error occured talking to the Web of Trust plugin
-        */
-       public Trust getTrust(OwnIdentity ownIdentity, Identity identity) throws PluginException {
-               Reply getTrustReply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetIdentity").put("Truster", ownIdentity.getId()).put("Identity", identity.getId()).get());
-               String trust = getTrustReply.getFields().get("Trust");
-               String score = getTrustReply.getFields().get("Score");
-               String rank = getTrustReply.getFields().get("Rank");
-               Integer explicit = null;
-               Integer implicit = null;
-               Integer distance = null;
-               try {
-                       explicit = Integer.valueOf(trust);
-               } catch (NumberFormatException nfe1) {
-                       /* ignore. */
-               }
-               try {
-                       implicit = Integer.valueOf(score);
-                       distance = Integer.valueOf(rank);
-               } catch (NumberFormatException nfe1) {
-                       /* ignore. */
-               }
-               return new Trust(explicit, implicit, distance);
-       }
-
-       /**
-        * Sets the trust for the given identity.
-        *
-        * @param ownIdentity
-        *            The trusting identity
-        * @param identity
-        *            The trusted identity
-        * @param trust
-        *            The amount of trust (-100 thru 100)
-        * @param comment
-        *            The comment or explanation of the trust value
-        * @throws PluginException
-        *             if an error occured talking to the Web of Trust plugin
-        */
-       public void setTrust(OwnIdentity ownIdentity, Identity identity, int trust, String comment) throws PluginException {
-               performRequest(SimpleFieldSetConstructor.create().put("Message", "SetTrust").put("Truster", ownIdentity.getId()).put("Trustee", identity.getId()).put("Value", String.valueOf(trust)).put("Comment", comment).get());
-       }
-
-       /**
-        * Removes any trust assignment of the given own identity for the given
-        * identity.
-        *
-        * @param ownIdentity
-        *            The own identity
-        * @param identity
-        *            The identity to remove all trust for
-        * @throws WebOfTrustException
-        *             if an error occurs
-        */
-       public void removeTrust(OwnIdentity ownIdentity, Identity identity) throws WebOfTrustException {
-               performRequest(SimpleFieldSetConstructor.create().put("Message", "RemoveTrust").put("Truster", ownIdentity.getId()).put("Trustee", identity.getId()).get());
-       }
-
-       /**
-        * Pings the Web of Trust plugin. If the plugin can not be reached, a
-        * {@link PluginException} is thrown.
-        *
-        * @throws PluginException
-        *             if the plugin is not loaded
-        */
-       public void ping() throws PluginException {
-               performRequest(SimpleFieldSetConstructor.create().put("Message", "Ping").get());
-       }
-
-       //
-       // PRIVATE ACTIONS
-       //
-
-       /**
-        * Parses the contexts from the given fields.
-        *
-        * @param prefix
-        *            The prefix to use to access the contexts
-        * @param fields
-        *            The fields to parse the contexts from
-        * @return The parsed contexts
-        */
-       private static Set<String> parseContexts(String prefix, SimpleFieldSet fields) {
-               Set<String> contexts = new HashSet<>();
-               int contextCounter = -1;
-               while (true) {
-                       String context = fields.get(prefix + "Context" + ++contextCounter);
-                       if (context == null) {
-                               break;
-                       }
-                       contexts.add(context);
-               }
-               return contexts;
-       }
-
-       /**
-        * Parses the properties from the given fields.
-        *
-        * @param prefix
-        *            The prefix to use to access the properties
-        * @param fields
-        *            The fields to parse the properties from
-        * @return The parsed properties
-        */
-       private static Map<String, String> parseProperties(String prefix, SimpleFieldSet fields) {
-               Map<String, String> properties = new HashMap<>();
-               int propertiesCounter = -1;
-               while (true) {
-                       String propertyName = fields.get(prefix + "Property" + ++propertiesCounter + ".Name");
-                       if (propertyName == null) {
-                               break;
-                       }
-                       String propertyValue = fields.get(prefix + "Property" + propertiesCounter + ".Value");
-                       properties.put(propertyName, propertyValue);
-               }
-               return properties;
-       }
-
-       /**
-        * Sends a request containing the given fields and waits for the target
-        * message.
-        *
-        * @param fields
-        *            The fields of the message
-        * @return The reply message
-        * @throws PluginException
-        *             if the request could not be sent
-        */
-       private Reply performRequest(SimpleFieldSet fields) throws PluginException {
-               return performRequest(fields, null);
-       }
-
-       /**
-        * Sends a request containing the given fields and waits for the target
-        * message.
-        *
-        * @param fields
-        *            The fields of the message
-        * @param data
-        *            The payload of the message
-        * @return The reply message
-        * @throws PluginException
-        *             if the request could not be sent
-        */
-       private Reply performRequest(SimpleFieldSet fields, Bucket data) throws PluginException {
-               String identifier = "FCP-Command-" + System.currentTimeMillis() + "-" + counter.getAndIncrement();
-               Reply reply = new Reply();
-               PluginIdentifier pluginIdentifier = new PluginIdentifier(WOT_PLUGIN_NAME, identifier);
-               replies.put(pluginIdentifier, reply);
-
-               logger.log(Level.FINE, String.format("Sending FCP Request: %s", fields.get("Message")));
-               synchronized (reply) {
-                       try {
-                               pluginConnector.sendRequest(WOT_PLUGIN_NAME, identifier, fields, data);
-                               while (reply.getFields() == null) {
-                                       try {
-                                               reply.wait();
-                                       } catch (InterruptedException ie1) {
-                                               logger.log(Level.WARNING, String.format("Got interrupted while waiting for reply on %s.", fields.get("Message")), ie1);
-                                       }
-                               }
-                       } finally {
-                               replies.remove(pluginIdentifier);
-                       }
-               }
-               logger.log(Level.FINEST, String.format("Received FCP Response for %s: %s", fields.get("Message"), (reply.getFields() != null) ? reply.getFields().get("Message") : null));
-               if ((reply.getFields() == null) || "Error".equals(reply.getFields().get("Message"))) {
-                       throw new PluginException("Could not perform request for " + fields.get("Message"));
-               }
-               return reply;
-       }
-
-       /**
-        * Notifies the connector that a plugin reply was received.
-        *
-        * @param receivedReplyEvent
-        *            The event
-        */
-       @Subscribe
-       public void receivedReply(ReceivedReplyEvent receivedReplyEvent) {
-               PluginIdentifier pluginIdentifier = new PluginIdentifier(receivedReplyEvent.pluginName(), receivedReplyEvent.identifier());
-               Reply reply = replies.remove(pluginIdentifier);
-               if (reply == null) {
-                       return;
-               }
-               logger.log(Level.FINEST, String.format("Received Reply from Plugin: %s", receivedReplyEvent.fieldSet().get("Message")));
-               synchronized (reply) {
-                       reply.setFields(receivedReplyEvent.fieldSet());
-                       reply.setData(receivedReplyEvent.data());
-                       reply.notify();
-               }
-       }
-
-       /**
-        * Container for the data of the reply from a plugin.
-        */
-       private static class Reply {
-
-               /** The fields of the reply. */
-               private SimpleFieldSet fields;
-
-               /** The payload of the reply. */
-               private Bucket data;
-
-               /** Empty constructor. */
-               public Reply() {
-                       /* do nothing. */
-               }
-
-               /**
-                * Returns the fields of the reply.
-                *
-                * @return The fields of the reply
-                */
-               public SimpleFieldSet getFields() {
-                       return fields;
-               }
-
-               /**
-                * Sets the fields of the reply.
-                *
-                * @param fields
-                *            The fields of the reply
-                */
-               public void setFields(SimpleFieldSet fields) {
-                       this.fields = fields;
-               }
-
-               /**
-                * Returns the payload of the reply.
-                *
-                * @return The payload of the reply (may be {@code null})
-                */
-               @SuppressWarnings("unused")
-               public Bucket getData() {
-                       return data;
-               }
-
-               /**
-                * Sets the payload of the reply.
-                *
-                * @param data
-                *            The payload of the reply (may be {@code null})
-                */
-               public void setData(Bucket data) {
-                       this.data = data;
-               }
-
-       }
-
-       /**
-        * Helper method to create {@link SimpleFieldSet}s with terser code.
-        */
-       private static class SimpleFieldSetConstructor {
-
-               /** The field set being created. */
-               private SimpleFieldSet simpleFieldSet;
-
-               /**
-                * Creates a new simple field set constructor.
-                *
-                * @param shortLived
-                *            {@code true} if the resulting simple field set should be
-                *            short-lived, {@code false} otherwise
-                */
-               private SimpleFieldSetConstructor(boolean shortLived) {
-                       simpleFieldSet = new SimpleFieldSet(shortLived);
-               }
-
-               //
-               // ACCESSORS
-               //
-
-               /**
-                * Returns the created simple field set.
-                *
-                * @return The created simple field set
-                */
-               public SimpleFieldSet get() {
-                       return simpleFieldSet;
-               }
-
-               /**
-                * Sets the field with the given name to the given value.
-                *
-                * @param name
-                *            The name of the fleld
-                * @param value
-                *            The value of the field
-                * @return This constructor (for method chaining)
-                */
-               public SimpleFieldSetConstructor put(String name, String value) {
-                       simpleFieldSet.putOverwrite(name, value);
-                       return this;
-               }
-
-               //
-               // ACTIONS
-               //
-
-               /**
-                * Creates a new simple field set constructor.
-                *
-                * @return The created simple field set constructor
-                */
-               public static SimpleFieldSetConstructor create() {
-                       return create(true);
-               }
-
-               /**
-                * Creates a new simple field set constructor.
-                *
-                * @param shortLived
-                *            {@code true} if the resulting simple field set should be
-                *            short-lived, {@code false} otherwise
-                * @return The created simple field set constructor
-                */
-               public static SimpleFieldSetConstructor create(boolean shortLived) {
-                       return new SimpleFieldSetConstructor(shortLived);
-               }
-
-       }
-
-       /**
-        * Container for identifying plugins. Plugins are identified by their plugin
-        * name and their unique identifier.
-        */
-       private static class PluginIdentifier {
-
-               /** The plugin name. */
-               private final String pluginName;
-
-               /** The plugin identifier. */
-               private final String identifier;
-
-               /**
-                * Creates a new plugin identifier.
-                *
-                * @param pluginName
-                *            The name of the plugin
-                * @param identifier
-                *            The identifier of the plugin
-                */
-               public PluginIdentifier(String pluginName, String identifier) {
-                       this.pluginName = pluginName;
-                       this.identifier = identifier;
-               }
-
-               //
-               // OBJECT METHODS
-               //
-
-               /**
-                * {@inheritDoc}
-                */
-               @Override
-               public int hashCode() {
-                       return pluginName.hashCode() ^ identifier.hashCode();
-               }
-
-               /**
-                * {@inheritDoc}
-                */
-               @Override
-               public boolean equals(Object object) {
-                       if (!(object instanceof PluginIdentifier)) {
-                               return false;
-                       }
-                       PluginIdentifier pluginIdentifier = (PluginIdentifier) object;
-                       return pluginName.equals(pluginIdentifier.pluginName) && identifier.equals(pluginIdentifier.identifier);
-               }
-
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/WebOfTrustException.java b/src/main/java/net/pterodactylus/sone/freenet/wot/WebOfTrustException.java
deleted file mode 100644 (file)
index 954fe04..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Sone - WebOfTrustException.java - Copyright © 2010–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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.freenet.wot;
-
-/**
- * Exception that signals an error processing web of trust identities, mostly
- * when communicating with the web of trust plugin.
- */
-public class WebOfTrustException extends Exception {
-
-       /**
-        * Creates a new web of trust exception.
-        */
-       public WebOfTrustException() {
-               super();
-       }
-
-       /**
-        * Creates a new web of trust exception.
-        *
-        * @param message
-        *            The message of the exception
-        */
-       public WebOfTrustException(String message) {
-               super(message);
-       }
-
-       /**
-        * Creates a new web of trust exception.
-        *
-        * @param cause
-        *            The cause of the exception
-        */
-       public WebOfTrustException(Throwable cause) {
-               super(cause);
-       }
-
-       /**
-        * Creates a new web of trust exception.
-        *
-        * @param message
-        *            The message of the exception
-        * @param cause
-        *            The cause of the exception
-        */
-       public WebOfTrustException(String message, Throwable cause) {
-               super(message, cause);
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityAddedEvent.java b/src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityAddedEvent.java
deleted file mode 100644 (file)
index 2d7ab32..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Sone - IdentityAddedEvent.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.freenet.wot.event;
-
-import net.pterodactylus.sone.freenet.wot.Identity;
-import net.pterodactylus.sone.freenet.wot.OwnIdentity;
-
-/**
- * Event that signals that an {@link Identity} was added.
- */
-public class IdentityAddedEvent extends IdentityEvent {
-
-       /**
-        * Creates a new “identity added” event.
-        *
-        * @param ownIdentity
-        *            The own identity that added the identity
-        * @param identity
-        *            The identity that was added
-        */
-       public IdentityAddedEvent(OwnIdentity ownIdentity, Identity identity) {
-               super(ownIdentity, identity);
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityEvent.java b/src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityEvent.java
deleted file mode 100644 (file)
index 8a23ae1..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Sone - IdentityEvent.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.freenet.wot.event;
-
-import net.pterodactylus.sone.freenet.wot.Identity;
-import net.pterodactylus.sone.freenet.wot.OwnIdentity;
-
-/**
- * Base class for {@link Identity} events.
- */
-public abstract class IdentityEvent {
-
-       /** The own identity this event relates to. */
-       private final OwnIdentity ownIdentity;
-
-       /** The identity this event is about. */
-       private final Identity identity;
-
-       /**
-        * Creates a new identity-based event.
-        *
-        * @param ownIdentity
-        *            The own identity that relates to the identity
-        * @param identity
-        *            The identity this event is about
-        */
-       protected IdentityEvent(OwnIdentity ownIdentity, Identity identity) {
-               this.ownIdentity = ownIdentity;
-               this.identity = identity;
-       }
-
-       //
-       // ACCESSORS
-       //
-
-       /**
-        * Returns the own identity this event relates to.
-        *
-        * @return The own identity this event relates to
-        */
-       public OwnIdentity ownIdentity() {
-               return ownIdentity;
-       }
-
-       /**
-        * Returns the identity this event is about.
-        *
-        * @return The identity this event is about
-        */
-       public Identity identity() {
-               return identity;
-       }
-
-       @Override
-       public int hashCode() {
-               return ownIdentity().hashCode() ^ identity().hashCode();
-       }
-
-       @Override
-       public boolean equals(Object object) {
-               if ((object == null) || !object.getClass().equals(getClass())) {
-                       return false;
-               }
-               IdentityEvent identityEvent = (IdentityEvent) object;
-               return ownIdentity().equals(identityEvent.ownIdentity()) && identity().equals(identityEvent.identity());
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityRemovedEvent.java b/src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityRemovedEvent.java
deleted file mode 100644 (file)
index 655015e..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Sone - IdentityRemovedEvent.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.freenet.wot.event;
-
-import net.pterodactylus.sone.freenet.wot.Identity;
-import net.pterodactylus.sone.freenet.wot.OwnIdentity;
-
-/**
- * Event that signals that an {@link Identity} was removed.
- */
-public class IdentityRemovedEvent extends IdentityEvent {
-
-       /**
-        * Creates a new “identity removed” event.
-        *
-        * @param ownIdentity
-        *            The own identity that removed the identity
-        * @param identity
-        *            The identity that was removed
-        */
-       public IdentityRemovedEvent(OwnIdentity ownIdentity, Identity identity) {
-               super(ownIdentity, identity);
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityUpdatedEvent.java b/src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityUpdatedEvent.java
deleted file mode 100644 (file)
index a71ad5b..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Sone - IdentityUpdatedEvent.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.freenet.wot.event;
-
-import net.pterodactylus.sone.freenet.wot.Identity;
-import net.pterodactylus.sone.freenet.wot.OwnIdentity;
-
-/**
- * Event that signals that an {@link Identity} was updated.
- */
-public class IdentityUpdatedEvent extends IdentityEvent {
-
-       /**
-        * Creates a new “identity updated” event.
-        *
-        * @param ownIdentity
-        *            The own identity that tracks the identity
-        * @param identity
-        *            The identity that was updated
-        */
-       public IdentityUpdatedEvent(OwnIdentity ownIdentity, Identity identity) {
-               super(ownIdentity, identity);
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityAddedEvent.java b/src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityAddedEvent.java
deleted file mode 100644 (file)
index fc34848..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Sone - OwnIdentityAddedEvent.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.freenet.wot.event;
-
-import net.pterodactylus.sone.freenet.wot.OwnIdentity;
-
-/**
- * Event that signals that an {@link OwnIdentity} was added.
- */
-public class OwnIdentityAddedEvent extends OwnIdentityEvent {
-
-       /**
-        * Creates new “own identity added” event.
-        *
-        * @param ownIdentity
-        *            The own identity that was added
-        */
-       public OwnIdentityAddedEvent(OwnIdentity ownIdentity) {
-               super(ownIdentity);
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityEvent.java b/src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityEvent.java
deleted file mode 100644 (file)
index 9e88d20..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Sone - OwnIdentityEvent.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.freenet.wot.event;
-
-import net.pterodactylus.sone.freenet.wot.OwnIdentity;
-
-/**
- * Base class for {@link OwnIdentity} events.
- */
-public abstract class OwnIdentityEvent {
-
-       /** The own identity this event is about. */
-       private final OwnIdentity ownIdentity;
-
-       /**
-        * Creates a new own identity-based event.
-        *
-        * @param ownIdentity
-        *            The own identity this event is about
-        */
-       protected OwnIdentityEvent(OwnIdentity ownIdentity) {
-               this.ownIdentity = ownIdentity;
-       }
-
-       //
-       // ACCESSORS
-       //
-
-       /**
-        * Returns the own identity this event is about.
-        *
-        * @return The own identity this event is about
-        */
-       public OwnIdentity ownIdentity() {
-               return ownIdentity;
-       }
-
-       @Override
-       public int hashCode() {
-               return ownIdentity().hashCode();
-       }
-
-       @Override
-       public boolean equals(Object object) {
-               if ((object == null) || !object.getClass().equals(getClass())) {
-                       return false;
-               }
-               OwnIdentityEvent ownIdentityEvent = (OwnIdentityEvent) object;
-               return ownIdentity().equals(ownIdentityEvent.ownIdentity());
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityRemovedEvent.java b/src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityRemovedEvent.java
deleted file mode 100644 (file)
index 73761b0..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Sone - OwnIdentityRemovedEvent.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.freenet.wot.event;
-
-import net.pterodactylus.sone.freenet.wot.OwnIdentity;
-
-/**
- * Event that signals that an {@link OwnIdentity} was removed.
- */
-public class OwnIdentityRemovedEvent extends OwnIdentityEvent {
-
-       /**
-        * Creates a new “own identity removed” event.
-        *
-        * @param ownIdentity
-        *            The own identity that was removed
-        */
-       public OwnIdentityRemovedEvent(OwnIdentity ownIdentity) {
-               super(ownIdentity);
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/notify/ListNotification.java b/src/main/java/net/pterodactylus/sone/notify/ListNotification.java
deleted file mode 100644 (file)
index 6a7b086..0000000
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * Sone - ListNotification.java - Copyright © 2010–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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.notify;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-import net.pterodactylus.util.notify.TemplateNotification;
-import net.pterodactylus.util.template.Template;
-
-/**
- * Notification that maintains a list of new elements.
- *
- * @param <T>
- *            The type of the items
- */
-public class ListNotification<T> extends TemplateNotification {
-
-       /** The key under which to store the elements in the template. */
-       private final String key;
-
-       /** The list of new elements. */
-       private final List<T> elements = new CopyOnWriteArrayList<>();
-
-       /**
-        * Creates a new list notification.
-        *
-        * @param id
-        *            The ID of the notification
-        * @param key
-        *            The key under which to store the elements in the template
-        * @param template
-        *            The template to render
-        */
-       public ListNotification(String id, String key, Template template) {
-               this(id, key, template, true);
-       }
-
-       /**
-        * Creates a new list notification.
-        *
-        * @param id
-        *            The ID of the notification
-        * @param key
-        *            The key under which to store the elements in the template
-        * @param template
-        *            The template to render
-        * @param dismissable
-        *            {@code true} if this notification should be dismissable by the
-        *            user, {@code false} otherwise
-        */
-       public ListNotification(String id, String key, Template template, boolean dismissable) {
-               super(id, System.currentTimeMillis(), System.currentTimeMillis(), dismissable, template);
-               this.key = key;
-               template.getInitialContext().set(key, elements);
-       }
-
-       /**
-        * Creates a new list notification that copies its ID and the template from
-        * the given list notification.
-        *
-        * @param listNotification
-        *            The list notification to copy
-        */
-       public ListNotification(ListNotification<T> listNotification) {
-               super(listNotification.getId(), listNotification.getCreatedTime(), listNotification.getLastUpdatedTime(), listNotification.isDismissable(), new Template());
-               this.key = listNotification.key;
-               getTemplate().add(listNotification.getTemplate());
-               getTemplate().getInitialContext().set(key, elements);
-       }
-
-       //
-       // ACTIONS
-       //
-
-       /**
-        * Returns the current list of elements.
-        *
-        * @return The current list of elements
-        */
-       public List<T> getElements() {
-               return new ArrayList<>(elements);
-       }
-
-       /**
-        * Sets the elements to show in this notification. This method will not call
-        * {@link #touch()}.
-        *
-        * @param elements
-        *            The elements to show
-        */
-       public void setElements(Collection<? extends T> elements) {
-               this.elements.clear();
-               this.elements.addAll(elements);
-               touch();
-       }
-
-       /**
-        * Returns whether there are any new elements.
-        *
-        * @return {@code true} if there are no new elements, {@code false} if there
-        *         are new elements
-        */
-       public boolean isEmpty() {
-               return elements.isEmpty();
-       }
-
-       /**
-        * Adds a discovered element.
-        *
-        * @param element
-        *            The new element
-        */
-       public void add(T element) {
-               elements.add(element);
-               touch();
-       }
-
-       /**
-        * Removes the given element from the list of new elements.
-        *
-        * @param element
-        *            The element to remove
-        */
-       public void remove(T element) {
-               while (elements.remove(element)) {
-                       /* do nothing, just remove all instances of the element. */
-               }
-               if (elements.isEmpty()) {
-                       dismiss();
-               }
-               touch();
-       }
-
-       //
-       // ABSTRACTNOTIFICATION METHODS
-       //
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public void dismiss() {
-               super.dismiss();
-               elements.clear();
-       }
-
-       //
-       // OBJECT METHODS
-       //
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public int hashCode() {
-               int hashCode = super.hashCode();
-               for (T element : elements) {
-                       hashCode ^= element.hashCode();
-               }
-               return hashCode;
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public boolean equals(Object object) {
-               if (!(object instanceof ListNotification)) {
-                       return false;
-               }
-               ListNotification<?> listNotification = (ListNotification<?>) object;
-               if (!super.equals(listNotification)) {
-                       return false;
-               }
-               if (!key.equals(listNotification.key)) {
-                       return false;
-               }
-               return elements.equals(listNotification.elements);
-       }
-
-}
index 0d4872b..5924e5a 100644 (file)
@@ -19,18 +19,18 @@ package net.pterodactylus.sone.template;
 
 import java.util.Map;
 
+import net.pterodactylus.sone.freenet.Translation;
 import net.pterodactylus.util.template.Filter;
 import net.pterodactylus.util.template.TemplateContext;
-import freenet.l10n.BaseL10n;
 
 /**
  * {@link Filter} implementation that replaces a {@link Long} with a value of
- * {@code 0} by a {@link String} from an {@link BaseL10n l10n handler}.
+ * {@code 0} by a {@link String} from a {@link Translation translation}.
  */
 public class UnknownDateFilter implements Filter {
 
-       /** The l10n handler. */
-       private BaseL10n l10nHandler;
+       /** The translation. */
+       private final Translation translation;
 
        /** The key for the text to show. */
        private final String unknownKey;
@@ -38,13 +38,11 @@ public class UnknownDateFilter implements Filter {
        /**
         * Creates a new unknown date filter.
         *
-        * @param l10nHandler
-        *            The l10n handler
-        * @param unknownKey
-        *            The key of the text to show
+        * @param translation The translation
+        * @param unknownKey  The key of the text to show
         */
-       public UnknownDateFilter(BaseL10n l10nHandler, String unknownKey) {
-               this.l10nHandler = l10nHandler;
+       public UnknownDateFilter(Translation translation, String unknownKey) {
+               this.translation = translation;
                this.unknownKey = unknownKey;
        }
 
@@ -55,7 +53,7 @@ public class UnknownDateFilter implements Filter {
        public Object format(TemplateContext templateContext, Object data, Map<String, Object> parameters) {
                if (data instanceof Long) {
                        if ((Long) data == 0) {
-                               return l10nHandler.getString(unknownKey);
+                               return translation.translate(unknownKey);
                        }
                }
                return data;
index 12526df..588ffb7 100644 (file)
@@ -19,9 +19,7 @@ package net.pterodactylus.sone.web;
 
 import static com.google.common.collect.FluentIterable.from;
 import static java.util.logging.Logger.getLogger;
-import static net.pterodactylus.util.template.TemplateParser.parse;
 
-import java.io.StringReader;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -46,6 +44,7 @@ import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.freenet.L10nFilter;
+import net.pterodactylus.sone.freenet.Translation;
 import net.pterodactylus.sone.main.Loaders;
 import net.pterodactylus.sone.main.PluginHomepage;
 import net.pterodactylus.sone.main.PluginVersion;
@@ -107,7 +106,6 @@ import net.pterodactylus.util.web.TemplatePage;
 import freenet.clients.http.SessionManager;
 import freenet.clients.http.SessionManager.Session;
 import freenet.clients.http.ToadletContext;
-import freenet.l10n.BaseL10n;
 
 import com.codahale.metrics.*;
 import com.google.common.base.Optional;
@@ -160,6 +158,7 @@ public class WebInterface implements SessionProvider {
 
        private final PageToadletRegistry pageToadletRegistry;
        private final MetricRegistry metricRegistry;
+       private final Translation translation;
 
        /** The “new Sone” notification. */
        private final ListNotification<Sone> newSoneNotification;
@@ -211,7 +210,7 @@ public class WebInterface implements SessionProvider {
                        ParserFilter parserFilter, ShortenFilter shortenFilter,
                        RenderFilter renderFilter,
                        LinkedElementRenderFilter linkedElementRenderFilter,
-                       PageToadletRegistry pageToadletRegistry, MetricRegistry metricRegistry) {
+                       PageToadletRegistry pageToadletRegistry, MetricRegistry metricRegistry, Translation translation, L10nFilter l10nFilter) {
                this.sonePlugin = sonePlugin;
                this.loaders = loaders;
                this.listNotificationFilter = listNotificationFilter;
@@ -225,9 +224,10 @@ public class WebInterface implements SessionProvider {
                this.linkedElementRenderFilter = linkedElementRenderFilter;
                this.pageToadletRegistry = pageToadletRegistry;
                this.metricRegistry = metricRegistry;
+               this.l10nFilter = l10nFilter;
+               this.translation = translation;
                formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
                soneTextParser = new SoneTextParser(getCore(), getCore());
-               l10nFilter = new L10nFilter(getL10n());
 
                this.templateContextFactory = templateContextFactory;
                templateContextFactory.addTemplateObject("webInterface", this);
@@ -381,13 +381,8 @@ public class WebInterface implements SessionProvider {
                return listNotificationFilter.filterNotifications(notificationManager.getNotifications(), currentSone);
        }
 
-       /**
-        * Returns the l10n helper of the node.
-        *
-        * @return The node’s l10n helper
-        */
-       public BaseL10n getL10n() {
-               return sonePlugin.l10n().getBase();
+       public Translation getTranslation() {
+               return translation;
        }
 
        /**
index 2b22377..08c935b 100644 (file)
@@ -34,7 +34,6 @@ import freenet.clients.http.SessionManager;
 import freenet.clients.http.Toadlet;
 import freenet.clients.http.ToadletContext;
 import freenet.clients.http.ToadletContextClosedException;
-import freenet.l10n.NodeL10n;
 import freenet.support.MultiValueTable;
 import freenet.support.api.HTTPRequest;
 
@@ -107,7 +106,7 @@ public class PageToadlet extends Toadlet implements LinkEnabledCallback, LinkFil
         *             if the toadlet context is closed
         */
        public void handleMethodGET(URI uri, HTTPRequest httpRequest, ToadletContext toadletContext) throws IOException, ToadletContextClosedException {
-               handleRequest(new FreenetRequest(uri, Method.GET, httpRequest, toadletContext, NodeL10n.getBase(), sessionManager));
+               handleRequest(new FreenetRequest(uri, Method.GET, httpRequest, toadletContext, sessionManager));
        }
 
        /**
@@ -125,7 +124,7 @@ public class PageToadlet extends Toadlet implements LinkEnabledCallback, LinkFil
         *             if the toadlet context is closed
         */
        public void handleMethodPOST(URI uri, HTTPRequest httpRequest, ToadletContext toadletContext) throws IOException, ToadletContextClosedException {
-               handleRequest(new FreenetRequest(uri, Method.POST, httpRequest, toadletContext, NodeL10n.getBase(), sessionManager));
+               handleRequest(new FreenetRequest(uri, Method.POST, httpRequest, toadletContext, sessionManager));
        }
 
        /**
diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/BaseL10nTranslation.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/BaseL10nTranslation.kt
new file mode 100644 (file)
index 0000000..1b41f70
--- /dev/null
@@ -0,0 +1,16 @@
+package net.pterodactylus.sone.freenet
+
+import freenet.l10n.*
+import java.util.*
+
+/**
+ * [Translation] implementation based on Fred’s [BaseL10n].
+ */
+class BaseL10nTranslation(private val baseL10n: BaseL10n) : Translation {
+
+       override val currentLocale: Locale
+               get() = Locale(baseL10n.selectedLanguage.shortCode)
+
+       override fun translate(key: String): String = baseL10n.getString(key)
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/FreenetURIs.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/FreenetURIs.kt
new file mode 100644 (file)
index 0000000..3a6d43c
--- /dev/null
@@ -0,0 +1,6 @@
+package net.pterodactylus.sone.freenet
+
+import freenet.keys.*
+import freenet.support.Base64.*
+
+val FreenetURI.routingKeyString: String get() = encode(routingKey)
diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/L10nFilter.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/L10nFilter.kt
new file mode 100644 (file)
index 0000000..b48bfec
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Sone - L10nFilter.java - Copyright © 2010–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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet
+
+import net.pterodactylus.util.template.*
+import java.text.*
+
+/**
+ * [Filter] implementation replaces [String] values with their
+ * translated equivalents.
+ */
+class L10nFilter(private val translation: Translation) : Filter {
+
+       override fun format(templateContext: TemplateContext?, data: Any?, parameters: Map<String, Any>?): String {
+               val parameterValues = getParameters(data, parameters)
+               val text = getText(data)
+               return if (parameterValues.isEmpty()) {
+                       translation.translate(text)
+               } else
+                       MessageFormat(translation.translate(text), translation.currentLocale).format(parameterValues.toTypedArray())
+       }
+
+       private fun getText(data: Any?) = (data as? L10nText)?.text ?: data.toString()
+
+       private fun getParameters(data: Any?, parameters: Map<String, Any>?) =
+                       if (data is L10nText)
+                               data.parameters
+                       else
+                               (parameters ?: emptyMap()).let { params ->
+                                       generateSequence(0) { it + 1 }
+                                                       .takeWhile { it.toString() in params }
+                                                       .map { params[it.toString()] }
+                                                       .toList()
+                               }
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/Translation.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/Translation.kt
new file mode 100644 (file)
index 0000000..78919a9
--- /dev/null
@@ -0,0 +1,38 @@
+/**
+ * Sone - Translation.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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet
+
+import java.util.*
+
+/**
+ * Facade for Fred’s [freenet.l10n.BaseL10n] object.
+ */
+interface Translation {
+
+       /** The currently selected locale. */
+       val currentLocale: Locale
+
+       /**
+        * Returns the translated string for the given key, defaulting to `""`.
+        *
+        * @param key The key to return the translated string for
+        * @return The translated string, or `""` if there is no translation
+        */
+       fun translate(key: String): String
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/plugin/FredPluginConnector.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/plugin/FredPluginConnector.kt
new file mode 100644 (file)
index 0000000..98849c4
--- /dev/null
@@ -0,0 +1,35 @@
+/* Fred’s plugin stuff is mostly deprecated. ¯\_(ツ)_/¯ */
+@file:Suppress("DEPRECATION")
+
+package net.pterodactylus.sone.freenet.plugin
+
+import freenet.pluginmanager.*
+import freenet.support.*
+import freenet.support.api.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import javax.inject.*
+
+/**
+ * [PluginConnector] implementation that uses a [PluginRespiratorFacade] and coroutines to send
+ * a request to another plugin and receive a reply.
+ */
+class FredPluginConnector @Inject constructor(private val pluginRespiratorFacade: PluginRespiratorFacade) : PluginConnector {
+
+       override suspend fun sendRequest(pluginName: String, fields: SimpleFieldSet, data: Bucket?): PluginReply {
+               val receivedReply = Channel<PluginReply>()
+               val responseReceiver = FredPluginTalker { _, _, responseFields, responseData ->
+                       GlobalScope.launch {
+                               receivedReply.send(PluginReply(responseFields, responseData))
+                       }
+               }
+               try {
+                       val pluginTalker = pluginRespiratorFacade.getPluginTalker(responseReceiver, pluginName, "")
+                       pluginTalker.send(fields, data)
+                       return receivedReply.receive()
+               } catch (e: PluginNotFoundException) {
+                       throw PluginException(cause = e)
+               }
+       }
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/plugin/PluginConnector.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/plugin/PluginConnector.kt
new file mode 100644 (file)
index 0000000..16c506f
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Sone - PluginConnector.kt - Copyright © 2010–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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.plugin
+
+import freenet.support.*
+import freenet.support.api.*
+
+/**
+ * Interface for talking to other plugins. Other plugins are identified by their
+ * name and a unique connection identifier.
+ */
+interface PluginConnector {
+
+       /**
+        * Sends a message to another plugin running in the same node.
+        *
+        * @param pluginName The fully qualified name of the plugin
+        * @param fields The message being sent
+        * @param data Optional data
+        * @return The reply from the plugin
+        * @throws PluginException if the plugin identified by [pluginName] does not exist
+        */
+       @Throws(PluginException::class)
+       suspend fun sendRequest(pluginName: String, fields: SimpleFieldSet, data: Bucket? = null): PluginReply
+
+}
+
+data class PluginReply(val fields: SimpleFieldSet, val data: Bucket?)
diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/plugin/PluginException.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/plugin/PluginException.kt
new file mode 100644 (file)
index 0000000..8515f0c
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Sone - PluginException.java - Copyright © 2010–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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.plugin
+
+import net.pterodactylus.sone.freenet.wot.*
+
+/**
+ * Exception that signals an error when communicating with a plugin.
+ */
+class PluginException(message: String? = null, cause: Throwable? = null) : WebOfTrustException(message, cause)
diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/plugin/PluginRespiratorFacade.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/plugin/PluginRespiratorFacade.kt
new file mode 100644 (file)
index 0000000..3ec52ce
--- /dev/null
@@ -0,0 +1,66 @@
+/**
+ * Sone - PluginRespiratorFacade.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 <http://www.gnu.org/licenses/>.
+ */
+
+/* Yes, this handle Fred-based stuff that’s mostly deprecated. */
+@file:Suppress("DEPRECATION")
+
+package net.pterodactylus.sone.freenet.plugin
+
+import freenet.pluginmanager.*
+import freenet.support.*
+import freenet.support.api.*
+import javax.inject.*
+
+/**
+ * Facade for the only method of a [plugin respirator][PluginRespirator] that Sone actually uses,
+ * for easier testing.
+ */
+interface PluginRespiratorFacade {
+
+       @Throws(PluginNotFoundException::class)
+       fun getPluginTalker(pluginTalker: FredPluginTalker, pluginName: String, identifier: String): PluginTalkerFacade
+
+}
+
+/**
+ * Facade for a [plugin talker][PluginTalker], for easier testing.
+ */
+interface PluginTalkerFacade {
+
+       fun send(pluginParameters: SimpleFieldSet, data: Bucket?)
+
+}
+
+/**
+ * Fred-based [PluginRespiratorFacade] implementation that proxies the given real [PluginRespirator].
+ */
+class FredPluginRespiratorFacade @Inject constructor(private val pluginRespirator: PluginRespirator) : PluginRespiratorFacade {
+
+       override fun getPluginTalker(pluginTalker: FredPluginTalker, pluginName: String, identifier: String) =
+                       FredPluginTalkerFacade(pluginRespirator.getPluginTalker(pluginTalker, pluginName, identifier))
+
+}
+
+/**
+ * Fred-based [PluginTalkerFacade] implementation that proxies the given real [PluginTalker].
+ */
+class FredPluginTalkerFacade(private val pluginTalker: PluginTalker) : PluginTalkerFacade {
+
+       override fun send(pluginParameters: SimpleFieldSet, data: Bucket?) =
+                       pluginTalker.send(pluginParameters, data)
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/Context.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/Context.kt
new file mode 100644 (file)
index 0000000..37d7b61
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Sone - Context.java - Copyright © 2014–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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.wot
+
+/**
+ * Custom container for the Web of Trust context. This allows easier
+ * configuration of dependency injection.
+ */
+class Context(val context: String)
diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/DefaultIdentity.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/DefaultIdentity.kt
new file mode 100644 (file)
index 0000000..fe84584
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * Sone - DefaultIdentity.java - Copyright © 2010–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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.wot
+
+import java.util.Collections.*
+
+/**
+ * A Web of Trust identity.
+ */
+open class DefaultIdentity(private val id: String, private val nickname: String?, private val requestUri: String) : Identity {
+
+       private val contexts = mutableSetOf<String>().synchronized()
+       private val properties = mutableMapOf<String, String>().synchronized()
+       private val trustCache = mutableMapOf<OwnIdentity, Trust>().synchronized()
+
+       override fun getId() = id
+       override fun getNickname() = nickname
+       override fun getRequestUri() = requestUri
+       override fun getContexts() = synchronized(contexts) { contexts.toSet() }
+
+       override fun hasContext(context: String) = context in contexts
+
+       override fun setContexts(contexts: Set<String>) {
+               synchronized(this.contexts) {
+                       this.contexts.clear()
+                       this.contexts.addAll(contexts)
+               }
+       }
+
+       override fun addContext(context: String): Identity = apply {
+               synchronized(this.contexts) {
+                       contexts += context
+               }
+       }
+
+       override fun removeContext(context: String): Identity = apply {
+               synchronized(this.contexts) {
+                       contexts -= context
+               }
+       }
+
+       override fun getProperties() = synchronized(properties) { properties.toMap() }
+
+       override fun setProperties(properties: Map<String, String>) {
+               synchronized(this.properties) {
+                       this.properties.clear()
+                       this.properties.putAll(properties)
+               }
+       }
+
+       override fun getProperty(name: String) = synchronized(properties) { properties[name] }
+
+       override fun setProperty(name: String, value: String): Identity = apply {
+               synchronized(properties) {
+                       properties[name] = value
+               }
+       }
+
+       override fun removeProperty(name: String): Identity = apply {
+               synchronized(properties) {
+                       properties -= name
+               }
+       }
+
+       override fun getTrust(ownIdentity: OwnIdentity) = synchronized(trustCache) {
+               trustCache[ownIdentity]
+       }
+
+       override fun setTrust(ownIdentity: OwnIdentity, trust: Trust) = apply {
+               synchronized(trustCache) {
+                       trustCache[ownIdentity] = trust
+               }
+       }
+
+       override fun removeTrust(ownIdentity: OwnIdentity) = apply {
+               synchronized(trustCache) {
+                       trustCache -= ownIdentity
+               }
+       }
+
+       override fun hashCode() = id.hashCode()
+
+       override fun equals(other: Any?) = if (other !is Identity) {
+               false
+       } else {
+               other.id == getId()
+       }
+
+       override fun toString() = "${javaClass.simpleName}[id=$id,nickname=$nickname,contexts=$contexts,properties=$properties]"
+
+}
+
+private fun <T> Set<T>.synchronized(): MutableSet<T> = synchronizedSet(this)
+private fun <K, V> Map<K, V>.synchronized(): MutableMap<K, V> = synchronizedMap(this)
diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentity.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentity.kt
new file mode 100644 (file)
index 0000000..f330c0a
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Sone - DefaultOwnIdentity.java - Copyright © 2010–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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.wot
+
+/**
+ * An own identity is an identity that the owner of the node has full control
+ * over.
+ */
+class DefaultOwnIdentity(id: String, nickname: String, requestUri: String, private val insertUri: String) : DefaultIdentity(id, nickname, requestUri), OwnIdentity {
+
+       override fun getInsertUri(): String {
+               return insertUri
+       }
+
+       override fun addContext(context: String) = super.addContext(context) as OwnIdentity
+
+       override fun removeContext(context: String) = super.removeContext(context) as OwnIdentity
+
+       override fun setProperty(name: String, value: String) = super.setProperty(name, value) as OwnIdentity
+
+       override fun removeProperty(name: String) = super.removeProperty(name) as OwnIdentity
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/IdentityChangeEventSender.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/IdentityChangeEventSender.kt
new file mode 100644 (file)
index 0000000..5ffffa3
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Sone - IdentityChangeEventSender.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.wot
+
+import com.google.common.eventbus.*
+import net.pterodactylus.sone.freenet.wot.event.*
+
+/**
+ * Detects changes in [Identity]s trusted by multiple [OwnIdentity]s.
+ *
+ * @see IdentityChangeDetector
+ */
+class IdentityChangeEventSender(private val eventBus: EventBus, private val oldIdentities: Map<OwnIdentity, Collection<Identity>>) {
+
+       fun detectChanges(identities: Map<OwnIdentity, Collection<Identity>>) {
+               val identityChangeDetector = IdentityChangeDetector(oldIdentities.keys)
+               identityChangeDetector.onNewIdentity = addNewOwnIdentityAndItsTrustedIdentities(identities)
+               identityChangeDetector.onRemovedIdentity = removeOwnIdentityAndItsTrustedIdentities(oldIdentities)
+               identityChangeDetector.onUnchangedIdentity = detectChangesInTrustedIdentities(identities, oldIdentities)
+               identityChangeDetector.detectChanges(identities.keys)
+       }
+
+       private fun addNewOwnIdentityAndItsTrustedIdentities(newIdentities: Map<OwnIdentity, Collection<Identity>>) =
+                       { identity: Identity ->
+                               eventBus.post(OwnIdentityAddedEvent(identity as OwnIdentity))
+                               newIdentities[identity]
+                                               ?.map { IdentityAddedEvent(identity, it) }
+                                               ?.forEach(eventBus::post) ?: Unit
+                       }
+
+       private fun removeOwnIdentityAndItsTrustedIdentities(oldIdentities: Map<OwnIdentity, Collection<Identity>>) =
+                       { identity: Identity ->
+                               eventBus.post(OwnIdentityRemovedEvent(identity as OwnIdentity))
+                               oldIdentities[identity]
+                                               ?.map { IdentityRemovedEvent(identity, it) }
+                                               ?.forEach(eventBus::post) ?: Unit
+                       }
+
+       private fun detectChangesInTrustedIdentities(newIdentities: Map<OwnIdentity, Collection<Identity>>, oldIdentities: Map<OwnIdentity, Collection<Identity>>) =
+                       { ownIdentity: Identity ->
+                               val identityChangeDetector = IdentityChangeDetector(oldIdentities[ownIdentity as OwnIdentity]!!)
+                               identityChangeDetector.onNewIdentity = { eventBus.post(IdentityAddedEvent(ownIdentity, it)) }
+                               identityChangeDetector.onRemovedIdentity = { eventBus.post(IdentityRemovedEvent(ownIdentity, it)) }
+                               identityChangeDetector.onChangedIdentity = { eventBus.post(IdentityUpdatedEvent(ownIdentity, it)) }
+                               identityChangeDetector.detectChanges(newIdentities[ownIdentity]!!)
+                       }
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/IdentityLoader.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/IdentityLoader.kt
new file mode 100644 (file)
index 0000000..f9019d0
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Sone - IdentityLoader.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.wot
+
+import com.google.common.base.*
+import com.google.inject.*
+import net.pterodactylus.sone.freenet.plugin.*
+import java.util.concurrent.TimeUnit.*
+import java.util.logging.*
+
+/**
+ * Loads [OwnIdentity]s and the [Identity]s they trust.
+ */
+class IdentityLoader @Inject constructor(private val webOfTrustConnector: WebOfTrustConnector, private val context: Context? = null) {
+
+       private val logger: Logger = Logger.getLogger(IdentityLoader::class.java.name)
+
+       @Throws(WebOfTrustException::class)
+       fun loadIdentities() =
+                       time({ stopwatch, identities -> "Loaded ${identities.size} own identities in ${stopwatch.elapsed(MILLISECONDS) / 1000.0}s." }) {
+                               webOfTrustConnector.loadAllOwnIdentities()
+                       }.let(this::loadTrustedIdentitiesForOwnIdentities)
+
+       @Throws(PluginException::class)
+       private fun loadTrustedIdentitiesForOwnIdentities(ownIdentities: Collection<OwnIdentity>) =
+                       ownIdentities
+                                       .also { logger.fine { "Getting trusted identities for ${it.size} own identities..." } }
+                                       .associateWith { ownIdentity ->
+                                               logger.fine { "Getting trusted identities for $ownIdentity..." }
+                                               if (ownIdentity.doesNotHaveCorrectContext()) {
+                                                       logger.fine { "Skipping $ownIdentity because of incorrect context." }
+                                                       emptySet()
+                                               } else {
+                                                       logger.fine { "Loading trusted identities for $ownIdentity from WoT..." }
+                                                       time({ stopwatch, identities -> "Loaded ${identities.size} identities for ${ownIdentity.nickname} in ${stopwatch.elapsed(MILLISECONDS) / 1000.0}s." }) {
+                                                               webOfTrustConnector.loadTrustedIdentities(ownIdentity, context?.context)
+                                                       }
+                                               }
+                                       }
+
+       private fun OwnIdentity.doesNotHaveCorrectContext() =
+                       context?.let { it.context !in contexts } ?: false
+
+       private fun <R> time(logMessage: (Stopwatch, R) -> String, loader: () -> R) =
+                       Stopwatch.createStarted().let { stopwatch ->
+                               loader().also { logger.fine(logMessage(stopwatch, it)) }
+                       }
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/IdentityManager.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/IdentityManager.kt
new file mode 100644 (file)
index 0000000..e90af91
--- /dev/null
@@ -0,0 +1,18 @@
+package net.pterodactylus.sone.freenet.wot
+
+import net.pterodactylus.util.service.Service
+
+import com.google.common.eventbus.EventBus
+import com.google.inject.ImplementedBy
+
+/**
+ * Connects to a [WebOfTrustConnector] and sends identity events to an
+ * [EventBus].
+ */
+@ImplementedBy(IdentityManagerImpl::class)
+interface IdentityManager : Service {
+
+       val isConnected: Boolean
+       val allOwnIdentities: Set<OwnIdentity>
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/IdentityManagerImpl.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/IdentityManagerImpl.kt
new file mode 100644 (file)
index 0000000..cb7d0bd
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Sone - IdentityManagerImpl.java - Copyright © 2010–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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.wot
+
+import com.google.common.eventbus.*
+import com.google.inject.*
+import net.pterodactylus.util.service.*
+import java.util.concurrent.TimeUnit.*
+import java.util.logging.*
+import java.util.logging.Logger.*
+
+/**
+ * The identity manager takes care of loading and storing identities, their
+ * contexts, and properties. It does so in a way that does not expose errors via
+ * exceptions but it only logs them and tries to return sensible defaults.
+ *
+ *
+ * It is also responsible for polling identities from the Web of Trust plugin
+ * and sending events to the [EventBus] when [Identity]s and
+ * [OwnIdentity]s are discovered or disappearing.
+ */
+@Singleton
+class IdentityManagerImpl @Inject constructor(
+               private val eventBus: EventBus,
+               private val webOfTrustConnector: WebOfTrustConnector,
+               private val identityLoader: IdentityLoader
+) : AbstractService("Sone Identity Manager", false), IdentityManager {
+
+       private val currentOwnIdentities = mutableSetOf<OwnIdentity>()
+
+       override val isConnected: Boolean
+               get() = notThrowing { webOfTrustConnector.ping() }
+
+       override val allOwnIdentities: Set<OwnIdentity>
+               get() = synchronized(currentOwnIdentities) {
+                       currentOwnIdentities.toSet()
+               }
+
+       override fun serviceRun() {
+               var oldIdentities = mapOf<OwnIdentity, Collection<Identity>>()
+
+               while (!shouldStop()) {
+                       try {
+                               val currentIdentities = identityLoader.loadIdentities()
+
+                               val identityChangeEventSender = IdentityChangeEventSender(eventBus, oldIdentities)
+                               identityChangeEventSender.detectChanges(currentIdentities)
+
+                               oldIdentities = currentIdentities
+
+                               synchronized(currentOwnIdentities) {
+                                       currentOwnIdentities.clear()
+                                       currentOwnIdentities.addAll(currentIdentities.keys)
+                               }
+                       } catch (wote1: WebOfTrustException) {
+                               logger.log(Level.WARNING, "WoT has disappeared!", wote1)
+                       } catch (e: Exception) {
+                               logger.log(Level.SEVERE, "Uncaught exception in IdentityManager thread!", e)
+                       }
+
+                       /* wait a minute before checking again. */
+                       sleep(SECONDS.toMillis(60))
+               }
+       }
+
+}
+
+private val logger: Logger = getLogger(IdentityManagerImpl::class.java.name)
+
+private fun notThrowing(action: () -> Unit): Boolean =
+               try {
+                       action()
+                       true
+               } catch (e: Exception) {
+                       false
+               }
diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/PluginWebOfTrustConnector.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/PluginWebOfTrustConnector.kt
new file mode 100644 (file)
index 0000000..fae90ae
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * Sone - WebOfTrustConnector.java - Copyright © 2010–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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.wot
+
+import com.google.inject.*
+import freenet.support.*
+import kotlinx.coroutines.*
+import net.pterodactylus.sone.freenet.*
+import net.pterodactylus.sone.freenet.plugin.*
+import java.lang.String.*
+import java.util.logging.*
+import java.util.logging.Logger
+import java.util.logging.Logger.*
+
+/**
+ * Connector for the Web of Trust plugin.
+ */
+class PluginWebOfTrustConnector @Inject constructor(private val pluginConnector: PluginConnector) : WebOfTrustConnector {
+
+       private val logger: Logger = getLogger(PluginWebOfTrustConnector::class.java.name)
+
+       @Throws(PluginException::class)
+       override fun loadAllOwnIdentities(): Set<OwnIdentity> =
+                       performRequest(SimpleFieldSetBuilder().put("Message", "GetOwnIdentities").get())
+                                       .fields
+                                       .parseIdentities { parseOwnIdentity(it) }
+
+       @Throws(PluginException::class)
+       override fun loadTrustedIdentities(ownIdentity: OwnIdentity, context: String?): Set<Identity> =
+                       performRequest(SimpleFieldSetBuilder().put("Message", "GetIdentitiesByScore").put("Truster", ownIdentity.id).put("Selection", "+").put("Context", context ?: "").put("WantTrustValues", "true").get())
+                                       .fields
+                                       .parseIdentities { parseTrustedIdentity(it, ownIdentity) }
+
+       @Throws(PluginException::class)
+       override fun addContext(ownIdentity: OwnIdentity, context: String) {
+               performRequest(SimpleFieldSetBuilder().put("Message", "AddContext").put("Identity", ownIdentity.id).put("Context", context).get())
+       }
+
+       @Throws(PluginException::class)
+       override fun removeContext(ownIdentity: OwnIdentity, context: String) {
+               performRequest(SimpleFieldSetBuilder().put("Message", "RemoveContext").put("Identity", ownIdentity.id).put("Context", context).get())
+       }
+
+       override fun setProperty(ownIdentity: OwnIdentity, name: String, value: String) {
+               performRequest(SimpleFieldSetBuilder().put("Message", "SetProperty").put("Identity", ownIdentity.id).put("Property", name).put("Value", value).get())
+       }
+
+       override fun removeProperty(ownIdentity: OwnIdentity, name: String) {
+               performRequest(SimpleFieldSetBuilder().put("Message", "RemoveProperty").put("Identity", ownIdentity.id).put("Property", name).get())
+       }
+
+       override fun getTrust(ownIdentity: OwnIdentity, identity: Identity) =
+                       performRequest(SimpleFieldSetBuilder().put("Message", "GetIdentity").put("Truster", ownIdentity.id).put("Identity", identity.id).get())
+                                       .fields
+                                       .parseTrust()
+
+       override fun setTrust(ownIdentity: OwnIdentity, identity: Identity, trust: Int, comment: String) {
+               performRequest(SimpleFieldSetBuilder().put("Message", "SetTrust").put("Truster", ownIdentity.id).put("Trustee", identity.id).put("Value", trust.toString()).put("Comment", comment).get())
+       }
+
+       override fun removeTrust(ownIdentity: OwnIdentity, identity: Identity) {
+               performRequest(SimpleFieldSetBuilder().put("Message", "RemoveTrust").put("Truster", ownIdentity.id).put("Trustee", identity.id).get())
+       }
+
+       override fun ping() {
+               performRequest(SimpleFieldSetBuilder().put("Message", "Ping").get())
+       }
+
+       private fun performRequest(fields: SimpleFieldSet): PluginReply {
+               logger.log(Level.FINE, format("Sending FCP Request: %s", fields.get("Message")))
+               return runBlocking {
+                       pluginConnector.sendRequest(WOT_PLUGIN_NAME, fields).also {
+                               logger.log(Level.FINEST, format("Received FCP Response for %s: %s", fields.get("Message"), it.fields.get("Message")))
+                               if ("Error" == it.fields.get("Message")) {
+                                       throw PluginException("Could not perform request for " + fields.get("Message"))
+                               }
+                       }
+               }
+       }
+
+}
+
+private const val WOT_PLUGIN_NAME = "plugins.WebOfTrust.WebOfTrust"
+
+private fun <I> SimpleFieldSet.parseIdentities(parser: SimpleFieldSet.(Int) -> I) =
+               scanPrefix { "Identity$it" }
+                               .map { parser(this, it) }
+                               .toSet()
+
+private fun SimpleFieldSet.parseOwnIdentity(index: Int) =
+               DefaultOwnIdentity(get("Identity$index"), get("Nickname$index"), get("RequestURI$index"), get("InsertURI$index"))
+                               .setContextsAndProperties(this@parseOwnIdentity, index)
+
+private fun SimpleFieldSet.parseTrustedIdentity(index: Int, ownIdentity: OwnIdentity) =
+               DefaultIdentity(get("Identity$index"), get("Nickname$index"), get("RequestURI$index"))
+                               .setContextsAndProperties(this@parseTrustedIdentity, index)
+                               .apply { setTrust(ownIdentity, this@parseTrustedIdentity.parseTrust(index.toString())) }
+
+private fun <I : Identity> I.setContextsAndProperties(simpleFieldSet: SimpleFieldSet, index: Int) = apply {
+       contexts = simpleFieldSet.contexts("Contexts$index.")
+       properties = simpleFieldSet.properties("Properties$index.")
+}
+
+private fun SimpleFieldSet.parseTrust(index: String = "") =
+               Trust(get("Trust$index")?.toIntOrNull(), get("Score$index")?.toIntOrNull(), get("Rank$index")?.toIntOrNull())
+
+private fun SimpleFieldSet.contexts(prefix: String) =
+               scanPrefix { "${prefix}Context$it" }
+                               .map { get("${prefix}Context$it") }
+                               .toSet()
+
+private fun SimpleFieldSet.properties(prefix: String) =
+               scanPrefix { "${prefix}Property${it}.Name" }
+                               .map { get("${prefix}Property${it}.Name") to get("${prefix}Property${it}.Value") }
+                               .toMap()
+
+private fun SimpleFieldSet.scanPrefix(prefix: (Int) -> String) =
+               generateSequence(0, Int::inc)
+                               .takeWhile { get(prefix(it)) != null }
diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/Trust.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/Trust.kt
new file mode 100644 (file)
index 0000000..9242de9
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * Sone - Trust.java - Copyright © 2010–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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.wot
+
+/**
+ * Container class for trust in the web of trust.
+ */
+data class Trust(val explicit: Int?, val implicit: Int?, val distance: Int?)
diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/WebOfTrustConnector.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/WebOfTrustConnector.kt
new file mode 100644 (file)
index 0000000..8488f16
--- /dev/null
@@ -0,0 +1,121 @@
+package net.pterodactylus.sone.freenet.wot
+
+import net.pterodactylus.sone.freenet.plugin.*
+
+/**
+ * Connector for the web of trust plugin.
+ */
+interface WebOfTrustConnector {
+
+       /**
+        * Loads all own identities from the Web of Trust plugin.
+        *
+        * @return All own identity
+        * @throws WebOfTrustException if the own identities can not be loaded
+        */
+       @Throws(WebOfTrustException::class)
+       fun loadAllOwnIdentities(): Set<OwnIdentity>
+
+       /**
+        * Loads all identities that the given identities trusts with a score of
+        * more than 0 and the (optional) given context.
+        *
+        * @param ownIdentity The own identity
+        * @param context The context to filter, or `null`
+        * @return All trusted identities
+        * @throws PluginException if an error occured talking to the Web of Trust plugin
+        */
+       @Throws(PluginException::class)
+       fun loadTrustedIdentities(ownIdentity: OwnIdentity, context: String? = null): Set<Identity>
+
+       /**
+        * Adds the given context to the given identity.
+        *
+        * @param ownIdentity The identity to add the context to
+        * @param context The context to add
+        * @throws PluginException if an error occured talking to the Web of Trust plugin
+        */
+       @Throws(PluginException::class)
+       fun addContext(ownIdentity: OwnIdentity, context: String)
+
+       /**
+        * Removes the given context from the given identity.
+        *
+        * @param ownIdentity The identity to remove the context from
+        * @param context The context to remove
+        * @throws PluginException if an error occured talking to the Web of Trust plugin
+        */
+       @Throws(PluginException::class)
+       fun removeContext(ownIdentity: OwnIdentity, context: String)
+
+       /**
+        * Sets the property with the given name to the given value.
+        *
+        * @param ownIdentity The identity to set the property on
+        * @param name The name of the property to set
+        * @param value The value to set
+        * @throws PluginException if an error occured talking to the Web of Trust plugin
+        */
+       @Throws(PluginException::class)
+       fun setProperty(ownIdentity: OwnIdentity, name: String, value: String)
+
+       /**
+        * Removes the property with the given name.
+        *
+        * @param ownIdentity The identity to remove the property from
+        * @param name The name of the property to remove
+        * @throws PluginException if an error occured talking to the Web of Trust plugin
+        */
+       @Throws(PluginException::class)
+       fun removeProperty(ownIdentity: OwnIdentity, name: String)
+
+       /**
+        * Returns the trust for the given identity assigned to it by the given own
+        * identity.
+        *
+        * @param ownIdentity The own identity
+        * @param identity The identity to get the trust for
+        * @return The trust for the given identity
+        * @throws PluginException if an error occured talking to the Web of Trust plugin
+        */
+       @Throws(PluginException::class)
+       fun getTrust(ownIdentity: OwnIdentity, identity: Identity): Trust
+
+       /**
+        * Sets the trust for the given identity.
+        *
+        * @param ownIdentity The trusting identity
+        * @param identity The trusted identity
+        * @param trust The amount of trust (-100 thru 100)
+        * @param comment The comment or explanation of the trust value
+        * @throws PluginException if an error occured talking to the Web of Trust plugin
+        */
+       @Throws(PluginException::class)
+       fun setTrust(ownIdentity: OwnIdentity, identity: Identity, trust: Int, comment: String)
+
+       /**
+        * Removes any trust assignment of the given own identity for the given
+        * identity.
+        *
+        * @param ownIdentity The own identity
+        * @param identity The identity to remove all trust for
+        * @throws WebOfTrustException if an error occurs
+        */
+       @Throws(WebOfTrustException::class)
+       fun removeTrust(ownIdentity: OwnIdentity, identity: Identity)
+
+       /**
+        * Pings the Web of Trust plugin. If the plugin can not be reached, a
+        * [PluginException] is thrown.
+        *
+        * @throws PluginException if the plugin is not loaded
+        */
+       @Throws(PluginException::class)
+       fun ping()
+
+       /**
+        * Stops the web of trust connector.
+        */
+       fun stop() = Unit
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/WebOfTrustException.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/WebOfTrustException.kt
new file mode 100644 (file)
index 0000000..a3ba9aa
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Sone - WebOfTrustException.java - Copyright © 2010–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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.wot
+
+/**
+ * Exception that signals an error processing web of trust identities, mostly
+ * when communicating with the web of trust plugin.
+ */
+open class WebOfTrustException(message: String? = null, cause: Throwable?) : Exception(message, cause)
diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/IdentityAddedEvent.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/IdentityAddedEvent.kt
new file mode 100644 (file)
index 0000000..9fb2bbf
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Sone - IdentityAddedEvent.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.wot.event
+
+import net.pterodactylus.sone.freenet.wot.*
+
+/**
+ * Event that signals that an [Identity] was added.
+ */
+data class IdentityAddedEvent(val ownIdentity: OwnIdentity, val identity: Identity)
diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/IdentityRemovedEvent.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/IdentityRemovedEvent.kt
new file mode 100644 (file)
index 0000000..f8c9cd0
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Sone - IdentityRemovedEvent.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.wot.event
+
+import net.pterodactylus.sone.freenet.wot.Identity
+import net.pterodactylus.sone.freenet.wot.OwnIdentity
+
+/**
+ * Event that signals that an [Identity] was removed.
+ */
+data class IdentityRemovedEvent(val ownIdentity: OwnIdentity, val identity: Identity)
diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/IdentityUpdatedEvent.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/IdentityUpdatedEvent.kt
new file mode 100644 (file)
index 0000000..40557d3
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Sone - IdentityUpdatedEvent.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.wot.event
+
+import net.pterodactylus.sone.freenet.wot.*
+
+/**
+ * Event that signals that an [Identity] was updated.
+ */
+data class IdentityUpdatedEvent(val ownIdentity: OwnIdentity, val identity: Identity)
diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/OwnIdentityAddedEvent.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/OwnIdentityAddedEvent.kt
new file mode 100644 (file)
index 0000000..6f76564
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Sone - OwnIdentityAddedEvent.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.wot.event
+
+import net.pterodactylus.sone.freenet.wot.OwnIdentity
+
+/**
+ * Event that signals that an [OwnIdentity] was added.
+ */
+data class OwnIdentityAddedEvent(val ownIdentity: OwnIdentity)
diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/OwnIdentityRemovedEvent.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/OwnIdentityRemovedEvent.kt
new file mode 100644 (file)
index 0000000..7b0e3fe
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Sone - OwnIdentityRemovedEvent.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.wot.event
+
+import net.pterodactylus.sone.freenet.wot.*
+
+/**
+ * Event that signals that an [OwnIdentity] was removed.
+ */
+data class OwnIdentityRemovedEvent(val ownIdentity: OwnIdentity)
index 9b5fa2e..1438c2a 100644 (file)
@@ -5,6 +5,7 @@ import freenet.client.*
 import freenet.clients.http.*
 import freenet.node.*
 import freenet.pluginmanager.*
+import net.pterodactylus.sone.freenet.plugin.*
 import javax.inject.Provider
 import javax.inject.Singleton
 
@@ -14,8 +15,9 @@ import javax.inject.Singleton
 class FreenetModule(private val pluginRespirator: PluginRespirator) : Module {
 
        override fun configure(binder: Binder): Unit = binder.run {
-               bind(PluginRespirator::class.java).toProvider(Provider<PluginRespirator> { pluginRespirator })
-               pluginRespirator.node!!.let { node -> bind(Node::class.java).toProvider(Provider<Node> { node }) }
+               bind(PluginRespiratorFacade::class.java).toProvider(Provider { FredPluginRespiratorFacade(pluginRespirator) }).`in`(Singleton::class.java)
+               bind(PluginConnector::class.java).to(FredPluginConnector::class.java).`in`(Singleton::class.java)
+               bind(Node::class.java).toProvider(Provider { pluginRespirator.node })
                bind(HighLevelSimpleClient::class.java).toProvider(Provider<HighLevelSimpleClient> { pluginRespirator.hlSimpleClient!! })
                bind(ToadletContainer::class.java).toProvider(Provider<ToadletContainer> { pluginRespirator.toadletContainer })
                bind(PageMaker::class.java).toProvider(Provider<PageMaker> { pluginRespirator.pageMaker })
index dcb5b3b..97b342a 100644 (file)
@@ -10,6 +10,7 @@ import com.google.inject.spi.*
 import freenet.l10n.*
 import net.pterodactylus.sone.database.*
 import net.pterodactylus.sone.database.memory.*
+import net.pterodactylus.sone.freenet.*
 import net.pterodactylus.sone.freenet.wot.*
 import net.pterodactylus.util.config.*
 import net.pterodactylus.util.config.ConfigurationException
@@ -50,9 +51,10 @@ open class SoneModule(private val sonePlugin: SonePlugin, private val eventBus:
                bind(PluginYear::class.java).toInstance(PluginYear(sonePlugin.year))
                bind(PluginHomepage::class.java).toInstance(PluginHomepage(sonePlugin.homepage))
                bind(Database::class.java).to(MemoryDatabase::class.java).`in`(Singleton::class.java)
-               bind(BaseL10n::class.java).toInstance(sonePlugin.l10n().base)
+               bind(Translation::class.java).toInstance(BaseL10nTranslation(sonePlugin.l10n().base))
                loaders?.let { bind(Loaders::class.java).toInstance(it) }
                bind(MetricRegistry::class.java).`in`(Singleton::class.java)
+               bind(WebOfTrustConnector::class.java).to(PluginWebOfTrustConnector::class.java).`in`(Singleton::class.java)
 
                bindListener(Matchers.any(), object : TypeListener {
                        override fun <I> hear(typeLiteral: TypeLiteral<I>, typeEncounter: TypeEncounter<I>) {
diff --git a/src/main/kotlin/net/pterodactylus/sone/notify/ListNotification.kt b/src/main/kotlin/net/pterodactylus/sone/notify/ListNotification.kt
new file mode 100644 (file)
index 0000000..9ee328e
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * Sone - ListNotification.java - Copyright © 2010–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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.notify
+
+import net.pterodactylus.util.notify.*
+import net.pterodactylus.util.template.*
+import java.lang.System.*
+import java.util.concurrent.*
+
+/**
+ * Notification that maintains a list of elements.
+ *
+ * @param <T>
+ * The type of the items
+ */
+class ListNotification<T> : TemplateNotification {
+
+       private val key: String
+       private val realElements = CopyOnWriteArrayList<T>()
+
+       val elements: List<T> get() = realElements.toList()
+
+       val isEmpty
+               get() = elements.isEmpty()
+
+       @JvmOverloads
+       constructor(id: String, key: String, template: Template, dismissable: Boolean = true) : super(id, currentTimeMillis(), currentTimeMillis(), dismissable, template) {
+               this.key = key
+               template.initialContext.set(key, realElements)
+       }
+
+       constructor(listNotification: ListNotification<T>) : super(listNotification.id, listNotification.createdTime, listNotification.lastUpdatedTime, listNotification.isDismissable, Template()) {
+               this.key = listNotification.key
+               template.add(listNotification.template)
+               template.initialContext.set(key, realElements)
+       }
+
+       fun setElements(elements: Collection<T>) {
+               realElements.clear()
+               realElements.addAll(elements.distinct())
+               touch()
+       }
+
+       fun add(element: T) {
+               if (element !in realElements) {
+                       realElements.add(element)
+                       touch()
+               }
+       }
+
+       fun remove(element: T) {
+               while (realElements.remove(element)) {
+                       /* do nothing, just remove all instances of the element. */
+               }
+               if (realElements.isEmpty()) {
+                       dismiss()
+               }
+               touch()
+       }
+
+       override fun dismiss() {
+               super.dismiss()
+               realElements.clear()
+       }
+
+       override fun hashCode() =
+                       realElements.fold(super.hashCode()) { hash, element -> hash xor element.hashCode() }
+
+       override fun equals(other: Any?): Boolean {
+               if (other !is ListNotification<*>) {
+                       return false
+               }
+               val listNotification = other as ListNotification<*>?
+               if (!super.equals(listNotification)) {
+                       return false
+               }
+               return (key == listNotification.key) && (realElements == listNotification.realElements)
+       }
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/template/HistogramRenderer.kt b/src/main/kotlin/net/pterodactylus/sone/template/HistogramRenderer.kt
new file mode 100644 (file)
index 0000000..dba32b2
--- /dev/null
@@ -0,0 +1,46 @@
+package net.pterodactylus.sone.template
+
+import com.codahale.metrics.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.util.template.*
+
+/**
+ * [Filter] that renders a [Histogram] as a table row.
+ */
+class HistogramRenderer : Filter {
+
+       override fun format(templateContext: TemplateContext, data: Any?, parameters: Map<String, Any?>?): Any? {
+               templateContext["metricName"] = (parameters?.get("name") as String?)?.dotToCamel()?.let { "Page.Metrics.$it.Title" }
+               (data as? Histogram)?.snapshot?.run {
+                       templateContext["count"] = data.count
+                       templateContext["min"] = min
+                       templateContext["max"] = max
+                       templateContext["mean"] = mean
+                       templateContext["median"] = median
+                       templateContext["percentile75"] = get75thPercentile()
+                       templateContext["percentile95"] = get95thPercentile()
+                       templateContext["percentile98"] = get98thPercentile()
+                       templateContext["percentile99"] = get99thPercentile()
+                       templateContext["percentile999"] = get999thPercentile()
+               }
+               return template.render(templateContext)
+       }
+
+}
+
+private val template = """<tr>
+       <td><% metricName|l10n|html></td>
+       <td class="numeric"><% count|html></td>
+       <td class="numeric"><% min|duration scale=='μs'|html></td>
+       <td class="numeric"><% max|duration scale=='μs'|html></td>
+       <td class="numeric"><% mean|duration scale=='μs'|html></td>
+       <td class="numeric"><% median|duration scale=='μs'|html></td>
+       <td class="numeric"><% percentile75|duration scale=='μs'|html></td>
+       <td class="numeric"><% percentile95|duration scale=='μs'|html></td>
+       <td class="numeric"><% percentile98|duration scale=='μs'|html></td>
+       <td class="numeric"><% percentile99|duration scale=='μs'|html></td>
+       <td class="numeric"><% percentile999|duration scale=='μs'|html></td>
+</tr>""".asTemplate()
+
+private fun String.dotToCamel() =
+               split(".").joinToString("", transform = String::capitalize)
index bfcb319..49deb87 100644 (file)
@@ -9,3 +9,11 @@ fun <R> Boolean.ifTrue(block: () -> R): R? = if (this) block() else null
  * Returns the value of [block] if `this` is false, returns `null` otherwise.
  */
 fun <R> Boolean.ifFalse(block: () -> R): R? = if (!this) block() else null
+
+/**
+ * Returns `this` but runs the given block if `this`  is `false`.
+ *
+ * @param block The block to run if `this` is `false`
+ * @return `this`
+ */
+fun Boolean.onFalse(block: () -> Unit): Boolean = this.also { if (!this) block() }
index 283b1ea..c996e30 100644 (file)
@@ -1,7 +1,6 @@
 package net.pterodactylus.sone.web
 
 import com.google.inject.*
-import freenet.l10n.*
 import freenet.support.api.*
 import net.pterodactylus.sone.core.*
 import net.pterodactylus.sone.data.*
@@ -73,6 +72,7 @@ class WebInterfaceModule : AbstractModule() {
                                addFilter("unique", UniqueElementFilter())
                                addFilter("mod", ModFilter())
                                addFilter("paginate", PaginationFilter())
+                               addFilter("render-histogram", HistogramRenderer())
 
                                addProvider(TemplateProvider.TEMPLATE_CONTEXT_PROVIDER)
                                addProvider(loaders.templateProvider)
@@ -99,8 +99,8 @@ class WebInterfaceModule : AbstractModule() {
                        ProfileAccessor(core)
 
        @Provides
-       fun getL10nFilter(l10n: BaseL10n) =
-                       L10nFilter(l10n)
+       fun getL10nFilter(translation: Translation) =
+                       L10nFilter(translation)
 
        @Provides
        fun getParserFilter(core: Core, soneTextParser: SoneTextParser) =
@@ -115,8 +115,8 @@ class WebInterfaceModule : AbstractModule() {
                        LinkedElementsFilter(elementLoader)
 
        @Provides
-       fun getUnknownDateFilter(l10n: BaseL10n) =
-                       UnknownDateFilter(l10n, "View.Sone.Text.UnknownDate")
+       fun getUnknownDateFilter(translation: Translation) =
+                       UnknownDateFilter(translation, "View.Sone.Text.UnknownDate")
 
        @Provides
        fun getImageLinkFilter(core: Core) =
index 41188ea..e2c41b6 100644 (file)
@@ -16,6 +16,6 @@ class GetTranslationAjaxPage @Inject constructor(webInterface: WebInterface) : J
 
        override fun createJsonObject(request: FreenetRequest) =
                        createSuccessJsonObject()
-                                       .put("value", webInterface.l10n.getString(request.parameters["key"]))
+                                       .put("value", webInterface.translation.translate(request.parameters["key"] ?: ""))
 
 }
index 54c8f7f..65b45d9 100644 (file)
@@ -19,8 +19,8 @@ package net.pterodactylus.sone.web.page
 
 import freenet.clients.http.*
 import freenet.clients.http.SessionManager.*
-import freenet.l10n.*
 import freenet.support.api.*
+import net.pterodactylus.sone.freenet.*
 import net.pterodactylus.util.web.*
 import java.net.*
 import java.util.UUID.*
@@ -28,7 +28,6 @@ import java.util.UUID.*
 open class FreenetRequest(uri: URI, method: Method,
                val httpRequest: HTTPRequest,
                val toadletContext: ToadletContext,
-               val l10n: BaseL10n,
                val sessionManager: SessionManager
 ) : Request(uri, method) {
 
index d3eed37..703f953 100644 (file)
@@ -1,16 +1,16 @@
 package net.pterodactylus.sone.web.page
 
 import freenet.clients.http.*
-import freenet.l10n.*
 import freenet.support.api.*
 import net.pterodactylus.sone.core.*
 import net.pterodactylus.sone.web.*
 import net.pterodactylus.util.web.*
 import java.net.*
 
-class SoneRequest(uri: URI, method: Method, httpRequest: HTTPRequest, toadletContext: ToadletContext, l10n: BaseL10n, sessionManager: SessionManager,
-               val core: Core,
-               val webInterface: WebInterface
-) : FreenetRequest(uri, method, httpRequest, toadletContext, l10n, sessionManager)
+class SoneRequest(uri: URI, method: Method, httpRequest: HTTPRequest, toadletContext: ToadletContext, sessionManager: SessionManager,
+                                 val core: Core,
+                                 val webInterface: WebInterface
+) : FreenetRequest(uri, method, httpRequest, toadletContext, sessionManager)
 
-fun FreenetRequest.toSoneRequest(core: Core, webInterface: WebInterface) = SoneRequest(uri, method, httpRequest, toadletContext, l10n, sessionManager, core, webInterface)
+fun FreenetRequest.toSoneRequest(core: Core, webInterface: WebInterface) =
+               SoneRequest(uri, method, httpRequest, toadletContext, sessionManager, core, webInterface)
index 236848d..837e2c8 100644 (file)
@@ -13,25 +13,7 @@ import javax.inject.*
 class MetricsPage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer, private val metricsRegistry: MetricRegistry) : SoneTemplatePage(webInterface, loaders, templateRenderer, "Page.Metrics.Title") {
 
        override fun handleRequest(soneRequest: SoneRequest, templateContext: TemplateContext) {
-               addHistogram(templateContext, "sone.parsing.duration", "soneParsingDuration")
-               addHistogram(templateContext, "sone.insert.duration", "soneInsertDuration")
-       }
-
-       private fun addHistogram(templateContext: TemplateContext, metricName: String, variablePrefix: String) {
-               metricsRegistry.histogram(metricName).also { histogram ->
-                       templateContext["${variablePrefix}Count"] = histogram.count
-                       histogram.snapshot.also { snapshot ->
-                               templateContext["${variablePrefix}Min"] = snapshot.min
-                               templateContext["${variablePrefix}Max"] = snapshot.max
-                               templateContext["${variablePrefix}Median"] = snapshot.median
-                               templateContext["${variablePrefix}Mean"] = snapshot.mean
-                               templateContext["${variablePrefix}Percentile75"] = snapshot.get75thPercentile()
-                               templateContext["${variablePrefix}Percentile95"] = snapshot.get95thPercentile()
-                               templateContext["${variablePrefix}Percentile98"] = snapshot.get98thPercentile()
-                               templateContext["${variablePrefix}Percentile99"] = snapshot.get99thPercentile()
-                               templateContext["${variablePrefix}Percentile999"] = snapshot.get999thPercentile()
-                       }
-               }
+               templateContext["histograms"] = metricsRegistry.histograms
        }
 
 }
index b93d09a..2ed4940 100644 (file)
@@ -21,10 +21,15 @@ class RescuePage @Inject constructor(webInterface: WebInterface, loaders: Loader
                val soneRescuer = soneRequest.core.getSoneRescuer(currentSone)
                templateContext["soneRescuer"] = soneRescuer
                if (soneRequest.isPOST) {
+                       soneRequest.parameters["edition", 9]?.toIntOrNull()?.also {
+                               if (it > -1) {
+                                       soneRescuer.setEdition(it.toLong())
+                               }
+                       }
                        if (soneRequest.parameters["fetch", 8] == "true") {
                                soneRescuer.startNextFetch()
                        }
-                       redirectTo("rescue.html")
+                       throw RedirectException("rescue.html")
                }
        }
 
index c905ed2..9734a03 100644 (file)
@@ -2,6 +2,7 @@ package net.pterodactylus.sone.web.pages
 
 import freenet.clients.http.*
 import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.freenet.*
 import net.pterodactylus.sone.main.*
 import net.pterodactylus.sone.utils.*
 import net.pterodactylus.sone.web.*
@@ -20,11 +21,12 @@ open class SoneTemplatePage(
                templateRenderer: TemplateRenderer,
                private val pageTitleKey: String? = null,
                private val requiresLogin: Boolean = false,
-               private val pageTitle: (FreenetRequest) -> String = { pageTitleKey?.let(webInterface.l10n::getString) ?: "" }
+               private val pageTitle: (FreenetRequest) -> String = { pageTitleKey?.let(webInterface.translation::translate) ?: "" }
 ) : FreenetTemplatePage(templateRenderer, loaders, "noPermission.html") {
 
        private val core = webInterface.core
        private val sessionProvider: SessionProvider = webInterface
+       protected val translation: Translation = webInterface.translation
 
        protected fun getCurrentSone(toadletContext: ToadletContext, createSession: Boolean = true) =
                        sessionProvider.getCurrentSone(toadletContext, createSession)
@@ -89,7 +91,7 @@ open class SoneTemplatePage(
        private val String.urlEncode: String get() = URLEncoder.encode(this, "UTF-8")
 
        override fun isEnabled(toadletContext: ToadletContext) =
-                       isEnabled(SoneRequest(toadletContext.uri, Method.GET, HTTPRequestImpl(toadletContext.uri, "GET"), toadletContext, webInterface.l10n, webInterface.sessionManager, core, webInterface))
+                       isEnabled(SoneRequest(toadletContext.uri, Method.GET, HTTPRequestImpl(toadletContext.uri, "GET"), toadletContext, webInterface.sessionManager, core, webInterface))
 
        open fun isEnabled(soneRequest: SoneRequest) = when {
                requiresLogin && getCurrentSone(soneRequest.toadletContext) == null -> false
index 392b272..0021c6b 100644 (file)
@@ -33,7 +33,7 @@ class UploadImagePage @Inject constructor(webInterface: WebInterface, loaders: L
                        val bytes = uploadedFile.data.use { it.toByteArray() }
                        val bufferedImage = bytes.toImage()
                        if (bufferedImage == null) {
-                               templateContext["messages"] = soneRequest.l10n.getString("Page.UploadImage.Error.InvalidImage")
+                               templateContext["messages"] = translation.translate("Page.UploadImage.Error.InvalidImage")
                                return
                        }
 
index a498d24..efaee3f 100644 (file)
@@ -51,7 +51,7 @@ class ViewSonePage @Inject constructor(webInterface: WebInterface, loaders: Load
 
        override fun getPageTitle(soneRequest: SoneRequest): String =
                        soneRequest.parameters["sone"]!!.let(soneRequest.core::getSone)?.let { sone ->
-                               "${SoneAccessor.getNiceName(sone)} - ${soneRequest.l10n.getString("Page.ViewSone.Title")}"
-                       } ?: soneRequest.l10n.getString("Page.ViewSone.Page.TitleWithoutSone")
+                               "${SoneAccessor.getNiceName(sone)} - ${translation.translate("Page.ViewSone.Title")}"
+                       } ?: translation.translate("Page.ViewSone.Page.TitleWithoutSone")
 
 }
index 7e42167..a3a95d6 100644 (file)
@@ -305,7 +305,7 @@ Page.Rescue.Text.Fetching=Die Sonerettung versucht gerade, Version {0} Ihrer Son
 Page.Rescue.Text.Fetched=Die Sonerettung hat Version {0} Ihrer Sone herunter geladen. Bitte überprüfen Sie Ihre Nachrichten, Antworten und Ihr Profile. Bei Gefallen können Sie die Sone einfach entsperren.
 Page.Rescue.Text.FetchedLast=Die Sonerettung hat die letzte verfügbare Version Ihrer Sone herunter geladen. Wenn bis jetzt keine Version dabei war, die Sie wiederherstellen möchten, haben Sie jetzt kein Glück.
 Page.Rescue.Text.NotFetched=Die Sonerettung konnte Version {0} Ihrer Sone nicht herunter laden. Bitte versuchen Sie erneut, Version {0} herunter zu laden, oder versuchen Sie die nächstältere Version.
-Page.Rescue.Label.NextEdition=Nächste Version:
+Page.Rescue.Label.NextEdition=Nächste Version
 Page.Rescue.Button.Fetch=Version herunter laden
 
 Page.NoPermission.Title=Unberechtigter Zugriff - Sone
@@ -331,6 +331,12 @@ Page.Invalid.Title=Ungültige Aktion ausgeführt - Sone
 Page.Invalid.Page.Title=Ungültige Aktion ausgeführt
 Page.Invalid.Text=Eine ungültige Aktion wurde ausgeführt, oder eine gültige Aktion hatte ungültige Parameter. Bitte kehren Sie zur {link}Hauptseite{/link} zurück und versuchen Sie Ihre Aktion erneut. Wenn der Fehler weiterhin besteht, haben Sie wahrscheinlich einen Programmierfehler gefunden.
 
+Page.Metrics.Title=Metriken
+Page.Metrics.Page.Title=Metriken
+Page.Metrics.SoneInsertDuration.Title=Hochladedauer einer Sone
+Page.Metrics.SoneParseDuration.Title=Parsdauer einer Sone
+Page.Metrics.ConfigurationSavingDuration.Title=Speicherdauer der Konfiguration
+
 View.Search.Button.Search=Suchen
 
 View.CreateSone.Text.WotIdentityRequired=Um eine Sone anzulegen, brauchen Sie eine Identität aus dem {link}„Web of Trust“ Plugin{/link}.
index 41fa10c..a23730c 100644 (file)
@@ -307,7 +307,7 @@ Page.Rescue.Text.Fetching=The Sone Rescuer is currently fetching edition {0} of
 Page.Rescue.Text.Fetched=The Sone Rescuer has downloaded edition {0} of your Sone. Please check your posts, replies, and profile. If you like what the current Sone contains, just unlock it.
 Page.Rescue.Text.FetchedLast=The Sone rescuer has downloaded the last available edition. If it did not manage to restore your Sone you are probably out of luck now.
 Page.Rescue.Text.NotFetched=The Sone Rescuer could not download edition {0} of your Sone. Please either try again with edition {0}, or try the next older edition.
-Page.Rescue.Label.NextEdition=Next edition:
+Page.Rescue.Label.NextEdition=Next edition
 Page.Rescue.Button.Fetch=Fetch edition
 
 Page.NoPermission.Title=Unauthorized Access - Sone
@@ -336,7 +336,8 @@ Page.Invalid.Text=An invalid action was performed, or the action was valid but t
 Page.Metrics.Title=Metrics
 Page.Metrics.Page.Title=Metrics
 Page.Metrics.SoneInsertDuration.Title=Sone Insert Duration
-Page.Metrics.SoneParsingDuration.Title=Sone Parsing Duration
+Page.Metrics.SoneParseDuration.Title=Sone Parse Duration
+Page.Metrics.ConfigurationSaveDuration.Title=Configuration Save Duration
 
 View.Search.Button.Search=Search
 
index f5072c8..8a6e200 100644 (file)
@@ -305,7 +305,7 @@ Page.Rescue.Text.Fetching=El modo rescate está obteniendo actualmente la edici
 Page.Rescue.Text.Fetched=El modo rescate ha descargado la edición {0} de tu Sone. Por favor, comprueba tus publicaciones, respuestas y perfil. Si te gusta lo que tiene el Sone actual, desbloquealo.
 Page.Rescue.Text.FetchedLast=El rescatador de Sone ha descargado la  última versión disponibe. Si no ha conseguido restaurar tu Sone probablemente no te queda suerte.
 Page.Rescue.Text.NotFetched=El rescatador de Sone no ha podido descargar la edición {0} de tu Sone. Por favor, vuelve ha intentarlo con la edición {0}, o prueba con la siguiente versión antigua.
-Page.Rescue.Label.NextEdition=Siguiente edición:
+Page.Rescue.Label.NextEdition=Siguiente edición
 Page.Rescue.Button.Fetch=Obtener edición
 
 Page.NoPermission.Title=Acceso desautorizado - Sone
@@ -331,6 +331,12 @@ Page.Invalid.Title=Acción invalida realizada - Sone
 Page.Invalid.Page.Title=Acción invalida realizada
 Page.Invalid.Text=Se ha realizado una acción inválida, o la acción era válida pero los parámetros no. Por favor, vuelve al {link}índice{/link} e intentalo de nuevo. Si el error persiste, probablemente hayas encontrado un bug.
 
+Page.Metrics.Title=Metrics
+Page.Metrics.Page.Title=Metrics
+Page.Metrics.SoneInsertDuration.Title=Sone Insert Duration
+Page.Metrics.SoneParseDuration.Title=Sone Parse Duration
+Page.Metrics.ConfigurationSaveDuration.Title=Configuration Save Duration
+
 View.Search.Button.Search=Buscar
 
 View.CreateSone.Text.WotIdentityRequired=Para crear un Sone necesitas una identidad del {link}plugin Web of Trust{/link}.
@@ -471,4 +477,4 @@ Notification.Mention.Text=Has sido mencionado en las siguientes publicaciones:
 Notification.SoneIsInserting.Text=Tu Sone sone://{0} está siendo insertado.
 Notification.SoneIsInserted.Text=Tu Sone sone://{0} ha sido insertado en {1,number} {1,choice,0#segundos|1#segundo|1<segundos}.
 Notification.SoneInsertAborted.Text=Tu Sone sone://{0} no pudo ser insertado.
-# 55-61
+# 55-61, 334–338
index 86015d0..69ad716 100644 (file)
@@ -305,7 +305,7 @@ Page.Rescue.Text.Fetching=Le récupérateur de Sone est en train de restaurer la
 Page.Rescue.Text.Fetched=Le récupérateur de Sone a restauré la version {0} de votre Sone. Merci de vérifier vos messages, réponses et profile. Si les informations vous conviennent, débloquez la version.
 Page.Rescue.Text.FetchedLast=Le récupérateur de Sone a restauré la dernière version disponible. Si vous ne souhaitiez pas récupérer une ancienne version de Sone. Considérez que vous n'avez pas de chance.
 Page.Rescue.Text.NotFetched=Le récupérateur de Sone ne peut pas restaurer la version {0} de votre Sone. Merci de réessayer, ou essayez avec une version plus ancienne.
-Page.Rescue.Label.NextEdition=Prochaine version:
+Page.Rescue.Label.NextEdition=Prochaine version
 Page.Rescue.Button.Fetch=Récupérer la version.
 
 Page.NoPermission.Title=Accès non autorisé - Sone
@@ -331,6 +331,12 @@ Page.Invalid.Title=Action invalide réalisée - Sone
 Page.Invalid.Page.Title=Action invalide réalisée
 Page.Invalid.Text=Une action invalide a été effectuée, ou l'action était valide mes les paramètres ne l'étaient pas. Veuillez retourner à la {link}page d'index{/link} et veuillez réessayer. Si l'erreur persiste, vous avez probablement trouvé un bug.
 
+Page.Metrics.Title=Metrics
+Page.Metrics.Page.Title=Metrics
+Page.Metrics.SoneInsertDuration.Title=Sone Insert Duration
+Page.Metrics.SoneParseDuration.Title=Sone Parse Duration
+Page.Metrics.ConfigurationSaveDuration.Title=Configuration Save Duration
+
 View.Search.Button.Search=Recherche
 
 View.CreateSone.Text.WotIdentityRequired=Pour créer un Sone vous avez besoin d'une identité venant du {link}plugin Web of Trust {/link}.
@@ -471,4 +477,4 @@ Notification.Mention.Text=Vous avez été mentionné dans les messages suivants:
 Notification.SoneIsInserting.Text=Votre Sone sone://{0} va maintenant être inséré.
 Notification.SoneIsInserted.Text=votre Sone sone://{0} a été inséré dans {1,number} {1,choice,0#seconds|1#second|1<seconds}.
 Notification.SoneInsertAborted.Text=Votre Sone sone://{0} ne peut pas être inséré.
-# 55-61
+# 55-61, 334–338
index bf81762..166270b 100644 (file)
@@ -305,7 +305,7 @@ Page.Rescue.Text.Fetching=Sone復帰モードでは現在{0}の版を読み込
 Page.Rescue.Text.Fetched=Sone復帰モードは{0}の版を読み込みました。投稿、返信、プロフィールなどを確認の上、問題がなければロックを解除してください。
 Page.Rescue.Text.FetchedLast=Sone復帰モードは存在する全ての版を読み込みました。この時点で読み込めていない場合は恐らく復帰は不可能でしょう。
 Page.Rescue.Text.NotFetched=Sone復帰モードは{0}の版を読み込むことはできませんでした。{0}の版を試してみるか、さらに次の版を試してみてください。
-Page.Rescue.Label.NextEdition=次の版:
+Page.Rescue.Label.NextEdition=次の版
 Page.Rescue.Button.Fetch=版を取り出す
 
 Page.NoPermission.Title=不正なアクセス - Sone
@@ -331,6 +331,12 @@ Page.Invalid.Title=不正な操作が行われました - Sone
 Page.Invalid.Page.Title=不正な操作が行われました
 Page.Invalid.Text=不正な操作が行われたか、操作が正常でもパラメーターが不正である可能性があります。{link}インデックスページ{/link}に戻って試してみてください。同じ問題が何度も発生するようであればバグを見つけた可能性があります。
 
+Page.Metrics.Title=Metrics
+Page.Metrics.Page.Title=Metrics
+Page.Metrics.SoneInsertDuration.Title=Sone Insert Duration
+Page.Metrics.SoneParseDuration.Title=Sone Parse Duration
+Page.Metrics.ConfigurationSaveDuration.Title=Configuration Save Duration
+
 View.Search.Button.Search=検索
 
 View.CreateSone.Text.WotIdentityRequired=Soneを作成するのには{link}Web of Trustプラグイン{/link}のプロフィールが必要です。
@@ -471,4 +477,4 @@ Notification.Mention.Text=次の投稿でメンションされています:
 Notification.SoneIsInserting.Text=あなたのSone sone://{0}は現在インサート中です。
 Notification.SoneIsInserted.Text=あなたのSone sone://{0}は{1,number}{1,choice,0#秒|1#秒|1<秒}でインサートされました。
 Notification.SoneInsertAborted.Text=あなたのSone sone://{0}のインサートに失敗しました。
-# 55-51, 67, 107, 465
+# 55-51, 67, 107, 334–338, 471
index 5a13696..d62d51c 100644 (file)
@@ -305,7 +305,7 @@ Page.Rescue.Text.Fetching=Sone-redderen laster foreløpig ned utgave {0} av din
 Page.Rescue.Text.Fetched=Sone-redderen har lastet ned utgave {0} av din Sone. Sjekk dine innlegg, svar og profil. Hvis du er fornøyd med redningen, kan du låse opp Sonen din.
 Page.Rescue.Text.FetchedLast=Sone-redderen har lastet ned den siste tilgjengelige utgaven. Hvis den ikke klarte å redde Sonen din, er det lite annet å gjøre.
 Page.Rescue.Text.NotFetched=Sone-redderen kunne ikke laste ned utgave {0} av din Sone. Enten prøv igjen på nytt med utgave {0}, eller prøv igjen med utgaven før.
-Page.Rescue.Label.NextEdition=Neste utgave:
+Page.Rescue.Label.NextEdition=Neste utgave
 Page.Rescue.Button.Fetch=Hent utgave
 
 Page.NoPermission.Title=Ikke-autorisert tilgang - Sone
@@ -331,6 +331,12 @@ Page.Invalid.Title=Ugyldig handling - Sone
 Page.Invalid.Page.Title=Ugyldig handling
 Page.Invalid.Text=En ugyldig handling ble gjort eller så var handlingens parametere ugyldig. Gå tilbake til {link}indeks{/link} og prøv på ny. Hvis feilmeldingen vedvarer har du sannsynligvis funnet en bug.
 
+Page.Metrics.Title=Metrics
+Page.Metrics.Page.Title=Metrics
+Page.Metrics.SoneInsertDuration.Title=Sone Insert Duration
+Page.Metrics.SoneParseDuration.Title=Sone Parse Duration
+Page.Metrics.ConfigurationSaveDuration.Title=Configuration Save Duration
+
 View.Search.Button.Search=Søk
 
 View.CreateSone.Text.WotIdentityRequired=For å lage en Sone trenger du et pseudonym fra {link}'Web of Trust'-tillegget{/link}.
@@ -471,4 +477,4 @@ Notification.Mention.Text=Du har blitt nevnt i følgende innlegg:
 Notification.SoneIsInserting.Text=Your Sone sone://{0} is now being inserted.
 Notification.SoneIsInserted.Text=Your Sone sone://{0} has been inserted in {1,number} {1,choice,0#seconds|1#second|1<seconds}.
 Notification.SoneInsertAborted.Text=Your Sone sone://{0} could not be inserted.
-# 55-61, 67, 107, 127-128, 315-317, 319-321, 465, 471-473
+# 55-61, 67, 107, 127-128, 315-317, 319-321, 334–338, 471, 477-479
index e9e460b..753932b 100644 (file)
@@ -305,7 +305,7 @@ Page.Rescue.Text.Fetching=Tryb Ratunkowy Sone pobiera właśnie edycję {0} twoj
 Page.Rescue.Text.Fetched=Tryb Ratunkowy Sone pobrał edycję {0} twojego Sone. Sprawdź swoje posty, odpowiedzi oraz profil. Jeśli podoba ci się zawartość aktualnego Sone, to mozesz go odblokować.
 Page.Rescue.Text.FetchedLast= Tryb Ratunkowy Sone pobrał ostatnią dostępną edycję. Jeśli nie udało się przywrócić twojego Sone, to nie masz teraz szczęścia.
 Page.Rescue.Text.NotFetched=Tryb Ratunkowy Sone nie mógł sćiągnąć edycji {0} twojego Sone. Spróbuj pobrać ponownie edycję {0}, albo pobierz kolejną starszą edycję.
-Page.Rescue.Label.NextEdition=Następna edycja:
+Page.Rescue.Label.NextEdition=Następna edycja
 Page.Rescue.Button.Fetch=Pobierz edycję
 
 Page.NoPermission.Title=Nieupoważniony dostęp- Sone
@@ -331,6 +331,12 @@ Page.Invalid.Title=Niepoprawne działanie
 Page.Invalid.Page.Title=Niepoprawne działanie
 Page.Invalid.Text=Podjęto niepoprawne działanie, lub też działanie było poprawne, ale parametry nie. Wróć do{link}strony głównej{/link} i spróbuj ponownie. Jeśli problem będzie się utrzymywać to najprawdopodobniej znalazłeś błąd.
 
+Page.Metrics.Title=Metrics
+Page.Metrics.Page.Title=Metrics
+Page.Metrics.SoneInsertDuration.Title=Sone Insert Duration
+Page.Metrics.SoneParseDuration.Title=Sone Parse Duration
+Page.Metrics.ConfigurationSaveDuration.Title=Configuration Save Duration
+
 View.Search.Button.Search=Szukaj
 
 View.CreateSone.Text.WotIdentityRequired=Żeby utworzyć Sone potrzebujesz tożsamości z {link} wtyczki Sieć Zaufania{/link}.
@@ -471,4 +477,4 @@ Notification.Mention.Text=Zostałeś oznaczony w następujących postach:
 Notification.SoneIsInserting.Text=Twoje Sone sone://{0} jest w tej chili wysyłane.
 Notification.SoneIsInserted.Text=Twoje sone://{0} zostało wysłane w {1,number} {1,choice,0#seconds|1#second|1<seconds}.
 Notification.SoneInsertAborted.Text=Twoje Sone sone://{0} nie mogło zostać wysłane.
-# 55-61, 465
+# 55-61, 334–338, 471
index bfc7301..5d1a108 100644 (file)
@@ -305,7 +305,7 @@ Page.Rescue.Text.Fetching=Восстановитель Sone в данный мо
 Page.Rescue.Text.Fetched=Восстановитель Sone скачал редакцию {0} вашего Sone. Пожалуйста проверьте свои сообщения, ответы и профиль.
 Page.Rescue.Text.FetchedLast=Восстановитель Sone скачал последнюю доступную редакцию. Если ему не удалось восстановить ваш Sone, у вас кончились варианты действий.
 Page.Rescue.Text.NotFetched=Восстановитель Sone не смог скачать редакцию {0} вашего Sone. Пожалуйста, либо попытайтесь снова с редакцией {0}, либо попробуйте более старую редакцию.
-Page.Rescue.Label.NextEdition=Следующая редакция:
+Page.Rescue.Label.NextEdition=Следующая редакция
 Page.Rescue.Button.Fetch=Загрузить редакцию
 
 Page.NoPermission.Title=Неавторизованный доступ - Sone
@@ -331,6 +331,12 @@ Page.Invalid.Title=Произведено неверное действие - So
 Page.Invalid.Page.Title=Произведено неверное действие
 Page.Invalid.Text=Было произведено неверное действие, или верное действие с неверными параметрами. Пожалуйста, вернитесь на {link}главную страницу{/link} и попытайтесь снова. Если ошибка повторяется, вы, вероятно, обнаружили баг.
 
+Page.Metrics.Title=Metrics
+Page.Metrics.Page.Title=Metrics
+Page.Metrics.SoneInsertDuration.Title=Sone Insert Duration
+Page.Metrics.SoneParseDuration.Title=Sone Parse Duration
+Page.Metrics.ConfigurationSaveDuration.Title=Configuration Save Duration
+
 View.Search.Button.Search=Поиск
 
 View.CreateSone.Text.WotIdentityRequired=Чтобы создать Sone, вам необходима личность от дополнения {link}Web of Trust{/link}.
@@ -471,4 +477,4 @@ Notification.Mention.Text=Вас упомянули в следующих соо
 Notification.SoneIsInserting.Text=Your Sone sone://{0} is now being inserted.
 Notification.SoneIsInserted.Text=Your Sone sone://{0} has been inserted in {1,number} {1,choice,0#seconds|1#second|1<seconds}.
 Notification.SoneInsertAborted.Text=Your Sone sone://{0} could not be inserted.
-# 55-61, 67, 107, 127-128, 315-317, 319-321, 465, 471-473
+# 55-61, 67, 107, 127-128, 315-317, 319-321, 334–338, 471, 477-479
index 349ab5c..4151e00 100644 (file)
@@ -419,16 +419,8 @@ textarea {
        font-weight: bold;
 }
 
-#sone .post .trust button {
-       color: rgb(0, 128, 0);
-}
-
-#sone .post .distrust button {
-       color: rgb(255, 0, 0);
-}
-
 #sone .post .untrust button {
-       color: rgb(64, 64, 64);
+       color: rgb(255, 0, 0);
 }
 
 #sone .post .delete button:hover, #sone .post .like button:hover, #sone .post .unlike button:hover, #sone .post .trust button:hover, #sone .post .distrust button:hover, #sone .post .untrust button:hover, #sone .post .bookmark button:hover, #sone .post .unbookmark button:hover {
@@ -937,3 +929,7 @@ textarea {
 #sone form#options li {
        list-style-type: none;
 }
+
+#sone table thead tr {
+       font-weight: bold;
+}
index 81683e4..7cdb924 100644 (file)
                                                        <input type="hidden" name="formPassword" value="<% formPassword|html>" />
                                                        <input type="hidden" name="returnPage" value="<% request.uri|html>" />
                                                        <input type="hidden" name="sone" value="<% post.sone.id|html>" />
-                                                       <button type="submit" title="<%= View.Trust.Tooltip.Trust|l10n|html>"></button>
+                                                       <button type="submit" title="<%= View.Trust.Tooltip.Trust|l10n|html>">👍</button>
                                                </form>
                                                <form class="distrust post-distrust<%if post.sone.trust.assigned> hidden<%/if>" action="distrust.html" method="post">
                                                        <input type="hidden" name="formPassword" value="<% formPassword|html>" />
                                                        <input type="hidden" name="returnPage" value="<% request.uri|html>" />
                                                        <input type="hidden" name="sone" value="<% post.sone.id|html>" />
-                                                       <button type="submit" title="<%= View.Trust.Tooltip.Distrust|l10n|html>"></button>
+                                                       <button type="submit" title="<%= View.Trust.Tooltip.Distrust|l10n|html>">👎</button>
                                                </form>
                                                <form class="untrust post-untrust<%if !post.sone.trust.assigned> hidden<%/if>" action="untrust.html" method="post">
                                                        <input type="hidden" name="formPassword" value="<% formPassword|html>" />
                                                        <input type="hidden" name="returnPage" value="<% request.uri|html>" />
                                                        <input type="hidden" name="sone" value="<% post.sone.id|html>" />
-                                                       <button type="submit" title="<%= View.Trust.Tooltip.Untrust|l10n|html>">â\86</button>
+                                                       <button type="submit" title="<%= View.Trust.Tooltip.Untrust|l10n|html>">â\9c\97</button>
                                                </form>
                                        <%/if>
                                <%/if>
index 3a5e59a..99c02e6 100644 (file)
                                                        <input type="hidden" name="formPassword" value="<% formPassword|html>" />
                                                        <input type="hidden" name="returnPage" value="<% request.uri|html>" />
                                                        <input type="hidden" name="sone" value="<% reply.sone.id|html>" />
-                                                       <button type="submit" title="<%= View.Trust.Tooltip.Trust|l10n|html>"></button>
+                                                       <button type="submit" title="<%= View.Trust.Tooltip.Trust|l10n|html>">👍</button>
                                                </form>
                                                <form class="distrust reply-distrust<%if reply.sone.trust.assigned> hidden<%/if>" action="distrust.html" method="post">
                                                        <input type="hidden" name="formPassword" value="<% formPassword|html>" />
                                                        <input type="hidden" name="returnPage" value="<% request.uri|html>" />
                                                        <input type="hidden" name="sone" value="<% reply.sone.id|html>" />
-                                                       <button type="submit" title="<%= View.Trust.Tooltip.Distrust|l10n|html>"></button>
+                                                       <button type="submit" title="<%= View.Trust.Tooltip.Distrust|l10n|html>">👎</button>
                                                </form>
                                                <form class="untrust reply-untrust<%if !reply.sone.trust.assigned> hidden<%/if>" action="untrust.html" method="post">
                                                        <input type="hidden" name="formPassword" value="<% formPassword|html>" />
                                                        <input type="hidden" name="returnPage" value="<% request.uri|html>" />
                                                        <input type="hidden" name="sone" value="<% reply.sone.id|html>" />
-                                                       <button type="submit" title="<%= View.Trust.Tooltip.Untrust|l10n|html>">â\86</button>
+                                                       <button type="submit" title="<%= View.Trust.Tooltip.Untrust|l10n|html>">â\9c\97</button>
                                                </form>
                                        <%/if>
                                <%/if>
index beaded8..5707476 100644 (file)
@@ -8,45 +8,22 @@
                <thead>
                        <tr>
                                <td>Metric</td>
-                               <td>Count</td>
-                               <td>Min</td>
-                               <td>Max</td>
-                               <td>Mean</td>
-                               <td>Median</td>
-                               <td>75%</td>
-                               <td>95%</td>
-                               <td>98%</td>
-                               <td>99%</td>
-                               <td>99.9%</td>
+                               <td class="numeric">Count</td>
+                               <td class="numeric">Min</td>
+                               <td class="numeric">Max</td>
+                               <td class="numeric">Mean</td>
+                               <td class="numeric">Median</td>
+                               <td class="numeric">75%</td>
+                               <td class="numeric">95%</td>
+                               <td class="numeric">98%</td>
+                               <td class="numeric">99%</td>
+                               <td class="numeric">99.9%</td>
                        </tr>
                </thead>
                <tbody>
-                       <tr>
-                               <td><%= Page.Metrics.SoneInsertDuration.Title|l10n|html></td>
-                               <td class="numeric"><% soneInsertDurationCount|html></td>
-                               <td class="numeric"><% soneInsertDurationMin|duration scale=="μs"|html></td>
-                               <td class="numeric"><% soneInsertDurationMax|duration scale=="μs"|html></td>
-                               <td class="numeric"><% soneInsertDurationMean|duration scale=="μs"|html></td>
-                               <td class="numeric"><% soneInsertDurationMedian|duration scale=="μs"|html></td>
-                               <td class="numeric"><% soneInsertDurationPercentile75|duration scale=="μs"|html></td>
-                               <td class="numeric"><% soneInsertDurationPercentile95|duration scale=="μs"|html></td>
-                               <td class="numeric"><% soneInsertDurationPercentile98|duration scale=="μs"|html></td>
-                               <td class="numeric"><% soneInsertDurationPercentile99|duration scale=="μs"|html></td>
-                               <td class="numeric"><% soneInsertDurationPercentile999|duration scale=="μs"|html></td>
-                       </tr>
-                       <tr>
-                               <td><%= Page.Metrics.SoneParsingDuration.Title|l10n|html></td>
-                               <td class="numeric"><% soneParsingDurationCount|html></td>
-                               <td class="numeric"><% soneParsingDurationMin|duration scale=="μs"|html></td>
-                               <td class="numeric"><% soneParsingDurationMax|duration scale=="μs"|html></td>
-                               <td class="numeric"><% soneParsingDurationMean|duration scale=="μs"|html></td>
-                               <td class="numeric"><% soneParsingDurationMedian|duration scale=="μs"|html></td>
-                               <td class="numeric"><% soneParsingDurationPercentile75|duration scale=="μs"|html></td>
-                               <td class="numeric"><% soneParsingDurationPercentile95|duration scale=="μs"|html></td>
-                               <td class="numeric"><% soneParsingDurationPercentile98|duration scale=="μs"|html></td>
-                               <td class="numeric"><% soneParsingDurationPercentile99|duration scale=="μs"|html></td>
-                               <td class="numeric"><% soneParsingDurationPercentile999|duration scale=="μs"|html></td>
-                       </tr>
+                       <%foreach histograms histogram>
+                               <% histogram.value|render-histogram name=histogram.key>
+                       <%/foreach>
                </tbody>
        </table>
 
index d711881..e08edc3 100644 (file)
                <%/if>
                <form action="rescue.html" method="post">
                        <input type="hidden" name="formPassword" value="<%formPassword|html>" />
-                       <div>
-                               <%= Page.Rescue.Label.NextEdition|l10n|html>: <%soneRescuer.nextEdition>
-                               <button type="submit" name="fetch" value="true"><%= Page.Rescue.Button.Fetch|l10n|html></button>
-                       </div>
+                       <label><%= Page.Rescue.Label.NextEdition|l10n|html></label>
+                       <input type="field" name="edition" value="<%soneRescuer.nextEdition>" />
+                       <button type="submit" name="fetch" value="true"><%= Page.Rescue.Button.Fetch|l10n|html></button>
                </form>
        <%else>
                <%if soneRescuer.lastFetchSuccessful>
diff --git a/src/test/java/net/pterodactylus/sone/core/SoneRescuerTest.java b/src/test/java/net/pterodactylus/sone/core/SoneRescuerTest.java
new file mode 100644 (file)
index 0000000..ff49a4c
--- /dev/null
@@ -0,0 +1,134 @@
+package net.pterodactylus.sone.core;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import net.pterodactylus.sone.data.Sone;
+
+import freenet.keys.FreenetURI;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/**
+ * Unit test for {@link SoneRescuer}.
+ */
+public class SoneRescuerTest {
+
+       private static final long CURRENT_EDITION = 12L;
+       private static final long SOME_OTHER_EDITION = 15L;
+       private final Core core = mock(Core.class);
+       private final SoneDownloader soneDownloader = mock(SoneDownloader.class);
+       private final Sone sone = mock(Sone.class);
+       private SoneRescuer soneRescuer;
+
+       @Before
+       public void setupSone() {
+               FreenetURI soneUri = mock(FreenetURI.class);
+               when(soneUri.getEdition()).thenReturn(CURRENT_EDITION);
+               when(sone.getRequestUri()).thenReturn(soneUri);
+       }
+
+       @Before
+       public void setupSoneRescuer() {
+               soneRescuer = new SoneRescuer(core, soneDownloader, sone);
+       }
+
+       @Test
+       public void newSoneRescuerIsNotFetchingAnything() {
+               assertThat(soneRescuer.isFetching(), is(false));
+       }
+
+       @Test
+       public void newSoneRescuerStartsAtCurrentEditionOfSone() {
+               assertThat(soneRescuer.getCurrentEdition(), is(CURRENT_EDITION));
+       }
+
+       @Test
+       public void newSoneRescuerHasANextEditionToGet() {
+               assertThat(soneRescuer.hasNextEdition(), is(true));
+       }
+
+       @Test
+       public void soneRescuerDoesNotHaveANextEditionIfCurrentEditionIsZero() {
+               when(sone.getRequestUri().getEdition()).thenReturn(0L);
+               soneRescuer = new SoneRescuer(core, soneDownloader, sone);
+               assertThat(soneRescuer.hasNextEdition(), is(false));
+       }
+
+       @Test
+       public void nextEditionIsOneSmallerThanTheCurrentEdition() {
+               assertThat(soneRescuer.getNextEdition(), is(CURRENT_EDITION - 1));
+       }
+
+       @Test
+       public void currentEditionCanBeSet() {
+               soneRescuer.setEdition(SOME_OTHER_EDITION);
+               assertThat(soneRescuer.getCurrentEdition(), is(SOME_OTHER_EDITION));
+       }
+
+       @Test
+       public void lastFetchOfANewSoneRescuerWasSuccessful() {
+               assertThat(soneRescuer.isLastFetchSuccessful(), is(true));
+       }
+
+       @Test
+       public void mainLoopStopsWhenItShould() {
+               soneRescuer.stop();
+               soneRescuer.serviceRun();
+       }
+
+       @Test
+       public void successfulInsert() {
+               final Sone fetchedSone = mock(Sone.class);
+               returnUriOnInsert(fetchedSone);
+               soneRescuer.startNextFetch();
+               soneRescuer.serviceRun();
+               verify(core).lockSone(eq(sone));
+               verify(core).updateSone(eq(fetchedSone), eq(true));
+               assertThat(soneRescuer.isLastFetchSuccessful(), is(true));
+               assertThat(soneRescuer.isFetching(), is(false));
+       }
+
+       @Test
+       public void nonSuccessfulInsertIsRecognized() {
+               returnUriOnInsert(null);
+               soneRescuer.startNextFetch();
+               soneRescuer.serviceRun();
+               verify(core).lockSone(eq(sone));
+               verify(core, never()).updateSone(any(Sone.class), eq(true));
+               assertThat(soneRescuer.isLastFetchSuccessful(), is(false));
+               assertThat(soneRescuer.isFetching(), is(false));
+       }
+
+       private void returnUriOnInsert(final Sone fetchedSone) {
+               FreenetURI keyWithMetaStrings = setupFreenetUri();
+               doAnswer(new Answer<Sone>() {
+                       @Override
+                       public Sone answer(InvocationOnMock invocation) throws Throwable {
+                               soneRescuer.stop();
+                               return fetchedSone;
+                       }
+               }).when(soneDownloader).fetchSone(eq(sone), eq(keyWithMetaStrings), eq(true));
+       }
+
+       private FreenetURI setupFreenetUri() {
+               FreenetURI sskKey = mock(FreenetURI.class);
+               FreenetURI keyWithDocName = mock(FreenetURI.class);
+               FreenetURI keyWithMetaStrings = mock(FreenetURI.class);
+               when(keyWithDocName.setMetaString(eq(new String[] { "sone.xml" }))).thenReturn(keyWithMetaStrings);
+               when(sskKey.setDocName(eq("Sone-" + CURRENT_EDITION))).thenReturn(keyWithDocName);
+               when(sone.getRequestUri().setKeyType(eq("SSK"))).thenReturn(sskKey);
+               return keyWithMetaStrings;
+       }
+
+}
diff --git a/src/test/java/net/pterodactylus/sone/freenet/KeyTest.java b/src/test/java/net/pterodactylus/sone/freenet/KeyTest.java
deleted file mode 100644 (file)
index 80e1e45..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-package net.pterodactylus.sone.freenet;
-
-import static freenet.support.Base64.encode;
-import static net.pterodactylus.sone.freenet.Key.from;
-import static net.pterodactylus.sone.freenet.Key.routingKey;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-
-import java.net.MalformedURLException;
-
-import freenet.keys.FreenetURI;
-
-import org.junit.Test;
-
-/**
- * Unit test for {@link Key}.
- */
-public class KeyTest {
-
-       private final FreenetURI uri;
-       private final Key key;
-
-       public KeyTest() throws MalformedURLException {
-               uri = new FreenetURI(
-                               "SSK@NfUYvxDwU9vqb2mh-qdT~DYJ6U0XNbxMGGoLe0aCHJs,Miglsgix0VR56ZiPl4NgjnUd~UdrnHqIvXJ3KKHmxmI,AQACAAE/some-site-12/foo/bar.html");
-               key = from(uri);
-       }
-
-       @Test
-       public void keyCanBeCreatedFromFreenetUri() throws MalformedURLException {
-               assertThat(key.getRoutingKey(),
-                               is("NfUYvxDwU9vqb2mh-qdT~DYJ6U0XNbxMGGoLe0aCHJs"));
-               assertThat(key.getCryptoKey(),
-                               is("Miglsgix0VR56ZiPl4NgjnUd~UdrnHqIvXJ3KKHmxmI"));
-               assertThat(key.getExtra(), is("AQACAAE"));
-       }
-
-       @Test
-       public void keyCanBeConvertedToUsk() throws MalformedURLException {
-               FreenetURI uskUri = key.toUsk("other-site", 15, "some", "path.html");
-               assertThat(uskUri.toString(),
-                               is("USK@NfUYvxDwU9vqb2mh-qdT~DYJ6U0XNbxMGGoLe0aCHJs,Miglsgix0VR56ZiPl4NgjnUd~UdrnHqIvXJ3KKHmxmI,AQACAAE/other-site/15/some/path.html"));
-       }
-
-       @Test
-       public void keyCanBeConvertedToSskWithoutEdition()
-       throws MalformedURLException {
-               FreenetURI uskUri = key.toSsk("other-site", "some", "path.html");
-               assertThat(uskUri.toString(),
-                               is("SSK@NfUYvxDwU9vqb2mh-qdT~DYJ6U0XNbxMGGoLe0aCHJs,Miglsgix0VR56ZiPl4NgjnUd~UdrnHqIvXJ3KKHmxmI,AQACAAE/other-site/some/path.html"));
-       }
-
-       @Test
-       public void keyCanBeConvertedToSskWithEdition()
-       throws MalformedURLException {
-               FreenetURI uskUri = key.toSsk("other-site", 15, "some", "path.html");
-               assertThat(uskUri.toString(),
-                               is("SSK@NfUYvxDwU9vqb2mh-qdT~DYJ6U0XNbxMGGoLe0aCHJs,Miglsgix0VR56ZiPl4NgjnUd~UdrnHqIvXJ3KKHmxmI,AQACAAE/other-site-15/some/path.html"));
-       }
-
-       @Test
-       public void routingKeyIsExtractCorrectly() {
-               assertThat(routingKey(uri),
-                               is("NfUYvxDwU9vqb2mh-qdT~DYJ6U0XNbxMGGoLe0aCHJs"));
-       }
-
-}
diff --git a/src/test/java/net/pterodactylus/sone/freenet/wot/DefaultIdentityTest.java b/src/test/java/net/pterodactylus/sone/freenet/wot/DefaultIdentityTest.java
deleted file mode 100644 (file)
index 9d59570..0000000
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Sone - DefaultIdentityTest.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.freenet.wot;
-
-import static com.google.common.collect.ImmutableMap.of;
-import static java.util.Arrays.asList;
-import static net.pterodactylus.sone.test.Matchers.matchesRegex;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.containsInAnyOrder;
-import static org.hamcrest.Matchers.empty;
-import static org.hamcrest.Matchers.hasEntry;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.not;
-import static org.hamcrest.Matchers.nullValue;
-import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
-import static org.mockito.Mockito.mock;
-
-import java.util.Collections;
-
-import org.junit.Test;
-
-/**
- * Unit test for {@link DefaultIdentity}.
- */
-public class DefaultIdentityTest {
-
-       protected final DefaultIdentity identity = createIdentity();
-
-       protected DefaultIdentity createIdentity() {
-               return new DefaultIdentity("Id", "Nickname", "RequestURI");
-       }
-
-       @Test
-       public void identityCanBeCreated() {
-               assertThat(identity.getId(), is("Id"));
-               assertThat(identity.getNickname(), is("Nickname"));
-               assertThat(identity.getRequestUri(), is("RequestURI"));
-               assertThat(identity.getContexts(), empty());
-               assertThat(identity.getProperties(), is(Collections.<String, String>emptyMap()));
-       }
-
-       @Test
-       public void contextsAreAddedCorrectly() {
-               identity.addContext("Test");
-               assertThat(identity.getContexts(), contains("Test"));
-               assertThat(identity.hasContext("Test"), is(true));
-       }
-
-       @Test
-       public void contextsAreRemovedCorrectly() {
-               identity.addContext("Test");
-               identity.removeContext("Test");
-               assertThat(identity.getContexts(), empty());
-               assertThat(identity.hasContext("Test"), is(false));
-       }
-
-       @Test
-       public void contextsAreSetCorrectlyInBulk() {
-               identity.addContext("Test");
-               identity.setContexts(asList("Test1", "Test2"));
-               assertThat(identity.getContexts(), containsInAnyOrder("Test1", "Test2"));
-               assertThat(identity.hasContext("Test"), is(false));
-               assertThat(identity.hasContext("Test1"), is(true));
-               assertThat(identity.hasContext("Test2"), is(true));
-       }
-
-       @Test
-       public void propertiesAreAddedCorrectly() {
-               identity.setProperty("Key", "Value");
-               assertThat(identity.getProperties().size(), is(1));
-               assertThat(identity.getProperties(), hasEntry("Key", "Value"));
-               assertThat(identity.getProperty("Key"), is("Value"));
-       }
-
-       @Test
-       public void propertiesAreRemovedCorrectly() {
-               identity.setProperty("Key", "Value");
-               identity.removeProperty("Key");
-               assertThat(identity.getProperties(), is(Collections.<String, String>emptyMap()));
-               assertThat(identity.getProperty("Key"), nullValue());
-       }
-
-       @Test
-       public void propertiesAreSetCorrectlyInBulk() {
-               identity.setProperty("Key", "Value");
-               identity.setProperties(of("Key1", "Value1", "Key2", "Value2"));
-               assertThat(identity.getProperties().size(), is(2));
-               assertThat(identity.getProperty("Key"), nullValue());
-               assertThat(identity.getProperty("Key1"), is("Value1"));
-               assertThat(identity.getProperty("Key2"), is("Value2"));
-       }
-
-       @Test
-       public void trustRelationshipsAreAddedCorrectly() {
-               OwnIdentity ownIdentity = mock(OwnIdentity.class);
-               Trust trust = mock(Trust.class);
-               identity.setTrust(ownIdentity, trust);
-               assertThat(identity.getTrust(ownIdentity), is(trust));
-       }
-
-       @Test
-       public void trustRelationshipsAreRemovedCorrectly() {
-               OwnIdentity ownIdentity = mock(OwnIdentity.class);
-               Trust trust = mock(Trust.class);
-               identity.setTrust(ownIdentity, trust);
-               identity.removeTrust(ownIdentity);
-               assertThat(identity.getTrust(ownIdentity), nullValue());
-       }
-
-       @Test
-       public void identitiesWithTheSameIdAreEqual() {
-               DefaultIdentity identity2 = new DefaultIdentity("Id", "Nickname2", "RequestURI2");
-               assertThat(identity2, is(identity));
-               assertThat(identity, is(identity2));
-       }
-
-       @Test
-       public void twoEqualIdentitiesHaveTheSameHashCode() {
-               DefaultIdentity identity2 = new DefaultIdentity("Id", "Nickname2", "RequestURI2");
-               assertThat(identity.hashCode(), is(identity2.hashCode()));
-       }
-
-       @Test
-       public void nullDoesNotMatchAnIdentity() {
-               assertThat(identity, not(is((Object) null)));
-       }
-
-       @Test
-       public void toStringContainsIdAndNickname() {
-               String identityString = identity.toString();
-               assertThat(identityString, matchesRegex(".*\\bId\\b.*"));
-               assertThat(identityString, matchesRegex(".*\\bNickname\\b.*"));
-       }
-
-}
diff --git a/src/test/java/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentityTest.java b/src/test/java/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentityTest.java
deleted file mode 100644 (file)
index 138ced3..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Sone - DefaultOwnIdentityTest.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.freenet.wot;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-
-import org.junit.Test;
-
-/**
- * Unit test for {@link DefaultOwnIdentity}.
- */
-public class DefaultOwnIdentityTest extends DefaultIdentityTest {
-
-       @Override
-       protected DefaultIdentity createIdentity() {
-               return new DefaultOwnIdentity("Id", "Nickname", "RequestURI", "InsertURI");
-       }
-
-       @Test
-       public void ownIdentityCanBeCreated() {
-               assertThat(((OwnIdentity) identity).getInsertUri(), is("InsertURI"));
-       }
-
-}
diff --git a/src/test/java/net/pterodactylus/sone/freenet/wot/Identities.java b/src/test/java/net/pterodactylus/sone/freenet/wot/Identities.java
deleted file mode 100644 (file)
index 7ed2b59..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Sone - Identities.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.freenet.wot;
-
-import java.util.Collection;
-import java.util.Map;
-
-/**
- * Creates {@link Identity}s and {@link OwnIdentity}s.
- */
-public class Identities {
-
-       public static OwnIdentity createOwnIdentity(String id, Collection<String> contexts, Map<String, String> properties) {
-               DefaultOwnIdentity ownIdentity = new DefaultOwnIdentity(id, "Nickname" + id, "Request" + id, "Insert" + id);
-               setContextsAndPropertiesOnIdentity(ownIdentity, contexts, properties);
-               return ownIdentity;
-       }
-
-       public static Identity createIdentity(String id, Collection<String> contexts, Map<String, String> properties) {
-               DefaultIdentity identity = new DefaultIdentity(id, "Nickname" + id, "Request" + id);
-               setContextsAndPropertiesOnIdentity(identity, contexts, properties);
-               return identity;
-       }
-
-       private static void setContextsAndPropertiesOnIdentity(Identity identity, Collection<String> contexts, Map<String, String> properties) {
-               identity.setContexts(contexts);
-               identity.setProperties(properties);
-       }
-
-}
diff --git a/src/test/java/net/pterodactylus/sone/freenet/wot/IdentityChangeDetectorTest.java b/src/test/java/net/pterodactylus/sone/freenet/wot/IdentityChangeDetectorTest.java
deleted file mode 100644 (file)
index e1eb1c8..0000000
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Sone - IdentityChangeDetectorTest.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.freenet.wot;
-
-import static com.google.common.collect.ImmutableMap.of;
-import static com.google.common.collect.Lists.newArrayList;
-import static java.util.Arrays.asList;
-import static net.pterodactylus.sone.freenet.wot.Identities.createIdentity;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.containsInAnyOrder;
-import static org.hamcrest.Matchers.empty;
-
-import java.util.Collection;
-
-import net.pterodactylus.sone.freenet.wot.IdentityChangeDetector.IdentityProcessor;
-
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Unit test for {@link IdentityChangeDetector}.
- */
-public class IdentityChangeDetectorTest {
-
-       private final IdentityChangeDetector identityChangeDetector = new IdentityChangeDetector(createOldIdentities());
-       private final Collection<Identity> newIdentities = newArrayList();
-       private final Collection<Identity> removedIdentities = newArrayList();
-       private final Collection<Identity> changedIdentities = newArrayList();
-       private final Collection<Identity> unchangedIdentities = newArrayList();
-
-       @Before
-       public void setup() {
-               identityChangeDetector.onNewIdentity(new IdentityProcessor() {
-                       @Override
-                       public void processIdentity(Identity identity) {
-                               newIdentities.add(identity);
-                       }
-               });
-               identityChangeDetector.onRemovedIdentity(new IdentityProcessor() {
-                       @Override
-                       public void processIdentity(Identity identity) {
-                               removedIdentities.add(identity);
-                       }
-               });
-               identityChangeDetector.onChangedIdentity(new IdentityProcessor() {
-                       @Override
-                       public void processIdentity(Identity identity) {
-                               changedIdentities.add(identity);
-                       }
-               });
-               identityChangeDetector.onUnchangedIdentity(new IdentityProcessor() {
-                       @Override
-                       public void processIdentity(Identity identity) {
-                               unchangedIdentities.add(identity);
-                       }
-               });
-       }
-
-       @Test
-       public void noDifferencesAreDetectedWhenSendingTheOldIdentitiesAgain() {
-               identityChangeDetector.detectChanges(createOldIdentities());
-               assertThat(newIdentities, empty());
-               assertThat(removedIdentities, empty());
-               assertThat(changedIdentities, empty());
-               assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity2(), createIdentity3()));
-       }
-
-       @Test
-       public void detectThatAnIdentityWasRemoved() {
-               identityChangeDetector.detectChanges(asList(createIdentity1(), createIdentity3()));
-               assertThat(newIdentities, empty());
-               assertThat(removedIdentities, containsInAnyOrder(createIdentity2()));
-               assertThat(changedIdentities, empty());
-               assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity3()));
-       }
-
-       @Test
-       public void detectThatAnIdentityWasAdded() {
-               identityChangeDetector.detectChanges(asList(createIdentity1(), createIdentity2(), createIdentity3(), createIdentity4()));
-               assertThat(newIdentities, containsInAnyOrder(createIdentity4()));
-               assertThat(removedIdentities, empty());
-               assertThat(changedIdentities, empty());
-               assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity2(), createIdentity3()));
-       }
-
-       @Test
-       public void detectThatAContextWasRemoved() {
-               Identity identity2 = createIdentity2();
-               identity2.removeContext("Context C");
-               identityChangeDetector.detectChanges(asList(createIdentity1(), identity2, createIdentity3()));
-               assertThat(newIdentities, empty());
-               assertThat(removedIdentities, empty());
-               assertThat(changedIdentities, containsInAnyOrder(identity2));
-               assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity3()));
-       }
-
-       @Test
-       public void detectThatAContextWasAdded() {
-               Identity identity2 = createIdentity2();
-               identity2.addContext("Context C1");
-               identityChangeDetector.detectChanges(asList(createIdentity1(), identity2, createIdentity3()));
-               assertThat(newIdentities, empty());
-               assertThat(removedIdentities, empty());
-               assertThat(changedIdentities, containsInAnyOrder(identity2));
-               assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity3()));
-       }
-
-       @Test
-       public void detectThatAPropertyWasRemoved() {
-               Identity identity1 = createIdentity1();
-               identity1.removeProperty("Key A");
-               identityChangeDetector.detectChanges(asList(identity1, createIdentity2(), createIdentity3()));
-               assertThat(newIdentities, empty());
-               assertThat(removedIdentities, empty());
-               assertThat(changedIdentities, containsInAnyOrder(identity1));
-               assertThat(unchangedIdentities, containsInAnyOrder(createIdentity2(), createIdentity3()));
-       }
-
-       @Test
-       public void detectThatAPropertyWasAdded() {
-               Identity identity3 = createIdentity3();
-               identity3.setProperty("Key A", "Value A");
-               identityChangeDetector.detectChanges(asList(createIdentity1(), createIdentity2(), identity3));
-               assertThat(newIdentities, empty());
-               assertThat(removedIdentities, empty());
-               assertThat(changedIdentities, containsInAnyOrder(identity3));
-               assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity2()));
-       }
-
-       @Test
-       public void detectThatAPropertyWasChanged() {
-               Identity identity3 = createIdentity3();
-               identity3.setProperty("Key E", "Value F");
-               identityChangeDetector.detectChanges(asList(createIdentity1(), createIdentity2(), identity3));
-               assertThat(newIdentities, empty());
-               assertThat(removedIdentities, empty());
-               assertThat(changedIdentities, containsInAnyOrder(identity3));
-               assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity2()));
-       }
-
-       @Test
-       public void noRemovedIdentitiesAreDetectedWithoutAnIdentityProcessor() {
-               identityChangeDetector.onRemovedIdentity(null);
-               identityChangeDetector.detectChanges(asList(createIdentity1(), createIdentity3()));
-       }
-
-       @Test
-       public void noAddedIdentitiesAreDetectedWithoutAnIdentityProcessor() {
-               identityChangeDetector.onNewIdentity(null);
-               identityChangeDetector.detectChanges(asList(createIdentity1(), createIdentity2(), createIdentity3(), createIdentity4()));
-       }
-
-       private static Collection<Identity> createOldIdentities() {
-               return asList(createIdentity1(), createIdentity2(), createIdentity3());
-       }
-
-       private static Identity createIdentity1() {
-               return createIdentity("Test1", asList("Context A", "Context B"), of("Key A", "Value A", "Key B", "Value B"));
-       }
-
-       private static Identity createIdentity2() {
-               return createIdentity("Test2", asList("Context C", "Context D"), of("Key C", "Value C", "Key D", "Value D"));
-       }
-
-       private static Identity createIdentity3() {
-               return createIdentity("Test3", asList("Context E", "Context F"), of("Key E", "Value E", "Key F", "Value F"));
-       }
-
-       private static Identity createIdentity4() {
-               return createIdentity("Test4", asList("Context G", "Context H"), of("Key G", "Value G", "Key H", "Value H"));
-       }
-
-}
diff --git a/src/test/java/net/pterodactylus/sone/freenet/wot/IdentityChangeEventSenderTest.java b/src/test/java/net/pterodactylus/sone/freenet/wot/IdentityChangeEventSenderTest.java
deleted file mode 100644 (file)
index ff442a4..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Sone - IdentityChangeEventSenderTest.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.freenet.wot;
-
-import static com.google.common.collect.ImmutableMap.of;
-import static java.util.Arrays.asList;
-import static net.pterodactylus.sone.freenet.wot.Identities.createIdentity;
-import static net.pterodactylus.sone.freenet.wot.Identities.createOwnIdentity;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import net.pterodactylus.sone.freenet.wot.event.IdentityAddedEvent;
-import net.pterodactylus.sone.freenet.wot.event.IdentityRemovedEvent;
-import net.pterodactylus.sone.freenet.wot.event.IdentityUpdatedEvent;
-import net.pterodactylus.sone.freenet.wot.event.OwnIdentityAddedEvent;
-import net.pterodactylus.sone.freenet.wot.event.OwnIdentityRemovedEvent;
-
-import com.google.common.eventbus.EventBus;
-import org.junit.Test;
-
-/**
- * Unit test for {@link IdentityChangeEventSender}.
- */
-public class IdentityChangeEventSenderTest {
-
-       private final EventBus eventBus = mock(EventBus.class);
-       private final List<OwnIdentity> ownIdentities = asList(
-                       createOwnIdentity("O1", asList("Test"), of("KeyA", "ValueA")),
-                       createOwnIdentity("O2", asList("Test2"), of("KeyB", "ValueB")),
-                       createOwnIdentity("O3", asList("Test3"), of("KeyC", "ValueC"))
-       );
-       private final List<Identity> identities = asList(
-                       createIdentity("I1", Collections.<String>emptyList(), Collections.<String, String>emptyMap()),
-                       createIdentity("I2", Collections.<String>emptyList(), Collections.<String, String>emptyMap()),
-                       createIdentity("I3", Collections.<String>emptyList(), Collections.<String, String>emptyMap()),
-                       createIdentity("I2", asList("Test"), Collections.<String, String>emptyMap())
-       );
-       private final IdentityChangeEventSender identityChangeEventSender = new IdentityChangeEventSender(eventBus, createOldIdentities());
-
-       @Test
-       public void addingAnOwnIdentityIsDetectedAndReportedCorrectly() {
-               Map<OwnIdentity, Collection<Identity>> newIdentities = createNewIdentities();
-               identityChangeEventSender.detectChanges(newIdentities);
-               verify(eventBus).post(eq(new OwnIdentityRemovedEvent(ownIdentities.get(0))));
-               verify(eventBus).post(eq(new IdentityRemovedEvent(ownIdentities.get(0), identities.get(0))));
-               verify(eventBus).post(eq(new IdentityRemovedEvent(ownIdentities.get(0), identities.get(1))));
-               verify(eventBus).post(eq(new OwnIdentityAddedEvent(ownIdentities.get(2))));
-               verify(eventBus).post(eq(new IdentityAddedEvent(ownIdentities.get(2), identities.get(1))));
-               verify(eventBus).post(eq(new IdentityAddedEvent(ownIdentities.get(2), identities.get(2))));
-               verify(eventBus).post(eq(new IdentityRemovedEvent(ownIdentities.get(1), identities.get(0))));
-               verify(eventBus).post(eq(new IdentityAddedEvent(ownIdentities.get(1), identities.get(2))));
-               verify(eventBus).post(eq(new IdentityUpdatedEvent(ownIdentities.get(1), identities.get(1))));
-       }
-
-       private Map<OwnIdentity, Collection<Identity>> createNewIdentities() {
-               Map<OwnIdentity, Collection<Identity>> oldIdentities = new HashMap<>();
-               oldIdentities.put(ownIdentities.get(1), asList(identities.get(3), identities.get(2)));
-               oldIdentities.put(ownIdentities.get(2), asList(identities.get(1), identities.get(2)));
-               return oldIdentities;
-       }
-
-       private Map<OwnIdentity, Collection<Identity>> createOldIdentities() {
-               Map<OwnIdentity, Collection<Identity>> oldIdentities = new HashMap<>();
-               oldIdentities.put(ownIdentities.get(0), asList(identities.get(0), identities.get(1)));
-               oldIdentities.put(ownIdentities.get(1), asList(identities.get(0), identities.get(1)));
-               return oldIdentities;
-       }
-
-}
diff --git a/src/test/java/net/pterodactylus/sone/freenet/wot/IdentityLoaderTest.java b/src/test/java/net/pterodactylus/sone/freenet/wot/IdentityLoaderTest.java
deleted file mode 100644 (file)
index 8744c15..0000000
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Sone - IdentityLoaderTest.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.freenet.wot;
-
-import static com.google.common.base.Optional.of;
-import static com.google.common.collect.Lists.newArrayList;
-import static com.google.common.collect.Sets.newHashSet;
-import static java.util.Arrays.asList;
-import static java.util.Collections.emptySet;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.containsInAnyOrder;
-import static org.hamcrest.Matchers.hasSize;
-import static org.hamcrest.Matchers.is;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import com.google.common.base.Optional;
-import com.google.common.collect.ImmutableMap;
-import org.hamcrest.Matchers;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Unit test for {@link IdentityLoader}.
- */
-public class IdentityLoaderTest {
-
-       private final WebOfTrustConnector webOfTrustConnector = mock(WebOfTrustConnector.class);
-       private final IdentityLoader identityLoader = new IdentityLoader(webOfTrustConnector, of(new Context("Test")));
-       private final IdentityLoader identityLoaderWithoutContext = new IdentityLoader(webOfTrustConnector);
-
-       @Before
-       public void setup() throws WebOfTrustException {
-               List<OwnIdentity> ownIdentities = createOwnIdentities();
-               when(webOfTrustConnector.loadAllOwnIdentities()).thenReturn(newHashSet(ownIdentities));
-               when(webOfTrustConnector.loadTrustedIdentities(eq(ownIdentities.get(0)), any(Optional.class))).thenReturn(createTrustedIdentitiesForFirstOwnIdentity());
-               when(webOfTrustConnector.loadTrustedIdentities(eq(ownIdentities.get(1)), any(Optional.class))).thenReturn(createTrustedIdentitiesForSecondOwnIdentity());
-               when(webOfTrustConnector.loadTrustedIdentities(eq(ownIdentities.get(2)), any(Optional.class))).thenReturn(createTrustedIdentitiesForThirdOwnIdentity());
-               when(webOfTrustConnector.loadTrustedIdentities(eq(ownIdentities.get(3)), any(Optional.class))).thenReturn(createTrustedIdentitiesForFourthOwnIdentity());
-       }
-
-       private List<OwnIdentity> createOwnIdentities() {
-               return newArrayList(
-                               createOwnIdentity("O1", "ON1", "OR1", "OI1", asList("Test", "Test2"), ImmutableMap.of("KeyA", "ValueA", "KeyB", "ValueB")),
-                               createOwnIdentity("O2", "ON2", "OR2", "OI2", asList("Test"), ImmutableMap.of("KeyC", "ValueC")),
-                               createOwnIdentity("O3", "ON3", "OR3", "OI3", asList("Test2"), ImmutableMap.of("KeyE", "ValueE", "KeyD", "ValueD")),
-                               createOwnIdentity("O4", "ON4", "OR$", "OI4", asList("Test"), ImmutableMap.of("KeyA", "ValueA", "KeyD", "ValueD"))
-               );
-       }
-
-       private Set<Identity> createTrustedIdentitiesForFirstOwnIdentity() {
-               return newHashSet(
-                               createIdentity("I11", "IN11", "IR11", asList("Test"), ImmutableMap.of("KeyA", "ValueA"))
-               );
-       }
-
-       private Set<Identity> createTrustedIdentitiesForSecondOwnIdentity() {
-               return newHashSet(
-                               createIdentity("I21", "IN21", "IR21", asList("Test", "Test2"), ImmutableMap.of("KeyB", "ValueB"))
-               );
-       }
-
-       private Set<Identity> createTrustedIdentitiesForThirdOwnIdentity() {
-               return newHashSet(
-                               createIdentity("I31", "IN31", "IR31", asList("Test", "Test3"), ImmutableMap.of("KeyC", "ValueC"))
-               );
-       }
-
-       private Set<Identity> createTrustedIdentitiesForFourthOwnIdentity() {
-               return emptySet();
-       }
-
-       private OwnIdentity createOwnIdentity(String id, String nickname, String requestUri, String insertUri, List<String> contexts, ImmutableMap<String, String> properties) {
-               OwnIdentity ownIdentity = new DefaultOwnIdentity(id, nickname, requestUri, insertUri);
-               ownIdentity.setContexts(contexts);
-               ownIdentity.setProperties(properties);
-               return ownIdentity;
-       }
-
-       private Identity createIdentity(String id, String nickname, String requestUri, List<String> contexts, ImmutableMap<String, String> properties) {
-               Identity identity = new DefaultIdentity(id, nickname, requestUri);
-               identity.setContexts(contexts);
-               identity.setProperties(properties);
-               return identity;
-       }
-
-       @Test
-       public void loadingIdentities() throws WebOfTrustException {
-               List<OwnIdentity> ownIdentities = createOwnIdentities();
-               Map<OwnIdentity, Collection<Identity>> identities = identityLoader.loadIdentities();
-               verify(webOfTrustConnector).loadAllOwnIdentities();
-               verify(webOfTrustConnector).loadTrustedIdentities(eq(ownIdentities.get(0)), eq(of("Test")));
-               verify(webOfTrustConnector).loadTrustedIdentities(eq(ownIdentities.get(1)), eq(of("Test")));
-               verify(webOfTrustConnector, never()).loadTrustedIdentities(eq(ownIdentities.get(2)), any(Optional.class));
-               verify(webOfTrustConnector).loadTrustedIdentities(eq(ownIdentities.get(3)), eq(of("Test")));
-               assertThat(identities.keySet(), hasSize(4));
-               assertThat(identities.keySet(), containsInAnyOrder(ownIdentities.get(0), ownIdentities.get(1), ownIdentities.get(2), ownIdentities.get(3)));
-               verifyIdentitiesForOwnIdentity(identities, ownIdentities.get(0), createTrustedIdentitiesForFirstOwnIdentity());
-               verifyIdentitiesForOwnIdentity(identities, ownIdentities.get(1), createTrustedIdentitiesForSecondOwnIdentity());
-               verifyIdentitiesForOwnIdentity(identities, ownIdentities.get(2), Collections.<Identity>emptySet());
-               verifyIdentitiesForOwnIdentity(identities, ownIdentities.get(3), createTrustedIdentitiesForFourthOwnIdentity());
-       }
-
-       @Test
-       public void loadingIdentitiesWithoutContext() throws WebOfTrustException {
-               List<OwnIdentity> ownIdentities = createOwnIdentities();
-               Map<OwnIdentity, Collection<Identity>> identities = identityLoaderWithoutContext.loadIdentities();
-               verify(webOfTrustConnector).loadAllOwnIdentities();
-               verify(webOfTrustConnector).loadTrustedIdentities(eq(ownIdentities.get(0)), eq(Optional.<String>absent()));
-               verify(webOfTrustConnector).loadTrustedIdentities(eq(ownIdentities.get(1)), eq(Optional.<String>absent()));
-               verify(webOfTrustConnector).loadTrustedIdentities(eq(ownIdentities.get(2)), eq(Optional.<String>absent()));
-               verify(webOfTrustConnector).loadTrustedIdentities(eq(ownIdentities.get(3)), eq(Optional.<String>absent()));
-               assertThat(identities.keySet(), hasSize(4));
-               OwnIdentity firstOwnIdentity = ownIdentities.get(0);
-               OwnIdentity secondOwnIdentity = ownIdentities.get(1);
-               OwnIdentity thirdOwnIdentity = ownIdentities.get(2);
-               OwnIdentity fourthOwnIdentity = ownIdentities.get(3);
-               assertThat(identities.keySet(), containsInAnyOrder(firstOwnIdentity, secondOwnIdentity, thirdOwnIdentity, fourthOwnIdentity));
-               verifyIdentitiesForOwnIdentity(identities, firstOwnIdentity, createTrustedIdentitiesForFirstOwnIdentity());
-               verifyIdentitiesForOwnIdentity(identities, secondOwnIdentity, createTrustedIdentitiesForSecondOwnIdentity());
-               verifyIdentitiesForOwnIdentity(identities, thirdOwnIdentity, createTrustedIdentitiesForThirdOwnIdentity());
-               verifyIdentitiesForOwnIdentity(identities, fourthOwnIdentity, createTrustedIdentitiesForFourthOwnIdentity());
-       }
-
-       private void verifyIdentitiesForOwnIdentity(Map<OwnIdentity, Collection<Identity>> identities, OwnIdentity ownIdentity, Set<Identity> trustedIdentities) {
-               assertThat(identities.get(ownIdentity), Matchers.<Collection<Identity>>is(trustedIdentities));
-       }
-
-}
diff --git a/src/test/java/net/pterodactylus/sone/freenet/wot/IdentityManagerTest.java b/src/test/java/net/pterodactylus/sone/freenet/wot/IdentityManagerTest.java
deleted file mode 100644 (file)
index 45e95db..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-package net.pterodactylus.sone.freenet.wot;
-
-import static com.google.common.base.Optional.of;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import net.pterodactylus.sone.freenet.plugin.PluginException;
-
-import com.google.common.eventbus.EventBus;
-import org.junit.Test;
-
-/**
- * Unit test for {@link IdentityManagerImpl}.
- */
-public class IdentityManagerTest {
-
-       private final EventBus eventBus = mock(EventBus.class);
-       private final WebOfTrustConnector webOfTrustConnector = mock(WebOfTrustConnector.class);
-       private final IdentityManager identityManager = new IdentityManagerImpl(eventBus, webOfTrustConnector, new IdentityLoader(webOfTrustConnector, of(new Context("Test"))));
-
-       @Test
-       public void identityManagerPingsWotConnector() throws PluginException {
-               assertThat(identityManager.isConnected(), is(true));
-               verify(webOfTrustConnector).ping();
-       }
-
-       @Test
-       public void disconnectedWotConnectorIsRecognized() throws PluginException {
-               doThrow(PluginException.class).when(webOfTrustConnector).ping();
-               assertThat(identityManager.isConnected(), is(false));
-               verify(webOfTrustConnector).ping();
-       }
-
-}
diff --git a/src/test/java/net/pterodactylus/sone/freenet/wot/IdentityManagerTest.kt b/src/test/java/net/pterodactylus/sone/freenet/wot/IdentityManagerTest.kt
new file mode 100644 (file)
index 0000000..3768efd
--- /dev/null
@@ -0,0 +1,33 @@
+package net.pterodactylus.sone.freenet.wot
+
+import com.google.common.eventbus.*
+import net.pterodactylus.sone.freenet.plugin.*
+import net.pterodactylus.sone.test.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
+
+/**
+ * Unit test for [IdentityManagerImpl].
+ */
+class IdentityManagerTest {
+
+       private val eventBus = mock<EventBus>()
+       private val webOfTrustConnector = mock<WebOfTrustConnector>()
+       private val identityManager = IdentityManagerImpl(eventBus, webOfTrustConnector, IdentityLoader(webOfTrustConnector, Context("Test")))
+
+       @Test
+       fun identityManagerPingsWotConnector() {
+               assertThat(identityManager.isConnected, equalTo(true))
+               verify(webOfTrustConnector).ping()
+       }
+
+       @Test
+       fun disconnectedWotConnectorIsRecognized() {
+               doThrow(PluginException::class.java).whenever(webOfTrustConnector).ping()
+               assertThat(identityManager.isConnected, equalTo(false))
+               verify(webOfTrustConnector).ping()
+       }
+
+}
diff --git a/src/test/java/net/pterodactylus/sone/freenet/wot/event/IdentityEventTest.java b/src/test/java/net/pterodactylus/sone/freenet/wot/event/IdentityEventTest.java
deleted file mode 100644 (file)
index 4fa5bc9..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-package net.pterodactylus.sone.freenet.wot.event;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.not;
-import static org.mockito.Mockito.mock;
-
-import net.pterodactylus.sone.freenet.wot.Identity;
-import net.pterodactylus.sone.freenet.wot.OwnIdentity;
-
-import org.junit.Test;
-
-/**
- * Unit test for {@link IdentityEvent}.
- */
-public class IdentityEventTest {
-
-       private final OwnIdentity ownIdentity = mock(OwnIdentity.class);
-       private final Identity identity = mock(Identity.class);
-       private final IdentityEvent identityEvent = createIdentityEvent(ownIdentity, identity);
-
-       private IdentityEvent createIdentityEvent(final OwnIdentity ownIdentity, final Identity identity) {
-               return new IdentityEvent(ownIdentity, identity) {
-               };
-       }
-
-       @Test
-       public void identityEventRetainsIdentities() {
-               assertThat(identityEvent.ownIdentity(), is(ownIdentity));
-               assertThat(identityEvent.identity(), is(identity));
-       }
-
-       @Test
-       public void eventsWithTheSameIdentityHaveTheSameHashCode() {
-               IdentityEvent secondIdentityEvent = createIdentityEvent(ownIdentity, identity);
-               assertThat(identityEvent.hashCode(), is(secondIdentityEvent.hashCode()));
-       }
-
-       @Test
-       public void eventsWithTheSameIdentitiesAreEqual() {
-               IdentityEvent secondIdentityEvent = createIdentityEvent(ownIdentity, identity);
-               assertThat(identityEvent, is(secondIdentityEvent));
-               assertThat(secondIdentityEvent, is(identityEvent));
-       }
-
-       @Test
-       public void nullDoesNotEqualIdentityEvent() {
-               assertThat(identityEvent, not(is((Object) null)));
-       }
-
-
-}
diff --git a/src/test/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityEventTest.java b/src/test/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityEventTest.java
deleted file mode 100644 (file)
index 3d27c34..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-package net.pterodactylus.sone.freenet.wot.event;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.not;
-import static org.mockito.Mockito.mock;
-
-import net.pterodactylus.sone.freenet.wot.OwnIdentity;
-
-import org.junit.Test;
-
-/**
- * Unit test for {@link OwnIdentityEvent}.
- */
-public class OwnIdentityEventTest {
-
-       private final OwnIdentity ownIdentity = mock(OwnIdentity.class);
-       private final OwnIdentityEvent ownIdentityEvent = createOwnIdentityEvent(ownIdentity);
-
-       @Test
-       public void eventRetainsOwnIdentity() {
-               assertThat(ownIdentityEvent.ownIdentity(), is(ownIdentity));
-       }
-
-       protected OwnIdentityEvent createOwnIdentityEvent(final OwnIdentity ownIdentity) {
-               return new OwnIdentityEvent(ownIdentity) {
-               };
-       }
-
-       @Test
-       public void twoOwnIdentityEventsWithTheSameIdentityHaveTheSameHashCode() {
-               OwnIdentityEvent secondOwnIdentityEvent = createOwnIdentityEvent(ownIdentity);
-               assertThat(secondOwnIdentityEvent.hashCode(), is(ownIdentityEvent.hashCode()));
-       }
-
-       @Test
-       public void ownIdentityEventDoesNotMatchNull() {
-               assertThat(ownIdentityEvent, not(is((Object) null)));
-       }
-
-       @Test
-       public void ownIdentityEventDoesNotMatchObjectWithADifferentClass() {
-               assertThat(ownIdentityEvent, not(is(new Object())));
-       }
-
-}
index 7a84978..72f0bf7 100644 (file)
@@ -23,7 +23,6 @@ import net.pterodactylus.util.web.Response;
 
 import freenet.clients.http.SessionManager;
 import freenet.clients.http.ToadletContext;
-import freenet.l10n.BaseL10n;
 import freenet.support.api.HTTPRequest;
 
 import com.google.common.base.Charsets;
@@ -41,7 +40,6 @@ public class DebugLoadersTest {
        @Rule
        public final TemporaryFolder temporaryFolder = new TemporaryFolder();
 
-       private final BaseL10n l10n = mock(BaseL10n.class);
        private final StringWriter stringWriter = new StringWriter();
        private final TemplateContext templateContext = new TemplateContext();
        private Loaders loaders;
@@ -72,7 +70,7 @@ public class DebugLoadersTest {
                HTTPRequest httpRequest = mock(HTTPRequest.class);
                ToadletContext toadletContext = mock(ToadletContext.class);
                SessionManager sessionManager = mock(SessionManager.class);
-               FreenetRequest request = new FreenetRequest(uri, method, httpRequest, toadletContext, l10n, sessionManager);
+               FreenetRequest request = new FreenetRequest(uri, method, httpRequest, toadletContext, sessionManager);
                OutputStream outputStream = new ByteArrayOutputStream();
                Response response = new Response(outputStream);
                page.handleRequest(request, response);
index dc51479..d91e9e3 100644 (file)
@@ -23,7 +23,6 @@ import net.pterodactylus.util.web.Response;
 
 import freenet.clients.http.SessionManager;
 import freenet.clients.http.ToadletContext;
-import freenet.l10n.BaseL10n;
 import freenet.support.api.HTTPRequest;
 
 import org.junit.Test;
@@ -33,7 +32,6 @@ import org.junit.Test;
  */
 public class DefaultLoadersTest {
 
-       private final BaseL10n l10n = mock(BaseL10n.class);
        private final Loaders loaders = new DefaultLoaders();
        private final StringWriter stringWriter = new StringWriter();
        private final TemplateContext templateContext = new TemplateContext();
@@ -53,7 +51,7 @@ public class DefaultLoadersTest {
                HTTPRequest httpRequest = mock(HTTPRequest.class);
                ToadletContext toadletContext = mock(ToadletContext.class);
                SessionManager sessionManager = mock(SessionManager.class);
-               FreenetRequest request = new FreenetRequest(uri, method, httpRequest, toadletContext, l10n, sessionManager);
+               FreenetRequest request = new FreenetRequest(uri, method, httpRequest, toadletContext, sessionManager);
                OutputStream outputStream = new ByteArrayOutputStream();
                Response response = new Response(outputStream);
                staticPage.handleRequest(request, response);
diff --git a/src/test/java/net/pterodactylus/sone/notify/ListNotificationTest.java b/src/test/java/net/pterodactylus/sone/notify/ListNotificationTest.java
deleted file mode 100644 (file)
index 5e40f81..0000000
+++ /dev/null
@@ -1,120 +0,0 @@
-package net.pterodactylus.sone.notify;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.emptyIterable;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.not;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.mockito.hamcrest.MockitoHamcrest.argThat;
-
-import java.util.Arrays;
-
-import net.pterodactylus.util.notify.NotificationListener;
-import net.pterodactylus.util.template.Template;
-import net.pterodactylus.util.template.TemplateContext;
-
-import org.hamcrest.Matchers;
-import org.junit.Test;
-
-/**
- * Unit test for {@link ListNotification}.
- */
-public class ListNotificationTest {
-
-       private static final String ID = "notification-id";
-       private static final String KEY = "element-key";
-       private static final String OTHER_KEY = "other-key";
-
-       private final Template template = mock(Template.class);
-       private final TemplateContext templateInitialContext = mock(TemplateContext.class);
-       private ListNotification<Object> listNotification;
-
-       public ListNotificationTest() {
-               when(template.getInitialContext()).thenReturn(templateInitialContext);
-               listNotification = new ListNotification<>(ID, KEY, template);
-       }
-
-       @Test
-       public void creatingAListNotificationSetsEmptyIterableOnElementKeyInTemplateContext() {
-               verify(templateInitialContext).set(eq(KEY), argThat(emptyIterable()));
-       }
-
-       @Test
-       public void newListNotificationHasNoElement() {
-               assertThat(listNotification.getElements(), emptyIterable());
-       }
-
-       @Test
-       public void newListNotificationIsEmpty() {
-               assertThat(listNotification.isEmpty(), is(true));
-       }
-
-       @Test
-       public void listNotificationRetainsSetElements() {
-               listNotification.setElements(Arrays.asList("a", "b", "c"));
-               assertThat(listNotification.getElements(), Matchers.<Object>contains("a", "b", "c"));
-       }
-
-       @Test
-       public void listNotificationRetainsAddedElements() {
-               listNotification.add("a");
-               listNotification.add("b");
-               listNotification.add("c");
-               assertThat(listNotification.getElements(), Matchers.<Object>contains("a", "b", "c"));
-       }
-
-       @Test
-       public void listNotificationRemovesCorrectElement() {
-               listNotification.setElements(Arrays.asList("a", "b", "c"));
-               listNotification.remove("b");
-               assertThat(listNotification.getElements(), Matchers.<Object>contains("a", "c"));
-       }
-
-       @Test
-       public void removingTheLastElementDismissesTheNotification() {
-               NotificationListener notificationListener = mock(NotificationListener.class);
-               listNotification.addNotificationListener(notificationListener);
-               listNotification.add("a");
-               listNotification.remove("a");
-               verify(notificationListener).notificationDismissed(listNotification);
-       }
-
-       @Test
-       public void dismissingTheListNotificationRemovesAllElements() {
-               listNotification.setElements(Arrays.asList("a", "b", "c"));
-               listNotification.dismiss();
-               assertThat(listNotification.getElements(), emptyIterable());
-       }
-
-       @Test
-       public void listNotificationWithDifferentElementsIsNotEqual() {
-               ListNotification secondNotification = new ListNotification(ID, KEY, template);
-               listNotification.add("a");
-               secondNotification.add("b");
-               assertThat(listNotification, not(is(secondNotification)));
-       }
-
-       @Test
-       public void listNotificationWithDifferentKeyIsNotEqual() {
-               ListNotification secondNotification = new ListNotification(ID, OTHER_KEY, template);
-               assertThat(listNotification, not(is(secondNotification)));
-       }
-
-       @Test
-       public void copiedNotificationsHaveTheSameHashCode() {
-               ListNotification secondNotification = new ListNotification(listNotification);
-               listNotification.add("a");
-               secondNotification.add("a");
-               listNotification.setLastUpdateTime(secondNotification.getLastUpdatedTime());
-               assertThat(listNotification.hashCode(), is(secondNotification.hashCode()));
-       }
-
-       @Test
-       public void listNotificationIsNotEqualToOtherObjects() {
-           assertThat(listNotification, not(is(new Object())));
-       }
-
-}
index d04da0f..5d2d641 100644 (file)
@@ -22,6 +22,8 @@ import static java.util.regex.Pattern.compile;
 import java.io.IOException;
 import java.io.InputStream;
 
+import javax.annotation.*;
+
 import net.pterodactylus.sone.data.Album;
 import net.pterodactylus.sone.data.Image;
 import net.pterodactylus.sone.data.Post;
@@ -91,8 +93,7 @@ public class Matchers {
                };
        }
 
-       public static Matcher<Post> isPost(String postId, long time,
-                       String text, Optional<String> recipient) {
+       public static Matcher<Post> isPost(String postId, long time, String text, @Nullable String recipient) {
                return new PostMatcher(postId, time, text, recipient);
        }
 
@@ -229,10 +230,10 @@ public class Matchers {
                private final String postId;
                private final long time;
                private final String text;
-               private final Optional<String> recipient;
+               @Nullable
+               private final String recipient;
 
-               private PostMatcher(String postId, long time, String text,
-                               Optional<String> recipient) {
+               private PostMatcher(String postId, long time, String text, @Nullable String recipient) {
                        this.postId = postId;
                        this.time = time;
                        this.text = text;
@@ -257,15 +258,15 @@ public class Matchers {
                                                .appendValue(text);
                                return false;
                        }
-                       if (recipient.isPresent()) {
+                       if (recipient != null) {
                                if (!post.getRecipientId().isPresent()) {
                                        mismatchDescription.appendText(
                                                        "Recipient not present");
                                        return false;
                                }
-                               if (!post.getRecipientId().get().equals(recipient.get())) {
+                               if (!post.getRecipientId().get().equals(recipient)) {
                                        mismatchDescription.appendText("Recipient is not ")
-                                                       .appendValue(recipient.get());
+                                                       .appendValue(recipient);
                                        return false;
                                }
                        } else {
@@ -283,9 +284,9 @@ public class Matchers {
                                        .appendValue(postId);
                        description.appendText(", created at @").appendValue(time);
                        description.appendText(", text ").appendValue(text);
-                       if (recipient.isPresent()) {
+                       if (recipient != null) {
                                description.appendText(", directed at ")
-                                               .appendValue(recipient.get());
+                                               .appendValue(recipient);
                        }
                }
 
index 6f93cd4..90e3fa6 100644 (file)
@@ -95,8 +95,8 @@ class ConfigurationSoneParserTest {
                val postBuilderFactory = createPostBuilderFactory()
                val posts = configurationSoneParser.parsePosts(postBuilderFactory)
                assertThat(posts, containsInAnyOrder(
-                               isPost("P0", 1000L, "T0", absent()),
-                               isPost("P1", 1001L, "T1", of("1234567890123456789012345678901234567890123"))
+                               isPost("P0", 1000L, "T0", null),
+                               isPost("P1", 1001L, "T1", "1234567890123456789012345678901234567890123")
                ))
        }
 
@@ -149,7 +149,7 @@ class ConfigurationSoneParserTest {
        fun postWithInvalidRecipientIdIsRecognized() {
                setupPostWithInvalidRecipientId()
                val posts = configurationSoneParser.parsePosts(createPostBuilderFactory())
-               assertThat(posts, contains(isPost("P0", 1000L, "T0", absent())))
+               assertThat(posts, contains(isPost("P0", 1000L, "T0", null)))
        }
 
        private fun setupPostWithInvalidRecipientId() {
index 0e6a0c0..fb9217e 100644 (file)
@@ -4,13 +4,11 @@ import com.google.common.base.Ticker
 import com.google.common.io.ByteStreams
 import freenet.keys.FreenetURI
 import net.pterodactylus.sone.core.FreenetInterface.BackgroundFetchCallback
-import net.pterodactylus.sone.test.capture
-import net.pterodactylus.sone.test.mock
+import net.pterodactylus.sone.test.*
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.equalTo
 import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import java.io.ByteArrayOutputStream
@@ -183,7 +181,7 @@ class DefaultElementLoaderTest {
                elementLoader.loadElement(IMAGE_ID)
                verify(freenetInterface).startFetch(eq(freenetURI), callback.capture())
                callback.value.failed(freenetURI)
-               `when`(ticker.read()).thenReturn(TimeUnit.MINUTES.toNanos(31))
+               whenever(ticker.read()).thenReturn(TimeUnit.MINUTES.toNanos(31))
                val linkedElement = elementLoader.loadElement(IMAGE_ID)
                assertThat(linkedElement.failed, equalTo(false))
                assertThat(linkedElement.loading, equalTo(true))
index cdf0dfb..6647434 100644 (file)
@@ -88,7 +88,7 @@ class FreenetInterfaceTest {
 
        @Before
        fun setupCallbackCaptorAndUskManager() {
-               doNothing().`when`(uskManager).subscribe(any(USK::class.java), callbackCaptor.capture(), anyBoolean(), any(RequestClient::class.java))
+               doNothing().whenever(uskManager).subscribe(any(USK::class.java), callbackCaptor.capture(), anyBoolean(), any(RequestClient::class.java))
        }
 
        @Test
index 39a11dc..60bb7e2 100644 (file)
@@ -37,7 +37,7 @@ class ImageInserterTest {
 
        @Test
        fun `exception when inserting image is ignored`() {
-               doThrow(SoneException::class.java).`when`(freenetInterface).insertImage(eq(temporaryImage), eq(image), any(InsertToken::class.java))
+               doThrow(SoneException::class.java).whenever(freenetInterface).insertImage(eq(temporaryImage), eq(image), any(InsertToken::class.java))
                imageInserter.insertImage(temporaryImage, image)
                verify(freenetInterface).insertImage(eq(temporaryImage), eq(image), any(InsertToken::class.java))
        }
index 8b061a7..2f90250 100644 (file)
@@ -104,7 +104,7 @@ class SoneInserterTest {
                doAnswer {
                        soneInserter.stop()
                        null
-               }.`when`(core).touchConfiguration()
+               }.whenever(core).touchConfiguration()
                soneInserter.serviceRun()
                val soneEvents = ArgumentCaptor.forClass(SoneEvent::class.java)
                verify(freenetInterface).insertDirectory(eq(insertUri), any<HashMap<String, Any>>(), eq("index.html"))
@@ -251,7 +251,7 @@ class SoneInserterTest {
                doAnswer {
                        soneInserter.stop()
                        null
-               }.`when`(core).touchConfiguration()
+               }.whenever(core).touchConfiguration()
                soneInserter.serviceRun()
                val histogram = metricRegistry.histogram("sone.insert.duration")
                assertThat(histogram.count, equalTo(1L))
index 6d9d4b8..336d855 100644 (file)
@@ -401,7 +401,7 @@ class SoneParserTest {
        fun `unsuccessful parsing does not add a histogram entry`() {
                val inputStream = javaClass.getResourceAsStream("sone-parser-with-invalid-image-height.xml")
                assertThat(soneParser.parseSone(sone, inputStream), nullValue())
-               val histogram = metricRegistry.histogram("sone.parsing.duration")
+               val histogram = metricRegistry.histogram("sone.parse.duration")
                assertThat(histogram.count, equalTo(0L))
        }
 
@@ -409,7 +409,7 @@ class SoneParserTest {
        fun `successful parsing adds histogram entry`() {
                val inputStream = javaClass.getResourceAsStream("sone-parser-without-images.xml")
                assertThat(soneParser.parseSone(sone, inputStream), notNullValue())
-               val histogram = metricRegistry.histogram("sone.parsing.duration")
+               val histogram = metricRegistry.histogram("sone.parse.duration")
                assertThat(histogram.count, equalTo(1L))
        }
 
diff --git a/src/test/kotlin/net/pterodactylus/sone/core/SoneRescuerTest.kt b/src/test/kotlin/net/pterodactylus/sone/core/SoneRescuerTest.kt
deleted file mode 100644 (file)
index a74b874..0000000
+++ /dev/null
@@ -1,112 +0,0 @@
-package net.pterodactylus.sone.core
-
-import freenet.keys.*
-import net.pterodactylus.sone.data.*
-import net.pterodactylus.sone.test.*
-import org.hamcrest.MatcherAssert.*
-import org.hamcrest.Matchers.equalTo
-import org.junit.*
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mockito.doAnswer
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-
-/**
- * Unit test for [SoneRescuer].
- */
-class SoneRescuerTest {
-
-       private val core = mock<Core>()
-       private val soneDownloader = mock<SoneDownloader>()
-       private val sone = mock<Sone>().apply {
-               val soneUri = mock<FreenetURI>()
-               whenever(soneUri.edition).thenReturn(currentEdition)
-               whenever(requestUri).thenReturn(soneUri)
-       }
-       private val soneRescuer = SoneRescuer(core, soneDownloader, sone)
-
-       @Test
-       fun newSoneRescuerIsNotFetchingAnything() {
-               assertThat(soneRescuer.isFetching, equalTo(false))
-       }
-
-       @Test
-       fun newSoneRescuerStartsAtCurrentEditionOfSone() {
-               assertThat(soneRescuer.currentEdition, equalTo(currentEdition))
-       }
-
-       @Test
-       fun newSoneRescuerHasANextEditionToGet() {
-               assertThat(soneRescuer.hasNextEdition(), equalTo(true))
-       }
-
-       @Test
-       fun soneRescuerDoesNotHaveANextEditionIfCurrentEditionIsZero() {
-               whenever(sone.requestUri.edition).thenReturn(0L)
-               val soneRescuer = SoneRescuer(core, soneDownloader, sone)
-               assertThat(soneRescuer.hasNextEdition(), equalTo(false))
-       }
-
-       @Test
-       fun nextEditionIsOneSmallerThanTheCurrentEdition() {
-               assertThat(soneRescuer.nextEdition, equalTo(currentEdition - 1))
-       }
-
-       @Test
-       fun lastFetchOfANewSoneRescuerWasSuccessful() {
-               assertThat(soneRescuer.isLastFetchSuccessful, equalTo(true))
-       }
-
-       @Test
-       fun mainLoopStopsWhenItShould() {
-               soneRescuer.stop()
-               soneRescuer.serviceRun()
-       }
-
-       @Test
-       fun successfulInsert() {
-               val fetchedSone = mock<Sone>()
-               returnUriOnInsert(fetchedSone)
-               soneRescuer.startNextFetch()
-               soneRescuer.serviceRun()
-               verify(core).lockSone(eq(sone))
-               verify(core).updateSone(eq(fetchedSone), eq(true))
-               assertThat(soneRescuer.isLastFetchSuccessful, equalTo(true))
-               assertThat(soneRescuer.isFetching, equalTo(false))
-               assertThat(soneRescuer.currentEdition, equalTo(currentEdition - 1))
-       }
-
-       @Test
-       fun nonSuccessfulInsertIsRecognized() {
-               returnUriOnInsert(null)
-               soneRescuer.startNextFetch()
-               soneRescuer.serviceRun()
-               verify(core).lockSone(eq(sone))
-               verify(core, never()).updateSone(any(Sone::class.java), eq(true))
-               assertThat(soneRescuer.isLastFetchSuccessful, equalTo(false))
-               assertThat(soneRescuer.isFetching, equalTo(false))
-               assertThat(soneRescuer.currentEdition, equalTo(currentEdition))
-       }
-
-       private fun returnUriOnInsert(fetchedSone: Sone?) {
-               val keyWithMetaStrings = setupFreenetUri()
-               doAnswer {
-                       soneRescuer.stop()
-                       fetchedSone
-               }.`when`(soneDownloader).fetchSone(eq(sone), eq(keyWithMetaStrings), eq(true))
-       }
-
-       private fun setupFreenetUri(): FreenetURI {
-               val sskKey = mock<FreenetURI>()
-               val keyWithDocName = mock<FreenetURI>()
-               val keyWithMetaStrings = mock<FreenetURI>()
-               whenever(keyWithDocName.setMetaString(eq(arrayOf("sone.xml")))).thenReturn(keyWithMetaStrings)
-               whenever(sskKey.setDocName(eq("Sone-" + (currentEdition - 1)))).thenReturn(keyWithDocName)
-               whenever(sone.requestUri.setKeyType(eq("SSK"))).thenReturn(sskKey)
-               return keyWithMetaStrings
-       }
-
-}
-
-private const val currentEdition = 12L
index 4d7a105..5d4fe3f 100644 (file)
@@ -50,8 +50,8 @@ class MemoryDatabaseTest {
        @Test
        fun `stored sone is made available`() {
                storeSone()
-               assertThat(memoryDatabase.getPost("post1"), isPost("post1", 1000L, "post1", absent()))
-               assertThat(memoryDatabase.getPost("post2"), isPost("post2", 2000L, "post2", of(RECIPIENT_ID)))
+               assertThat(memoryDatabase.getPost("post1"), isPost("post1", 1000L, "post1", null))
+               assertThat(memoryDatabase.getPost("post2"), isPost("post2", 2000L, "post2", RECIPIENT_ID))
                assertThat(memoryDatabase.getPost("post3"), nullValue())
                assertThat(memoryDatabase.getPostReply("reply1"), isPostReply("reply1", "post1", 3000L, "reply1"))
                assertThat(memoryDatabase.getPostReply("reply2"), isPostReply("reply2", "post2", 4000L, "reply2"))
@@ -417,6 +417,28 @@ class MemoryDatabaseTest {
                verify(configuration.getStringValue("KnownPosts/0/ID"), times(1)).value = null
        }
 
+       @Test
+       @Dirty("the rate limiter should be mocked")
+       fun `setting posts as knows twice in a row only saves the database once`() {
+               prepareConfigurationValues()
+               val post = mock<Post>()
+               whenever(post.id).thenReturn("post-id")
+               memoryDatabase.setPostKnown(post, true)
+               memoryDatabase.setPostKnown(post, true)
+               verify(configuration, times(1)).getStringValue("KnownPosts/1/ID")
+       }
+
+       @Test
+       @Dirty("the rate limiter should be mocked")
+       fun `setting post replies as knows twice in a row only saves the database once`() {
+               prepareConfigurationValues()
+               val postReply = mock<PostReply>()
+               whenever(postReply.id).thenReturn("post-reply-id")
+               memoryDatabase.setPostReplyKnown(postReply, true)
+               memoryDatabase.setPostReplyKnown(postReply, true)
+               verify(configuration, times(1)).getStringValue("KnownReplies/1/ID")
+       }
+
 }
 
 private const val SONE_ID = "sone"
diff --git a/src/test/kotlin/net/pterodactylus/sone/freenet/BaseL10nTranslationTest.kt b/src/test/kotlin/net/pterodactylus/sone/freenet/BaseL10nTranslationTest.kt
new file mode 100644 (file)
index 0000000..a188d47
--- /dev/null
@@ -0,0 +1,30 @@
+package net.pterodactylus.sone.freenet
+
+import freenet.l10n.*
+import net.pterodactylus.sone.test.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import java.util.*
+
+/**
+ * Test for [BaseL10nTranslation].
+ */
+class BaseL10nTranslationTest {
+
+       private val baseL10n = mock<BaseL10n>()
+       private val translation = BaseL10nTranslation(baseL10n)
+
+       @Test
+       fun `translate method is facade for the correct method`() {
+               whenever(baseL10n.getString("test")).thenReturn("answer")
+               assertThat(translation.translate("test"), equalTo("answer"))
+       }
+
+       @Test
+       fun `language exposes correct short code`() {
+               whenever(baseL10n.selectedLanguage).thenReturn(BaseL10n.LANGUAGE.ENGLISH)
+               assertThat(translation.currentLocale, equalTo(Locale.ENGLISH))
+       }
+
+}
diff --git a/src/test/kotlin/net/pterodactylus/sone/freenet/FreenetURIsTest.kt b/src/test/kotlin/net/pterodactylus/sone/freenet/FreenetURIsTest.kt
new file mode 100644 (file)
index 0000000..ca1fc6e
--- /dev/null
@@ -0,0 +1,24 @@
+package net.pterodactylus.sone.freenet
+
+import freenet.keys.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+
+/**
+ * Unit test for [Key].
+ */
+class FreenetURIsTest {
+
+       private val uri = FreenetURI("SSK@$routingKey,$cryptoKey,$extra/some-site-12/foo/bar.html")
+
+       @Test
+       fun routingKeyIsExtractCorrectly() {
+               assertThat(uri.routingKeyString, equalTo(routingKey))
+       }
+
+}
+
+private const val routingKey = "NfUYvxDwU9vqb2mh-qdT~DYJ6U0XNbxMGGoLe0aCHJs"
+private const val cryptoKey = "Miglsgix0VR56ZiPl4NgjnUd~UdrnHqIvXJ3KKHmxmI"
+private const val extra = "AQACAAE"
index 66b1ab1..358d106 100644 (file)
@@ -1,60 +1,51 @@
 package net.pterodactylus.sone.freenet
 
-import freenet.l10n.BaseL10n
-import freenet.l10n.BaseL10n.LANGUAGE.ENGLISH
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.util.template.TemplateContext
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Before
-import org.junit.Test
-import org.mockito.ArgumentMatchers.anyString
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import java.util.*
+import kotlin.collections.*
 
 /**
  * Unit test for [L10nFilter].
  */
 class L10nFilterTest {
 
-       private val l10n = mock<BaseL10n>()
-       private val filter = L10nFilter(l10n)
-       private val templateContext = mock<TemplateContext>()
        private val translations = mutableMapOf<String, String>()
-
-       @Before
-       fun setupL10n() {
-               whenever(l10n.selectedLanguage).thenReturn(ENGLISH)
-               whenever(l10n.getString(anyString())).then { translations[it.arguments[0]] }
+       private val translation = object : Translation {
+               override val currentLocale = Locale.ENGLISH
+               override fun translate(key: String): String = translations[key] ?: ""
        }
+       private val filter = L10nFilter(translation)
 
        @Test
        fun `translation without parameters returns translated string`() {
                translations["data"] = "translated data"
-               assertThat(filter.format(templateContext, "data", emptyMap()), equalTo("translated data"))
+               assertThat(filter.format(null, "data", emptyMap()), equalTo("translated data"))
        }
 
        @Test
        fun `translation with parameters returned translated string`() {
                translations["data"] = "translated {0,number} {1}"
-               assertThat(filter.format(templateContext, "data", mapOf("0" to 4.5, "1" to "data")), equalTo("translated 4.5 data"))
+               assertThat(filter.format(null, "data", mapOf("0" to 4.5, "1" to "data")), equalTo("translated 4.5 data"))
        }
 
        @Test
        fun `filter processes l10n text without parameters correctly`() {
                translations["data"] = "translated data"
-               assertThat(filter.format(templateContext, L10nText("data"), emptyMap()), equalTo("translated data"))
+               assertThat(filter.format(null, L10nText("data"), emptyMap()), equalTo("translated data"))
        }
 
        @Test
        fun `filter processes l10n text with parameters correctly`() {
                translations["data"] = "translated {0,number} {1}"
-               assertThat(filter.format(templateContext, L10nText("data", listOf(4.5, "data")), emptyMap()), equalTo("translated 4.5 data"))
+               assertThat(filter.format(null, L10nText("data", listOf(4.5, "data")), emptyMap()), equalTo("translated 4.5 data"))
        }
 
        @Test
        fun `filter does not replace values if there are no parameters`() {
                translations["data"] = "{link}"
-               assertThat(filter.format(templateContext, "data", emptyMap()), equalTo("{link}"))
+               assertThat(filter.format(null, "data", emptyMap()), equalTo("{link}"))
        }
 
 }
diff --git a/src/test/kotlin/net/pterodactylus/sone/freenet/plugin/FredPluginConnectorTest.kt b/src/test/kotlin/net/pterodactylus/sone/freenet/plugin/FredPluginConnectorTest.kt
new file mode 100644 (file)
index 0000000..1eaa91a
--- /dev/null
@@ -0,0 +1,77 @@
+/**
+ * Sone - FredPluginConnectorTest.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 <http://www.gnu.org/licenses/>.
+ */
+
+/* Fred-based plugin stuff is mostly deprecated. ¯\_(ツ)_/¯ */
+@file:Suppress("DEPRECATION")
+
+package net.pterodactylus.sone.freenet.plugin
+
+import freenet.pluginmanager.*
+import freenet.support.*
+import freenet.support.api.*
+import freenet.support.io.*
+import kotlinx.coroutines.*
+import net.pterodactylus.sone.freenet.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.junit.rules.*
+import kotlin.concurrent.*
+
+class FredPluginConnectorTest {
+
+       @Rule
+       @JvmField
+       val expectedException = ExpectedException.none()!!
+
+       @Test
+       fun `connector throws exception if plugin can not be found`() = runBlocking {
+               val pluginConnector = FredPluginConnector(pluginRespiratorFacade)
+               expectedException.expect(PluginException::class.java)
+               pluginConnector.sendRequest("wrong.plugin", requestFields, requestData)
+               Unit
+       }
+
+       @Test
+       fun `connector returns correct fields and data`() = runBlocking {
+               val pluginConnector = FredPluginConnector(pluginRespiratorFacade)
+               val reply = pluginConnector.sendRequest("test.plugin", requestFields, requestData)
+               assertThat(reply.fields, equalTo(responseFields))
+               assertThat(reply.data, equalTo(responseData))
+       }
+
+}
+
+private val requestFields = SimpleFieldSetBuilder().put("foo", "bar").get()
+private val requestData: Bucket? = ArrayBucket(byteArrayOf(1, 2))
+private val responseFields = SimpleFieldSetBuilder().put("baz", "quo").get()
+private val responseData: Bucket? = ArrayBucket(byteArrayOf(3, 4))
+
+private val pluginRespiratorFacade = object : PluginRespiratorFacade {
+       override fun getPluginTalker(pluginTalker: FredPluginTalker, pluginName: String, identifier: String) =
+                       if (pluginName == "test.plugin") {
+                               object : PluginTalkerFacade {
+                                       override fun send(pluginParameters: SimpleFieldSet, data: Bucket?) {
+                                               if ((pluginParameters == requestFields) && (data == requestData)) {
+                                                       thread { pluginTalker.onReply(pluginName, identifier, responseFields, responseData) }
+                                               }
+                                       }
+                               }
+                       } else {
+                               throw PluginNotFoundException()
+                       }
+}
diff --git a/src/test/kotlin/net/pterodactylus/sone/freenet/plugin/PluginRespiratorFacadeTest.kt b/src/test/kotlin/net/pterodactylus/sone/freenet/plugin/PluginRespiratorFacadeTest.kt
new file mode 100644 (file)
index 0000000..4e44d5d
--- /dev/null
@@ -0,0 +1,46 @@
+package net.pterodactylus.sone.freenet.plugin
+
+import freenet.pluginmanager.*
+import freenet.support.*
+import freenet.support.api.*
+import freenet.support.io.*
+import net.pterodactylus.sone.freenet.*
+import net.pterodactylus.sone.test.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.mockito.ArgumentMatchers.*
+import kotlin.test.*
+
+/**
+ * Unit test for [FredPluginRespiratorFacade] and [FredPluginTalkerFacade].
+ */
+@Suppress("DEPRECATION")
+class PluginRespiratorFacadeTest {
+
+       @Test
+       fun `respirator facade creates correct plugin talker facade`() {
+               val pluginTalkerSendParameters = mutableListOf<PluginTalkerSendParameters>()
+               val originalPluginTalker = mock<PluginTalker>().apply {
+                       whenever(send(any(), any())).then { invocation ->
+                               pluginTalkerSendParameters += PluginTalkerSendParameters(invocation.getArgument(0), invocation.getArgument(1))
+                               Unit
+                       }
+               }
+               val fredPluginTalker = FredPluginTalker { _, _, _, _ -> }
+               val pluginRespirator = mock<PluginRespirator>().apply {
+                       whenever(getPluginTalker(fredPluginTalker, "test.plugin", "test-request-1")).thenReturn(originalPluginTalker)
+               }
+               val pluginRespiratorFacade = FredPluginRespiratorFacade(pluginRespirator)
+               val pluginTalker = pluginRespiratorFacade.getPluginTalker(fredPluginTalker, "test.plugin", "test-request-1")
+               pluginTalker.send(fields, data)
+               assertThat(pluginTalkerSendParameters, hasSize(1))
+               assertThat(pluginTalkerSendParameters[0].parameter, equalTo(fields))
+               assertThat(pluginTalkerSendParameters[0].data, equalTo(data))
+       }
+
+}
+
+private val fields = SimpleFieldSetBuilder().put("foo", "bar").get()
+private val data: Bucket? = ArrayBucket(byteArrayOf(1, 2))
+
+private data class PluginTalkerSendParameters(val parameter: SimpleFieldSet, val data: Bucket?)
diff --git a/src/test/kotlin/net/pterodactylus/sone/freenet/wot/DefaultIdentityTest.kt b/src/test/kotlin/net/pterodactylus/sone/freenet/wot/DefaultIdentityTest.kt
new file mode 100644 (file)
index 0000000..2cd7bda
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * Sone - DefaultIdentityTest.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.wot
+
+import com.google.common.collect.ImmutableMap.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.test.Matchers.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.containsInAnyOrder
+import org.hamcrest.Matchers.empty
+import org.hamcrest.Matchers.equalTo
+import org.hamcrest.Matchers.hasEntry
+import org.hamcrest.Matchers.not
+import org.hamcrest.Matchers.nullValue
+import org.hamcrest.collection.IsIterableContainingInOrder.contains
+import org.junit.*
+
+/**
+ * Unit test for [DefaultIdentity].
+ */
+open class DefaultIdentityTest {
+
+       protected open val identity = DefaultIdentity("Id", "Nickname", "RequestURI")
+
+       @Test
+       fun `identity can be created`() {
+               assertThat(identity.id, equalTo("Id"))
+               assertThat(identity.nickname, equalTo("Nickname"))
+               assertThat(identity.requestUri, equalTo("RequestURI"))
+               assertThat(identity.contexts, empty())
+               assertThat(identity.properties, equalTo(emptyMap()))
+       }
+
+       @Test
+       fun `contexts are added correctly`() {
+               identity.addContext("Test")
+               assertThat(identity.contexts, contains("Test"))
+               assertThat(identity.hasContext("Test"), equalTo(true))
+       }
+
+       @Test
+       fun `contexts are removed correctly`() {
+               identity.addContext("Test")
+               identity.removeContext("Test")
+               assertThat(identity.contexts, empty())
+               assertThat(identity.hasContext("Test"), equalTo(false))
+       }
+
+       @Test
+       fun `contexts are set correctly in bulk`() {
+               identity.addContext("Test")
+               identity.contexts = setOf("Test1", "Test2")
+               assertThat(identity.contexts, containsInAnyOrder("Test1", "Test2"))
+               assertThat(identity.hasContext("Test"), equalTo(false))
+               assertThat(identity.hasContext("Test1"), equalTo(true))
+               assertThat(identity.hasContext("Test2"), equalTo(true))
+       }
+
+       @Test
+       fun `properties are added correctly`() {
+               identity.setProperty("Key", "Value")
+               assertThat(identity.properties.size, equalTo(1))
+               assertThat(identity.properties, hasEntry("Key", "Value"))
+               assertThat(identity.getProperty("Key"), equalTo("Value"))
+       }
+
+       @Test
+       fun `properties are removed correctly`() {
+               identity.setProperty("Key", "Value")
+               identity.removeProperty("Key")
+               assertThat(identity.properties, equalTo(emptyMap()))
+               assertThat(identity.getProperty("Key"), nullValue())
+       }
+
+       @Test
+       fun `properties are set correctly in bulk`() {
+               identity.setProperty("Key", "Value")
+               identity.properties = of("Key1", "Value1", "Key2", "Value2")
+               assertThat(identity.properties.size, equalTo(2))
+               assertThat(identity.getProperty("Key"), nullValue())
+               assertThat(identity.getProperty("Key1"), equalTo("Value1"))
+               assertThat(identity.getProperty("Key2"), equalTo("Value2"))
+       }
+
+       @Test
+       fun `trust relationships are added correctly`() {
+               val ownIdentity = mock<OwnIdentity>()
+               val trust = mock<Trust>()
+               identity.setTrust(ownIdentity, trust)
+               assertThat(identity.getTrust(ownIdentity), equalTo(trust))
+       }
+
+       @Test
+       fun `trust relationships are removed correctly`() {
+               val ownIdentity = mock<OwnIdentity>()
+               val trust = mock<Trust>()
+               identity.setTrust(ownIdentity, trust)
+               identity.removeTrust(ownIdentity)
+               assertThat(identity.getTrust(ownIdentity), nullValue())
+       }
+
+       @Test
+       fun `identities with the same id are equal`() {
+               val identity2 = DefaultIdentity("Id", "Nickname2", "RequestURI2")
+               assertThat(identity2, equalTo(identity))
+               assertThat(identity, equalTo(identity2))
+       }
+
+       @Test
+       fun `two equal identities have the same hash code`() {
+               val identity2 = DefaultIdentity("Id", "Nickname2", "RequestURI2")
+               assertThat(identity.hashCode(), equalTo(identity2.hashCode()))
+       }
+
+       @Test
+       fun `null does not match an identity`() {
+               assertThat(identity, not(equalTo<Any>(null as Any?)))
+       }
+
+       @Test
+       fun `toString() contains id and nickname`() {
+               val identityString = identity.toString()
+               assertThat(identityString, matchesRegex(".*\\bId\\b.*"))
+               assertThat(identityString, matchesRegex(".*\\bNickname\\b.*"))
+       }
+
+}
diff --git a/src/test/kotlin/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentityTest.kt b/src/test/kotlin/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentityTest.kt
new file mode 100644 (file)
index 0000000..593b157
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Sone - DefaultOwnIdentityTest.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.wot
+
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+
+/**
+ * Unit test for [DefaultOwnIdentity].
+ */
+class DefaultOwnIdentityTest : DefaultIdentityTest() {
+
+       override val identity = DefaultOwnIdentity("Id", "Nickname", "RequestURI", "InsertURI")
+
+       @Test
+       fun `own identity can be created`() {
+               assertThat((identity as OwnIdentity).insertUri, equalTo("InsertURI"))
+       }
+
+}
diff --git a/src/test/kotlin/net/pterodactylus/sone/freenet/wot/Identities.kt b/src/test/kotlin/net/pterodactylus/sone/freenet/wot/Identities.kt
new file mode 100644 (file)
index 0000000..9d74b65
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Sone - Identities.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.wot
+
+fun createOwnIdentity(id: String, contexts: Set<String>, vararg properties: Pair<String, String>): OwnIdentity {
+       val ownIdentity = DefaultOwnIdentity(id, "Nickname$id", "Request$id", "Insert$id")
+       setContextsAndPropertiesOnIdentity(ownIdentity, contexts, mapOf(*properties))
+       return ownIdentity
+}
+
+fun createIdentity(id: String, contexts: Set<String>, vararg properties: Pair<String, String>): Identity {
+       val identity = DefaultIdentity(id, "Nickname$id", "Request$id")
+       setContextsAndPropertiesOnIdentity(identity, contexts, mapOf(*properties))
+       return identity
+}
+
+private fun setContextsAndPropertiesOnIdentity(identity: Identity, contexts: Set<String>, properties: Map<String, String>) {
+       identity.contexts = contexts
+       identity.properties = properties
+}
diff --git a/src/test/kotlin/net/pterodactylus/sone/freenet/wot/IdentityChangeDetectorTest.kt b/src/test/kotlin/net/pterodactylus/sone/freenet/wot/IdentityChangeDetectorTest.kt
new file mode 100644 (file)
index 0000000..e9f8092
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ * Sone - IdentityChangeDetectorTest.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.wot
+
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+
+/**
+ * Unit test for [IdentityChangeDetector].
+ */
+class IdentityChangeDetectorTest {
+
+       private val identityChangeDetector = IdentityChangeDetector(createOldIdentities())
+       private val newIdentities = mutableListOf<Identity>()
+       private val removedIdentities = mutableListOf<Identity>()
+       private val changedIdentities = mutableListOf<Identity>()
+       private val unchangedIdentities = mutableListOf<Identity>()
+
+       @Before
+       fun setup() {
+               identityChangeDetector.onNewIdentity = { identity -> newIdentities.add(identity) }
+               identityChangeDetector.onRemovedIdentity = { identity -> removedIdentities.add(identity) }
+               identityChangeDetector.onChangedIdentity = { identity -> changedIdentities.add(identity) }
+               identityChangeDetector.onUnchangedIdentity = { identity -> unchangedIdentities.add(identity) }
+       }
+
+       @Test
+       fun `no differences are detected when sending the old identities again`() {
+               identityChangeDetector.detectChanges(createOldIdentities())
+               assertThat(newIdentities, empty())
+               assertThat(removedIdentities, empty())
+               assertThat(changedIdentities, empty())
+               assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity2(), createIdentity3()))
+       }
+
+       @Test
+       fun `detect that an identity was removed`() {
+               identityChangeDetector.detectChanges(listOf(createIdentity1(), createIdentity3()))
+               assertThat(newIdentities, empty())
+               assertThat(removedIdentities, containsInAnyOrder(createIdentity2()))
+               assertThat(changedIdentities, empty())
+               assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity3()))
+       }
+
+       @Test
+       fun `detect that an identity was added`() {
+               identityChangeDetector.detectChanges(listOf(createIdentity1(), createIdentity2(), createIdentity3(), createIdentity4()))
+               assertThat(newIdentities, containsInAnyOrder(createIdentity4()))
+               assertThat(removedIdentities, empty())
+               assertThat(changedIdentities, empty())
+               assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity2(), createIdentity3()))
+       }
+
+       @Test
+       fun `detect that a context was removed`() {
+               val identity2 = createIdentity2()
+               identity2.removeContext("Context C")
+               identityChangeDetector.detectChanges(listOf(createIdentity1(), identity2, createIdentity3()))
+               assertThat(newIdentities, empty())
+               assertThat(removedIdentities, empty())
+               assertThat(changedIdentities, containsInAnyOrder(identity2))
+               assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity3()))
+       }
+
+       @Test
+       fun `detect that a context was added`() {
+               val identity2 = createIdentity2()
+               identity2.addContext("Context C1")
+               identityChangeDetector.detectChanges(listOf(createIdentity1(), identity2, createIdentity3()))
+               assertThat(newIdentities, empty())
+               assertThat(removedIdentities, empty())
+               assertThat(changedIdentities, containsInAnyOrder(identity2))
+               assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity3()))
+       }
+
+       @Test
+       fun `detect that a property was removed`() {
+               val identity1 = createIdentity1()
+               identity1.removeProperty("Key A")
+               identityChangeDetector.detectChanges(listOf(identity1, createIdentity2(), createIdentity3()))
+               assertThat(newIdentities, empty())
+               assertThat(removedIdentities, empty())
+               assertThat(changedIdentities, containsInAnyOrder(identity1))
+               assertThat(unchangedIdentities, containsInAnyOrder(createIdentity2(), createIdentity3()))
+       }
+
+       @Test
+       fun `detect that a property was added`() {
+               val identity3 = createIdentity3()
+               identity3.setProperty("Key A", "Value A")
+               identityChangeDetector.detectChanges(listOf(createIdentity1(), createIdentity2(), identity3))
+               assertThat(newIdentities, empty())
+               assertThat(removedIdentities, empty())
+               assertThat(changedIdentities, containsInAnyOrder(identity3))
+               assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity2()))
+       }
+
+       @Test
+       fun `detect that a property was changed`() {
+               val identity3 = createIdentity3()
+               identity3.setProperty("Key E", "Value F")
+               identityChangeDetector.detectChanges(listOf(createIdentity1(), createIdentity2(), identity3))
+               assertThat(newIdentities, empty())
+               assertThat(removedIdentities, empty())
+               assertThat(changedIdentities, containsInAnyOrder(identity3))
+               assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity2()))
+       }
+
+       @Test
+       fun `no removed identities are detected without an identity processor`() {
+               identityChangeDetector.onRemovedIdentity = null
+               identityChangeDetector.detectChanges(listOf(createIdentity1(), createIdentity3()))
+               assertThat(removedIdentities, empty())
+       }
+
+       @Test
+       fun `no added identities are detected without an identity processor`() {
+               identityChangeDetector.onNewIdentity = null
+               identityChangeDetector.detectChanges(listOf(createIdentity1(), createIdentity2(), createIdentity3(), createIdentity4()))
+               assertThat(newIdentities, empty())
+       }
+
+       private fun createOldIdentities() =
+                       listOf(createIdentity1(), createIdentity2(), createIdentity3())
+
+       private fun createIdentity1() =
+                       createIdentity("Test1", setOf("Context A", "Context B"), "Key A" to "Value A", "Key B" to "Value B")
+
+       private fun createIdentity2() =
+                       createIdentity("Test2", setOf("Context C", "Context D"), "Key C" to "Value C", "Key D" to "Value D")
+
+       private fun createIdentity3() =
+                       createIdentity("Test3", setOf("Context E", "Context F"), "Key E" to "Value E", "Key F" to "Value F")
+
+       private fun createIdentity4() =
+                       createIdentity("Test4", setOf("Context G", "Context H"), "Key G" to "Value G", "Key H" to "Value H")
+
+}
diff --git a/src/test/kotlin/net/pterodactylus/sone/freenet/wot/IdentityChangeEventSenderTest.kt b/src/test/kotlin/net/pterodactylus/sone/freenet/wot/IdentityChangeEventSenderTest.kt
new file mode 100644 (file)
index 0000000..19fdda0
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * Sone - IdentityChangeEventSenderTest.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.wot
+
+import com.google.common.eventbus.*
+import net.pterodactylus.sone.freenet.wot.event.*
+import net.pterodactylus.sone.test.*
+import org.junit.*
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.verify
+
+/**
+ * Unit test for [IdentityChangeEventSender].
+ */
+class IdentityChangeEventSenderTest {
+
+       private val eventBus = mock<EventBus>()
+       private val ownIdentities = listOf(
+                       createOwnIdentity("O1", setOf("Test"), "KeyA" to "ValueA"),
+                       createOwnIdentity("O2", setOf("Test2"), "KeyB" to "ValueB"),
+                       createOwnIdentity("O3", setOf("Test3"), "KeyC" to "ValueC")
+       )
+       private val identities = listOf(
+                       createIdentity("I1", setOf()),
+                       createIdentity("I2", setOf()),
+                       createIdentity("I3", setOf()),
+                       createIdentity("I2", setOf("Test"))
+       )
+       private val identityChangeEventSender = IdentityChangeEventSender(eventBus, createOldIdentities())
+
+       @Test
+       fun addingAnOwnIdentityIsDetectedAndReportedCorrectly() {
+               val newIdentities = createNewIdentities()
+               identityChangeEventSender.detectChanges(newIdentities)
+               verify(eventBus).post(eq(OwnIdentityRemovedEvent(ownIdentities[0])))
+               verify(eventBus).post(eq(IdentityRemovedEvent(ownIdentities[0], identities[0])))
+               verify(eventBus).post(eq(IdentityRemovedEvent(ownIdentities[0], identities[1])))
+               verify(eventBus).post(eq(OwnIdentityAddedEvent(ownIdentities[2])))
+               verify(eventBus).post(eq(IdentityAddedEvent(ownIdentities[2], identities[1])))
+               verify(eventBus).post(eq(IdentityAddedEvent(ownIdentities[2], identities[2])))
+               verify(eventBus).post(eq(IdentityRemovedEvent(ownIdentities[1], identities[0])))
+               verify(eventBus).post(eq(IdentityAddedEvent(ownIdentities[1], identities[2])))
+               verify(eventBus).post(eq(IdentityUpdatedEvent(ownIdentities[1], identities[1])))
+       }
+
+       private fun createNewIdentities() = mapOf(
+                       ownIdentities[1] to listOf(identities[3], identities[2]),
+                       ownIdentities[2] to listOf(identities[1], identities[2])
+       )
+
+       private fun createOldIdentities() = mapOf(
+                       ownIdentities[0] to listOf(identities[0], identities[1]),
+                       ownIdentities[1] to listOf(identities[0], identities[1])
+       )
+
+}
diff --git a/src/test/kotlin/net/pterodactylus/sone/freenet/wot/IdentityLoaderTest.kt b/src/test/kotlin/net/pterodactylus/sone/freenet/wot/IdentityLoaderTest.kt
new file mode 100644 (file)
index 0000000..780288d
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * Sone - IdentityLoaderTest.java - Copyright © 2013–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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.wot
+
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+
+/**
+ * Unit test for [IdentityLoader].
+ */
+class IdentityLoaderTest {
+
+       private val ownIdentities = createOwnIdentities()
+       private val webOfTrustConnector = object : TestWebOfTrustConnector() {
+               override fun loadAllOwnIdentities() = ownIdentities.toSet()
+               override fun loadTrustedIdentities(ownIdentity: OwnIdentity, context: String?) =
+                               when (ownIdentity) {
+                                       ownIdentities[0] -> createTrustedIdentitiesForFirstOwnIdentity()
+                                       ownIdentities[1] -> createTrustedIdentitiesForSecondOwnIdentity()
+                                       ownIdentities[2] -> createTrustedIdentitiesForThirdOwnIdentity()
+                                       ownIdentities[3] -> createTrustedIdentitiesForFourthOwnIdentity()
+                                       else -> throw RuntimeException()
+                               }
+       }
+
+       @Test
+       fun loadingIdentities() {
+               val identityLoader = IdentityLoader(webOfTrustConnector, Context("Test"))
+               val identities = identityLoader.loadIdentities()
+               assertThat(identities.keys, hasSize(4))
+               assertThat(identities.keys, containsInAnyOrder(ownIdentities[0], ownIdentities[1], ownIdentities[2], ownIdentities[3]))
+               verifyIdentitiesForOwnIdentity(identities, ownIdentities[0], createTrustedIdentitiesForFirstOwnIdentity())
+               verifyIdentitiesForOwnIdentity(identities, ownIdentities[1], createTrustedIdentitiesForSecondOwnIdentity())
+               verifyIdentitiesForOwnIdentity(identities, ownIdentities[2], emptySet())
+               verifyIdentitiesForOwnIdentity(identities, ownIdentities[3], createTrustedIdentitiesForFourthOwnIdentity())
+       }
+
+       @Test
+       fun loadingIdentitiesWithoutContext() {
+               val identityLoaderWithoutContext = IdentityLoader(webOfTrustConnector)
+               val identities = identityLoaderWithoutContext.loadIdentities()
+               assertThat(identities.keys, hasSize(4))
+               assertThat(identities.keys, containsInAnyOrder(ownIdentities[0], ownIdentities[1], ownIdentities[2], ownIdentities[3]))
+               verifyIdentitiesForOwnIdentity(identities, ownIdentities[0], createTrustedIdentitiesForFirstOwnIdentity())
+               verifyIdentitiesForOwnIdentity(identities, ownIdentities[1], createTrustedIdentitiesForSecondOwnIdentity())
+               verifyIdentitiesForOwnIdentity(identities, ownIdentities[2], createTrustedIdentitiesForThirdOwnIdentity())
+               verifyIdentitiesForOwnIdentity(identities, ownIdentities[3], createTrustedIdentitiesForFourthOwnIdentity())
+       }
+
+       private fun verifyIdentitiesForOwnIdentity(identities: Map<OwnIdentity, Collection<Identity>>, ownIdentity: OwnIdentity, trustedIdentities: Set<Identity>) {
+               assertThat(identities[ownIdentity], equalTo<Collection<Identity>>(trustedIdentities))
+       }
+
+}
+
+private fun createOwnIdentities() = listOf(
+               createOwnIdentity("O1", "ON1", "OR1", "OI1", setOf("Test", "Test2"), mapOf("KeyA" to "ValueA", "KeyB" to "ValueB")),
+               createOwnIdentity("O2", "ON2", "OR2", "OI2", setOf("Test"), mapOf("KeyC" to "ValueC")),
+               createOwnIdentity("O3", "ON3", "OR3", "OI3", setOf("Test2"), mapOf("KeyE" to "ValueE", "KeyD" to "ValueD")),
+               createOwnIdentity("O4", "ON4", "OR$", "OI4", setOf("Test"), mapOf("KeyA" to "ValueA", "KeyD" to "ValueD"))
+)
+
+private fun createTrustedIdentitiesForFirstOwnIdentity() = setOf(
+               createIdentity("I11", "IN11", "IR11", setOf("Test"), mapOf("KeyA" to "ValueA"))
+)
+
+private fun createTrustedIdentitiesForSecondOwnIdentity() = setOf(
+               createIdentity("I21", "IN21", "IR21", setOf("Test", "Test2"), mapOf("KeyB" to "ValueB"))
+)
+
+private fun createTrustedIdentitiesForThirdOwnIdentity() = setOf(
+               createIdentity("I31", "IN31", "IR31", setOf("Test", "Test3"), mapOf("KeyC" to "ValueC"))
+)
+
+private fun createTrustedIdentitiesForFourthOwnIdentity(): Set<Identity> = emptySet()
+
+private fun createOwnIdentity(id: String, nickname: String, requestUri: String, insertUri: String, contexts: Set<String>, properties: Map<String, String>): OwnIdentity =
+               DefaultOwnIdentity(id, nickname, requestUri, insertUri).apply {
+                       setContexts(contexts)
+                       this.properties = properties
+               }
+
+private fun createIdentity(id: String, nickname: String, requestUri: String, contexts: Set<String>, properties: Map<String, String>): Identity =
+               DefaultIdentity(id, nickname, requestUri).apply {
+                       setContexts(contexts)
+                       this.properties = properties
+               }
+
+private open class TestWebOfTrustConnector : WebOfTrustConnector {
+
+       override fun loadAllOwnIdentities() = emptySet<OwnIdentity>()
+       override fun loadTrustedIdentities(ownIdentity: OwnIdentity, context: String?) = emptySet<Identity>()
+       override fun addContext(ownIdentity: OwnIdentity, context: String) = Unit
+       override fun removeContext(ownIdentity: OwnIdentity, context: String) = Unit
+       override fun setProperty(ownIdentity: OwnIdentity, name: String, value: String) = Unit
+       override fun removeProperty(ownIdentity: OwnIdentity, name: String) = Unit
+       override fun getTrust(ownIdentity: OwnIdentity, identity: Identity) = Trust(null, null, null)
+       override fun setTrust(ownIdentity: OwnIdentity, identity: Identity, trust: Int, comment: String) = Unit
+       override fun removeTrust(ownIdentity: OwnIdentity, identity: Identity) = Unit
+       override fun ping() = Unit
+
+}
diff --git a/src/test/kotlin/net/pterodactylus/sone/freenet/wot/PluginWebOfTrustConnectorTest.kt b/src/test/kotlin/net/pterodactylus/sone/freenet/wot/PluginWebOfTrustConnectorTest.kt
new file mode 100644 (file)
index 0000000..1af177f
--- /dev/null
@@ -0,0 +1,331 @@
+package net.pterodactylus.sone.freenet.wot
+
+import freenet.support.*
+import freenet.support.api.*
+import net.pterodactylus.sone.freenet.*
+import net.pterodactylus.sone.freenet.plugin.*
+import net.pterodactylus.sone.test.*
+import org.hamcrest.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.hamcrest.core.*
+import kotlin.test.*
+
+/**
+ * Unit test for [PluginWebOfTrustConnector].
+ */
+class PluginWebOfTrustConnectorTest {
+
+       private val ownIdentity = DefaultOwnIdentity("id", "nickname", "requestUri", "insertUri")
+       private val identity = DefaultIdentity("id-a", "alpha", "url://alpha")
+
+       @Test
+       fun `wot plugin can be pinged`() {
+               createPluginConnector("Ping")
+                               .connect { ping() }
+       }
+
+       @Test
+       fun `own identities are returned correctly`() {
+               val ownIdentities = createPluginConnector("GetOwnIdentities") {
+                       put("Identity0", "id-0")
+                       put("RequestURI0", "request-uri-0")
+                       put("InsertURI0", "insert-uri-0")
+                       put("Nickname0", "nickname-0")
+                       put("Contexts0.Context0", "id-0-context-0")
+                       put("Properties0.Property0.Name", "id-0-property-0-name")
+                       put("Properties0.Property0.Value", "id-0-property-0-value")
+                       put("Identity1", "id-1")
+                       put("RequestURI1", "request-uri-1")
+                       put("InsertURI1", "insert-uri-1")
+                       put("Nickname1", "nickname-1")
+                       put("Contexts1.Context0", "id-1-context-0")
+                       put("Properties1.Property0.Name", "id-1-property-0-name")
+                       put("Properties1.Property0.Value", "id-1-property-0-value")
+               }.connect { loadAllOwnIdentities() }
+               assertThat(ownIdentities, containsInAnyOrder(
+                               isOwnIdentity("id-0", "nickname-0", "request-uri-0", "insert-uri-0", contains("id-0-context-0"), hasEntry("id-0-property-0-name", "id-0-property-0-value")),
+                               isOwnIdentity("id-1", "nickname-1", "request-uri-1", "insert-uri-1", contains("id-1-context-0"), hasEntry("id-1-property-0-name", "id-1-property-0-value"))
+               ))
+       }
+
+       @Test
+       fun `trusted identities are requested with correct own identity`() {
+               createPluginConnector("GetIdentitiesByScore", hasField("Truster", equalTo("id")))
+                               .connect { loadTrustedIdentities(ownIdentity) }
+       }
+
+       @Test
+       fun `trusted identities are requested with correct selection parameter`() {
+               createPluginConnector("GetIdentitiesByScore", hasField("Selection", equalTo("+")))
+                               .connect { loadTrustedIdentities(ownIdentity) }
+       }
+
+       @Test
+       fun `trusted identities are requested with empty context if null context requested`() {
+               createPluginConnector("GetIdentitiesByScore", hasField("Context", equalTo("")))
+                               .connect { loadTrustedIdentities(ownIdentity) }
+       }
+
+       @Test
+       fun `trusted identities are requested with context if context requested`() {
+               createPluginConnector("GetIdentitiesByScore", hasField("Context", equalTo("TestContext")))
+                               .connect { loadTrustedIdentities(ownIdentity, "TestContext") }
+       }
+
+       @Test
+       fun `trusted identities are requested with trust values`() {
+               createPluginConnector("GetIdentitiesByScore", hasField("WantTrustValues", equalTo("true")))
+                               .connect { loadTrustedIdentities(ownIdentity) }
+       }
+
+       @Test
+       fun `empty list of trusted identities is returned correctly`() {
+               val trustedIdentities = createPluginConnector("GetIdentitiesByScore")
+                               .connect { loadTrustedIdentities(ownIdentity) }
+               assertThat(trustedIdentities, empty())
+       }
+
+       @Test
+       fun `trusted identities without context, properties, or trust value are returned correctly`() {
+               val trustedIdentities = createPluginConnector("GetIdentitiesByScore") {
+                       put("Identity0", "id0")
+                       put("Nickname0", "nickname0")
+                       put("RequestURI0", "request-uri0")
+                       put("Identity1", "id1")
+                       put("Nickname1", "nickname1")
+                       put("RequestURI1", "request-uri1")
+               }.connect { loadTrustedIdentities(ownIdentity) }
+               assertThat(trustedIdentities, contains(
+                               allOf(
+                                               isIdentity("id0", "nickname0", "request-uri0", empty<String>(), isEmptyMap()),
+                                               isTrusted(ownIdentity, isTrust(null, null, null))
+                               ),
+                               allOf(
+                                               isIdentity("id1", "nickname1", "request-uri1", empty<String>(), isEmptyMap()),
+                                               isTrusted(ownIdentity, isTrust(null, null, null))
+                               )
+               ))
+       }
+
+       @Test
+       fun `trusted identity without nickname is returned correctly`() {
+               val trustedIdentities = createPluginConnector("GetIdentitiesByScore") {
+                       put("Identity0", "id0")
+                       put("RequestURI0", "request-uri0")
+               }.connect { loadTrustedIdentities(ownIdentity) }
+               assertThat(trustedIdentities, contains(
+                               allOf(
+                                               isIdentity("id0", null, "request-uri0", empty<String>(), isEmptyMap()),
+                                               isTrusted(ownIdentity, isTrust(null, null, null))
+                               )
+               ))
+       }
+
+       @Test
+       fun `trusted identity with contexts is returned correctly`() {
+               val trustedIdentities = createPluginConnector("GetIdentitiesByScore") {
+                       put("Identity0", "id0")
+                       put("Nickname0", "nickname0")
+                       put("RequestURI0", "request-uri0")
+                       put("Contexts0.Context0", "Context0")
+                       put("Contexts0.Context1", "Context1")
+               }.connect { loadTrustedIdentities(ownIdentity) }
+               assertThat(trustedIdentities, contains(
+                               isIdentity("id0", "nickname0", "request-uri0", containsInAnyOrder("Context0", "Context1"), isEmptyMap())
+               ))
+       }
+
+       @Test
+       fun `trusted identity with properties is returned correctly`() {
+               val trustedIdentities = createPluginConnector("GetIdentitiesByScore") {
+                       put("Identity0", "id0")
+                       put("Nickname0", "nickname0")
+                       put("RequestURI0", "request-uri0")
+                       put("Properties0.Property0.Name", "foo")
+                       put("Properties0.Property0.Value", "bar")
+                       put("Properties0.Property1.Name", "baz")
+                       put("Properties0.Property1.Value", "quo")
+               }.connect { loadTrustedIdentities(ownIdentity) }
+               assertThat(trustedIdentities, contains(
+                               isIdentity("id0", "nickname0", "request-uri0", empty(), allOf(hasEntry("foo", "bar"), hasEntry("baz", "quo")))
+               ))
+       }
+
+       @Test
+       fun `trusted identity with trust value is returned correctly`() {
+               val trustedIdentities = createPluginConnector("GetIdentitiesByScore") {
+                       put("Identity0", "id0")
+                       put("Nickname0", "nickname0")
+                       put("RequestURI0", "request-uri0")
+                       put("Trust0", "12")
+                       put("Score0", "34")
+                       put("Rank0", "56")
+               }.connect { loadTrustedIdentities(ownIdentity) }
+               assertThat(trustedIdentities, contains(
+                               allOf(
+                                               isIdentity("id0", "nickname0", "request-uri0", empty(), isEmptyMap()),
+                                               isTrusted(ownIdentity, isTrust(12, 34, 56))
+                               )
+               ))
+       }
+
+       @Test
+       fun `adding a context sends the correct own identity id`() {
+               createPluginConnector("AddContext", hasField("Identity", equalTo(ownIdentity.id)))
+                               .connect { addContext(ownIdentity, "TestContext") }
+       }
+
+       @Test
+       fun `adding a context sends the correct context`() {
+               createPluginConnector("AddContext", hasField("Context", equalTo("TestContext")))
+                               .connect { addContext(ownIdentity, "TestContext") }
+       }
+
+       @Test
+       fun `removing a context sends the correct own identity id`() {
+               createPluginConnector("RemoveContext", hasField("Identity", equalTo(ownIdentity.id)))
+                               .connect { removeContext(ownIdentity, "TestContext") }
+       }
+
+       @Test
+       fun `removing a context sends the correct context`() {
+               createPluginConnector("RemoveContext", hasField("Context", equalTo("TestContext")))
+                               .connect { removeContext(ownIdentity, "TestContext") }
+       }
+
+       @Test
+       fun `setting a property sends the correct identity id`() {
+               createPluginConnector("SetProperty", hasField("Identity", equalTo(ownIdentity.id)))
+                               .connect { setProperty(ownIdentity, "TestProperty", "TestValue") }
+       }
+
+       @Test
+       fun `setting a property sends the correct property name`() {
+               createPluginConnector("SetProperty", hasField("Property", equalTo("TestProperty")))
+                               .connect { setProperty(ownIdentity, "TestProperty", "TestValue") }
+       }
+
+       @Test
+       fun `setting a property sends the correct property value`() {
+               createPluginConnector("SetProperty", hasField("Value", equalTo("TestValue")))
+                               .connect { setProperty(ownIdentity, "TestProperty", "TestValue") }
+       }
+
+       @Test
+       fun `removing a property sends the correct identity id`() {
+               createPluginConnector("RemoveProperty", hasField("Identity", equalTo(ownIdentity.id)))
+                               .connect { removeProperty(ownIdentity, "TestProperty") }
+       }
+
+       @Test
+       fun `removing a property sends the correct property name`() {
+               createPluginConnector("RemoveProperty", hasField("Property", equalTo("TestProperty")))
+                               .connect { removeProperty(ownIdentity, "TestProperty") }
+       }
+
+       @Test
+       fun `getting trust sends correct own identity id`() {
+               createPluginConnector("GetIdentity", hasField("Truster", equalTo(ownIdentity.id)))
+                               .connect { getTrust(ownIdentity, identity) }
+       }
+
+       @Test
+       fun `getting trust sends correct identity id`() {
+               createPluginConnector("GetIdentity", hasField("Identity", equalTo(identity.id)))
+                               .connect { getTrust(ownIdentity, identity) }
+       }
+
+       @Test
+       fun `getting trust returns correct trust values`() {
+               val trust = createPluginConnector("GetIdentity", hasField("Identity", equalTo(identity.id))) {
+                       put("Trust", "12")
+                       put("Score", "34")
+                       put("Rank", "56")
+               }.connect { getTrust(ownIdentity, identity) }
+               assertThat(trust, isTrust(12, 34, 56))
+       }
+
+       @Test
+       fun `getting trust reads incorrect numbers for trust as null`() {
+               val trust = createPluginConnector("GetIdentity", hasField("Identity", equalTo(identity.id))) {
+                       put("Trust", "incorrect")
+                       put("Score", "34")
+                       put("Rank", "56")
+               }.connect { getTrust(ownIdentity, identity) }
+               assertThat(trust, isTrust(null, 34, 56))
+       }
+
+       @Test
+       fun `getting trust reads incorrect numbers for score as null`() {
+               val trust = createPluginConnector("GetIdentity", hasField("Identity", equalTo(identity.id))) {
+                       put("Trust", "12")
+                       put("Score", "incorrect")
+                       put("Rank", "56")
+               }.connect { getTrust(ownIdentity, identity) }
+               assertThat(trust, isTrust(12, null, 56))
+       }
+
+       @Test
+       fun `getting trust reads incorrect numbers for rank as null`() {
+               val trust = createPluginConnector("GetIdentity", hasField("Identity", equalTo(identity.id))) {
+                       put("Trust", "12")
+                       put("Score", "34")
+                       put("Rank", "incorrect")
+               }.connect { getTrust(ownIdentity, identity) }
+               assertThat(trust, isTrust(12, 34, null))
+       }
+
+       @Test
+       fun `setting trust sends correct own identity id`() {
+               createPluginConnector("SetTrust", hasField("Truster", equalTo(ownIdentity.id)))
+                               .connect { setTrust(ownIdentity, identity, 123, "Test Trust") }
+       }
+
+       @Test
+       fun `setting trust sends correct identity id`() {
+               createPluginConnector("SetTrust", hasField("Trustee", equalTo(identity.id)))
+                               .connect { setTrust(ownIdentity, identity, 123, "Test Trust") }
+       }
+
+       @Test
+       fun `setting trust sends correct trust value`() {
+               createPluginConnector("SetTrust", hasField("Value", equalTo("123")))
+                               .connect { setTrust(ownIdentity, identity, 123, "Test Trust") }
+       }
+
+       @Test
+       fun `setting trust sends correct comment`() {
+               createPluginConnector("SetTrust", hasField("Comment", equalTo("Test Trust")))
+                               .connect { setTrust(ownIdentity, identity, 123, "Test Trust") }
+       }
+
+       @Test
+       fun `removing trust sends correct own identity id`() {
+               createPluginConnector("RemoveTrust", hasField("Truster", equalTo(ownIdentity.id)))
+                               .connect { removeTrust(ownIdentity, identity) }
+       }
+
+       @Test
+       fun `removing trust sends correct identity id`() {
+               createPluginConnector("RemoveTrust", hasField("Trustee", equalTo(identity.id)))
+                               .connect { removeTrust(ownIdentity, identity) }
+       }
+
+}
+
+private fun <R> PluginConnector.connect(block: PluginWebOfTrustConnector.() -> R) =
+               PluginWebOfTrustConnector(this).let(block)
+
+fun createPluginConnector(message: String, fieldsMatcher: Matcher<SimpleFieldSet> = IsAnything<SimpleFieldSet>(), build: SimpleFieldSetBuilder.() -> Unit = {}) =
+               object : PluginConnector {
+                       override suspend fun sendRequest(pluginName: String, fields: SimpleFieldSet, data: Bucket?) =
+                                       if ((pluginName != wotPluginName) || (fields.get("Message") != message)) {
+                                               throw PluginException()
+                                       } else {
+                                               assertThat(fields, fieldsMatcher)
+                                               PluginReply(SimpleFieldSetBuilder().apply(build).get(), null)
+                                       }
+               }
+
+private const val wotPluginName = "plugins.WebOfTrust.WebOfTrust"
index 2f55d52..e94e34e 100644 (file)
@@ -5,10 +5,13 @@ import freenet.client.*
 import freenet.clients.http.*
 import freenet.node.*
 import freenet.pluginmanager.*
+import net.pterodactylus.sone.freenet.plugin.*
 import net.pterodactylus.sone.test.*
 import org.hamcrest.MatcherAssert.*
 import org.hamcrest.Matchers.*
 import org.junit.*
+import org.junit.rules.*
+import org.mockito.*
 import org.mockito.Mockito.*
 
 /**
@@ -16,6 +19,10 @@ import org.mockito.Mockito.*
  */
 class FreenetModuleTest {
 
+       @Rule
+       @JvmField
+       val expectedException = ExpectedException.none()!!
+
        private val sessionManager = mock<SessionManager>()
        private val pluginRespirator = deepMock<PluginRespirator>().apply {
                whenever(getSessionManager("Sone")).thenReturn(sessionManager)
@@ -34,13 +41,9 @@ class FreenetModuleTest {
        }
 
        @Test
-       fun `plugin respirator is returned correctly`() {
-               assertThat(injector.getInstance(), sameInstance(pluginRespirator))
-       }
-
-       @Test
-       fun `plugin respirator is returned as singleton`() {
-               verifySingletonInstance<PluginRespirator>()
+       fun `plugin respirator is not bound`() {
+               expectedException.expect(Exception::class.java)
+               injector.getInstance<PluginRespirator>()
        }
 
        @Test
@@ -90,8 +93,30 @@ class FreenetModuleTest {
        }
 
        @Test
-       fun `page maker is returned as singleten`() {
+       fun `page maker is returned as singleton`() {
                verifySingletonInstance<PageMaker>()
        }
 
+       @Test
+       fun `plugin respirator facade is returned correctly`() {
+               val pluginRespiratorFacade = injector.getInstance<PluginRespiratorFacade>()
+               pluginRespiratorFacade.getPluginTalker(mock(), "test.plugin", "test-request-1")
+               verify(pluginRespirator).getPluginTalker(any(), ArgumentMatchers.eq("test.plugin"), ArgumentMatchers.eq("test-request-1"))
+       }
+
+       @Test
+       fun `plugin respirator facade is returned as singleton`() {
+               verifySingletonInstance<PluginRespiratorFacade>()
+       }
+
+       @Test
+       fun `plugin connector is returned correctly`() {
+               assertThat(injector.getInstance<PluginConnector>(), notNullValue())
+       }
+
+       @Test
+       fun `plugin connector facade is returned as singleton`() {
+               verifySingletonInstance<PluginConnector>()
+       }
+
 }
index 31b232d..e95955c 100644 (file)
@@ -10,6 +10,8 @@ import freenet.pluginmanager.*
 import net.pterodactylus.sone.core.*
 import net.pterodactylus.sone.database.*
 import net.pterodactylus.sone.database.memory.*
+import net.pterodactylus.sone.freenet.*
+import net.pterodactylus.sone.freenet.plugin.*
 import net.pterodactylus.sone.freenet.wot.*
 import net.pterodactylus.sone.test.*
 import net.pterodactylus.util.config.*
@@ -41,7 +43,8 @@ class SoneModuleTest {
                createInjector(
                                SoneModule(sonePlugin, EventBus()),
                                FreenetInterface::class.isProvidedByDeepMock(),
-                               PluginRespirator::class.isProvidedByDeepMock()
+                               PluginRespiratorFacade::class.isProvidedByDeepMock(),
+                               PluginConnector::class.isProvidedByDeepMock()
                )
        }
 
@@ -147,8 +150,8 @@ class SoneModuleTest {
        }
 
        @Test
-       fun `base l10n is bound correctly`() {
-               assertThat(injector.getInstance(), sameInstance(l10n.base))
+       fun `translation is bound correctly`() {
+               assertThat(injector.getInstance<Translation>(), notNullValue())
        }
 
        @Test
@@ -198,7 +201,8 @@ class SoneModuleTest {
                val injector = createInjector(
                                SoneModule(sonePlugin, eventBus),
                                FreenetInterface::class.isProvidedByDeepMock(),
-                               PluginRespirator::class.isProvidedByDeepMock()
+                               PluginRespiratorFacade::class.isProvidedByDeepMock(),
+                               PluginConnector::class.isProvidedByDeepMock()
                )
                val core = injector.getInstance<Core>()
                verify(eventBus).register(core)
@@ -216,4 +220,16 @@ class SoneModuleTest {
                assertThat(firstMetricRegistry, sameInstance(secondMetricRegistry))
        }
 
+       @Test
+       fun `wot connector can be created`() {
+               assertThat(injector.getInstance<WebOfTrustConnector>(), notNullValue())
+       }
+
+       @Test
+       fun `wot connector is created as singleton`() {
+               val firstWebOfTrustConnector = injector.getInstance<WebOfTrustConnector>()
+               val secondWebOfTrustConnector = injector.getInstance<WebOfTrustConnector>()
+               assertThat(firstWebOfTrustConnector, sameInstance(secondWebOfTrustConnector))
+       }
+
 }
diff --git a/src/test/kotlin/net/pterodactylus/sone/notify/ListNotificationTest.kt b/src/test/kotlin/net/pterodactylus/sone/notify/ListNotificationTest.kt
new file mode 100644 (file)
index 0000000..4c82035
--- /dev/null
@@ -0,0 +1,125 @@
+package net.pterodactylus.sone.notify
+
+import net.pterodactylus.util.notify.*
+import net.pterodactylus.util.template.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import java.util.concurrent.atomic.*
+
+/**
+ * Unit test for [ListNotification].
+ */
+class ListNotificationTest {
+
+       private val template = Template()
+       private val listNotification = ListNotification<String>(ID, KEY, template)
+
+       @Test
+       fun `creating a list notification sets empty iterable on element key in template context`() {
+               assertThat(template.initialContext.get(KEY) as Iterable<*>, emptyIterable())
+       }
+
+       @Test
+       @Suppress("UNCHECKED_CAST")
+       fun `list in template context gets updated when elements are added`() {
+               listNotification.add("a")
+               listNotification.add("b")
+               assertThat(template.initialContext.get(KEY) as Iterable<String>, contains("a", "b"))
+       }
+
+       @Test
+       fun `new list notification has no element`() {
+               assertThat(listNotification.elements, emptyIterable())
+       }
+
+       @Test
+       fun `new list notification is empty`() {
+               assertThat(listNotification.isEmpty, equalTo(true))
+       }
+
+       @Test
+       fun `list notification retains set elements`() {
+               listNotification.setElements(listOf("a", "b", "c"))
+               assertThat(listNotification.elements, contains("a", "b", "c"))
+       }
+
+       @Test
+       fun `list notification deduplicates set elements`() {
+               listNotification.setElements(listOf("a", "b", "a"))
+               assertThat(listNotification.elements, contains("a", "b"))
+       }
+
+       @Test
+       fun `list notification retains added elements`() {
+               listNotification.add("a")
+               listNotification.add("b")
+               listNotification.add("c")
+               assertThat(listNotification.elements, contains("a", "b", "c"))
+       }
+
+       @Test
+       fun `list notification deduplicates elements`() {
+               listNotification.add("a")
+               listNotification.add("b")
+               listNotification.add("a")
+               assertThat(listNotification.elements, contains("a", "b"))
+       }
+
+       @Test
+       fun `list notification removes correct element`() {
+               listNotification.setElements(listOf("a", "b", "c"))
+               listNotification.remove("b")
+               assertThat(listNotification.elements, contains("a", "c"))
+       }
+
+       @Test
+       fun `removing the last element dismisses the notification`() {
+               val notificationDismissed = AtomicBoolean()
+               val notificationListener = NotificationListener { notificationDismissed.set(it == listNotification) }
+               listNotification.addNotificationListener(notificationListener)
+               listNotification.add("a")
+               listNotification.remove("a")
+               assertThat(notificationDismissed.get(), equalTo(true))
+       }
+
+       @Test
+       fun `dismissing the list notification removes all elements`() {
+               listNotification.setElements(listOf("a", "b", "c"))
+               listNotification.dismiss()
+               assertThat(listNotification.elements, emptyIterable())
+       }
+
+       @Test
+       fun `list notification with different elements is not equal`() {
+               val secondNotification = ListNotification<String>(ID, KEY, template)
+               listNotification.add("a")
+               secondNotification.add("b")
+               assertThat(listNotification, not(equalTo(secondNotification)))
+       }
+
+       @Test
+       fun `list notification with different key is not equal`() {
+               val secondNotification = ListNotification<String>(ID, OTHER_KEY, template)
+               assertThat(listNotification, not(equalTo(secondNotification)))
+       }
+
+       @Test
+       fun `copied notifications have the same hash code`() {
+               val secondNotification = ListNotification(listNotification)
+               listNotification.add("a")
+               secondNotification.add("a")
+               listNotification.setLastUpdateTime(secondNotification.lastUpdatedTime)
+               assertThat(listNotification.hashCode(), equalTo(secondNotification.hashCode()))
+       }
+
+       @Test
+       fun `list notification is not equal to other objects`() {
+               assertThat(listNotification, not(equalTo(Any())))
+       }
+
+}
+
+private const val ID = "notification-id"
+private const val KEY = "element-key"
+private const val OTHER_KEY = "other-key"
diff --git a/src/test/kotlin/net/pterodactylus/sone/template/HistogramRendererTest.kt b/src/test/kotlin/net/pterodactylus/sone/template/HistogramRendererTest.kt
new file mode 100644 (file)
index 0000000..cc450d6
--- /dev/null
@@ -0,0 +1,204 @@
+/**
+ * Sone - HistogramRendererTest.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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.template
+
+import com.codahale.metrics.*
+import net.pterodactylus.sone.freenet.*
+import net.pterodactylus.util.template.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.jsoup.*
+import org.jsoup.nodes.*
+import org.junit.*
+import java.util.*
+
+/**
+ * Unit test for [HistogramRenderer].
+ */
+class HistogramRendererTest {
+
+       private val translation = object : Translation {
+               override val currentLocale = Locale.ENGLISH
+               override fun translate(key: String) = "Metric Name".takeIf { key == "Page.Metrics.TestHistogram.Title" } ?: ""
+       }
+       private val metricRenderer = HistogramRenderer()
+       private val templateContext = TemplateContext().apply {
+               addFilter("html", HtmlFilter())
+               addFilter("duration", DurationFormatFilter())
+               addFilter("l10n", L10nFilter(translation))
+       }
+
+       @Test
+       fun `histogram is rendered as table row`() {
+               createAndVerifyTableRow {
+                       assertThat(it.nodeName(), equalTo("tr"))
+               }
+       }
+
+       @Test
+       fun `histogram has eleven columns`() {
+               createAndVerifyTableRow {
+                       assertThat(it.getElementsByTag("td"), hasSize(11))
+               }
+       }
+
+       @Test
+       fun `first column contains translated metric name`() {
+               createAndVerifyTableRow(mapOf("name" to "test.histogram")) {
+                       assertThat(it.getElementsByTag("td")[0].text(), equalTo("Metric Name"))
+               }
+       }
+
+       @Test
+       fun `second column is numeric`() {
+               verifyColumnIsNumeric(1)
+       }
+
+       @Test
+       fun `second column contains count`() {
+               createAndVerifyTableRow {
+                       assertThat(it.getElementsByTag("td")[1].text(), equalTo("2001"))
+               }
+       }
+
+       @Test
+       fun `third column is numeric`() {
+               verifyColumnIsNumeric(2)
+       }
+
+       @Test
+       fun `third column contains min value`() {
+               createAndVerifyTableRow {
+                       assertThat(it.getElementsByTag("td")[2].text(), equalTo("2.0ms"))
+               }
+       }
+
+       @Test
+       fun `fourth column is numeric`() {
+               verifyColumnIsNumeric(3)
+       }
+
+       @Test
+       fun `fourth column contains max value`() {
+               createAndVerifyTableRow {
+                       assertThat(it.getElementsByTag("td")[3].text(), equalTo("998.0ms"))
+               }
+       }
+
+       @Test
+       fun `fifth column is numeric`() {
+               verifyColumnIsNumeric(4)
+       }
+
+       @Test
+       fun `fifth column contains mean value`() {
+               createAndVerifyTableRow {
+                       assertThat(it.getElementsByTag("td")[4].text(), equalTo("492.7ms"))
+               }
+       }
+
+       @Test
+       fun `sixth column is numeric`() {
+               verifyColumnIsNumeric(5)
+       }
+
+       @Test
+       fun `sixth column contains median value`() {
+               createAndVerifyTableRow {
+                       assertThat(it.getElementsByTag("td")[5].text(), equalTo("483.6ms"))
+               }
+       }
+
+       @Test
+       fun `seventh column is numeric`() {
+               verifyColumnIsNumeric(6)
+       }
+
+       @Test
+       fun `seventh column contains 75th percentile`() {
+               createAndVerifyTableRow {
+                       assertThat(it.getElementsByTag("td")[6].text(), equalTo("740.9ms"))
+               }
+       }
+
+       @Test
+       fun `eighth column is numeric`() {
+               verifyColumnIsNumeric(7)
+       }
+
+       @Test
+       fun `eighth column contains 95th percentile`() {
+               createAndVerifyTableRow {
+                       assertThat(it.getElementsByTag("td")[7].text(), equalTo("940.9ms"))
+               }
+       }
+
+       @Test
+       fun `ninth column is numeric`() {
+               verifyColumnIsNumeric(8)
+       }
+
+       @Test
+       fun `ninth column contains 98th percentile`() {
+               createAndVerifyTableRow {
+                       assertThat(it.getElementsByTag("td")[8].text(), equalTo("975.6ms"))
+               }
+       }
+
+       @Test
+       fun `tenth column is numeric`() {
+               verifyColumnIsNumeric(9)
+       }
+
+       @Test
+       fun `tenth column contains 99th percentile`() {
+               createAndVerifyTableRow {
+                       assertThat(it.getElementsByTag("td")[9].text(), equalTo("991.6ms"))
+               }
+       }
+
+       @Test
+       fun `eleventh column is numeric`() {
+               verifyColumnIsNumeric(10)
+       }
+
+       @Test
+       fun `eleventh column contains 99,9th percentile`() {
+               createAndVerifyTableRow {
+                       assertThat(it.getElementsByTag("td")[10].text(), equalTo("998.0ms"))
+               }
+       }
+
+       private fun createAndVerifyTableRow(parameters: Map<String, Any?>? = null, verify: (Element) -> Unit) =
+                       metricRenderer.format(templateContext, histogram, parameters)
+                                       .let { "<table id='t'>$it</table>" }
+                                       .let(Jsoup::parseBodyFragment)
+                                       .getElementById("t").child(0).child(0)
+                                       .let(verify)
+
+       private fun verifyColumnIsNumeric(column: Int) =
+                       createAndVerifyTableRow {
+                               assertThat(it.getElementsByTag("td")[column].classNames(), hasItem("numeric"))
+                       }
+
+}
+
+private val random = Random(1)
+private val histogram = MetricRegistry().histogram("test.histogram") { Histogram(SlidingWindowReservoir(1028)) }.apply {
+       (0..2000).map { random.nextInt(1_000_000) }.forEach(this::update)
+}
index 1e83b85..32e8587 100644 (file)
@@ -10,7 +10,7 @@ import net.pterodactylus.sone.data.SoneOptions.LoadExternalContent.MANUALLY_TRUS
 import net.pterodactylus.sone.data.SoneOptions.LoadExternalContent.TRUSTED
 import net.pterodactylus.sone.freenet.wot.OwnIdentity
 import net.pterodactylus.sone.freenet.wot.Trust
-import net.pterodactylus.sone.test.mock
+import net.pterodactylus.sone.test.*
 import net.pterodactylus.sone.text.FreenetLinkPart
 import net.pterodactylus.sone.text.LinkPart
 import net.pterodactylus.sone.text.Part
@@ -21,7 +21,6 @@ import org.hamcrest.Matchers.contains
 import org.hamcrest.Matchers.emptyIterable
 import org.junit.Before
 import org.junit.Test
-import org.mockito.Mockito.`when`
 
 /**
  * Unit test for [LinkedElementsFilter].
@@ -44,14 +43,14 @@ class LinkedElementsFilterTest {
 
        @Before
        fun setupSone() {
-               `when`(sone.options).thenReturn(DefaultSoneOptions())
+               whenever(sone.options).thenReturn(DefaultSoneOptions())
        }
 
        @Before
        fun setupImageLoader() {
-               `when`(imageLoader.loadElement("KSK@link")).thenReturn(LinkedElement("KSK@link", failed = true))
-               `when`(imageLoader.loadElement("KSK@loading.png")).thenReturn(LinkedElement("KSK@loading.png", loading = true))
-               `when`(imageLoader.loadElement("KSK@link.png")).thenReturn(LinkedElement("KSK@link.png"))
+               whenever(imageLoader.loadElement("KSK@link")).thenReturn(LinkedElement("KSK@link", failed = true))
+               whenever(imageLoader.loadElement("KSK@loading.png")).thenReturn(LinkedElement("KSK@loading.png", loading = true))
+               whenever(imageLoader.loadElement("KSK@link.png")).thenReturn(LinkedElement("KSK@link.png"))
        }
 
        @Test
@@ -89,7 +88,7 @@ class LinkedElementsFilterTest {
        fun `filter finds images if the remote sone is local`() {
                sone.options.loadLinkedImages = MANUALLY_TRUSTED
                templateContext.set("currentSone", sone)
-               `when`(remoteSone.isLocal).thenReturn(true)
+               whenever(remoteSone.isLocal).thenReturn(true)
                parameters["sone"] = remoteSone
                verifyThatImagesArePresent()
        }
@@ -106,7 +105,7 @@ class LinkedElementsFilterTest {
        fun `filter does not find images if local sone requires manual trust and remote sone has only implicit trust`() {
                sone.options.loadLinkedImages = MANUALLY_TRUSTED
                templateContext.set("currentSone", sone)
-               `when`(remoteSone.identity.getTrust(this.sone.identity as OwnIdentity)).thenReturn(Trust(null, 100, null))
+               whenever(remoteSone.identity.getTrust(this.sone.identity as OwnIdentity)).thenReturn(Trust(null, 100, null))
                parameters["sone"] = remoteSone
                verifyThatImagesAreNotPresent()
        }
@@ -115,7 +114,7 @@ class LinkedElementsFilterTest {
        fun `filter does not find images if local sone requires manual trust and remote sone has explicit trust of zero`() {
                sone.options.loadLinkedImages = MANUALLY_TRUSTED
                templateContext.set("currentSone", sone)
-               `when`(remoteSone.identity.getTrust(this.sone.identity as OwnIdentity)).thenReturn(Trust(0, null, null))
+               whenever(remoteSone.identity.getTrust(this.sone.identity as OwnIdentity)).thenReturn(Trust(0, null, null))
                parameters["sone"] = remoteSone
                verifyThatImagesAreNotPresent()
        }
@@ -124,7 +123,7 @@ class LinkedElementsFilterTest {
        fun `filter finds images if local sone requires manual trust and remote sone has explicit trust of one`() {
                sone.options.loadLinkedImages = MANUALLY_TRUSTED
                templateContext.set("currentSone", sone)
-               `when`(remoteSone.identity.getTrust(this.sone.identity as OwnIdentity)).thenReturn(Trust(1, null, null))
+               whenever(remoteSone.identity.getTrust(this.sone.identity as OwnIdentity)).thenReturn(Trust(1, null, null))
                parameters["sone"] = remoteSone
                verifyThatImagesArePresent()
        }
@@ -140,7 +139,7 @@ class LinkedElementsFilterTest {
        @Test
        fun `filter finds images if local sone requires following and remote sone is followed`() {
            sone.options.loadLinkedImages = FOLLOWED
-               `when`(sone.hasFriend("remote-id")).thenReturn(true)
+               whenever(sone.hasFriend("remote-id")).thenReturn(true)
                templateContext["currentSone"] = sone
                parameters["sone"] = remoteSone
                verifyThatImagesArePresent()
@@ -158,7 +157,7 @@ class LinkedElementsFilterTest {
        fun `filter finds images if following is required and remote sone is a local sone`() {
                sone.options.loadLinkedImages = FOLLOWED
                templateContext["currentSone"] = sone
-               `when`(remoteSone.isLocal).thenReturn(true)
+               whenever(remoteSone.isLocal).thenReturn(true)
                parameters["sone"] = remoteSone
                verifyThatImagesArePresent()
        }
@@ -175,7 +174,7 @@ class LinkedElementsFilterTest {
        fun `filter does not find images if any trust is required and remote sone has implicit trust of zero`() {
            sone.options.loadLinkedImages = TRUSTED
                templateContext["currentSone"] = sone
-               `when`(remoteSone.identity.getTrust(sone.identity as OwnIdentity)).thenReturn(Trust(null, 0, null))
+               whenever(remoteSone.identity.getTrust(sone.identity as OwnIdentity)).thenReturn(Trust(null, 0, null))
                parameters["sone"] = remoteSone
                verifyThatImagesAreNotPresent()
        }
@@ -184,7 +183,7 @@ class LinkedElementsFilterTest {
        fun `filter finds images if any trust is required and remote sone has implicit trust of one`() {
            sone.options.loadLinkedImages = TRUSTED
                templateContext["currentSone"] = sone
-               `when`(remoteSone.identity.getTrust(sone.identity as OwnIdentity)).thenReturn(Trust(null, 1, null))
+               whenever(remoteSone.identity.getTrust(sone.identity as OwnIdentity)).thenReturn(Trust(null, 1, null))
                parameters["sone"] = remoteSone
                verifyThatImagesArePresent()
        }
@@ -193,7 +192,7 @@ class LinkedElementsFilterTest {
        fun `filter does not find images if any trust is required and remote sone has explicit trust of zero but implicit trust of one`() {
                sone.options.loadLinkedImages = TRUSTED
                templateContext["currentSone"] = sone
-               `when`(remoteSone.identity.getTrust(sone.identity as OwnIdentity)).thenReturn(Trust(0, 1, null))
+               whenever(remoteSone.identity.getTrust(sone.identity as OwnIdentity)).thenReturn(Trust(0, 1, null))
                parameters["sone"] = remoteSone
                verifyThatImagesAreNotPresent()
        }
@@ -202,7 +201,7 @@ class LinkedElementsFilterTest {
        fun `filter finds images if any trust is required and remote sone has explicit trust of one but no implicit trust`() {
                sone.options.loadLinkedImages = TRUSTED
                templateContext["currentSone"] = sone
-               `when`(remoteSone.identity.getTrust(sone.identity as OwnIdentity)).thenReturn(Trust(1, null, null))
+               whenever(remoteSone.identity.getTrust(sone.identity as OwnIdentity)).thenReturn(Trust(1, null, null))
                parameters["sone"] = remoteSone
                verifyThatImagesArePresent()
        }
@@ -211,7 +210,7 @@ class LinkedElementsFilterTest {
        fun `filter finds images if any trust is required and remote sone is a local sone`() {
                sone.options.loadLinkedImages = TRUSTED
                templateContext["currentSone"] = sone
-               `when`(remoteSone.isLocal).thenReturn(true)
+               whenever(remoteSone.isLocal).thenReturn(true)
                parameters["sone"] = remoteSone
                verifyThatImagesArePresent()
        }
@@ -238,9 +237,9 @@ class LinkedElementsFilterTest {
 
        private fun createSone(id: String = "sone-id"): Sone {
                val sone = mock<Sone>()
-               `when`(sone.id).thenReturn(id)
-               `when`(sone.options).thenReturn(DefaultSoneOptions())
-               `when`(sone.identity).thenReturn(mock<OwnIdentity>())
+               whenever(sone.id).thenReturn(id)
+               whenever(sone.options).thenReturn(DefaultSoneOptions())
+               whenever(sone.identity).thenReturn(mock<OwnIdentity>())
                return sone
        }
 
index 0e44568..5864a6a 100644 (file)
@@ -3,20 +3,17 @@ package net.pterodactylus.sone.template
 import com.google.inject.Guice
 import net.pterodactylus.sone.core.Core
 import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.test.getInstance
-import net.pterodactylus.sone.test.isProvidedByMock
-import net.pterodactylus.sone.test.mock
+import net.pterodactylus.sone.test.*
 import net.pterodactylus.sone.text.SoneTextParser
 import net.pterodactylus.sone.text.SoneTextParserContext
 import net.pterodactylus.util.template.TemplateContext
 import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.`is`
+import org.hamcrest.Matchers.equalTo
 import org.hamcrest.Matchers.emptyIterable
 import org.hamcrest.Matchers.notNullValue
 import org.hamcrest.Matchers.sameInstance
 import org.junit.Test
 import org.mockito.ArgumentCaptor.forClass
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.eq
 import org.mockito.Mockito.verify
 
@@ -38,8 +35,8 @@ class ParserFilterTest {
 
        private fun setupSone(identity: String): Sone {
                val sone = mock<Sone>()
-               `when`(sone.id).thenReturn(identity)
-               `when`(core.getSone(identity)).thenReturn(sone)
+               whenever(sone.id).thenReturn(identity)
+               whenever(core.getSone(identity)).thenReturn(sone)
                return sone
        }
 
@@ -63,7 +60,7 @@ class ParserFilterTest {
                filter.format(templateContext, "text", parameters)
                val context = forClass(SoneTextParserContext::class.java)
                verify(soneTextParser).parse(eq<String>("text") ?: "", context.capture())
-               assertThat(context.value.postingSone, `is`(sone))
+               assertThat(context.value.postingSone, equalTo(sone))
        }
 
        @Test
index a2c4ad7..4513ce0 100644 (file)
@@ -17,7 +17,7 @@ import net.pterodactylus.sone.test.mock
 import net.pterodactylus.sone.test.whenever
 import net.pterodactylus.util.template.TemplateContext
 import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.`is`
+import org.hamcrest.Matchers.equalTo
 import org.hamcrest.Matchers.nullValue
 import org.junit.Before
 import org.junit.Test
@@ -86,7 +86,7 @@ class ProfileAccessorTest {
        @Test
        fun `avatar ID is returned if profile belongs to local sone`() {
                whenever(remoteSone.isLocal).thenReturn(true)
-               assertThat(accessor.get(templateContext, profile, "avatar"), `is`<Any>("avatar-id"))
+               assertThat(accessor.get(templateContext, profile, "avatar"), equalTo<Any>("avatar-id"))
        }
 
        @Test
@@ -98,14 +98,14 @@ class ProfileAccessorTest {
        @Test
        fun `avatar ID is returned if sone is configure to always show avatars`() {
                currentSone.options.showCustomAvatars = ALWAYS
-               assertThat(accessor.get(templateContext, profile, "avatar"), `is`<Any>("avatar-id"))
+               assertThat(accessor.get(templateContext, profile, "avatar"), equalTo<Any>("avatar-id"))
        }
 
        @Test
        fun `avatar ID is returned if sone is configure to show avatars of followed sones and remote sone is followed`() {
                currentSone.options.showCustomAvatars = FOLLOWED
                whenever(currentSone.hasFriend("remote-sone")).thenReturn(true)
-               assertThat(accessor.get(templateContext, profile, "avatar"), `is`<Any>("avatar-id"))
+               assertThat(accessor.get(templateContext, profile, "avatar"), equalTo<Any>("avatar-id"))
        }
 
        @Test
@@ -142,7 +142,7 @@ class ProfileAccessorTest {
        fun `avatar ID is returned if sone is configure to show avatars based on manual trust and explicit trust is one`() {
                currentSone.options.showCustomAvatars = MANUALLY_TRUSTED
                setTrust(Trust(1, null, null))
-               assertThat(accessor.get(templateContext, profile, "avatar"), `is`<Any>("avatar-id"))
+               assertThat(accessor.get(templateContext, profile, "avatar"), equalTo<Any>("avatar-id"))
        }
 
        @Test
@@ -156,7 +156,7 @@ class ProfileAccessorTest {
        fun `avatar ID is returned if sone is configure to show avatars based on trust and explicit trust is one`() {
                currentSone.options.showCustomAvatars = TRUSTED
                setTrust(Trust(1, null, null))
-               assertThat(accessor.get(templateContext, profile, "avatar"), `is`<Any>("avatar-id"))
+               assertThat(accessor.get(templateContext, profile, "avatar"), equalTo<Any>("avatar-id"))
        }
 
        @Test
@@ -177,12 +177,12 @@ class ProfileAccessorTest {
        fun `avatar ID is returned if sone is configure to show avatars based on trust and implicit trust is one`() {
                currentSone.options.showCustomAvatars = TRUSTED
                setTrust(Trust(0, 1, null))
-               assertThat(accessor.get(templateContext, profile, "avatar"), `is`<Any>("avatar-id"))
+               assertThat(accessor.get(templateContext, profile, "avatar"), equalTo<Any>("avatar-id"))
        }
 
        @Test
        fun `accessing other members uses reflection accessor`() {
-               assertThat(accessor.get(templateContext, profile, "hashCode"), `is`<Any>(profile.hashCode()))
+               assertThat(accessor.get(templateContext, profile, "hashCode"), equalTo<Any>(profile.hashCode()))
        }
 
 }
index c6990a7..efed7a3 100644 (file)
@@ -2,7 +2,7 @@ package net.pterodactylus.sone.template
 
 import net.pterodactylus.sone.data.Profile
 import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.test.mock
+import net.pterodactylus.sone.test.*
 import net.pterodactylus.sone.text.FreenetLinkPart
 import net.pterodactylus.sone.text.Part
 import net.pterodactylus.sone.text.PlainTextPart
@@ -10,7 +10,6 @@ import net.pterodactylus.sone.text.SonePart
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.contains
 import org.junit.Test
-import org.mockito.Mockito.`when`
 
 /**
  * Unit test for [ShortenFilter].
@@ -76,7 +75,7 @@ class ShortenFilterTest {
        @Test
        fun `sone parts are added but their length is ignored`() {
                val sone = mock<Sone>()
-               `when`(sone.profile).thenReturn(Profile(sone))
+               whenever(sone.profile).thenReturn(Profile(sone))
                assertThat(shortenParts(15, 10, SonePart(sone), PlainTextPart("This is a long text.")), contains<Part>(
                                SonePart(sone),
                                PlainTextPart("This is a …")
@@ -86,7 +85,7 @@ class ShortenFilterTest {
        @Test
        fun `additional sone parts are ignored`() {
                val sone = mock<Sone>()
-               `when`(sone.profile).thenReturn(Profile(sone))
+               whenever(sone.profile).thenReturn(Profile(sone))
                assertThat(shortenParts(15, 10, PlainTextPart("This is a long text."), SonePart(sone)), contains<Part>(
                                PlainTextPart("This is a …")
                ))
index f18033e..b1c2cc5 100644 (file)
@@ -1,30 +1,32 @@
 package net.pterodactylus.sone.template
 
-import freenet.l10n.BaseL10n
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
+import net.pterodactylus.sone.freenet.*
+import net.pterodactylus.sone.test.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import java.util.*
 
 /**
  * Unit test for [UnknownDateFilter].
  */
 class UnknownDateFilterTest {
 
-       private val baseL10n = mock<BaseL10n>()
+       private val translation = object : Translation {
+               override val currentLocale = Locale.ENGLISH
+               override fun translate(key: String) = if (key == unknownKey) "translated" else ""
+       }
        private val unknownKey = "unknown.key"
-       private val filter = UnknownDateFilter(baseL10n, unknownKey)
+       private val filter = UnknownDateFilter(translation, unknownKey)
 
        @Test
        fun `filter returns given object for non-longs`() {
-           val someObject = Any()
+               val someObject = Any()
                assertThat(filter.format(null, someObject, null), equalTo<Any>(someObject))
        }
 
        @Test
        fun `filter returns translated value of unknown key if zero is given`() {
-           whenever(baseL10n.getString(unknownKey)).thenReturn("translated")
                assertThat(filter.format(null, 0L, null), equalTo<Any>("translated"))
        }
 
index c084d35..7fc9428 100644 (file)
@@ -1,7 +1,11 @@
 package net.pterodactylus.sone.test
 
+import freenet.support.*
+import net.pterodactylus.sone.freenet.wot.*
+import net.pterodactylus.sone.utils.*
 import net.pterodactylus.util.web.*
 import org.hamcrest.*
+import org.hamcrest.Matchers.*
 
 fun hasHeader(name: String, value: String) = object : TypeSafeDiagnosingMatcher<Header>() {
        override fun matchesSafely(item: Header, mismatchDescription: Description) =
@@ -19,3 +23,123 @@ fun <T : Any> compare(value: T, comparison: (T) -> Boolean, onError: (T) -> Unit
                false.takeUnless { comparison(value) }
                                ?.also { onError(value) }
 
+fun <K, V> isEmptyMap() = object : TypeSafeDiagnosingMatcher<Map<K, V>>() {
+       override fun describeTo(description: Description) {
+               description.appendText("empty map")
+       }
+
+       override fun matchesSafely(item: Map<K, V>, mismatchDescription: Description) =
+                       item.isEmpty().onFalse {
+                               mismatchDescription.appendText("was ").appendValue(item)
+                       }
+}
+
+fun isTrust(trust: Int?, score: Int?, rank: Int?) =
+               AttributeMatcher<Trust>("trust")
+                               .addAttribute("trust", trust, Trust::explicit)
+                               .addAttribute("score", score, Trust::implicit)
+                               .addAttribute("rank", rank, Trust::distance)
+
+fun isTrusted(ownIdentity: OwnIdentity, trust: Matcher<Trust>) = object : TypeSafeDiagnosingMatcher<Identity>() {
+       override fun matchesSafely(item: Identity, mismatchDescription: Description) =
+                       item.getTrust(ownIdentity)?.let { foundTrust ->
+                               trust.matches(foundTrust).onFalse {
+                                       trust.describeMismatch(foundTrust, mismatchDescription)
+                               }
+                       } ?: {
+                               mismatchDescription.appendText("not trusted")
+                               false
+                       }()
+
+       override fun describeTo(description: Description) {
+               description
+                               .appendText("trusted by ").appendValue(ownIdentity)
+                               .appendText(" with ").appendValue(trust)
+       }
+}
+
+fun isIdentity(id: String, nickname: String?, requestUri: String, contexts: Matcher<out Iterable<String>>, properties: Matcher<out Map<out String, String>>) =
+               AttributeMatcher<Identity>("identity")
+                               .addAttribute("id", id, Identity::getId)
+                               .addAttribute("nickname", nickname, Identity::getNickname)
+                               .addAttribute("requestUri", requestUri, Identity::getRequestUri)
+                               .addAttribute("contexts", Identity::getContexts, contexts)
+                               .addAttribute("properties", Identity::getProperties, properties)
+
+fun isOwnIdentity(id: String, nickname: String, requestUri: String, insertUri: String, contexts: Matcher<Iterable<String>>, properties: Matcher<Map<out String, String>>) =
+               AttributeMatcher<OwnIdentity>("own identity")
+                               .addAttribute("id", id, OwnIdentity::getId)
+                               .addAttribute("nickname", nickname, OwnIdentity::getNickname)
+                               .addAttribute("request uri", requestUri, OwnIdentity::getRequestUri)
+                               .addAttribute("insert uri", insertUri, OwnIdentity::getInsertUri)
+                               .addAttribute("contexts", OwnIdentity::getContexts, contexts)
+                               .addAttribute("properties", OwnIdentity::getProperties, properties)
+
+fun hasField(name: String, valueMatcher: Matcher<String>) = object : TypeSafeDiagnosingMatcher<SimpleFieldSet>() {
+       override fun matchesSafely(item: SimpleFieldSet, mismatchDescription: Description) =
+                       valueMatcher.matches(item.get(name)).onFalse {
+                               valueMatcher.describeMismatch(item, mismatchDescription)
+                       }
+
+       override fun describeTo(description: Description) {
+               description
+                               .appendText("simple field set with key ").appendValue(name)
+                               .appendText(", value ").appendValue(valueMatcher)
+       }
+}
+
+/**
+ * [TypeSafeDiagnosingMatcher] implementation that aims to cut down boilerplate on verifying the attributes
+ * of typical container objects.
+ */
+class AttributeMatcher<T>(private val objectName: String) : TypeSafeDiagnosingMatcher<T>() {
+
+       private data class AttributeToMatch<T, V>(
+                       val name: String,
+                       val getter: (T) -> V,
+                       val matcher: Matcher<out V>
+       )
+
+       private val attributesToMatch = mutableListOf<AttributeToMatch<T, *>>()
+
+       /**
+        * Adds an attribute to check for equality, returning `this`.
+        */
+       fun <V> addAttribute(name: String, expected: V, getter: (T) -> V): AttributeMatcher<T> = apply {
+               attributesToMatch.add(AttributeToMatch(name, getter, describedAs("$name %0", equalTo(expected), expected)))
+       }
+
+       /**
+        * Adds an attribute to check with the given [hamcrest matcher][Matcher].
+        */
+       fun <V> addAttribute(name: String, getter: (T) -> V, matcher: Matcher<out V>) = apply {
+               attributesToMatch.add(AttributeToMatch(name, getter, matcher))
+       }
+
+       override fun describeTo(description: Description) {
+               attributesToMatch.forEachIndexed { index, attributeToMatch ->
+                       if (index == 0) {
+                               description.appendText("$objectName with ")
+                       } else {
+                               description.appendText(", ")
+                       }
+                       attributeToMatch.matcher.describeTo(description)
+               }
+       }
+
+       override fun matchesSafely(item: T, mismatchDescription: Description): Boolean =
+                       attributesToMatch.fold(true) { matches, attributeToMatch ->
+                               if (!matches) {
+                                       false
+                               } else {
+                                       if (!attributeToMatch.matcher.matches(attributeToMatch.getter(item))) {
+                                               mismatchDescription.appendText("but ${attributeToMatch.name} ")
+                                               attributeToMatch.matcher.describeMismatch(attributeToMatch.getter(item), mismatchDescription)
+                                               false
+                                       } else {
+                                               true
+                                       }
+                               }
+                       }
+
+}
index 85b86d5..4d345b3 100644 (file)
@@ -3,7 +3,7 @@ package net.pterodactylus.sone.test
 import com.google.inject.Module
 import org.mockito.*
 import org.mockito.invocation.InvocationOnMock
-import org.mockito.stubbing.OngoingStubbing
+import org.mockito.stubbing.*
 
 inline fun <reified T : Any> mock(): T = Mockito.mock<T>(T::class.java)!!
 inline fun <reified T : Any> mockBuilder(): T = Mockito.mock<T>(T::class.java, Mockito.RETURNS_SELF)!!
@@ -20,6 +20,7 @@ inline fun <reified T : Any> bindMock(): Module =
                Module { it!!.bind(T::class.java).toInstance(mock()) }
 
 inline fun <reified T: Any?> whenever(methodCall: T) = Mockito.`when`(methodCall)!!
+inline fun <reified T: Any?> Stubber.whenever(mock: T) = `when`(mock)!!
 
 inline fun <reified T : Any> OngoingStubbing<T>.thenReturnMock(): OngoingStubbing<T> = this.thenReturn(mock())
 
index fcf6e67..aeba2c7 100644 (file)
@@ -1,7 +1,7 @@
 package net.pterodactylus.sone.text
 
 import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.`is`
+import org.hamcrest.Matchers.equalTo
 import org.junit.Test
 
 /**
@@ -11,7 +11,7 @@ class FreenetLinkPartTest {
 
        @Test
        fun linkIsUsedAsTitleIfNoTextIsGiven() {
-               assertThat(FreenetLinkPart("link", "text", true).title, `is`("link"))
+               assertThat(FreenetLinkPart("link", "text", true).title, equalTo("link"))
        }
 
 }
index 373a848..e38343c 100644 (file)
@@ -1,7 +1,7 @@
 package net.pterodactylus.sone.text
 
 import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.`is`
+import org.hamcrest.Matchers.equalTo
 import org.junit.Test
 
 /**
@@ -11,7 +11,7 @@ class LinkPartTest {
 
        @Test
        fun linkIsUsedAsTitleIfNoTitleIsGiven() {
-               assertThat(LinkPart("link", "text").title, `is`("link"))
+               assertThat(LinkPart("link", "text").title, equalTo("link"))
        }
 
 }
index 31fca48..ef1b1c0 100644 (file)
@@ -1,12 +1,10 @@
 package net.pterodactylus.sone.text
 
-import net.pterodactylus.sone.data.Profile
 import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.test.mock
+import net.pterodactylus.sone.test.*
 import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.`is`
+import org.hamcrest.Matchers.equalTo
 import org.junit.Test
-import org.mockito.Mockito.`when`
 
 /**
  * Unit test for [SonePart].
@@ -16,15 +14,15 @@ class SonePartTest {
        private val sone = mock<Sone>()
 
        init {
-               `when`(sone.profile).thenReturn(mock())
-               `when`(sone.name).thenReturn("sone")
+               whenever(sone.profile).thenReturn(mock())
+               whenever(sone.name).thenReturn("sone")
        }
 
        private val part = SonePart(sone)
 
        @Test
        fun textIsConstructedFromSonesNiceName() {
-               assertThat<String>(part.text, `is`<String>("sone"))
+               assertThat<String>(part.text, equalTo<String>("sone"))
        }
 
 }
index 56627c3..a1b6da7 100644 (file)
@@ -1,9 +1,8 @@
 package net.pterodactylus.sone.utils
 
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.hamcrest.Matchers.nullValue
-import org.junit.Test
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import kotlin.test.*
 
 /**
  * Unit test for [Booleans].
@@ -30,4 +29,24 @@ class BooleansTest {
                assertThat(true.ifFalse { true }, nullValue())
        }
 
+       @Test
+       fun `onFalse returns true on true`() {
+               assertThat(true.onFalse {}, equalTo(true))
+       }
+
+       @Test
+       fun `onFalse returns false on false`() {
+               assertThat(false.onFalse {}, equalTo(false))
+       }
+
+       @Test
+       fun `onFalse is not executed on true`() {
+               assertThat(true.onFalse { throw RuntimeException() }, equalTo(true))
+       }
+
+       @Test(expected = RuntimeException::class)
+       fun `onFalse is executed on false`() {
+               false.onFalse { throw RuntimeException() }
+       }
+
 }
index ec09b20..14427ef 100644 (file)
@@ -20,17 +20,21 @@ import net.pterodactylus.util.web.*
 import org.hamcrest.MatcherAssert.*
 import org.hamcrest.Matchers.*
 import org.junit.*
+import java.util.*
 import kotlin.test.Test
 
 class WebInterfaceModuleTest {
 
        private val webInterfaceModule = WebInterfaceModule()
-       private val l10n = mock<BaseL10n>()
        private val loaders = mock<Loaders>()
+       private val translation = object : Translation {
+               override val currentLocale = Locale.ENGLISH
+               override fun translate(key: String) = if (key == "View.Sone.Text.UnknownDate") "unknown" else key
+       }
        private val additionalModules = arrayOf(
                        Core::class.isProvidedByMock(),
                        SoneProvider::class.isProvidedByMock(),
-                       BaseL10n::class.isProvidedBy(l10n),
+                       Translation::class.isProvidedBy(translation),
                        SoneTextParser::class.isProvidedByMock(),
                        ElementLoader::class.isProvidedByMock(),
                        Loaders::class.isProvidedBy(loaders),
@@ -191,7 +195,6 @@ class WebInterfaceModuleTest {
 
        @Test
        fun `unknown date filter uses correct l10n key`() {
-               whenever(l10n.getString("View.Sone.Text.UnknownDate")).thenReturn("unknown")
                assertThat(getFilter("unknown")!!.format(null, 0L, emptyMap()), equalTo<Any>("unknown"))
        }
 
@@ -240,6 +243,11 @@ class WebInterfaceModuleTest {
                verifyFilter<PaginationFilter>("paginate")
        }
 
+       @Test
+       fun `template context histogram renderer`() {
+               verifyFilter<HistogramRenderer>("render-histogram")
+       }
+
        private inline fun <reified F : Filter> verifyFilter(name: String) {
                assertThat(getFilter(name), instanceOf(F::class.java))
        }
index a42e5ba..f902533 100644 (file)
@@ -3,7 +3,6 @@ package net.pterodactylus.sone.web.ajax
 import com.fasterxml.jackson.databind.ObjectMapper
 import com.google.common.eventbus.EventBus
 import freenet.clients.http.ToadletContext
-import freenet.l10n.BaseL10n
 import freenet.support.SimpleReadOnlyArrayBucket
 import freenet.support.api.HTTPRequest
 import net.pterodactylus.sone.core.Core
@@ -20,6 +19,7 @@ import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.data.Sone.SoneStatus
 import net.pterodactylus.sone.data.Sone.SoneStatus.idle
 import net.pterodactylus.sone.data.SoneOptions.DefaultSoneOptions
+import net.pterodactylus.sone.freenet.*
 import net.pterodactylus.sone.test.deepMock
 import net.pterodactylus.sone.test.get
 import net.pterodactylus.sone.test.mock
@@ -32,7 +32,7 @@ import net.pterodactylus.util.template.TemplateContextFactory
 import net.pterodactylus.util.web.Method.GET
 import net.pterodactylus.util.web.Method.POST
 import org.mockito.ArgumentMatchers
-import java.util.NoSuchElementException
+import java.util.*
 import javax.naming.SizeLimitExceededException
 
 /**
@@ -44,7 +44,6 @@ open class TestObjects {
 
        val webInterface = mock<WebInterface>()
        var formPassword = "form-password"
-       val l10n = mock<BaseL10n>()
        val core = mock<Core>()
        val eventBus = mock<EventBus>()
        val preferences = Preferences(eventBus)
@@ -74,6 +73,11 @@ open class TestObjects {
        val images = mutableMapOf<String, Image>()
        val translations = mutableMapOf<String, String>()
 
+       private val translation = object : Translation {
+               override val currentLocale = Locale.ENGLISH
+               override fun translate(key: String) = translations[key] ?: ""
+       }
+
        init {
                whenever(webInterface.templateContextFactory).thenReturn(TemplateContextFactory())
                whenever(webInterface.getCurrentSone(ArgumentMatchers.eq(toadletContext), ArgumentMatchers.anyBoolean())).thenReturn(currentSone)
@@ -85,9 +89,7 @@ open class TestObjects {
                whenever(webInterface.getNotification(ArgumentMatchers.anyString())).then { notifications[it[0]].asOptional() }
                whenever(webInterface.getNewPosts(currentSone)).thenAnswer { newPosts.values }
                whenever(webInterface.getNewReplies(currentSone)).thenAnswer { newReplies.values }
-               whenever(webInterface.l10n).thenReturn(l10n)
-
-               whenever(l10n.getString(ArgumentMatchers.anyString())).then { translations[it[0]] }
+               whenever(webInterface.translation).thenReturn(translation)
 
                whenever(core.preferences).thenReturn(preferences)
                whenever(core.updateChecker).thenReturn(updateChecker)
index e05ad45..3295bdd 100644 (file)
@@ -2,7 +2,6 @@ package net.pterodactylus.sone.web.page
 
 import freenet.clients.http.*
 import freenet.clients.http.SessionManager.*
-import freenet.l10n.*
 import freenet.support.api.*
 import net.pterodactylus.sone.test.*
 import net.pterodactylus.util.web.*
@@ -19,9 +18,8 @@ class FreenetRequestTest {
        private val method = Method.GET
        private val httpRequest = mock(HTTPRequest::class.java)
        private val toadletContext = mock(ToadletContext::class.java)
-       private val l10n = mock<BaseL10n>()
        private val sessionManager = mock<SessionManager>()
-       private val request = FreenetRequest(uri, method, httpRequest, toadletContext, l10n, sessionManager)
+       private val request = FreenetRequest(uri, method, httpRequest, toadletContext, sessionManager)
 
        @Test
        fun `uri is retained correctly`() {
@@ -44,11 +42,6 @@ class FreenetRequestTest {
        }
 
        @Test
-       fun `l10n is retained correctly`() {
-               assertThat(request.l10n, equalTo(l10n))
-       }
-
-       @Test
        fun `null is returned if no session exists`() {
                assertThat(request.existingSession, nullValue())
        }
index 50c6ce7..5be6ff1 100644 (file)
@@ -19,11 +19,10 @@ class SoneRequestTest {
        private val method = Method.GET
        private val httpRequest = Mockito.mock(HTTPRequest::class.java)
        private val toadletContext = Mockito.mock(ToadletContext::class.java)
-       private val l10n = mock<BaseL10n>()
        private val sessionManager = mock<SessionManager>()
        private val core = mock<Core>()
        private val webInterface = mock<WebInterface>()
-       private val soneRequest = SoneRequest(uri, method, httpRequest, toadletContext, l10n, sessionManager, core, webInterface)
+       private val soneRequest = SoneRequest(uri, method, httpRequest, toadletContext, sessionManager, core, webInterface)
 
        @Test
        fun `freenet request properties are retained correctly`() {
@@ -31,7 +30,6 @@ class SoneRequestTest {
                assertThat(soneRequest.method, equalTo(method))
                assertThat(soneRequest.httpRequest, equalTo(httpRequest))
                assertThat(soneRequest.toadletContext, equalTo(toadletContext))
-               assertThat(soneRequest.l10n, equalTo(l10n))
                assertThat(soneRequest.sessionManager, equalTo(sessionManager))
        }
 
@@ -47,13 +45,12 @@ class SoneRequestTest {
 
        @Test
        fun `freenet request is wrapped correctly`() {
-           val freenetRequest = FreenetRequest(uri, method, httpRequest, toadletContext, l10n, sessionManager)
+           val freenetRequest = FreenetRequest(uri, method, httpRequest, toadletContext, sessionManager)
                val wrappedSoneRequest = freenetRequest.toSoneRequest(core, webInterface)
                assertThat(wrappedSoneRequest.uri, equalTo(uri))
                assertThat(wrappedSoneRequest.method, equalTo(method))
                assertThat(wrappedSoneRequest.httpRequest, equalTo(httpRequest))
                assertThat(wrappedSoneRequest.toadletContext, equalTo(toadletContext))
-               assertThat(wrappedSoneRequest.l10n, equalTo(l10n))
                assertThat(wrappedSoneRequest.sessionManager, equalTo(sessionManager))
                assertThat(wrappedSoneRequest.core, sameInstance(core))
                assertThat(wrappedSoneRequest.webInterface, sameInstance(webInterface))
index 21cfdfc..5bcdd6a 100644 (file)
@@ -26,7 +26,7 @@ class DeleteSonePageTest : WebPageTest(::DeleteSonePage) {
 
        @Test
        fun `page returns correct title`() {
-               whenever(l10n.getString("Page.DeleteSone.Title")).thenReturn("delete sone page")
+               addTranslation("Page.DeleteSone.Title", "delete sone page")
                assertThat(page.getPageTitle(soneRequest), equalTo("delete sone page"))
        }
 
index e8583df..7893210 100644 (file)
@@ -28,7 +28,7 @@ class DismissNotificationPageTest : WebPageTest(::DismissNotificationPage) {
 
        @Test
        fun `page returns correct title`() {
-               whenever(l10n.getString("Page.DismissNotification.Title")).thenReturn("dismiss notification page")
+               addTranslation("Page.DismissNotification.Title", "dismiss notification page")
                assertThat(page.getPageTitle(soneRequest), equalTo("dismiss notification page"))
        }
 
index d706e05..df16a53 100644 (file)
@@ -26,7 +26,7 @@ class DistrustPageTest : WebPageTest(::DistrustPage) {
 
        @Test
        fun `page returns correct title`() {
-               whenever(l10n.getString("Page.Distrust.Title")).thenReturn("distrust page title")
+               addTranslation("Page.Distrust.Title", "distrust page title")
                assertThat(page.getPageTitle(soneRequest), equalTo("distrust page title"))
        }
 
index 4d435b4..72e814b 100644 (file)
@@ -45,7 +45,7 @@ class EditAlbumPageTest : WebPageTest(::EditAlbumPage) {
 
        @Test
        fun `page returns correct title`() {
-               whenever(l10n.getString("Page.EditAlbum.Title")).thenReturn("edit album page")
+               addTranslation("Page.EditAlbum.Title", "edit album page")
                assertThat(page.getPageTitle(soneRequest), equalTo("edit album page"))
        }
 
index 0961622..e43551c 100644 (file)
@@ -43,7 +43,7 @@ class EditImagePageTest : WebPageTest(::EditImagePage) {
 
        @Test
        fun `page returns correct title`() {
-               whenever(l10n.getString("Page.EditImage.Title")).thenReturn("edit image page title")
+               addTranslation("Page.EditImage.Title", "edit image page title")
                assertThat(page.getPageTitle(soneRequest), equalTo("edit image page title"))
        }
 
index 7731fa2..4a84644 100644 (file)
@@ -35,7 +35,7 @@ class EditProfileFieldPageTest : WebPageTest(::EditProfileFieldPage) {
 
        @Test
        fun `page returns correct title`() {
-               whenever(l10n.getString("Page.EditProfileField.Title")).thenReturn("edit profile field title")
+               addTranslation("Page.EditProfileField.Title", "edit profile field title")
                assertThat(page.getPageTitle(soneRequest), equalTo("edit profile field title"))
        }
 
index a31f04c..74c3851 100644 (file)
@@ -46,7 +46,7 @@ class EditProfilePageTest : WebPageTest(::EditProfilePage) {
 
        @Test
        fun `page returns correct title`() {
-               whenever(l10n.getString("Page.EditProfile.Title")).thenReturn("edit profile page title")
+               addTranslation("Page.EditProfile.Title", "edit profile page title")
                assertThat(page.getPageTitle(soneRequest), equalTo("edit profile page title"))
        }
 
index ba1dc42..3873434 100644 (file)
@@ -30,7 +30,7 @@ class FollowSonePageTest : WebPageTest(::FollowSonePage) {
 
        @Test
        fun `page returns correct title`() {
-               whenever(l10n.getString("Page.FollowSone.Title")).thenReturn("follow sone page title")
+               addTranslation("Page.FollowSone.Title", "follow sone page title")
                assertThat(page.getPageTitle(soneRequest), equalTo("follow sone page title"))
        }
 
index a0e9372..1533f4b 100644 (file)
@@ -26,7 +26,7 @@ class ImageBrowserPageTest : WebPageTest(::ImageBrowserPage) {
 
        @Test
        fun `page returns correct title`() {
-               whenever(l10n.getString("Page.ImageBrowser.Title")).thenReturn("image browser page title")
+               addTranslation("Page.ImageBrowser.Title", "image browser page title")
                assertThat(page.getPageTitle(soneRequest), equalTo("image browser page title"))
        }
 
index 9104127..8c86365 100644 (file)
@@ -34,7 +34,7 @@ class IndexPageTest : WebPageTest({ webInterface, loaders, templateRenderer -> I
 
        @Test
        fun `page returns correct title`() {
-               whenever(l10n.getString("Page.Index.Title")).thenReturn("index page title")
+               addTranslation("Page.Index.Title", "index page title")
                assertThat(page.getPageTitle(soneRequest), equalTo("index page title"))
        }
 
index 37e0e95..14cae74 100644 (file)
@@ -73,7 +73,7 @@ class KnownSonesPageTest : WebPageTest(::KnownSonesPage) {
 
        @Test
        fun `page returns correct title`() {
-               whenever(l10n.getString("Page.KnownSones.Title")).thenReturn("known sones page title")
+               addTranslation("Page.KnownSones.Title", "known sones page title")
                assertThat(page.getPageTitle(soneRequest), equalTo("known sones page title"))
        }
 
index d805f65..9e2c492 100644 (file)
@@ -62,37 +62,16 @@ class MetricsPageTest : WebPageTest() {
        }
 
        @Test
-       fun `metrics page lists stats about sone parsing durations`() {
-               createHistogram("sone.parsing.duration")
+       @Suppress("UNCHECKED_CAST")
+       fun `metrics page stores histograms in template context`() {
+               createHistogram("sone.random.duration2")
+               createHistogram("sone.random.duration1")
                page.handleRequest(soneRequest, templateContext)
-               verifyHistogram("soneParsingDuration")
-       }
-
-       @Test
-       fun `metrice pags lists stats about sone insert durations`() {
-               createHistogram("sone.insert.duration")
-               page.handleRequest(soneRequest, templateContext)
-               verifyHistogram("soneInsertDuration")
-       }
-
-       @Test
-       fun `metrics page delivers correct histogram size`() {
-               val histogram = metricRegistry.histogram("sone.parsing.duration")
-               (0..4000).forEach(histogram::update)
-               page.handleRequest(soneRequest, templateContext)
-               assertThat(templateContext["soneParsingDurationCount"] as Long, equalTo(4001L))
-       }
-
-       private fun verifyHistogram(name: String) {
-               assertThat(templateContext["${name}Count"] as Long, equalTo(5L))
-               assertThat(templateContext["${name}Min"] as Long, equalTo(1L))
-               assertThat(templateContext["${name}Max"] as Long, equalTo(10L))
-               assertThat(templateContext["${name}Median"] as Double, equalTo(8.0))
-               assertThat(templateContext["${name}Percentile75"] as Double, equalTo(9.0))
-               assertThat(templateContext["${name}Percentile95"] as Double, equalTo(10.0))
-               assertThat(templateContext["${name}Percentile98"] as Double, equalTo(10.0))
-               assertThat(templateContext["${name}Percentile99"] as Double, equalTo(10.0))
-               assertThat(templateContext["${name}Percentile999"] as Double, equalTo(10.0))
+               val histograms = templateContext["histograms"] as Map<String, Histogram>
+               assertThat(histograms.entries.map { it.key to it.value }, containsInAnyOrder(
+                               "sone.random.duration1" to metricRegistry.histogram("sone.random.duration1"),
+                               "sone.random.duration2" to metricRegistry.histogram("sone.random.duration2")
+               ))
        }
 
        private fun createHistogram(name: String) = metricRegistry.histogram(name).run {
index 85e1f6b..d11352f 100644 (file)
@@ -54,10 +54,33 @@ class RescuePageTest : WebPageTest(::RescuePage) {
        }
 
        @Test
-       fun `post request with fetch starts next fetch`() {
+       fun `post request with fetch and invalid edition starts next fetch`() {
                setMethod(POST)
                addHttpRequestPart("fetch", "true")
                verifyRedirect("rescue.html") {
+                       verify(soneRescuer, never()).setEdition(anyLong())
+                       verify(soneRescuer).startNextFetch()
+               }
+       }
+
+       @Test
+       fun `post request with fetch and valid edition sets edition and starts next fetch`() {
+               setMethod(POST)
+               addHttpRequestPart("fetch", "true")
+               addHttpRequestPart("edition", "123")
+               verifyRedirect("rescue.html") {
+                       verify(soneRescuer).setEdition(123L)
+                       verify(soneRescuer).startNextFetch()
+               }
+       }
+
+       @Test
+       fun `post request with negative edition will not set edition`() {
+               setMethod(POST)
+               addHttpRequestPart("fetch", "true")
+               addHttpRequestPart("edition", "-123")
+               verifyRedirect("rescue.html") {
+                       verify(soneRescuer, never()).setEdition(anyLong())
                        verify(soneRescuer).startNextFetch()
                }
        }
index 8e29df1..cffd478 100644 (file)
@@ -31,7 +31,7 @@ class SoneTemplatePageTest : WebPageTest({ webInterface, loaders, templateRender
        @Test
        fun `page title is retrieved from l10n if page title key is given`() {
                SoneTemplatePage(webInterface, loaders, templateRenderer, pageTitleKey = "page.title", requiresLogin = false).let { page ->
-                       whenever(l10n.getString("page.title")).thenReturn("Page Title")
+                       addTranslation("page.title", "Page Title")
                        assertThat(page.getPageTitle(soneRequest), equalTo("Page Title"))
                }
        }
index 61d1a03..07ae727 100644 (file)
@@ -6,6 +6,7 @@ import freenet.support.*
 import freenet.support.api.*
 import net.pterodactylus.sone.core.*
 import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.freenet.*
 import net.pterodactylus.sone.freenet.wot.*
 import net.pterodactylus.sone.main.*
 import net.pterodactylus.sone.test.deepMock
@@ -26,6 +27,7 @@ import org.mockito.ArgumentMatchers.eq
 import java.io.*
 import java.net.*
 import java.nio.charset.*
+import java.util.*
 import kotlin.text.Charsets.UTF_8
 
 /**
@@ -40,7 +42,6 @@ open class WebPageTest(pageSupplier: (WebInterface, Loaders, TemplateRenderer) -
        val core = webInterface.core
        val eventBus = mock<EventBus>()
        val preferences = Preferences(eventBus)
-       val l10n = webInterface.l10n!!
        val sessionManager = mock<SessionManager>()
 
        open val page by lazy { pageSupplier(webInterface, loaders, templateRenderer) }
@@ -49,7 +50,6 @@ open class WebPageTest(pageSupplier: (WebInterface, Loaders, TemplateRenderer) -
        val freenetRequest = mock<FreenetRequest>()
 
        init {
-               whenever(freenetRequest.l10n).thenReturn(l10n)
                whenever(freenetRequest.sessionManager).thenReturn(sessionManager)
                whenever(freenetRequest.uri).thenReturn(mock())
        }
@@ -77,12 +77,16 @@ open class WebPageTest(pageSupplier: (WebInterface, Loaders, TemplateRenderer) -
        private val notifications = mutableMapOf<String, Notification>()
        private val translations = mutableMapOf<String, String>()
 
+       private val translation = object : Translation {
+               override val currentLocale = Locale.ENGLISH
+               override fun translate(key: String) = translations[key] ?: key
+       }
+
        init {
                setupCore()
                setupWebInterface()
                setupHttpRequest()
                setupFreenetRequest()
-               setupTranslations()
        }
 
        private fun setupCore() {
@@ -108,6 +112,7 @@ open class WebPageTest(pageSupplier: (WebInterface, Loaders, TemplateRenderer) -
                whenever(webInterface.getCurrentSoneWithoutCreatingSession(eq(toadletContext))).thenReturn(currentSone)
                whenever(webInterface.getNotifications(currentSone)).then { notifications.values }
                whenever(webInterface.getNotification(anyString())).then { notifications[it[0]].asOptional() }
+               whenever(webInterface.translation).thenReturn(translation)
        }
 
        private fun setupHttpRequest() {
@@ -147,10 +152,6 @@ open class WebPageTest(pageSupplier: (WebInterface, Loaders, TemplateRenderer) -
                whenever(freenetRequest.toadletContext).thenReturn(toadletContext)
        }
 
-       private fun setupTranslations() {
-               whenever(l10n.getString(anyString())).then { translations[it[0]] ?: it[0] }
-       }
-
        fun setMethod(method: Method) {
                whenever(httpRequest.method).thenReturn(method.name)
                whenever(freenetRequest.method).thenReturn(method)