compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8'
compile group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: '1.3.0-RC'
- compile group: 'net.pterodactylus', name: 'utils', version: '0.12.4'
+ compile group: 'net.pterodactylus', name: 'utils', version: '0.13.1'
compile group: 'com.google.inject', name: 'guice', version: '4.2.2'
- compile group: 'com.google.guava', name: 'guava', version: '27.0.1-android'
+ compile group: 'com.google.guava', name: 'guava', version: '27.0.1-jre'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.1'
compile group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.9.1'
compile group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2'
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.data.AlbumsKt.getAllImages;
import java.util.ArrayList;
import java.util.Collection;
loadSone(sone);
database.storeSone(sone);
sone.setStatus(SoneStatus.idle);
- if (sone.getPosts().isEmpty() && sone.getReplies().isEmpty()) {
+ if (sone.getPosts().isEmpty() && sone.getReplies().isEmpty() && getAllImages(sone.getRootAlbum()).isEmpty()) {
// dirty hack
lockSone(sone);
+ eventBus.post(new SoneLockedOnStartup(sone));
}
soneInserter.start();
return sone;
+++ /dev/null
-package net.pterodactylus.sone.core;
-
-import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired;
-import net.pterodactylus.util.config.Configuration;
-import net.pterodactylus.util.config.ConfigurationException;
-
-/**
- * Loads preferences stored in a {@link Configuration} into a {@link
- * Preferences} object.
- */
-public class PreferencesLoader {
-
- private final Preferences preferences;
-
- public PreferencesLoader(Preferences preferences) {
- this.preferences = preferences;
- }
-
- public void loadFrom(Configuration configuration) {
- loadInsertionDelay(configuration);
- loadPostsPerPage(configuration);
- loadImagesPerPage(configuration);
- loadCharactersPerPost(configuration);
- loadPostCutOffLength(configuration);
- loadRequireFullAccess(configuration);
- loadFcpInterfaceActive(configuration);
- loadFcpFullAccessRequired(configuration);
- }
-
- private void loadInsertionDelay(Configuration configuration) {
- preferences.setNewInsertionDelay(configuration.getIntValue(
- "Option/InsertionDelay").getValue(null));
- }
-
- private void loadPostsPerPage(Configuration configuration) {
- preferences.setNewPostsPerPage(
- configuration.getIntValue("Option/PostsPerPage")
- .getValue(null));
- }
-
- private void loadImagesPerPage(Configuration configuration) {
- preferences.setNewImagesPerPage(
- configuration.getIntValue("Option/ImagesPerPage")
- .getValue(null));
- }
-
- private void loadCharactersPerPost(Configuration configuration) {
- preferences.setNewCharactersPerPost(
- configuration.getIntValue("Option/CharactersPerPost")
- .getValue(null));
- }
-
- private void loadPostCutOffLength(Configuration configuration) {
- try {
- preferences.setNewPostCutOffLength(
- configuration.getIntValue("Option/PostCutOffLength")
- .getValue(null));
- } catch (IllegalArgumentException iae1) {
- /* previous versions allowed -1, ignore and use default. */
- }
- }
-
- private void loadRequireFullAccess(Configuration configuration) {
- preferences.setNewRequireFullAccess(
- configuration.getBooleanValue("Option/RequireFullAccess")
- .getValue(null));
- }
-
- private void loadFcpInterfaceActive(Configuration configuration) {
- preferences.setNewFcpInterfaceActive(configuration.getBooleanValue(
- "Option/ActivateFcpInterface").getValue(null));
- }
-
- private void loadFcpFullAccessRequired(Configuration configuration) {
- Integer fullAccessRequiredInteger = configuration
- .getIntValue("Option/FcpFullAccessRequired").getValue(null);
- preferences.setNewFcpFullAccessRequired(
- (fullAccessRequiredInteger == null) ? null :
- FullAccessRequired.values()[fullAccessRequiredInteger]);
- }
-
-}
--- /dev/null
+package net.pterodactylus.sone.core
+
+import net.pterodactylus.sone.fcp.FcpInterface.*
+import net.pterodactylus.util.config.*
+
+/**
+ * Loads preferences stored in a [Configuration] into a [Preferences] object.
+ */
+class PreferencesLoader(private val preferences: Preferences) {
+
+ fun loadFrom(configuration: Configuration) {
+ loadInsertionDelay(configuration)
+ loadPostsPerPage(configuration)
+ loadImagesPerPage(configuration)
+ loadCharactersPerPost(configuration)
+ loadPostCutOffLength(configuration)
+ loadRequireFullAccess(configuration)
+ loadFcpInterfaceActive(configuration)
+ loadFcpFullAccessRequired(configuration)
+ }
+
+ private fun loadInsertionDelay(configuration: Configuration) {
+ preferences.newInsertionDelay = configuration.getIntValue("Option/InsertionDelay").getValue(null)
+ }
+
+ private fun loadPostsPerPage(configuration: Configuration) {
+ preferences.newPostsPerPage = configuration.getIntValue("Option/PostsPerPage").getValue(null)
+ }
+
+ private fun loadImagesPerPage(configuration: Configuration) {
+ preferences.newImagesPerPage = configuration.getIntValue("Option/ImagesPerPage").getValue(null)
+ }
+
+ private fun loadCharactersPerPost(configuration: Configuration) {
+ preferences.newCharactersPerPost = configuration.getIntValue("Option/CharactersPerPost").getValue(null)
+ }
+
+ private fun loadPostCutOffLength(configuration: Configuration) {
+ try {
+ preferences.newPostCutOffLength = configuration.getIntValue("Option/PostCutOffLength").getValue(null)
+ } catch (iae1: IllegalArgumentException) { /* previous versions allowed -1, ignore and use default. */
+ }
+ }
+
+ private fun loadRequireFullAccess(configuration: Configuration) {
+ preferences.newRequireFullAccess = configuration.getBooleanValue("Option/RequireFullAccess").getValue(null)
+ }
+
+ private fun loadFcpInterfaceActive(configuration: Configuration) {
+ preferences.newFcpInterfaceActive = configuration.getBooleanValue("Option/ActivateFcpInterface").getValue(null)
+ }
+
+ private fun loadFcpFullAccessRequired(configuration: Configuration) {
+ val fullAccessRequiredInteger = configuration.getIntValue("Option/FcpFullAccessRequired").getValue(null)
+ preferences.newFcpFullAccessRequired = fullAccessRequiredInteger?.let { FullAccessRequired.values()[it] }
+ }
+
+}
package net.pterodactylus.sone.main;
import java.io.File;
+import javax.annotation.Nonnull;
import net.pterodactylus.sone.template.FilesystemTemplate;
import net.pterodactylus.sone.web.pages.ReloadingPage;
this.filesystemPath = filesystemPath;
}
+ @Nonnull
@Override
- public Template loadTemplate(String path) {
+ public Template loadTemplate(@Nonnull String path) {
return new FilesystemTemplate(new File(filesystemPath, path).getAbsolutePath());
}
+ @Nonnull
@Override
- public <REQ extends Request> Page<REQ> loadStaticPage(String basePath, String prefix, String mimeType) {
+ public <REQ extends Request> Page<REQ> loadStaticPage(@Nonnull String basePath, @Nonnull String prefix, @Nonnull String mimeType) {
return new ReloadingPage<>(basePath, new File(filesystemPath, prefix).getAbsolutePath(), mimeType);
}
+ @Nonnull
@Override
public TemplateProvider getTemplateProvider() {
return new FilesystemTemplateProvider(new File(filesystemPath, "/templates/").getAbsolutePath());
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
+import javax.annotation.Nonnull;
import net.pterodactylus.sone.web.WebInterface;
import net.pterodactylus.util.io.Closer;
*/
public class DefaultLoaders implements Loaders {
+ @Nonnull
@Override
- public Template loadTemplate(String path) {
+ public Template loadTemplate(@Nonnull String path) {
InputStream templateInputStream = null;
Reader reader = null;
try {
}
}
+ @Nonnull
@Override
- public <REQ extends Request> Page<REQ> loadStaticPage(String pathPrefix, String basePath, String mimeType) {
+ public <REQ extends Request> Page<REQ> loadStaticPage(@Nonnull String pathPrefix, @Nonnull String basePath, @Nonnull String mimeType) {
return new StaticPage<REQ>(pathPrefix, basePath, mimeType) {
};
}
+ @Nonnull
@Override
public TemplateProvider getTemplateProvider() {
return new ClassPathTemplateProvider(WebInterface.class, "/templates/");
package net.pterodactylus.sone.main;
+import javax.annotation.Nonnull;
+
import net.pterodactylus.util.template.Template;
import net.pterodactylus.util.template.TemplateProvider;
import net.pterodactylus.util.web.Page;
@ImplementedBy(DefaultLoaders.class)
public interface Loaders {
- Template loadTemplate(String path);
- <REQ extends Request> Page<REQ> loadStaticPage(String basePath, String prefix, String mimeType);
- TemplateProvider getTemplateProvider();
+ @Nonnull Template loadTemplate(@Nonnull String path);
+ @Nonnull <REQ extends Request> Page<REQ> loadStaticPage(@Nonnull String basePath, @Nonnull String prefix, @Nonnull String mimeType);
+ @Nonnull TemplateProvider getTemplateProvider();
}
import net.pterodactylus.sone.fcp.*;
import net.pterodactylus.sone.freenet.wot.*;
import net.pterodactylus.sone.web.*;
+import net.pterodactylus.sone.web.notification.NotificationHandler;
import freenet.l10n.BaseL10n.*;
import freenet.l10n.*;
/* create the web interface. */
webInterface = injector.getInstance(WebInterface.class);
+ NotificationHandler notificationHandler = injector.getInstance(NotificationHandler.class);
/* start core! */
core.start();
+
+ /* start the web interface! */
webInterface.start();
webInterface.setFirstStart(injector.getInstance(Key.get(Boolean.class, Names.named("FirstStart"))));
webInterface.setNewConfig(injector.getInstance(Key.get(Boolean.class, Names.named("NewConfig"))));
+ notificationHandler.start();
}
@VisibleForTesting
private final Loaders loaders;
/** The notification manager. */
- private final NotificationManager notificationManager = new NotificationManager();
+ private final NotificationManager notificationManager;
/** The Sone plugin. */
private final SonePlugin sonePlugin;
ParserFilter parserFilter, ShortenFilter shortenFilter,
RenderFilter renderFilter,
LinkedElementRenderFilter linkedElementRenderFilter,
- PageToadletRegistry pageToadletRegistry, MetricRegistry metricRegistry, Translation translation, L10nFilter l10nFilter) {
+ PageToadletRegistry pageToadletRegistry, MetricRegistry metricRegistry, Translation translation, L10nFilter l10nFilter,
+ NotificationManager notificationManager) {
this.sonePlugin = sonePlugin;
this.loaders = loaders;
this.listNotificationFilter = listNotificationFilter;
this.metricRegistry = metricRegistry;
this.l10nFilter = l10nFilter;
this.translation = translation;
+ this.notificationManager = notificationManager;
formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
soneTextParser = new SoneTextParser(getCore(), getCore());
--- /dev/null
+/**
+ * Sone - SoneLockedOnStartup.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.core.event
+
+import net.pterodactylus.sone.data.*
+
+/**
+ * Signals that a Sone was locked on startup because it’s empty.
+ */
+class SoneLockedOnStartup(sone: Sone) : SoneEvent(sone)
--- /dev/null
+/**
+ * Sone - Albums.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.data
+
+/** Returns all images contained in this album and all its albums. */
+val Album.allImages: Collection<Image>
+ get() =
+ images + albums.flatMap { it.allImages }
--- /dev/null
+/**
+ * Sone - Renderables.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.utils
+
+import net.pterodactylus.util.io.*
+import java.io.*
+
+/**
+ * Renders the [Renderable] into a [String].
+ */
+fun Renderable.render() =
+ StringWriter().use { it.also(::render) }.toString()
package net.pterodactylus.sone.web
+import com.google.common.eventbus.*
import com.google.inject.*
import freenet.support.api.*
import net.pterodactylus.sone.core.*
import net.pterodactylus.sone.main.*
import net.pterodactylus.sone.template.*
import net.pterodactylus.sone.text.*
+import net.pterodactylus.sone.web.notification.*
+import net.pterodactylus.util.notify.*
import net.pterodactylus.util.template.*
import javax.inject.*
import javax.inject.Singleton
@Named("toadletPathPrefix")
fun getPathPrefix(): String = "/Sone/"
+ @Provides
+ @Singleton
+ fun getNotificationManager() =
+ NotificationManager()
+
+ @Provides
+ @Singleton
+ fun getNotificationHandler(eventBus: EventBus, loaders: Loaders, notificationManager: NotificationManager) =
+ NotificationHandler(eventBus, loaders, notificationManager)
+
}
import net.pterodactylus.sone.data.Sone
import net.pterodactylus.sone.data.SoneOptions
import net.pterodactylus.sone.main.SonePlugin
-import net.pterodactylus.sone.utils.jsonArray
-import net.pterodactylus.sone.utils.jsonObject
+import net.pterodactylus.sone.utils.*
import net.pterodactylus.sone.web.WebInterface
import net.pterodactylus.sone.web.page.*
import net.pterodactylus.util.notify.Notification
"ShowNotification/NewReplies" to options.isShowNewReplyNotifications
)
} ?: jsonObject {}
-
-private fun Notification.render() = StringWriter().use { it.also { render(it) } }.toString()
--- /dev/null
+/**
+ * Sone - NotificationHandler.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.web.notification
+
+import com.google.common.eventbus.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.util.notify.*
+import javax.inject.*
+
+/**
+ * Handler for notifications that can create notifications and register them with an event bus.
+ */
+@Suppress("UnstableApiUsage")
+class NotificationHandler @Inject constructor(private val eventBus: EventBus, private val loaders: Loaders, private val notificationManager: NotificationManager) {
+
+ fun start() {
+ SoneLockedOnStartupHandler(notificationManager, loaders.loadTemplate("/templates/notify/soneLockedOnStartupNotification.html"))
+ .also(eventBus::register)
+ }
+
+}
--- /dev/null
+/**
+ * Sone - SoneLockedOnStartupNotification.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.web.notification
+
+import com.google.common.eventbus.*
+import net.pterodactylus.sone.core.event.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.notify.*
+import net.pterodactylus.util.notify.*
+import net.pterodactylus.util.template.*
+
+/**
+ * Handler for [SoneLockedOnStartup][net.pterodactylus.sone.core.event.SoneLockedOnStartup] events
+ * that adds the appropriate notification to the [NotificationManager].
+ */
+class SoneLockedOnStartupHandler(private val notificationManager: NotificationManager, template: Template) {
+
+ private val notification = ListNotification<Sone>("sone-locked-on-startup", "sones", template)
+
+ @Subscribe
+ @Suppress("UnstableApiUsage")
+ fun soneLockedOnStartup(soneLockedOnStartup: SoneLockedOnStartup) {
+ notification.add(soneLockedOnStartup.sone)
+ notificationManager.addNotification(notification)
+ }
+
+}
Notification.SoneIsInserting.Text=Ihre Sone sone://{0} wird jetzt hoch geladen.
Notification.SoneIsInserted.Text=Ihre Sone sone://{0} wurde in {1,number} {1,choice,0#Sekunden|1#Sekunde|1<Sekunden} hoch geladen.
Notification.SoneInsertAborted.Text=Ihre Sone sone://{0} konnte nicht hoch geladen werden.
+Notification.SoneLockedOnStartup.Text=Einige Sones wurden beim Starten gesperrt, weil sie keine Inhalte enthielten. Versionen vor v81 hatten einen Fehler, der Sones ohne Inhalte zur Folge hatte. Um zu verhindern, dass solchermaßen defekte Sones hoch geladen werden, wurden sie automatisch gesperrt. Bitte überprüfen Sie Ihre Sones, benutzen Sie den Sonerettungsmodus und entsperren Sie die Sones manuell, wenn Sie mit den Inhalten Ihrer Sones zufrieden sind. Die gesperrten Sones sind:
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.
+Notification.SoneLockedOnStartup.Text=Some Sones were locked on startup because they don’t contain anything. Versions prior to v81 had a bug that resulted in empty Sones. To prevent buggy Sones from being inserted they have been automatically locked. Please check your Sones, use the Rescue Mode if necessary, and unlock your Sones once you are satisfied with the results. Locked Sones are:
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, 324–328, 360
+Notification.SoneLockedOnStartup.Text=Some Sones were locked on startup because they don’t contain anything. Versions prior to v81 had a bug that resulted in empty Sones. To prevent buggy Sones from being inserted they have been automatically locked. Please check your Sones, use the Rescue Mode if necessary, and unlock your Sones once you are satisfied with the results. Locked Sones are:
+# 55-61, 324–328, 360, 464
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, 324–328, 360
+Notification.SoneLockedOnStartup.Text=Some Sones were locked on startup because they don’t contain anything. Versions prior to v81 had a bug that resulted in empty Sones. To prevent buggy Sones from being inserted they have been automatically locked. Please check your Sones, use the Rescue Mode if necessary, and unlock your Sones once you are satisfied with the results. Locked Sones are:
+# 55-61, 324–328, 360, 464
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, 103, 324–328, 360, 455
+Notification.SoneLockedOnStartup.Text=Some Sones were locked on startup because they don’t contain anything. Versions prior to v81 had a bug that resulted in empty Sones. To prevent buggy Sones from being inserted they have been automatically locked. Please check your Sones, use the Rescue Mode if necessary, and unlock your Sones once you are satisfied with the results. Locked Sones are:
+# 55-51, 67, 103, 324–328, 360, 455, 464
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, 103, 123-124, 305-307, 309-311, 324–328, 360, 455, 461-463
+Notification.SoneLockedOnStartup.Text=Some Sones were locked on startup because they don’t contain anything. Versions prior to v81 had a bug that resulted in empty Sones. To prevent buggy Sones from being inserted they have been automatically locked. Please check your Sones, use the Rescue Mode if necessary, and unlock your Sones once you are satisfied with the results. Locked Sones are:
+# 55-61, 67, 103, 123-124, 305-307, 309-311, 324–328, 360, 455, 461-464
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, 324–328, 360, 455
+Notification.SoneLockedOnStartup.Text=Some Sones were locked on startup because they don’t contain anything. Versions prior to v81 had a bug that resulted in empty Sones. To prevent buggy Sones from being inserted they have been automatically locked. Please check your Sones, use the Rescue Mode if necessary, and unlock your Sones once you are satisfied with the results. Locked Sones are:
+# 55-61, 324–328, 360, 455, 464
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, 103, 123-124, 305-307, 309-311, 324–328, 360, 455, 461-463
+Notification.SoneLockedOnStartup.Text=Some Sones were locked on startup because they don’t contain anything. Versions prior to v81 had a bug that resulted in empty Sones. To prevent buggy Sones from being inserted they have been automatically locked. Please check your Sones, use the Rescue Mode if necessary, and unlock your Sones once you are satisfied with the results. Locked Sones are:
+# 55-61, 67, 103, 123-124, 305-307, 309-311, 324–328, 360, 455, 461-464
--- /dev/null
+<div class="text">
+ <%= Notification.SoneLockedOnStartup.Text|l10n|html>
+ <%foreach sones sone>
+ <a href="viewSone.html?sone=<% sone.id|html>" title="<% sone.requestUri|html>"><% sone.niceName|html></a><%notlast>,<%/notlast><%last>.<%/last>
+ <%/foreach>
+</div>
+++ /dev/null
-package net.pterodactylus.sone.core;
-
-import static net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.WRITING;
-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 static org.mockito.Mockito.when;
-
-import net.pterodactylus.sone.test.TestValue;
-import net.pterodactylus.util.config.Configuration;
-
-import com.google.common.eventbus.EventBus;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Unit test for {@link PreferencesLoader}.
- */
-public class PreferencesLoaderTest {
-
- private final EventBus eventBus = mock(EventBus.class);
- private final Preferences preferences = new Preferences(eventBus);
- private final Configuration configuration = mock(Configuration.class);
- private final PreferencesLoader preferencesLoader =
- new PreferencesLoader(preferences);
-
- @Before
- public void setupConfiguration() {
- setupIntValue("InsertionDelay", 15);
- setupIntValue("PostsPerPage", 25);
- setupIntValue("ImagesPerPage", 12);
- setupIntValue("CharactersPerPost", 150);
- setupIntValue("PostCutOffLength", 300);
- setupBooleanValue("RequireFullAccess", true);
- setupBooleanValue("ActivateFcpInterface", true);
- setupIntValue("FcpFullAccessRequired", 1);
- }
-
- private void setupIntValue(String optionName, int value) {
- when(configuration.getIntValue("Option/" + optionName)).thenReturn(
- TestValue.from(value));
- }
-
- private void setupBooleanValue(String optionName, boolean value) {
- when(configuration.getBooleanValue(
- "Option/" + optionName)).thenReturn(
- TestValue.from(value));
- }
-
- @Test
- public void configurationIsLoadedCorrectly() {
- setupConfiguration();
- preferencesLoader.loadFrom(configuration);
- assertThat(preferences.getInsertionDelay(), is(15));
- assertThat(preferences.getPostsPerPage(), is(25));
- assertThat(preferences.getImagesPerPage(), is(12));
- assertThat(preferences.getCharactersPerPost(), is(150));
- assertThat(preferences.getPostCutOffLength(), is(300));
- assertThat(preferences.getRequireFullAccess(), is(true));
- assertThat(preferences.getFcpInterfaceActive(), is(true));
- assertThat(preferences.getFcpFullAccessRequired(), is(WRITING));
- }
-
- @Test
- public void configurationIsLoadedCorrectlyWithCutOffLengthMinusOne() {
- setupConfiguration();
- setupIntValue("PostCutOffLength", -1);
- preferencesLoader.loadFrom(configuration);
- assertThat(preferences.getPostCutOffLength(), not(is(-1)));
- }
-
-}
--- /dev/null
+package net.pterodactylus.sone.core
+
+import com.google.common.eventbus.*
+import net.pterodactylus.sone.fcp.FcpInterface.*
+import net.pterodactylus.util.config.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+
+/**
+ * Unit test for [PreferencesLoader].
+ */
+class PreferencesLoaderTest {
+
+ @Suppress("UnstableApiUsage")
+ private val eventBus = EventBus()
+ private val preferences = Preferences(eventBus)
+ private val configuration = Configuration(MapConfigurationBackend())
+ private val preferencesLoader = PreferencesLoader(preferences)
+
+ @Before
+ fun setupConfiguration() {
+ setupIntValue("InsertionDelay", 15)
+ setupIntValue("PostsPerPage", 25)
+ setupIntValue("ImagesPerPage", 12)
+ setupIntValue("CharactersPerPost", 150)
+ setupIntValue("PostCutOffLength", 300)
+ setupBooleanValue("RequireFullAccess", true)
+ setupBooleanValue("ActivateFcpInterface", true)
+ setupIntValue("FcpFullAccessRequired", 1)
+ }
+
+ private fun setupIntValue(optionName: String, value: Int) {
+ configuration.getIntValue("Option/$optionName").value = value
+ }
+
+ private fun setupBooleanValue(optionName: String, value: Boolean) {
+ configuration.getBooleanValue("Option/$optionName").value = value
+ }
+
+ @Test
+ fun `configuration is loaded correctly`() {
+ preferencesLoader.loadFrom(configuration)
+ assertThat(preferences.insertionDelay, equalTo(15))
+ assertThat(preferences.postsPerPage, equalTo(25))
+ assertThat(preferences.imagesPerPage, equalTo(12))
+ assertThat(preferences.charactersPerPost, equalTo(150))
+ assertThat(preferences.postCutOffLength, equalTo(300))
+ assertThat(preferences.requireFullAccess, equalTo(true))
+ assertThat(preferences.fcpInterfaceActive, equalTo(true))
+ assertThat(preferences.fcpFullAccessRequired, equalTo(FullAccessRequired.WRITING))
+ }
+
+ @Test
+ fun `configuration is loaded correctly with cut off length minus one`() {
+ setupIntValue("PostCutOffLength", -1)
+ preferencesLoader.loadFrom(configuration)
+ assertThat(preferences.postCutOffLength, not(equalTo(-1)))
+ }
+
+}
--- /dev/null
+/**
+ * Sone - AlbumsTest.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.data
+
+import net.pterodactylus.sone.data.impl.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import kotlin.test.*
+
+/**
+ * Unit test for various helper method in `Albums.kt`.
+ */
+class AlbumsTest {
+
+ @Test
+ fun `recursive list of all images for album is returned correctly`() {
+ val sone = IdOnlySone("sone")
+ val album = AlbumImpl(sone)
+ val firstNestedAlbum = AlbumImpl(sone)
+ val secondNestedAlbum = AlbumImpl(sone)
+ firstNestedAlbum.addImage(createImage(sone, "image-1"))
+ firstNestedAlbum.addImage(createImage(sone, "image-2"))
+ secondNestedAlbum.addImage(createImage(sone, "image-3"))
+ album.addImage(createImage(sone, "image-4"))
+ album.addAlbum(firstNestedAlbum)
+ album.addAlbum(secondNestedAlbum)
+ val images = album.allImages
+ assertThat(images.map(Image::id), containsInAnyOrder("image-1", "image-2", "image-3", "image-4"))
+ }
+
+ private fun createImage(sone: IdOnlySone, id: String) = ImageImpl(id).modify().setSone(sone).update()
+
+}
import net.pterodactylus.sone.freenet.wot.*
import net.pterodactylus.sone.test.*
import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.notification.*
import org.hamcrest.MatcherAssert.*
import org.hamcrest.Matchers.*
import org.mockito.Mockito.*
assertThat(injector.getInstance<WebOfTrustConnector>(), notNullValue())
}
+ @Test
+ fun `notification handler can be created`() {
+ val injector: Injector = runSonePluginWithRealInjector()
+ assertThat(injector.getInstance<NotificationHandler>(), notNullValue())
+ }
+
private fun runSonePluginWithRealInjector(): Injector {
lateinit var injector: Injector
val sonePlugin = SonePlugin {
verify(core).start()
}
+ @Test
+ fun `notification handler is being started`() {
+ sonePlugin.runPlugin(pluginRespirator)
+ val notificationHandler = injector.getInstance<NotificationHandler>()
+ verify(notificationHandler).start()
+ }
+
}
private fun mockInjector() = mock<Injector>().apply {
--- /dev/null
+/**
+ * Sone - RenderablesTest.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.utils
+
+import net.pterodactylus.util.io.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import kotlin.test.*
+
+/**
+ * Unit tests for tools in `Renderables.kt`.
+ */
+class RenderablesTest {
+
+ @Test
+ fun `render method renders notification`() {
+ val notification = Renderable { writer -> writer.use { it.append("Test!\n") } }
+ assertThat(notification.render(), equalTo("Test!\n"))
+ }
+
+}
import net.pterodactylus.sone.template.*
import net.pterodactylus.sone.test.*
import net.pterodactylus.sone.text.*
+import net.pterodactylus.sone.web.notification.*
import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.notify.*
import net.pterodactylus.util.template.*
import net.pterodactylus.util.web.*
import org.hamcrest.MatcherAssert.*
assertThat(injector.getInstance<PageToadletFactory>().createPageToadlet(page).path(), startsWith("/Sone/"))
}
+ @Test
+ fun `notification manager is created as singleton`() {
+ val firstNotificationManager = injector.getInstance<NotificationManager>()
+ val secondNotificationManager = injector.getInstance<NotificationManager>()
+ assertThat(firstNotificationManager, sameInstance(secondNotificationManager))
+ }
+
+ @Test
+ fun `notification handler can be created`() {
+ assertThat(injector.getInstance<NotificationHandler>(), notNullValue())
+ }
+
+ @Test
+ fun `notification handler is created as singleton`() {
+ val firstNotificationHandler = injector.getInstance<NotificationHandler>()
+ val secondNotificationHandler = injector.getInstance<NotificationHandler>()
+ assertThat(firstNotificationHandler, sameInstance(secondNotificationHandler))
+ }
+
}
--- /dev/null
+/**
+ * Sone - NotificationHandlerTest.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.web.notification
+
+import com.google.common.eventbus.*
+import com.google.inject.*
+import com.google.inject.Guice.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.util.notify.*
+import net.pterodactylus.util.template.*
+import net.pterodactylus.util.web.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import kotlin.test.*
+
+/**
+ * Unit test for [NotificationHandler].
+ */
+class NotificationHandlerTest {
+
+ private val eventBus = TestEventBus()
+ private val loaders = TestLoaders()
+ private val notificationManager = NotificationManager()
+ private val handler = NotificationHandler(eventBus, loaders, notificationManager)
+
+ @Test
+ fun `notification handler can be created by guice`() {
+ val injector = createInjector(
+ EventBus::class.isProvidedBy(eventBus),
+ NotificationManager::class.isProvidedBy(notificationManager),
+ Loaders::class.isProvidedBy(loaders)
+ )
+ assertThat(injector.getInstance<NotificationHandler>(), notNullValue())
+ }
+
+ @Test
+ fun `notification handler registers handler for sone-locked event`() {
+ handler.start()
+ assertThat(eventBus.registeredObjects.any { it.javaClass == SoneLockedOnStartupHandler::class.java }, equalTo(true))
+ }
+
+ @Test
+ fun `notification handler loads sone-locked notification template`() {
+ handler.start()
+ assertThat(loaders.requestedTemplatePaths.any { it == "/templates/notify/soneLockedOnStartupNotification.html" }, equalTo(true))
+ }
+
+}
+
+@Suppress("UnstableApiUsage")
+private class TestEventBus : EventBus() {
+ private val _registeredObjects = mutableListOf<Any>()
+ val registeredObjects: List<Any>
+ get() = _registeredObjects
+
+ override fun register(`object`: Any) {
+ super.register(`object`)
+ _registeredObjects += `object`
+ }
+
+}
+
+private class TestLoaders : Loaders {
+ val requestedTemplatePaths = mutableListOf<String>()
+
+ override fun loadTemplate(path: String) =
+ Template().also { requestedTemplatePaths += path }
+
+ override fun <REQ : Request> loadStaticPage(basePath: String, prefix: String, mimeType: String) = object : Page<REQ> {
+
+ override fun getPath() = ""
+ override fun isPrefixPage() = false
+ override fun handleRequest(request: REQ, response: Response) = response
+
+ }
+
+ override fun getTemplateProvider() = TemplateProvider { _, _ -> Template() }
+
+}
--- /dev/null
+/**
+ * Sone - SoneLockedOnStartupNotificationTest.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.web.notification
+
+import com.google.common.eventbus.*
+import net.pterodactylus.sone.core.event.*
+import net.pterodactylus.sone.data.impl.*
+import net.pterodactylus.sone.notify.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.util.notify.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import kotlin.test.*
+
+/**
+ * Unit test for [SoneLockedOnStartupHandler].
+ */
+class SoneLockedOnStartupHandlerTest {
+
+ @Suppress("UnstableApiUsage")
+ private val eventBus = EventBus()
+ private val manager = NotificationManager()
+ private val notification by lazy { manager.notifications.single() as ListNotification<*> }
+
+ init {
+ SoneLockedOnStartupHandler(manager, template).also(eventBus::register)
+ eventBus.post(SoneLockedOnStartup(sone))
+ }
+
+ @Test
+ fun `notification has correct id`() {
+ assertThat(notification.id, equalTo("sone-locked-on-startup"))
+ }
+
+ @Test
+ fun `handler adds sone to notification when event is posted`() {
+ assertThat(notification.elements, contains<Any>(sone))
+ }
+
+ @Test
+ fun `handler creates notification with correct key`() {
+ assertThat(notification.render(), equalTo(listOf(sone).toString()))
+ }
+
+}
+
+private val sone = IdOnlySone("sone-id")
+private val template = "<% sones>".asTemplate()