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 f2f2ea620..5a072ed0f 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,38 @@ 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 + : this.filterRemotePosts(sortedPosts); synchronized (this) { this.posts.clear(); - this.posts.addAll(posts); + this.posts.addAll(limitedPosts); } 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 List filterRemotePosts(List sortedPosts) { + return sortedPosts.stream() + .filter(post -> noOldPost().invoke(post, this.getOptions().getDownloadBackwardsLimitDays())) + .limit(this.getOptions().getDownloadCountLimit()) + .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 +460,28 @@ 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 + : this.filterRemoteReplies(sortedReplies); this.replies.clear(); - this.replies.addAll(replies); + this.replies.addAll(limitedReplies); return this; } + @Override + @NotNull + public List filterRemoteReplies(List sortedReplies) { + return sortedReplies.stream() + .filter(reply -> noOldReply().invoke(reply, this.getOptions().getDownloadBackwardsLimitDays())) + .limit(this.getOptions().getDownloadCountLimit()) + .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/core/Preferences.kt b/src/main/kotlin/net/pterodactylus/sone/core/Preferences.kt index fe1e3c3ec..9d9d04b8c 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 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/data/Post.kt b/src/main/kotlin/net/pterodactylus/sone/data/Post.kt index d87bd3c2e..3a654f0e0 100644 --- a/src/main/kotlin/net/pterodactylus/sone/data/Post.kt +++ b/src/main/kotlin/net/pterodactylus/sone/data/Post.kt @@ -1,6 +1,8 @@ package net.pterodactylus.sone.data import java.util.Comparator.comparing +import kotlin.time.ExperimentalTime +import kotlin.time.days /** * Predicate that returns whether a post is _not_ from the future, @@ -9,6 +11,14 @@ 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. + */ +@OptIn(ExperimentalTime::class) +@get:JvmName("noOldPost") +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 cfc940a20..73c34de0a 100644 --- a/src/main/kotlin/net/pterodactylus/sone/data/Reply.kt +++ b/src/main/kotlin/net/pterodactylus/sone/data/Reply.kt @@ -18,6 +18,8 @@ package net.pterodactylus.sone.data import java.util.Comparator.comparing +import kotlin.time.ExperimentalTime +import kotlin.time.days /** * Comparator that orders replies by their time, newest replies first. @@ -26,6 +28,14 @@ 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. + */ +@OptIn(ExperimentalTime::class) +@get:JvmName("noOldReply") +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, * i.e. whether it should be visible now. 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..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 @@ -72,6 +84,8 @@ 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() } 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 cd42c458a..fdab72ebc 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. @@ -414,7 +416,13 @@ WebInterface.DefaultText.BirthDay=Tag WebInterface.DefaultText.BirthMonth=Monat WebInterface.DefaultText.BirthYear=Jahr WebInterface.DefaultText.FieldName=Feldname +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 @@ -424,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 7c65dcf84..e518fd1f7 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. @@ -414,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 @@ -424,10 +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.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 9ef5ceb70..bca9274a0 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); }); @@ -60,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>

diff --git a/src/test/kotlin/net/pterodactylus/sone/data/SoneTest.kt b/src/test/kotlin/net/pterodactylus/sone/data/SoneTest.kt index fb2312125..c941b819d 100644 --- a/src/test/kotlin/net/pterodactylus/sone/data/SoneTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/data/SoneTest.kt @@ -21,6 +21,7 @@ import net.pterodactylus.sone.data.impl.* import net.pterodactylus.sone.test.* import org.hamcrest.MatcherAssert.* import org.hamcrest.Matchers.* +import java.util.concurrent.TimeUnit import kotlin.test.* /** @@ -120,6 +121,38 @@ 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))); + 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))); + 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))); + 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))); + val sone1 = object : IdOnlySone("1"){ + } + assertThat(sone1.filterRemoteReplies(recentReplies), hasSize(recentReplies.size)) + } + @Test fun `image count comparator sorts Sones correctly if number of images is different`() { val sone1 = object : IdOnlySone("1") { 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..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,6 +38,8 @@ class OptionsPageTest : WebPageTest(::OptionsPage) { isShowNewReplyNotifications = true isShowNewSoneNotifications = true isSoneInsertNotificationEnabled = true + downloadBackwardsLimitDays = 30 + downloadCountLimit = 10 loadLinkedImages = FOLLOWED showCustomAvatars = FOLLOWED }) @@ -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)) @@ -195,10 +199,50 @@ 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 } } + @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`() { + verifyThatOptionCanBeSet("download-backwards-limit", "0", 0) { currentSone.options.downloadBackwardsLimitDays } + } + + @Test + fun `download backwards limit can be set to -1 days`() { + verifyThatOptionCanBeSet("download-backwards-limit", "-1", -1) { currentSone.options.downloadBackwardsLimitDays } + } + + @Test + fun `setting download backwards limit to an invalid value will reset it`() { + verifyThatOptionCanBeSet("download-backwards-limit", "foo", 365) { currentSone.options.downloadBackwardsLimitDays } + } + + @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`() { + verifyThatOptionCanBeSet("download-count-limit", "-1", -1) { currentSone.options.downloadCountLimit } + } + + @Test + fun `download count limit can be set to 0 posts or replies`() { + verifyThatOptionCanBeSet("download-count-limit", "0", 0) { currentSone.options.downloadCountLimit } + } + + @Test + fun `setting download count limit to an invalid value will reset it`() { + verifyThatOptionCanBeSet("download-count-limit", "foo", 100) { currentSone.options.downloadCountLimit } + } + @Test fun `characters per post can not be set to less than -1`() { verifyThatWrongValueForPreferenceIsDetected("characters-per-post", "-2")