From 9fd8201ee2baf94ae7dc0635b7a210b2c34c0b4e Mon Sep 17 00:00:00 2001 From: Arne Babenhauserheide Date: Sat, 8 Jul 2023 12:42:41 +0200 Subject: [PATCH 1/6] Only accept the 100 most recent posts from the past year --- .../sone/data/impl/SoneImpl.java | 43 +++++++++++++++++-- .../net/pterodactylus/sone/data/Post.kt | 8 ++++ .../net/pterodactylus/sone/data/Reply.kt | 8 ++++ .../net/pterodactylus/sone/data/SoneTest.kt | 27 ++++++++++++ 4 files changed, 83 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/pterodactylus/sone/data/impl/SoneImpl.java b/src/main/java/net/pterodactylus/sone/data/impl/SoneImpl.java index f2f2ea620..9e233be63 100644 --- a/src/main/java/net/pterodactylus/sone/data/impl/SoneImpl.java +++ b/src/main/java/net/pterodactylus/sone/data/impl/SoneImpl.java @@ -21,8 +21,11 @@ import static java.lang.String.format; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.logging.Logger.getLogger; +import static java.util.stream.Collectors.toList; import static net.pterodactylus.sone.data.PostKt.newestPostFirst; +import static net.pterodactylus.sone.data.PostKt.noOldPost; import static net.pterodactylus.sone.data.ReplyKt.newestReplyFirst; +import static net.pterodactylus.sone.data.ReplyKt.noOldReply; import static net.pterodactylus.sone.data.SoneKt.*; import java.net.MalformedURLException; @@ -38,6 +41,8 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import org.jetbrains.annotations.NotNull; + import net.pterodactylus.sone.data.Album; import net.pterodactylus.sone.data.AlbumKt; import net.pterodactylus.sone.data.Client; @@ -367,7 +372,7 @@ public List getPosts() { sortedPosts = new ArrayList<>(posts); } sortedPosts.sort(newestPostFirst()); - return sortedPosts; + return sortedPosts; } /** @@ -379,13 +384,29 @@ public List getPosts() { */ @Nonnull public Sone setPosts(@Nonnull Collection posts) { + List sortedPosts; + synchronized (this) { + sortedPosts = new ArrayList<>(posts); + } + sortedPosts.sort(newestPostFirst()); + List limitedPosts = this.local + ? sortedPosts + : filterRemotePosts(sortedPosts); synchronized (this) { this.posts.clear(); - this.posts.addAll(posts); + this.posts.addAll(limitedPosts); } return this; } + @NotNull + public static List filterRemotePosts(List sortedPosts) { + return sortedPosts.stream() + .filter(noOldPost()::invoke) + .limit(100) + .collect(toList()); + } + /** * Adds the given post to this Sone. The post will not be added if its {@link * Post#getSone() Sone} is not this Sone. @@ -430,11 +451,27 @@ public Set getReplies() { */ @Nonnull public Sone setReplies(@Nonnull Collection replies) { + List sortedReplies; + synchronized (this) { + sortedReplies = new ArrayList<>(replies); + } + sortedReplies.sort(newestReplyFirst()); + List limitedReplies = this.local + ? sortedReplies + : filterRemoteReplies(sortedReplies); this.replies.clear(); - this.replies.addAll(replies); + this.replies.addAll(limitedReplies); return this; } + @NotNull + public static List filterRemoteReplies(List sortedReplies) { + return sortedReplies.stream() + .filter(noOldReply()::invoke) + .limit(100) + .collect(toList()); + } + /** * Adds a reply to this Sone. If the given reply was not made by this Sone, * nothing is added to this Sone. diff --git a/src/main/kotlin/net/pterodactylus/sone/data/Post.kt b/src/main/kotlin/net/pterodactylus/sone/data/Post.kt index d87bd3c2e..cbfb62c5d 100644 --- a/src/main/kotlin/net/pterodactylus/sone/data/Post.kt +++ b/src/main/kotlin/net/pterodactylus/sone/data/Post.kt @@ -1,6 +1,7 @@ package net.pterodactylus.sone.data import java.util.Comparator.comparing +import java.util.concurrent.TimeUnit /** * Predicate that returns whether a post is _not_ from the future, @@ -9,6 +10,13 @@ import java.util.Comparator.comparing @get:JvmName("noFuturePost") val noFuturePost: (Post) -> Boolean = { it.time <= System.currentTimeMillis() } +/** + * Predicate that returns whether a post less than a year old, + * i.e. whether it should be visible now. + */ +@get:JvmName("noOldPost") +val noOldPost: (Post) -> Boolean = { it.time > (System.currentTimeMillis() - (TimeUnit.DAYS.toMillis(365))) } + /** * Comparator that orders posts by their time, newest posts first. */ diff --git a/src/main/kotlin/net/pterodactylus/sone/data/Reply.kt b/src/main/kotlin/net/pterodactylus/sone/data/Reply.kt index cfc940a20..f9403b84c 100644 --- a/src/main/kotlin/net/pterodactylus/sone/data/Reply.kt +++ b/src/main/kotlin/net/pterodactylus/sone/data/Reply.kt @@ -18,6 +18,7 @@ package net.pterodactylus.sone.data import java.util.Comparator.comparing +import java.util.concurrent.TimeUnit /** * Comparator that orders replies by their time, newest replies first. @@ -26,6 +27,13 @@ import java.util.Comparator.comparing val newestReplyFirst: Comparator> = comparing(Reply<*>::getTime).reversed() +/** + * Predicate that returns whether a reply less than a year old, + * i.e. whether it should be visible now. + */ +@get:JvmName("noOldReply") +val noOldReply: (Reply<*>) -> Boolean = { it.getTime() > (System.currentTimeMillis() - (TimeUnit.DAYS.toMillis(365))) } + /** * Predicate that returns whether a reply is _not_ from the future, * i.e. whether it should be visible now. diff --git a/src/test/kotlin/net/pterodactylus/sone/data/SoneTest.kt b/src/test/kotlin/net/pterodactylus/sone/data/SoneTest.kt index fb2312125..8ffe54f1d 100644 --- a/src/test/kotlin/net/pterodactylus/sone/data/SoneTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/data/SoneTest.kt @@ -18,9 +18,12 @@ package net.pterodactylus.sone.data import net.pterodactylus.sone.data.impl.* +import net.pterodactylus.sone.data.impl.SoneImpl.filterRemotePosts +import net.pterodactylus.sone.data.impl.SoneImpl.filterRemoteReplies import net.pterodactylus.sone.test.* import org.hamcrest.MatcherAssert.* import org.hamcrest.Matchers.* +import java.util.concurrent.TimeUnit import kotlin.test.* /** @@ -120,6 +123,30 @@ class SoneTest { assertThat(postCountComparator.compare(sone1, sone2), equalTo(0)) } + @Test + fun `post filtering skips old posts`() { + var oldPosts = listOf(createPost(time = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(400))); + assertThat(filterRemotePosts(oldPosts), empty()) + } + + @Test + fun `post filtering keeps recent posts`() { + var recentPosts = listOf(createPost(time = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(300))); + assertThat(filterRemotePosts(recentPosts), hasSize(recentPosts.size)); + } + + @Test + fun `reply filtering skips old replies`() { + var oldReplies = listOf(emptyPostReply(time = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(400))); + assertThat(filterRemoteReplies(oldReplies), empty()) + } + + @Test + fun `reply filtering keeps recent replies`() { + var recentReplies = listOf(emptyPostReply(time = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(300))); + assertThat(filterRemoteReplies(recentReplies), hasSize(recentReplies.size)) + } + @Test fun `image count comparator sorts Sones correctly if number of images is different`() { val sone1 = object : IdOnlySone("1") { From 5ed3ca7f6678dc2d1c64851779d9786d36c4393e Mon Sep 17 00:00:00 2001 From: Arne Babenhauserheide Date: Sat, 15 Jul 2023 15:04:32 +0200 Subject: [PATCH 2/6] Add GUI option for download backwards limit and count limit --- .../pterodactylus/sone/core/Preferences.kt | 24 ++++++++++ .../sone/core/PreferencesLoader.kt | 10 +++++ .../DownloadBackwardsLimitChangedEvent.kt | 3 ++ .../event/DownloadCountLimitChangedEvent.kt | 3 ++ .../sone/web/pages/OptionsPage.kt | 6 +++ src/main/resources/i18n/sone.de.properties | 2 + src/main/resources/i18n/sone.en.properties | 2 + src/main/resources/templates/options.html | 18 ++++++++ .../sone/web/pages/OptionsPageTest.kt | 44 +++++++++++++++++++ 9 files changed, 112 insertions(+) create mode 100644 src/main/kotlin/net/pterodactylus/sone/core/event/DownloadBackwardsLimitChangedEvent.kt create mode 100644 src/main/kotlin/net/pterodactylus/sone/core/event/DownloadCountLimitChangedEvent.kt diff --git a/src/main/kotlin/net/pterodactylus/sone/core/Preferences.kt b/src/main/kotlin/net/pterodactylus/sone/core/Preferences.kt index fe1e3c3ec..36364fab5 100644 --- a/src/main/kotlin/net/pterodactylus/sone/core/Preferences.kt +++ b/src/main/kotlin/net/pterodactylus/sone/core/Preferences.kt @@ -21,6 +21,8 @@ import com.google.common.eventbus.EventBus import net.pterodactylus.sone.core.event.InsertionDelayChangedEvent import net.pterodactylus.sone.core.event.StrictFilteringActivatedEvent import net.pterodactylus.sone.core.event.StrictFilteringDeactivatedEvent +import net.pterodactylus.sone.core.event.DownloadBackwardsLimitChangedEvent +import net.pterodactylus.sone.core.event.DownloadCountLimitChangedEvent import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.ALWAYS import net.pterodactylus.sone.fcp.event.FcpInterfaceActivatedEvent @@ -47,6 +49,26 @@ class Preferences(private val eventBus: EventBus) { eventBus.post(PreferenceChangedEvent("InsertionDelay", insertionDelay)) } + private val _downloadBackwardsLimit = DefaultOption(365) { it in -1..MAX_VALUE } + val downloadBackwardsLimit: Int get() = _downloadBackwardsLimit.get() + var newDownloadBackwardsLimit: Int? + get() = unsupported + set(value) { + _downloadBackwardsLimit.set(value) + eventBus.post(DownloadBackwardsLimitChangedEvent(downloadBackwardsLimit)) + eventBus.post(PreferenceChangedEvent("downloadBackwardsLimit", downloadBackwardsLimit)) + } + + private val _downloadCountLimit = DefaultOption(100) { it in -1..MAX_VALUE } + val downloadCountLimit: Int get() = _downloadCountLimit.get() + var newDownloadCountLimit: Int? + get() = unsupported + set(value) { + _downloadCountLimit.set(value) + eventBus.post(DownloadCountLimitChangedEvent(downloadCountLimit)) + eventBus.post(PreferenceChangedEvent("downloadCountLimit", downloadCountLimit)) + } + private val _postsPerPage = DefaultOption(10) { it in 1..MAX_VALUE } val postsPerPage: Int get() = _postsPerPage.get() var newPostsPerPage: Int? @@ -116,6 +138,8 @@ class Preferences(private val eventBus: EventBus) { fun saveTo(configuration: Configuration) { configuration.getIntValue("Option/ConfigurationVersion").value = 0 configuration.getIntValue("Option/InsertionDelay").value = _insertionDelay.real + configuration.getIntValue("Option/DownloadBackwardsLimit").value = _downloadBackwardsLimit.real + configuration.getIntValue("Option/DownloadCountLimit").value = _downloadCountLimit.real configuration.getIntValue("Option/PostsPerPage").value = _postsPerPage.real configuration.getIntValue("Option/ImagesPerPage").value = _imagesPerPage.real configuration.getIntValue("Option/CharactersPerPost").value = _charactersPerPost.real diff --git a/src/main/kotlin/net/pterodactylus/sone/core/PreferencesLoader.kt b/src/main/kotlin/net/pterodactylus/sone/core/PreferencesLoader.kt index 62b60ae9d..2b71a8e93 100644 --- a/src/main/kotlin/net/pterodactylus/sone/core/PreferencesLoader.kt +++ b/src/main/kotlin/net/pterodactylus/sone/core/PreferencesLoader.kt @@ -10,6 +10,8 @@ class PreferencesLoader(private val preferences: Preferences) { fun loadFrom(configuration: Configuration) { loadInsertionDelay(configuration) + loadDownloadBackwardsLimit(configuration) + loadDownloadCountLimit(configuration) loadPostsPerPage(configuration) loadImagesPerPage(configuration) loadCharactersPerPost(configuration) @@ -24,6 +26,14 @@ class PreferencesLoader(private val preferences: Preferences) { preferences.newInsertionDelay = configuration.getIntValue("Option/InsertionDelay").getValue(null) } + private fun loadDownloadBackwardsLimit(configuration: Configuration) { + preferences.newDownloadBackwardsLimit = configuration.getIntValue("Option/DownloadBackwardsLimit").getValue(null) + } + + private fun loadDownloadCountLimit(configuration: Configuration) { + preferences.newDownloadCountLimit = configuration.getIntValue("Option/DownloadCountLimit").getValue(null) + } + private fun loadPostsPerPage(configuration: Configuration) { preferences.newPostsPerPage = configuration.getIntValue("Option/PostsPerPage").getValue(null) } diff --git a/src/main/kotlin/net/pterodactylus/sone/core/event/DownloadBackwardsLimitChangedEvent.kt b/src/main/kotlin/net/pterodactylus/sone/core/event/DownloadBackwardsLimitChangedEvent.kt new file mode 100644 index 000000000..c9d7e2224 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/core/event/DownloadBackwardsLimitChangedEvent.kt @@ -0,0 +1,3 @@ +package net.pterodactylus.sone.core.event + +data class DownloadBackwardsLimitChangedEvent(val insertionDelay: Int) diff --git a/src/main/kotlin/net/pterodactylus/sone/core/event/DownloadCountLimitChangedEvent.kt b/src/main/kotlin/net/pterodactylus/sone/core/event/DownloadCountLimitChangedEvent.kt new file mode 100644 index 000000000..0109f6c37 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/core/event/DownloadCountLimitChangedEvent.kt @@ -0,0 +1,3 @@ +package net.pterodactylus.sone.core.event + +data class DownloadCountLimitChangedEvent(val insertionDelay: Int) diff --git a/src/main/kotlin/net/pterodactylus/sone/web/pages/OptionsPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/pages/OptionsPage.kt index 9465a6a28..c9f09255e 100644 --- a/src/main/kotlin/net/pterodactylus/sone/web/pages/OptionsPage.kt +++ b/src/main/kotlin/net/pterodactylus/sone/web/pages/OptionsPage.kt @@ -51,6 +51,8 @@ class OptionsPage @Inject constructor(webInterface: WebInterface, loaders: Loade val postCutOffLength = soneRequest.parameters["post-cut-off-length"]?.toIntOrNull() val imagesPerPage = soneRequest.parameters["images-per-page"]?.toIntOrNull() val insertionDelay = soneRequest.parameters["insertion-delay"]?.toIntOrNull() + val downloadBackwardsLimit = soneRequest.parameters["download-backwards-limit"]?.toIntOrNull() + val downloadCountLimit = soneRequest.parameters["download-count-limit"]?.toIntOrNull() val fcpFullAccessRequired = soneRequest.parameters["fcp-full-access-required"]?.toIntOrNull() if (cantSetOption { soneRequest.core.preferences.newPostsPerPage = postsPerPage }) fieldsWithErrors += "posts-per-page" @@ -58,6 +60,8 @@ class OptionsPage @Inject constructor(webInterface: WebInterface, loaders: Loade if (cantSetOption { soneRequest.core.preferences.newPostCutOffLength = postCutOffLength }) fieldsWithErrors += "post-cut-off-length" if (cantSetOption { soneRequest.core.preferences.newImagesPerPage = imagesPerPage }) fieldsWithErrors += "images-per-page" if (cantSetOption { soneRequest.core.preferences.newInsertionDelay = insertionDelay }) fieldsWithErrors += "insertion-delay" + if (cantSetOption { soneRequest.core.preferences.newDownloadBackwardsLimit = downloadBackwardsLimit }) fieldsWithErrors += "download-backwards-limit" + if (cantSetOption { soneRequest.core.preferences.newDownloadCountLimit = downloadCountLimit }) fieldsWithErrors += "download-count-limit" fcpFullAccessRequired?.also { if (cantSetOption { soneRequest.core.preferences.newFcpFullAccessRequired = FullAccessRequired.values()[fcpFullAccessRequired] }) fieldsWithErrors += "fcp-full-access-required" } if (fieldsWithErrors.isEmpty()) { @@ -77,6 +81,8 @@ class OptionsPage @Inject constructor(webInterface: WebInterface, loaders: Loade } soneRequest.core.preferences.let { preferences -> templateContext["insertion-delay"] = preferences.insertionDelay + templateContext["download-backwards-limit"] = preferences.downloadBackwardsLimit + templateContext["download-count-limit"] = preferences.downloadCountLimit templateContext["characters-per-post"] = preferences.charactersPerPost templateContext["fcp-full-access-required"] = preferences.fcpFullAccessRequired.ordinal templateContext["images-per-page"] = preferences.imagesPerPage diff --git a/src/main/resources/i18n/sone.de.properties b/src/main/resources/i18n/sone.de.properties index cd42c458a..13ed14ff9 100644 --- a/src/main/resources/i18n/sone.de.properties +++ b/src/main/resources/i18n/sone.de.properties @@ -63,6 +63,8 @@ Page.Options.Option.LoadLinkedImages.Trusted.Description=Nur Bilder aus Nachrich Page.Options.Option.LoadLinkedImages.Always.Description=Immer verlinkte Bilder laden. Warnung: Verlinkte Bilder können beliebiges Bildmaterial enthalten! Page.Options.Section.RuntimeOptions.Title=Laufzeitverhalten Page.Options.Option.InsertionDelay.Description=Anzahl der Sekunden, die vor dem Hochladen einer Sone nach einer Änderung gewartet wird. +Page.Options.Option.DownloadBackwardsLimit.Description=Maximalalter von Nachrichten und Antworten (-1 to disable). Ältere werden von nicht-lokalen Sones verworfen. Betrifft nur zukünftige Downloads. +Page.Options.Option.DownloadCountLimit.Description=Maximalzahl Nachrichten und Antworten, die je entfernter Sone geladen werden (-1 für beliebig viele), neuste zuerst. Nachrichten und Antworten werden unabhängig gezählt. Betrifft nur zukünftige Downloads. Page.Options.Option.PostsPerPage.Description=Anzahl der Nachrichten pro Seite. Page.Options.Option.ImagesPerPage.Description=Anzahl der Bilder pro Seite. Page.Options.Option.CharactersPerPost.Description=Die Anzahl der Zeichen, die eine Nachricht enthalten muss, damit sie gekürzt angezeigt wird (-1 für „nie kürzen“). Die Anzahl der tatsächlich angezeigten Zeichen wird in der nächsten Option konfiguriert. diff --git a/src/main/resources/i18n/sone.en.properties b/src/main/resources/i18n/sone.en.properties index 7c65dcf84..6e77b35ba 100644 --- a/src/main/resources/i18n/sone.en.properties +++ b/src/main/resources/i18n/sone.en.properties @@ -63,6 +63,8 @@ Page.Options.Option.LoadLinkedImages.Trusted.Description=Only load images linked Page.Options.Option.LoadLinkedImages.Always.Description=Always load linked images. Be warned: some images might be disturbing or considered offensive. Page.Options.Section.RuntimeOptions.Title=Runtime Behaviour Page.Options.Option.InsertionDelay.Description=The number of seconds the Sone inserter waits after a modification of a Sone before it is being inserted. +Page.Options.Option.DownloadBackwardsLimit.Description=The maximum age in days of posts and replies (-1 to disable). Older posts and replies are discarded for non-local sones. Affects only newly downloaded Sones. +Page.Options.Option.DownloadCountLimit.Description=The number of posts and replies to retrieve per non-local Sone (-1 to disable), newest first. Posts and replies are counted independently. Affects only newly downloaded Sones. Page.Options.Option.PostsPerPage.Description=The number of posts to display on a page before pagination controls are being shown. Page.Options.Option.ImagesPerPage.Description=The number of images to display on a page before pagination controls are being shown. Page.Options.Option.CharactersPerPost.Description=The number of characters to display from a post before cutting it off and showing a link to expand it (-1 to disable). The actual length of the snippet is determined by the option below. diff --git a/src/main/resources/templates/options.html b/src/main/resources/templates/options.html index 9ef5ceb70..beec5b748 100644 --- a/src/main/resources/templates/options.html +++ b/src/main/resources/templates/options.html @@ -5,6 +5,12 @@ getTranslation("WebInterface.DefaultText.Option.InsertionDelay", function(insertionDelayDefaultText) { registerInputTextareaSwap("#sone #options input[name=insertion-delay]", insertionDelayDefaultText, "insertion-delay", true, true); }); + getTranslation("WebInterface.DefaultText.Option.DownloadBackwardsLimit", function(downloadBackwardsLimitDefaultText) { + registerInputTextareaSwap("#sone #options input[name=download-backwards-limit]", downloadBackwardsLimitDefaultText, "download-backwards-limit", true, true); + }); + getTranslation("WebInterface.DefaultText.Option.DownloadCountLimit", function(downloadCountLimitDefaultText) { + registerInputTextareaSwap("#sone #options input[name=download-count-limit]", downloadCountLimitDefaultText, "download-count-limit", true, true); + }); getTranslation("WebInterface.DefaultText.Option.PostsPerPage", function(postsPerPageText) { registerInputTextareaSwap("#sone #options input[name=posts-per-page]", postsPerPageText, "posts-per-page", true, true); }); @@ -122,6 +128,18 @@

<%= Page.Options.Section.RuntimeOptions.Title|l10n|html>

<%/if>

+

<%= Page.Options.Option.DownloadBackwardsLimit.Description|l10n|html>

+ <%if =download-backwards-limit|in collection=fieldErrors> +

<%= Page.Options.Warnings.ValueNotChanged|l10n|html>

+ <%/if> +

+ +

<%= Page.Options.Option.DownloadCountLimit.Description|l10n|html>

+ <%if =download-count-limit|in collection=fieldErrors> +

<%= Page.Options.Warnings.ValueNotChanged|l10n|html>

+ <%/if> +

+

<%= Page.Options.Option.PostsPerPage.Description|l10n|html>

<%if =posts-per-page|in collection=fieldErrors>

<%= Page.Options.Warnings.ValueNotChanged|l10n|html>

diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/OptionsPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/OptionsPageTest.kt index d679e85f8..0bf1066b5 100644 --- a/src/test/kotlin/net/pterodactylus/sone/web/pages/OptionsPageTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/OptionsPageTest.kt @@ -20,6 +20,8 @@ class OptionsPageTest : WebPageTest(::OptionsPage) { @Before fun setupPreferences() { core.preferences.newInsertionDelay = 1 + core.preferences.newDownloadBackwardsLimit = 30 + core.preferences.newDownloadCountLimit = 10 core.preferences.newCharactersPerPost = 50 core.preferences.newFcpFullAccessRequired = WRITING core.preferences.newImagesPerPage = 4 @@ -69,6 +71,8 @@ class OptionsPageTest : WebPageTest(::OptionsPage) { assertThat(templateContext["enable-sone-insert-notifications"], equalTo(true)) assertThat(templateContext["load-linked-images"], equalTo("FOLLOWED")) assertThat(templateContext["show-custom-avatars"], equalTo("FOLLOWED")) + assertThat(templateContext["download-backwards-limit"], equalTo(30)) + assertThat(templateContext["download-count-limit"], equalTo(10)) assertThat(templateContext["insertion-delay"], equalTo(1)) assertThat(templateContext["characters-per-post"], equalTo(50)) assertThat(templateContext["fcp-full-access-required"], equalTo(1)) @@ -199,6 +203,46 @@ class OptionsPageTest : WebPageTest(::OptionsPage) { verifyThatPreferencesCanBeSet("insertion-delay", "foo", 60) { core.preferences.insertionDelay } } + @Test + fun `download backwards limit can not be set to less than -1 days`() { + verifyThatWrongValueForPreferenceIsDetected("download-backwards-limit", "-2") + } + + @Test + fun `download backwards limit can be set to 0 days`() { + verifyThatPreferencesCanBeSet("download-backwards-limit", "0", 0) { core.preferences.downloadBackwardsLimit } + } + + @Test + fun `download backwards limit can be set to -1 days`() { + verifyThatPreferencesCanBeSet("download-backwards-limit", "-1", -1) { core.preferences.downloadBackwardsLimit } + } + + @Test + fun `setting download backwards limit to an invalid value will reset it`() { + verifyThatPreferencesCanBeSet("download-backwards-limit", "foo", 365) { core.preferences.downloadBackwardsLimit } + } + + @Test + fun `download count limit can not be set to less than -1 posts or replies`() { + verifyThatWrongValueForPreferenceIsDetected("download-count-limit", "-2") + } + + @Test + fun `download count limit can be set to -1 posts or replies`() { + verifyThatPreferencesCanBeSet("download-count-limit", "-1", -1) { core.preferences.downloadCountLimit } + } + + @Test + fun `download count limit can be set to 0 posts or replies`() { + verifyThatPreferencesCanBeSet("download-count-limit", "0", 0) { core.preferences.downloadCountLimit } + } + + @Test + fun `setting download count limit to an invalid value will reset it`() { + verifyThatPreferencesCanBeSet("download-count-limit", "foo", 100) { core.preferences.downloadCountLimit } + } + @Test fun `characters per post can not be set to less than -1`() { verifyThatWrongValueForPreferenceIsDetected("characters-per-post", "-2") From 8f79754a874d42e4b6dd7b53d3bc97ceaf1eb794 Mon Sep 17 00:00:00 2001 From: Arne Babenhauserheide Date: Sat, 15 Jul 2023 15:05:06 +0200 Subject: [PATCH 3/6] Add missing "delay" in test description --- .../kotlin/net/pterodactylus/sone/web/pages/OptionsPageTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/OptionsPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/OptionsPageTest.kt index 0bf1066b5..921c89311 100644 --- a/src/test/kotlin/net/pterodactylus/sone/web/pages/OptionsPageTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/OptionsPageTest.kt @@ -199,7 +199,7 @@ class OptionsPageTest : WebPageTest(::OptionsPage) { } @Test - fun `setting insertion to an invalid value will reset it`() { + fun `setting insertion delay to an invalid value will reset it`() { verifyThatPreferencesCanBeSet("insertion-delay", "foo", 60) { core.preferences.insertionDelay } } From 21c1608357eb209a311701412397231295617311 Mon Sep 17 00:00:00 2001 From: Arne Babenhauserheide Date: Thu, 4 Jul 2024 21:27:34 +0200 Subject: [PATCH 4/6] Connect config and post/reply limiting Also add missing default text for the options --- .../net/pterodactylus/sone/core/Core.java | 4 +++ .../net/pterodactylus/sone/data/Sone.java | 8 +++++ .../pterodactylus/sone/data/SoneOptions.java | 29 ++++++++++++++-- .../sone/data/impl/IdOnlySone.java | 24 +++++++++++++ .../sone/data/impl/SoneImpl.java | 34 ++++++++++++------- .../pterodactylus/sone/core/Preferences.kt | 22 ------------ .../sone/core/PreferencesLoader.kt | 10 ------ .../net/pterodactylus/sone/data/Post.kt | 6 ++-- .../net/pterodactylus/sone/data/Reply.kt | 6 ++-- .../sone/web/pages/OptionsPage.kt | 22 ++++++++---- .../sone/web/pages/SearchPage.kt | 7 ++-- src/main/resources/i18n/sone.de.properties | 2 ++ src/main/resources/i18n/sone.en.properties | 2 ++ src/main/resources/templates/options.html | 24 ++++++------- .../net/pterodactylus/sone/data/SoneTest.kt | 18 ++++++---- .../sone/web/pages/OptionsPageTest.kt | 16 ++++----- 16 files changed, 149 insertions(+), 85 deletions(-) diff --git a/src/main/java/net/pterodactylus/sone/core/Core.java b/src/main/java/net/pterodactylus/sone/core/Core.java index da1b2f853..7b375f6e2 100644 --- a/src/main/java/net/pterodactylus/sone/core/Core.java +++ b/src/main/java/net/pterodactylus/sone/core/Core.java @@ -951,6 +951,8 @@ public void loadSone(Sone sone) { sone.getOptions().setShowNewSoneNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewSones").getValue(true)); sone.getOptions().setShowNewPostNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").getValue(true)); sone.getOptions().setShowNewReplyNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").getValue(true)); + sone.getOptions().setDownloadBackwardsLimitDays(configuration.getIntValue(sonePrefix + "/Options/DownloadBackwardsLimit").getValue(365)); + sone.getOptions().setDownloadCountLimit(configuration.getIntValue(sonePrefix + "/Options/DownloadCountLimit").getValue(100)); sone.getOptions().setShowCustomAvatars(LoadExternalContent.valueOf(configuration.getStringValue(sonePrefix + "/Options/ShowCustomAvatars").getValue(LoadExternalContent.NEVER.name()))); sone.getOptions().setLoadLinkedImages(LoadExternalContent.valueOf(configuration.getStringValue(sonePrefix + "/Options/LoadLinkedImages").getValue(LoadExternalContent.NEVER.name()))); @@ -1430,6 +1432,8 @@ private synchronized void saveSone(Sone sone) { configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewSones").setValue(sone.getOptions().isShowNewSoneNotifications()); configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").setValue(sone.getOptions().isShowNewPostNotifications()); configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").setValue(sone.getOptions().isShowNewReplyNotifications()); + configuration.getIntValue(sonePrefix + "/Options/DownloadBackwardsLimit").setValue(sone.getOptions().getDownloadBackwardsLimitDays()); + configuration.getIntValue(sonePrefix + "/Options/DownloadCountLimit").setValue(sone.getOptions().getDownloadCountLimit()); configuration.getStringValue(sonePrefix + "/Options/ShowCustomAvatars").setValue(sone.getOptions().getShowCustomAvatars().name()); configuration.getStringValue(sonePrefix + "/Options/LoadLinkedImages").setValue(sone.getOptions().getLoadLinkedImages().name()); diff --git a/src/main/java/net/pterodactylus/sone/data/Sone.java b/src/main/java/net/pterodactylus/sone/data/Sone.java index 2b0a2eb42..75137d20a 100644 --- a/src/main/java/net/pterodactylus/sone/data/Sone.java +++ b/src/main/java/net/pterodactylus/sone/data/Sone.java @@ -24,6 +24,8 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import org.jetbrains.annotations.NotNull; + import net.pterodactylus.sone.freenet.wot.Identity; import freenet.keys.FreenetURI; @@ -228,6 +230,9 @@ public enum SoneStatus { @Nonnull Sone setPosts(@Nonnull Collection posts); + @NotNull + List filterRemotePosts(List sortedPosts); + /** * Adds the given post to this Sone. The post will not be added if its {@link * Post#getSone() Sone} is not this Sone. @@ -263,6 +268,9 @@ public enum SoneStatus { @Nonnull Sone setReplies(@Nonnull Collection replies); + @NotNull + List filterRemoteReplies(List sortedReplies); + /** * Adds a reply to this Sone. If the given reply was not made by this Sone, * nothing is added to this Sone. diff --git a/src/main/java/net/pterodactylus/sone/data/SoneOptions.java b/src/main/java/net/pterodactylus/sone/data/SoneOptions.java index d7089d966..8e53f98a0 100644 --- a/src/main/java/net/pterodactylus/sone/data/SoneOptions.java +++ b/src/main/java/net/pterodactylus/sone/data/SoneOptions.java @@ -23,6 +23,10 @@ public interface SoneOptions { boolean isShowNewReplyNotifications(); void setShowNewReplyNotifications(boolean showNewReplyNotifications); + int getDownloadBackwardsLimitDays(); + void setDownloadBackwardsLimitDays(int days); + int getDownloadCountLimit(); + void setDownloadCountLimit(int count); LoadExternalContent getShowCustomAvatars(); void setShowCustomAvatars(LoadExternalContent showCustomAvatars); @@ -62,9 +66,10 @@ public class DefaultSoneOptions implements SoneOptions { private boolean showNewSoneNotifications = true; private boolean showNewPostNotifications = true; private boolean showNewReplyNotifications = true; + private int downloadBackwardsLimitDays = 365; + private int downloadCountLimitDays = 100; private LoadExternalContent showCustomAvatars = NEVER; private LoadExternalContent loadLinkedImages = NEVER; - @Override public boolean isAutoFollow() { return autoFollow; @@ -115,6 +120,27 @@ public void setShowNewReplyNotifications(boolean showNewReplyNotifications) { this.showNewReplyNotifications = showNewReplyNotifications; } + @Override + public int getDownloadBackwardsLimitDays() { + return downloadBackwardsLimitDays; + } + + @Override + public void setDownloadBackwardsLimitDays(int downloadBackwardsLimitDays) { + this.downloadBackwardsLimitDays = downloadBackwardsLimitDays; + } + + @Override + public int getDownloadCountLimit() { + return downloadCountLimitDays; + } + + @Override + public void setDownloadCountLimit(int count) { + this.downloadCountLimitDays = count; + + } + @Override public LoadExternalContent getShowCustomAvatars() { return showCustomAvatars; @@ -135,7 +161,6 @@ public LoadExternalContent getLoadLinkedImages() { public void setLoadLinkedImages(@Nonnull LoadExternalContent loadLinkedImages) { this.loadLinkedImages = loadLinkedImages; } - } } diff --git a/src/main/java/net/pterodactylus/sone/data/impl/IdOnlySone.java b/src/main/java/net/pterodactylus/sone/data/impl/IdOnlySone.java index ddd96b951..6e8bdd7b1 100644 --- a/src/main/java/net/pterodactylus/sone/data/impl/IdOnlySone.java +++ b/src/main/java/net/pterodactylus/sone/data/impl/IdOnlySone.java @@ -7,10 +7,16 @@ import java.util.List; import java.util.Set; +import org.jetbrains.annotations.NotNull; + import net.pterodactylus.sone.data.Album; import net.pterodactylus.sone.data.Client; import net.pterodactylus.sone.data.Post; import net.pterodactylus.sone.data.PostReply; + +import static java.util.stream.Collectors.toList; +import static net.pterodactylus.sone.data.PostKt.noOldPost; +import static net.pterodactylus.sone.data.ReplyKt.noOldReply; import net.pterodactylus.sone.data.Profile; import net.pterodactylus.sone.data.Sone; import net.pterodactylus.sone.data.SoneOptions; @@ -130,6 +136,15 @@ public Sone setPosts(Collection posts) { return this; } + @NotNull + @Override + public List filterRemotePosts(List sortedPosts) { + return sortedPosts.stream() + .filter(post -> noOldPost().invoke(post, this.getOptions().getDownloadBackwardsLimitDays())) + .limit(this.getOptions().getDownloadCountLimit()) + .collect(toList()); + } + @Override public void addPost(Post post) { } @@ -148,6 +163,15 @@ public Sone setReplies(Collection replies) { return this; } + @NotNull + @Override + public List filterRemoteReplies(List sortedReplies) { + return sortedReplies.stream() + .filter(reply -> noOldReply().invoke(reply, this.getOptions().getDownloadBackwardsLimitDays())) + .limit(this.getOptions().getDownloadCountLimit()) + .collect(toList()); + } + @Override public void addReply(PostReply reply) { } diff --git a/src/main/java/net/pterodactylus/sone/data/impl/SoneImpl.java b/src/main/java/net/pterodactylus/sone/data/impl/SoneImpl.java index 9e233be63..5a072ed0f 100644 --- a/src/main/java/net/pterodactylus/sone/data/impl/SoneImpl.java +++ b/src/main/java/net/pterodactylus/sone/data/impl/SoneImpl.java @@ -389,9 +389,9 @@ public Sone setPosts(@Nonnull Collection posts) { sortedPosts = new ArrayList<>(posts); } sortedPosts.sort(newestPostFirst()); - List limitedPosts = this.local - ? sortedPosts - : filterRemotePosts(sortedPosts); + List limitedPosts = this.local + ? sortedPosts + : this.filterRemotePosts(sortedPosts); synchronized (this) { this.posts.clear(); this.posts.addAll(limitedPosts); @@ -399,11 +399,20 @@ public Sone setPosts(@Nonnull Collection posts) { return this; } + /** + * Filters posts of downloaded Sones. + * + * @param sortedPosts + * The new (and only) posts of this Sone + * + * @return posts limited by download-count-limit and may time backwards. + * */ + @Override @NotNull - public static List filterRemotePosts(List sortedPosts) { + public List filterRemotePosts(List sortedPosts) { return sortedPosts.stream() - .filter(noOldPost()::invoke) - .limit(100) + .filter(post -> noOldPost().invoke(post, this.getOptions().getDownloadBackwardsLimitDays())) + .limit(this.getOptions().getDownloadCountLimit()) .collect(toList()); } @@ -456,19 +465,20 @@ public Sone setReplies(@Nonnull Collection replies) { sortedReplies = new ArrayList<>(replies); } sortedReplies.sort(newestReplyFirst()); - List limitedReplies = this.local - ? sortedReplies - : filterRemoteReplies(sortedReplies); + List limitedReplies = this.local + ? sortedReplies + : this.filterRemoteReplies(sortedReplies); this.replies.clear(); this.replies.addAll(limitedReplies); return this; } + @Override @NotNull - public static List filterRemoteReplies(List sortedReplies) { + public List filterRemoteReplies(List sortedReplies) { return sortedReplies.stream() - .filter(noOldReply()::invoke) - .limit(100) + .filter(reply -> noOldReply().invoke(reply, this.getOptions().getDownloadBackwardsLimitDays())) + .limit(this.getOptions().getDownloadCountLimit()) .collect(toList()); } diff --git a/src/main/kotlin/net/pterodactylus/sone/core/Preferences.kt b/src/main/kotlin/net/pterodactylus/sone/core/Preferences.kt index 36364fab5..9d9d04b8c 100644 --- a/src/main/kotlin/net/pterodactylus/sone/core/Preferences.kt +++ b/src/main/kotlin/net/pterodactylus/sone/core/Preferences.kt @@ -49,26 +49,6 @@ class Preferences(private val eventBus: EventBus) { eventBus.post(PreferenceChangedEvent("InsertionDelay", insertionDelay)) } - private val _downloadBackwardsLimit = DefaultOption(365) { it in -1..MAX_VALUE } - val downloadBackwardsLimit: Int get() = _downloadBackwardsLimit.get() - var newDownloadBackwardsLimit: Int? - get() = unsupported - set(value) { - _downloadBackwardsLimit.set(value) - eventBus.post(DownloadBackwardsLimitChangedEvent(downloadBackwardsLimit)) - eventBus.post(PreferenceChangedEvent("downloadBackwardsLimit", downloadBackwardsLimit)) - } - - private val _downloadCountLimit = DefaultOption(100) { it in -1..MAX_VALUE } - val downloadCountLimit: Int get() = _downloadCountLimit.get() - var newDownloadCountLimit: Int? - get() = unsupported - set(value) { - _downloadCountLimit.set(value) - eventBus.post(DownloadCountLimitChangedEvent(downloadCountLimit)) - eventBus.post(PreferenceChangedEvent("downloadCountLimit", downloadCountLimit)) - } - private val _postsPerPage = DefaultOption(10) { it in 1..MAX_VALUE } val postsPerPage: Int get() = _postsPerPage.get() var newPostsPerPage: Int? @@ -138,8 +118,6 @@ class Preferences(private val eventBus: EventBus) { fun saveTo(configuration: Configuration) { configuration.getIntValue("Option/ConfigurationVersion").value = 0 configuration.getIntValue("Option/InsertionDelay").value = _insertionDelay.real - configuration.getIntValue("Option/DownloadBackwardsLimit").value = _downloadBackwardsLimit.real - configuration.getIntValue("Option/DownloadCountLimit").value = _downloadCountLimit.real configuration.getIntValue("Option/PostsPerPage").value = _postsPerPage.real configuration.getIntValue("Option/ImagesPerPage").value = _imagesPerPage.real configuration.getIntValue("Option/CharactersPerPost").value = _charactersPerPost.real diff --git a/src/main/kotlin/net/pterodactylus/sone/core/PreferencesLoader.kt b/src/main/kotlin/net/pterodactylus/sone/core/PreferencesLoader.kt index 2b71a8e93..62b60ae9d 100644 --- a/src/main/kotlin/net/pterodactylus/sone/core/PreferencesLoader.kt +++ b/src/main/kotlin/net/pterodactylus/sone/core/PreferencesLoader.kt @@ -10,8 +10,6 @@ class PreferencesLoader(private val preferences: Preferences) { fun loadFrom(configuration: Configuration) { loadInsertionDelay(configuration) - loadDownloadBackwardsLimit(configuration) - loadDownloadCountLimit(configuration) loadPostsPerPage(configuration) loadImagesPerPage(configuration) loadCharactersPerPost(configuration) @@ -26,14 +24,6 @@ class PreferencesLoader(private val preferences: Preferences) { preferences.newInsertionDelay = configuration.getIntValue("Option/InsertionDelay").getValue(null) } - private fun loadDownloadBackwardsLimit(configuration: Configuration) { - preferences.newDownloadBackwardsLimit = configuration.getIntValue("Option/DownloadBackwardsLimit").getValue(null) - } - - private fun loadDownloadCountLimit(configuration: Configuration) { - preferences.newDownloadCountLimit = configuration.getIntValue("Option/DownloadCountLimit").getValue(null) - } - private fun loadPostsPerPage(configuration: Configuration) { preferences.newPostsPerPage = configuration.getIntValue("Option/PostsPerPage").getValue(null) } diff --git a/src/main/kotlin/net/pterodactylus/sone/data/Post.kt b/src/main/kotlin/net/pterodactylus/sone/data/Post.kt index cbfb62c5d..3a654f0e0 100644 --- a/src/main/kotlin/net/pterodactylus/sone/data/Post.kt +++ b/src/main/kotlin/net/pterodactylus/sone/data/Post.kt @@ -1,7 +1,8 @@ package net.pterodactylus.sone.data import java.util.Comparator.comparing -import java.util.concurrent.TimeUnit +import kotlin.time.ExperimentalTime +import kotlin.time.days /** * Predicate that returns whether a post is _not_ from the future, @@ -14,8 +15,9 @@ val noFuturePost: (Post) -> Boolean = { it.time <= System.currentTimeMillis() } * Predicate that returns whether a post less than a year old, * i.e. whether it should be visible now. */ +@OptIn(ExperimentalTime::class) @get:JvmName("noOldPost") -val noOldPost: (Post) -> Boolean = { it.time > (System.currentTimeMillis() - (TimeUnit.DAYS.toMillis(365))) } +val noOldPost: (Post, Int) -> Boolean = { p: Post, days: Int -> p.time > (System.currentTimeMillis() - days.days.inMilliseconds) } /** * Comparator that orders posts by their time, newest posts first. diff --git a/src/main/kotlin/net/pterodactylus/sone/data/Reply.kt b/src/main/kotlin/net/pterodactylus/sone/data/Reply.kt index f9403b84c..73c34de0a 100644 --- a/src/main/kotlin/net/pterodactylus/sone/data/Reply.kt +++ b/src/main/kotlin/net/pterodactylus/sone/data/Reply.kt @@ -18,7 +18,8 @@ package net.pterodactylus.sone.data import java.util.Comparator.comparing -import java.util.concurrent.TimeUnit +import kotlin.time.ExperimentalTime +import kotlin.time.days /** * Comparator that orders replies by their time, newest replies first. @@ -31,8 +32,9 @@ val newestReplyFirst: Comparator> = * Predicate that returns whether a reply less than a year old, * i.e. whether it should be visible now. */ +@OptIn(ExperimentalTime::class) @get:JvmName("noOldReply") -val noOldReply: (Reply<*>) -> Boolean = { it.getTime() > (System.currentTimeMillis() - (TimeUnit.DAYS.toMillis(365))) } +val noOldReply: (Reply<*>, Int) -> Boolean = { r: Reply<*>, days: Int -> r.getTime() > (System.currentTimeMillis() - days.days.inMilliseconds) } /** * Predicate that returns whether a reply is _not_ from the future, diff --git a/src/main/kotlin/net/pterodactylus/sone/web/pages/OptionsPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/pages/OptionsPage.kt index c9f09255e..e33b8998d 100644 --- a/src/main/kotlin/net/pterodactylus/sone/web/pages/OptionsPage.kt +++ b/src/main/kotlin/net/pterodactylus/sone/web/pages/OptionsPage.kt @@ -29,6 +29,8 @@ class OptionsPage @Inject constructor(webInterface: WebInterface, loaders: Loade val showNewSoneNotification = "show-notification-new-sones" in soneRequest.parameters val showNewPostNotification = "show-notification-new-posts" in soneRequest.parameters val showNewReplyNotification = "show-notification-new-replies" in soneRequest.parameters + val downloadBackwardsLimitDays = soneRequest.parameters["download-backwards-limit"].emptyToNull + val downloadCountLimitDays = soneRequest.parameters["download-count-limit"].emptyToNull options.isAutoFollow = autoFollow options.isSoneInsertNotificationEnabled = enableSoneInsertNotification @@ -37,7 +39,17 @@ class OptionsPage @Inject constructor(webInterface: WebInterface, loaders: Loade options.isShowNewReplyNotifications = showNewReplyNotification loadLinkedImages?.also { if (cantSetOption { options.loadLinkedImages = LoadExternalContent.valueOf(loadLinkedImages) }) fieldsWithErrors += "load-linked-images" } showCustomAvatars?.also { if (cantSetOption { options.showCustomAvatars = LoadExternalContent.valueOf(showCustomAvatars) }) fieldsWithErrors += "show-custom-avatars" } - } + downloadBackwardsLimitDays?.also { if (cantSetOption { options.downloadBackwardsLimitDays = downloadBackwardsLimitDays.toInt() }) fieldsWithErrors += "download-backwards-limit" } + if (options.downloadBackwardsLimitDays < -1 || downloadBackwardsLimitDays.isNullOrBlank()) { + options.downloadBackwardsLimitDays = 365 + fieldsWithErrors += "download-backwards-limit" + } + downloadCountLimitDays?.also { if (cantSetOption { options.downloadCountLimit = downloadCountLimitDays.toInt() }) fieldsWithErrors += "download-count-limit" } + if (options.downloadCountLimit < -1 || downloadCountLimitDays.isNullOrBlank()) { + options.downloadCountLimit = 100 + fieldsWithErrors += "download-count-limit" + } + } val fullAccessRequired = "require-full-access" in soneRequest.parameters val fcpInterfaceActive = "fcp-interface-active" in soneRequest.parameters val strictFiltering = "strict-filtering" in soneRequest.parameters @@ -51,8 +63,6 @@ class OptionsPage @Inject constructor(webInterface: WebInterface, loaders: Loade val postCutOffLength = soneRequest.parameters["post-cut-off-length"]?.toIntOrNull() val imagesPerPage = soneRequest.parameters["images-per-page"]?.toIntOrNull() val insertionDelay = soneRequest.parameters["insertion-delay"]?.toIntOrNull() - val downloadBackwardsLimit = soneRequest.parameters["download-backwards-limit"]?.toIntOrNull() - val downloadCountLimit = soneRequest.parameters["download-count-limit"]?.toIntOrNull() val fcpFullAccessRequired = soneRequest.parameters["fcp-full-access-required"]?.toIntOrNull() if (cantSetOption { soneRequest.core.preferences.newPostsPerPage = postsPerPage }) fieldsWithErrors += "posts-per-page" @@ -60,8 +70,6 @@ class OptionsPage @Inject constructor(webInterface: WebInterface, loaders: Loade if (cantSetOption { soneRequest.core.preferences.newPostCutOffLength = postCutOffLength }) fieldsWithErrors += "post-cut-off-length" if (cantSetOption { soneRequest.core.preferences.newImagesPerPage = imagesPerPage }) fieldsWithErrors += "images-per-page" if (cantSetOption { soneRequest.core.preferences.newInsertionDelay = insertionDelay }) fieldsWithErrors += "insertion-delay" - if (cantSetOption { soneRequest.core.preferences.newDownloadBackwardsLimit = downloadBackwardsLimit }) fieldsWithErrors += "download-backwards-limit" - if (cantSetOption { soneRequest.core.preferences.newDownloadCountLimit = downloadCountLimit }) fieldsWithErrors += "download-count-limit" fcpFullAccessRequired?.also { if (cantSetOption { soneRequest.core.preferences.newFcpFullAccessRequired = FullAccessRequired.values()[fcpFullAccessRequired] }) fieldsWithErrors += "fcp-full-access-required" } if (fieldsWithErrors.isEmpty()) { @@ -76,13 +84,13 @@ class OptionsPage @Inject constructor(webInterface: WebInterface, loaders: Loade templateContext["show-notification-new-posts"] = options.isShowNewPostNotifications templateContext["show-notification-new-replies"] = options.isShowNewReplyNotifications templateContext["enable-sone-insert-notifications"] = options.isSoneInsertNotificationEnabled + templateContext["download-count-limit"] = options.downloadCountLimit + templateContext["download-backwards-limit"] = options.downloadBackwardsLimitDays templateContext["load-linked-images"] = options.loadLinkedImages.toString() templateContext["show-custom-avatars"] = options.showCustomAvatars.toString() } soneRequest.core.preferences.let { preferences -> templateContext["insertion-delay"] = preferences.insertionDelay - templateContext["download-backwards-limit"] = preferences.downloadBackwardsLimit - templateContext["download-count-limit"] = preferences.downloadCountLimit templateContext["characters-per-post"] = preferences.charactersPerPost templateContext["fcp-full-access-required"] = preferences.fcpFullAccessRequired.ordinal templateContext["images-per-page"] = preferences.imagesPerPage diff --git a/src/main/kotlin/net/pterodactylus/sone/web/pages/SearchPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/pages/SearchPage.kt index 9bbc3e5ba..bc9e24d7f 100644 --- a/src/main/kotlin/net/pterodactylus/sone/web/pages/SearchPage.kt +++ b/src/main/kotlin/net/pterodactylus/sone/web/pages/SearchPage.kt @@ -11,13 +11,16 @@ import net.pterodactylus.sone.web.page.* import net.pterodactylus.sone.web.pages.SearchPage.Optionality.* import net.pterodactylus.util.template.* import net.pterodactylus.util.text.* -import java.util.concurrent.TimeUnit.* import javax.inject.* +import kotlin.time.ExperimentalTime +import kotlin.time.minutes +import kotlin.time.toJavaDuration /** * This page lets the user search for posts and replies that contain certain * words. */ +@OptIn(ExperimentalTime::class) @TemplatePath("/templates/search.html") @ToadletPath("search.html") class SearchPage(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer, ticker: Ticker = Ticker.systemTicker()) : @@ -27,7 +30,7 @@ class SearchPage(webInterface: WebInterface, loaders: Loaders, templateRenderer: constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) : this(webInterface, loaders, templateRenderer, Ticker.systemTicker()) - private val cache: Cache, Pagination> = CacheBuilder.newBuilder().ticker(ticker).expireAfterAccess(5, MINUTES).build() + private val cache: Cache, Pagination> = CacheBuilder.newBuilder().ticker(ticker).expireAfterAccess(5.minutes.toJavaDuration()).build() override fun handleRequest(soneRequest: SoneRequest, templateContext: TemplateContext) { val startTime = System.currentTimeMillis() diff --git a/src/main/resources/i18n/sone.de.properties b/src/main/resources/i18n/sone.de.properties index 13ed14ff9..15db1eaa0 100644 --- a/src/main/resources/i18n/sone.de.properties +++ b/src/main/resources/i18n/sone.de.properties @@ -417,6 +417,8 @@ WebInterface.DefaultText.BirthMonth=Monat WebInterface.DefaultText.BirthYear=Jahr WebInterface.DefaultText.FieldName=Feldname WebInterface.DefaultText.Option.InsertionDelay=Zeit, die vor dem Hochladen einer geänderten Sone gewartet wird (in Sekunden) +WebInterface.DefaultText.Option.DownloadBackwardsLimit=Maximal-Alter von Nachrichten Anderer (in Tagen) +WebInterface.DefaultText.Option.DownloadCountLimit=Maximal-Zahl von Posts und von Antworten WebInterface.DefaultText.Search=Was suchen Sie? WebInterface.DefaultText.CreateAlbum.Name=Albumtitel WebInterface.DefaultText.CreateAlbum.Description=Albumbeschreibung diff --git a/src/main/resources/i18n/sone.en.properties b/src/main/resources/i18n/sone.en.properties index 6e77b35ba..d88ed51a5 100644 --- a/src/main/resources/i18n/sone.en.properties +++ b/src/main/resources/i18n/sone.en.properties @@ -430,6 +430,8 @@ WebInterface.DefaultText.Option.PostsPerPage=Number of posts to show on a page WebInterface.DefaultText.Option.ImagesPerPage=Number of images to show on a page WebInterface.DefaultText.Option.CharactersPerPost=Number of characters a post must have to be shortened WebInterface.DefaultText.Option.PostCutOffLength=Number of characters for the snippet of the shortened post +WebInterface.DefaultText.Option.DownloadCountLimit=Maximum number of posts and of replies +WebInterface.DefaultText.Option.DownloadBackwardsLimit=Maximum age of messages of others (in days) WebInterface.Button.Comment=Comment WebInterface.Confirmation.DeletePostButton=Yes, delete! WebInterface.Confirmation.DeleteReplyButton=Yes, delete! diff --git a/src/main/resources/templates/options.html b/src/main/resources/templates/options.html index beec5b748..bca9274a0 100644 --- a/src/main/resources/templates/options.html +++ b/src/main/resources/templates/options.html @@ -66,6 +66,18 @@

<%= Page.Options.Section.SoneSpecificOptions.Title|l10n|html>

<%= Page.Options.Option.ShowNotificationNewReplies.Description|l10n|html>

+

<%= Page.Options.Option.DownloadBackwardsLimit.Description|l10n|html>

+ <%if =download-backwards-limit|in collection=fieldErrors> +

<%= Page.Options.Warnings.ValueNotChanged|l10n|html>

+ <%/if> +

disabled="disabled"<%/if> value="<% download-backwards-limit|html>" />

+ +

<%= Page.Options.Option.DownloadCountLimit.Description|l10n|html>

+ <%if =download-count-limit|in collection=fieldErrors> +

<%= Page.Options.Warnings.ValueNotChanged|l10n|html>

+ <%/if> +

disabled="disabled"<%/if> value="<% download-count-limit|html>" />

+

<%= Page.Options.Section.AvatarOptions.Title|l10n|html>

<%= Page.Options.Option.ShowAvatars.Description|l10n|html>

@@ -128,18 +140,6 @@

<%= Page.Options.Section.RuntimeOptions.Title|l10n|html>

<%/if>

-

<%= Page.Options.Option.DownloadBackwardsLimit.Description|l10n|html>

- <%if =download-backwards-limit|in collection=fieldErrors> -

<%= Page.Options.Warnings.ValueNotChanged|l10n|html>

- <%/if> -

- -

<%= Page.Options.Option.DownloadCountLimit.Description|l10n|html>

- <%if =download-count-limit|in collection=fieldErrors> -

<%= Page.Options.Warnings.ValueNotChanged|l10n|html>

- <%/if> -

-

<%= Page.Options.Option.PostsPerPage.Description|l10n|html>

<%if =posts-per-page|in collection=fieldErrors>

<%= Page.Options.Warnings.ValueNotChanged|l10n|html>

diff --git a/src/test/kotlin/net/pterodactylus/sone/data/SoneTest.kt b/src/test/kotlin/net/pterodactylus/sone/data/SoneTest.kt index 8ffe54f1d..c941b819d 100644 --- a/src/test/kotlin/net/pterodactylus/sone/data/SoneTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/data/SoneTest.kt @@ -18,8 +18,6 @@ package net.pterodactylus.sone.data import net.pterodactylus.sone.data.impl.* -import net.pterodactylus.sone.data.impl.SoneImpl.filterRemotePosts -import net.pterodactylus.sone.data.impl.SoneImpl.filterRemoteReplies import net.pterodactylus.sone.test.* import org.hamcrest.MatcherAssert.* import org.hamcrest.Matchers.* @@ -126,25 +124,33 @@ class SoneTest { @Test fun `post filtering skips old posts`() { var oldPosts = listOf(createPost(time = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(400))); - assertThat(filterRemotePosts(oldPosts), empty()) + val sone1 = object : IdOnlySone("1"){ + } + assertThat(sone1.filterRemotePosts(oldPosts), empty()) } @Test fun `post filtering keeps recent posts`() { var recentPosts = listOf(createPost(time = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(300))); - assertThat(filterRemotePosts(recentPosts), hasSize(recentPosts.size)); + val sone1 = object : IdOnlySone("1"){ + } + assertThat(sone1.filterRemotePosts(recentPosts), hasSize(recentPosts.size)); } @Test fun `reply filtering skips old replies`() { var oldReplies = listOf(emptyPostReply(time = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(400))); - assertThat(filterRemoteReplies(oldReplies), empty()) + val sone1 = object : IdOnlySone("1"){ + } + assertThat(sone1.filterRemoteReplies(oldReplies), empty()) } @Test fun `reply filtering keeps recent replies`() { var recentReplies = listOf(emptyPostReply(time = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(300))); - assertThat(filterRemoteReplies(recentReplies), hasSize(recentReplies.size)) + val sone1 = object : IdOnlySone("1"){ + } + assertThat(sone1.filterRemoteReplies(recentReplies), hasSize(recentReplies.size)) } @Test diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/OptionsPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/OptionsPageTest.kt index 921c89311..a48856378 100644 --- a/src/test/kotlin/net/pterodactylus/sone/web/pages/OptionsPageTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/OptionsPageTest.kt @@ -20,8 +20,6 @@ class OptionsPageTest : WebPageTest(::OptionsPage) { @Before fun setupPreferences() { core.preferences.newInsertionDelay = 1 - core.preferences.newDownloadBackwardsLimit = 30 - core.preferences.newDownloadCountLimit = 10 core.preferences.newCharactersPerPost = 50 core.preferences.newFcpFullAccessRequired = WRITING core.preferences.newImagesPerPage = 4 @@ -40,6 +38,8 @@ class OptionsPageTest : WebPageTest(::OptionsPage) { isShowNewReplyNotifications = true isShowNewSoneNotifications = true isSoneInsertNotificationEnabled = true + downloadBackwardsLimitDays = 30 + downloadCountLimit = 10 loadLinkedImages = FOLLOWED showCustomAvatars = FOLLOWED }) @@ -210,17 +210,17 @@ class OptionsPageTest : WebPageTest(::OptionsPage) { @Test fun `download backwards limit can be set to 0 days`() { - verifyThatPreferencesCanBeSet("download-backwards-limit", "0", 0) { core.preferences.downloadBackwardsLimit } + verifyThatOptionCanBeSet("download-backwards-limit", "0", 0) { currentSone.options.downloadBackwardsLimitDays } } @Test fun `download backwards limit can be set to -1 days`() { - verifyThatPreferencesCanBeSet("download-backwards-limit", "-1", -1) { core.preferences.downloadBackwardsLimit } + verifyThatOptionCanBeSet("download-backwards-limit", "-1", -1) { currentSone.options.downloadBackwardsLimitDays } } @Test fun `setting download backwards limit to an invalid value will reset it`() { - verifyThatPreferencesCanBeSet("download-backwards-limit", "foo", 365) { core.preferences.downloadBackwardsLimit } + verifyThatOptionCanBeSet("download-backwards-limit", "foo", 365) { currentSone.options.downloadBackwardsLimitDays } } @Test @@ -230,17 +230,17 @@ class OptionsPageTest : WebPageTest(::OptionsPage) { @Test fun `download count limit can be set to -1 posts or replies`() { - verifyThatPreferencesCanBeSet("download-count-limit", "-1", -1) { core.preferences.downloadCountLimit } + verifyThatOptionCanBeSet("download-count-limit", "-1", -1) { currentSone.options.downloadCountLimit } } @Test fun `download count limit can be set to 0 posts or replies`() { - verifyThatPreferencesCanBeSet("download-count-limit", "0", 0) { core.preferences.downloadCountLimit } + verifyThatOptionCanBeSet("download-count-limit", "0", 0) { currentSone.options.downloadCountLimit } } @Test fun `setting download count limit to an invalid value will reset it`() { - verifyThatPreferencesCanBeSet("download-count-limit", "foo", 100) { core.preferences.downloadCountLimit } + verifyThatOptionCanBeSet("download-count-limit", "foo", 100) { currentSone.options.downloadCountLimit } } @Test From cfb862457dde28eba3dd805ade3ea61627e1e307 Mon Sep 17 00:00:00 2001 From: Arne Babenhauserheide Date: Thu, 4 Jul 2024 22:00:58 +0200 Subject: [PATCH 5/6] Harmonize order of Webinterface Option Default Text --- src/main/resources/i18n/sone.de.properties | 10 +++++----- src/main/resources/i18n/sone.en.properties | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/resources/i18n/sone.de.properties b/src/main/resources/i18n/sone.de.properties index 15db1eaa0..fdab72ebc 100644 --- a/src/main/resources/i18n/sone.de.properties +++ b/src/main/resources/i18n/sone.de.properties @@ -416,9 +416,13 @@ WebInterface.DefaultText.BirthDay=Tag WebInterface.DefaultText.BirthMonth=Monat WebInterface.DefaultText.BirthYear=Jahr WebInterface.DefaultText.FieldName=Feldname -WebInterface.DefaultText.Option.InsertionDelay=Zeit, die vor dem Hochladen einer geänderten Sone gewartet wird (in Sekunden) +WebInterface.DefaultText.Option.CharactersPerPost=Anzahl der Zeichen, die eine Nachricht haben muss, damit er gekürzt wird WebInterface.DefaultText.Option.DownloadBackwardsLimit=Maximal-Alter von Nachrichten Anderer (in Tagen) WebInterface.DefaultText.Option.DownloadCountLimit=Maximal-Zahl von Posts und von Antworten +WebInterface.DefaultText.Option.ImagesPerPage=Anzahl der Bilder pro Seite +WebInterface.DefaultText.Option.InsertionDelay=Zeit, die vor dem Hochladen einer geänderten Sone gewartet wird (in Sekunden) +WebInterface.DefaultText.Option.PostCutOffLength=Anzahl der Zeichen, die von einer gekürzten Nachricht angezeigt werden +WebInterface.DefaultText.Option.PostsPerPage=Anzahl der Nachrichten pro Seite WebInterface.DefaultText.Search=Was suchen Sie? WebInterface.DefaultText.CreateAlbum.Name=Albumtitel WebInterface.DefaultText.CreateAlbum.Description=Albumbeschreibung @@ -428,10 +432,6 @@ WebInterface.DefaultText.UploadImage.Title=Bildtitel WebInterface.DefaultText.UploadImage.Description=Bildbeschreibung WebInterface.DefaultText.EditImage.Title=Bildtitel WebInterface.DefaultText.EditImage.Description=Bildbeschreibung -WebInterface.DefaultText.Option.PostsPerPage=Anzahl der Nachrichten pro Seite -WebInterface.DefaultText.Option.ImagesPerPage=Anzahl der Bilder pro Seite -WebInterface.DefaultText.Option.CharactersPerPost=Anzahl der Zeichen, die eine Nachricht haben muss, damit er gekürzt wird -WebInterface.DefaultText.Option.PostCutOffLength=Anzahl der Zeichen, die von einer gekürzten Nachricht angezeigt werden WebInterface.Button.Comment=Antworten WebInterface.Confirmation.DeletePostButton=Ja, löschen! WebInterface.Confirmation.DeleteReplyButton=Ja, löschen! diff --git a/src/main/resources/i18n/sone.en.properties b/src/main/resources/i18n/sone.en.properties index d88ed51a5..e518fd1f7 100644 --- a/src/main/resources/i18n/sone.en.properties +++ b/src/main/resources/i18n/sone.en.properties @@ -416,7 +416,13 @@ WebInterface.DefaultText.BirthDay=Day WebInterface.DefaultText.BirthMonth=Month WebInterface.DefaultText.BirthYear=Year WebInterface.DefaultText.FieldName=Field name +WebInterface.DefaultText.Option.CharactersPerPost=Number of characters a post must have to be shortened +WebInterface.DefaultText.Option.DownloadBackwardsLimit=Maximum age of messages of others (in days) +WebInterface.DefaultText.Option.DownloadCountLimit=Maximum number of posts and of replies +WebInterface.DefaultText.Option.ImagesPerPage=Number of images to show on a page WebInterface.DefaultText.Option.InsertionDelay=Time to wait after a Sone is modified before insert (in seconds) +WebInterface.DefaultText.Option.PostCutOffLength=Number of characters for the snippet of the shortened post +WebInterface.DefaultText.Option.PostsPerPage=Number of posts to show on a page WebInterface.DefaultText.Search=What are you looking for? WebInterface.DefaultText.CreateAlbum.Name=Album title WebInterface.DefaultText.CreateAlbum.Description=Album description @@ -426,12 +432,6 @@ WebInterface.DefaultText.UploadImage.Title=Image title WebInterface.DefaultText.UploadImage.Description=Image description WebInterface.DefaultText.EditImage.Title=Image title WebInterface.DefaultText.EditImage.Description=Image description -WebInterface.DefaultText.Option.PostsPerPage=Number of posts to show on a page -WebInterface.DefaultText.Option.ImagesPerPage=Number of images to show on a page -WebInterface.DefaultText.Option.CharactersPerPost=Number of characters a post must have to be shortened -WebInterface.DefaultText.Option.PostCutOffLength=Number of characters for the snippet of the shortened post -WebInterface.DefaultText.Option.DownloadCountLimit=Maximum number of posts and of replies -WebInterface.DefaultText.Option.DownloadBackwardsLimit=Maximum age of messages of others (in days) WebInterface.Button.Comment=Comment WebInterface.Confirmation.DeletePostButton=Yes, delete! WebInterface.Confirmation.DeleteReplyButton=Yes, delete! From 003f5c1d31d1f6ffab6ffaf24c364171fdd633fd Mon Sep 17 00:00:00 2001 From: Arne Babenhauserheide Date: Sat, 15 Mar 2025 01:28:01 +0100 Subject: [PATCH 6/6] Fix indentation (tabify) --- .../net/pterodactylus/sone/web/pages/OptionsPageTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/OptionsPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/OptionsPageTest.kt index a48856378..7a97eb3af 100644 --- a/src/test/kotlin/net/pterodactylus/sone/web/pages/OptionsPageTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/OptionsPageTest.kt @@ -38,8 +38,8 @@ class OptionsPageTest : WebPageTest(::OptionsPage) { isShowNewReplyNotifications = true isShowNewSoneNotifications = true isSoneInsertNotificationEnabled = true - downloadBackwardsLimitDays = 30 - downloadCountLimit = 10 + downloadBackwardsLimitDays = 30 + downloadCountLimit = 10 loadLinkedImages = FOLLOWED showCustomAvatars = FOLLOWED })