Merge branch 'release-0.9.5' 0.9.5
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Mon, 11 Jul 2016 17:55:51 +0000 (19:55 +0200)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Mon, 11 Jul 2016 17:55:51 +0000 (19:55 +0200)
301 files changed:
pom.xml
src/main/java/net/pterodactylus/sone/core/ConfigurationSoneParser.java
src/main/java/net/pterodactylus/sone/core/Core.java
src/main/java/net/pterodactylus/sone/core/FreenetInterface.java
src/main/java/net/pterodactylus/sone/core/ImageInserter.java
src/main/java/net/pterodactylus/sone/core/Options.java
src/main/java/net/pterodactylus/sone/core/Preferences.java
src/main/java/net/pterodactylus/sone/core/SoneDownloaderImpl.java
src/main/java/net/pterodactylus/sone/core/SoneException.java
src/main/java/net/pterodactylus/sone/core/SoneInsertException.java
src/main/java/net/pterodactylus/sone/core/SoneInserter.java
src/main/java/net/pterodactylus/sone/core/SoneParser.java
src/main/java/net/pterodactylus/sone/core/SoneRescuer.java
src/main/java/net/pterodactylus/sone/core/SoneUri.java
src/main/java/net/pterodactylus/sone/core/UpdateChecker.java
src/main/java/net/pterodactylus/sone/core/WebOfTrustUpdaterImpl.java
src/main/java/net/pterodactylus/sone/core/event/ImageEvent.java
src/main/java/net/pterodactylus/sone/core/event/ImageInsertAbortedEvent.java
src/main/java/net/pterodactylus/sone/core/event/ImageInsertFailedEvent.java
src/main/java/net/pterodactylus/sone/core/event/ImageInsertFinishedEvent.java
src/main/java/net/pterodactylus/sone/core/event/ImageInsertStartedEvent.java
src/main/java/net/pterodactylus/sone/core/event/MarkPostKnownEvent.java
src/main/java/net/pterodactylus/sone/core/event/MarkPostReplyKnownEvent.java
src/main/java/net/pterodactylus/sone/core/event/MarkSoneKnownEvent.java
src/main/java/net/pterodactylus/sone/core/event/NewPostFoundEvent.java
src/main/java/net/pterodactylus/sone/core/event/NewPostReplyFoundEvent.java
src/main/java/net/pterodactylus/sone/core/event/NewSoneFoundEvent.java
src/main/java/net/pterodactylus/sone/core/event/PostEvent.java
src/main/java/net/pterodactylus/sone/core/event/PostRemovedEvent.java
src/main/java/net/pterodactylus/sone/core/event/PostReplyEvent.java
src/main/java/net/pterodactylus/sone/core/event/PostReplyRemovedEvent.java
src/main/java/net/pterodactylus/sone/core/event/SoneEvent.java
src/main/java/net/pterodactylus/sone/core/event/SoneInsertAbortedEvent.java
src/main/java/net/pterodactylus/sone/core/event/SoneInsertedEvent.java
src/main/java/net/pterodactylus/sone/core/event/SoneInsertingEvent.java
src/main/java/net/pterodactylus/sone/core/event/SoneLockedEvent.java
src/main/java/net/pterodactylus/sone/core/event/SoneRemovedEvent.java
src/main/java/net/pterodactylus/sone/core/event/SoneUnlockedEvent.java
src/main/java/net/pterodactylus/sone/core/event/UpdateFoundEvent.java
src/main/java/net/pterodactylus/sone/data/Album.java
src/main/java/net/pterodactylus/sone/data/Client.java
src/main/java/net/pterodactylus/sone/data/Fingerprintable.java
src/main/java/net/pterodactylus/sone/data/Identified.java
src/main/java/net/pterodactylus/sone/data/Image.java
src/main/java/net/pterodactylus/sone/data/Post.java
src/main/java/net/pterodactylus/sone/data/PostReply.java
src/main/java/net/pterodactylus/sone/data/Profile.java
src/main/java/net/pterodactylus/sone/data/Reply.java
src/main/java/net/pterodactylus/sone/data/Sone.java
src/main/java/net/pterodactylus/sone/data/TemporaryImage.java
src/main/java/net/pterodactylus/sone/data/impl/AbstractAlbumBuilder.java
src/main/java/net/pterodactylus/sone/data/impl/AbstractImageBuilder.java
src/main/java/net/pterodactylus/sone/data/impl/AbstractPostBuilder.java
src/main/java/net/pterodactylus/sone/data/impl/AbstractPostReplyBuilder.java
src/main/java/net/pterodactylus/sone/data/impl/AbstractReplyBuilder.java
src/main/java/net/pterodactylus/sone/data/impl/AlbumBuilderImpl.java
src/main/java/net/pterodactylus/sone/data/impl/AlbumImpl.java
src/main/java/net/pterodactylus/sone/data/impl/DefaultPostBuilderFactory.java
src/main/java/net/pterodactylus/sone/data/impl/DefaultPostReplyBuilderFactory.java
src/main/java/net/pterodactylus/sone/data/impl/ImageBuilderImpl.java
src/main/java/net/pterodactylus/sone/data/impl/ImageImpl.java
src/main/java/net/pterodactylus/sone/data/impl/PostBuilderImpl.java
src/main/java/net/pterodactylus/sone/data/impl/PostImpl.java
src/main/java/net/pterodactylus/sone/data/impl/PostReplyBuilderImpl.java
src/main/java/net/pterodactylus/sone/data/impl/PostReplyImpl.java
src/main/java/net/pterodactylus/sone/data/impl/ReplyImpl.java
src/main/java/net/pterodactylus/sone/data/impl/SoneImpl.java
src/main/java/net/pterodactylus/sone/database/AlbumBuilder.java
src/main/java/net/pterodactylus/sone/database/AlbumBuilderFactory.java
src/main/java/net/pterodactylus/sone/database/AlbumDatabase.java
src/main/java/net/pterodactylus/sone/database/AlbumProvider.java
src/main/java/net/pterodactylus/sone/database/AlbumStore.java
src/main/java/net/pterodactylus/sone/database/Database.java
src/main/java/net/pterodactylus/sone/database/DatabaseException.java
src/main/java/net/pterodactylus/sone/database/ImageBuilder.java
src/main/java/net/pterodactylus/sone/database/ImageBuilderFactory.java
src/main/java/net/pterodactylus/sone/database/ImageDatabase.java
src/main/java/net/pterodactylus/sone/database/ImageProvider.java
src/main/java/net/pterodactylus/sone/database/ImageStore.java
src/main/java/net/pterodactylus/sone/database/PostBuilder.java
src/main/java/net/pterodactylus/sone/database/PostBuilderFactory.java
src/main/java/net/pterodactylus/sone/database/PostDatabase.java
src/main/java/net/pterodactylus/sone/database/PostProvider.java
src/main/java/net/pterodactylus/sone/database/PostReplyBuilder.java
src/main/java/net/pterodactylus/sone/database/PostReplyBuilderFactory.java
src/main/java/net/pterodactylus/sone/database/PostReplyDatabase.java
src/main/java/net/pterodactylus/sone/database/PostReplyProvider.java
src/main/java/net/pterodactylus/sone/database/PostReplyStore.java
src/main/java/net/pterodactylus/sone/database/PostStore.java
src/main/java/net/pterodactylus/sone/database/ReplyBuilder.java
src/main/java/net/pterodactylus/sone/database/SoneProvider.java
src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java
src/main/java/net/pterodactylus/sone/database/memory/MemoryPost.java
src/main/java/net/pterodactylus/sone/database/memory/MemoryPostBuilder.java
src/main/java/net/pterodactylus/sone/database/memory/MemoryPostReply.java
src/main/java/net/pterodactylus/sone/database/memory/MemoryPostReplyBuilder.java
src/main/java/net/pterodactylus/sone/fcp/AbstractSoneCommand.java
src/main/java/net/pterodactylus/sone/fcp/CreatePostCommand.java
src/main/java/net/pterodactylus/sone/fcp/CreateReplyCommand.java
src/main/java/net/pterodactylus/sone/fcp/DeletePostCommand.java
src/main/java/net/pterodactylus/sone/fcp/DeleteReplyCommand.java
src/main/java/net/pterodactylus/sone/fcp/FcpInterface.java
src/main/java/net/pterodactylus/sone/fcp/GetLocalSonesCommand.java
src/main/java/net/pterodactylus/sone/fcp/GetPostCommand.java
src/main/java/net/pterodactylus/sone/fcp/GetPostFeedCommand.java
src/main/java/net/pterodactylus/sone/fcp/GetPostsCommand.java
src/main/java/net/pterodactylus/sone/fcp/GetSoneCommand.java
src/main/java/net/pterodactylus/sone/fcp/GetSonesCommand.java
src/main/java/net/pterodactylus/sone/fcp/LikePostCommand.java
src/main/java/net/pterodactylus/sone/fcp/LikeReplyCommand.java
src/main/java/net/pterodactylus/sone/fcp/LockSoneCommand.java
src/main/java/net/pterodactylus/sone/fcp/UnlockSoneCommand.java
src/main/java/net/pterodactylus/sone/fcp/VersionCommand.java
src/main/java/net/pterodactylus/sone/freenet/L10nFilter.java
src/main/java/net/pterodactylus/sone/freenet/PluginStoreConfigurationBackend.java
src/main/java/net/pterodactylus/sone/freenet/SimpleFieldSetBuilder.java
src/main/java/net/pterodactylus/sone/freenet/fcp/AbstractCommand.java
src/main/java/net/pterodactylus/sone/freenet/fcp/Command.java
src/main/java/net/pterodactylus/sone/freenet/fcp/FcpException.java
src/main/java/net/pterodactylus/sone/freenet/plugin/PluginConnector.java
src/main/java/net/pterodactylus/sone/freenet/plugin/PluginException.java
src/main/java/net/pterodactylus/sone/freenet/plugin/event/ReceivedReplyEvent.java
src/main/java/net/pterodactylus/sone/freenet/wot/Context.java
src/main/java/net/pterodactylus/sone/freenet/wot/DefaultIdentity.java
src/main/java/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentity.java
src/main/java/net/pterodactylus/sone/freenet/wot/Identity.java
src/main/java/net/pterodactylus/sone/freenet/wot/IdentityChangeDetector.java
src/main/java/net/pterodactylus/sone/freenet/wot/IdentityChangeEventSender.java
src/main/java/net/pterodactylus/sone/freenet/wot/IdentityLoader.java
src/main/java/net/pterodactylus/sone/freenet/wot/IdentityManagerImpl.java
src/main/java/net/pterodactylus/sone/freenet/wot/OwnIdentity.java
src/main/java/net/pterodactylus/sone/freenet/wot/Trust.java
src/main/java/net/pterodactylus/sone/freenet/wot/WebOfTrustConnector.java
src/main/java/net/pterodactylus/sone/freenet/wot/WebOfTrustException.java
src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityAddedEvent.java
src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityEvent.java
src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityRemovedEvent.java
src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityUpdatedEvent.java
src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityAddedEvent.java
src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityEvent.java
src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityRemovedEvent.java
src/main/java/net/pterodactylus/sone/main/SonePlugin.java
src/main/java/net/pterodactylus/sone/notify/ListNotification.java
src/main/java/net/pterodactylus/sone/notify/ListNotificationFilter.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/notify/ListNotificationFilters.java [deleted file]
src/main/java/net/pterodactylus/sone/notify/PostVisibilityFilter.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/notify/ReplyVisibilityFilter.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/template/AlbumAccessor.java
src/main/java/net/pterodactylus/sone/template/CollectionAccessor.java
src/main/java/net/pterodactylus/sone/template/CssClassNameFilter.java
src/main/java/net/pterodactylus/sone/template/GetPagePlugin.java
src/main/java/net/pterodactylus/sone/template/HttpRequestAccessor.java
src/main/java/net/pterodactylus/sone/template/IdentityAccessor.java
src/main/java/net/pterodactylus/sone/template/ImageAccessor.java
src/main/java/net/pterodactylus/sone/template/ImageLinkFilter.java
src/main/java/net/pterodactylus/sone/template/JavascriptFilter.java
src/main/java/net/pterodactylus/sone/template/ParserFilter.java
src/main/java/net/pterodactylus/sone/template/PostAccessor.java
src/main/java/net/pterodactylus/sone/template/ProfileAccessor.java
src/main/java/net/pterodactylus/sone/template/ReplyAccessor.java
src/main/java/net/pterodactylus/sone/template/ReplyGroupFilter.java
src/main/java/net/pterodactylus/sone/template/RequestChangeFilter.java
src/main/java/net/pterodactylus/sone/template/SoneAccessor.java
src/main/java/net/pterodactylus/sone/template/SubstringFilter.java
src/main/java/net/pterodactylus/sone/template/TrustAccessor.java
src/main/java/net/pterodactylus/sone/template/UniqueElementFilter.java
src/main/java/net/pterodactylus/sone/template/UnknownDateFilter.java
src/main/java/net/pterodactylus/sone/text/FreenetLinkPart.java
src/main/java/net/pterodactylus/sone/text/LinkPart.java
src/main/java/net/pterodactylus/sone/text/Parser.java
src/main/java/net/pterodactylus/sone/text/ParserContext.java
src/main/java/net/pterodactylus/sone/text/Part.java
src/main/java/net/pterodactylus/sone/text/PartContainer.java
src/main/java/net/pterodactylus/sone/text/PlainTextPart.java
src/main/java/net/pterodactylus/sone/text/PostPart.java
src/main/java/net/pterodactylus/sone/text/SonePart.java
src/main/java/net/pterodactylus/sone/text/SoneTextParser.java
src/main/java/net/pterodactylus/sone/text/SoneTextParserContext.java
src/main/java/net/pterodactylus/sone/text/TextFilter.java
src/main/java/net/pterodactylus/sone/utils/IntegerRangePredicate.java
src/main/java/net/pterodactylus/sone/web/AboutPage.java
src/main/java/net/pterodactylus/sone/web/BookmarkPage.java
src/main/java/net/pterodactylus/sone/web/BookmarksPage.java
src/main/java/net/pterodactylus/sone/web/CreateAlbumPage.java
src/main/java/net/pterodactylus/sone/web/CreatePostPage.java
src/main/java/net/pterodactylus/sone/web/CreateReplyPage.java
src/main/java/net/pterodactylus/sone/web/CreateSonePage.java
src/main/java/net/pterodactylus/sone/web/DeleteAlbumPage.java
src/main/java/net/pterodactylus/sone/web/DeleteImagePage.java
src/main/java/net/pterodactylus/sone/web/DeletePostPage.java
src/main/java/net/pterodactylus/sone/web/DeleteProfileFieldPage.java
src/main/java/net/pterodactylus/sone/web/DeleteReplyPage.java
src/main/java/net/pterodactylus/sone/web/DeleteSonePage.java
src/main/java/net/pterodactylus/sone/web/DismissNotificationPage.java
src/main/java/net/pterodactylus/sone/web/DistrustPage.java
src/main/java/net/pterodactylus/sone/web/EditAlbumPage.java
src/main/java/net/pterodactylus/sone/web/EditImagePage.java
src/main/java/net/pterodactylus/sone/web/EditProfileFieldPage.java
src/main/java/net/pterodactylus/sone/web/EditProfilePage.java
src/main/java/net/pterodactylus/sone/web/FollowSonePage.java
src/main/java/net/pterodactylus/sone/web/GetImagePage.java
src/main/java/net/pterodactylus/sone/web/ImageBrowserPage.java
src/main/java/net/pterodactylus/sone/web/IndexPage.java
src/main/java/net/pterodactylus/sone/web/KnownSonesPage.java
src/main/java/net/pterodactylus/sone/web/LikePage.java
src/main/java/net/pterodactylus/sone/web/LockSonePage.java
src/main/java/net/pterodactylus/sone/web/LoginPage.java
src/main/java/net/pterodactylus/sone/web/LogoutPage.java
src/main/java/net/pterodactylus/sone/web/MarkAsKnownPage.java
src/main/java/net/pterodactylus/sone/web/NewPage.java
src/main/java/net/pterodactylus/sone/web/OptionsPage.java
src/main/java/net/pterodactylus/sone/web/ReloadingPage.java
src/main/java/net/pterodactylus/sone/web/RescuePage.java
src/main/java/net/pterodactylus/sone/web/SearchPage.java
src/main/java/net/pterodactylus/sone/web/SoneTemplatePage.java
src/main/java/net/pterodactylus/sone/web/TrustPage.java
src/main/java/net/pterodactylus/sone/web/UnbookmarkPage.java
src/main/java/net/pterodactylus/sone/web/UnfollowSonePage.java
src/main/java/net/pterodactylus/sone/web/UnlikePage.java
src/main/java/net/pterodactylus/sone/web/UnlockSonePage.java
src/main/java/net/pterodactylus/sone/web/UntrustPage.java
src/main/java/net/pterodactylus/sone/web/UploadImagePage.java
src/main/java/net/pterodactylus/sone/web/ViewPostPage.java
src/main/java/net/pterodactylus/sone/web/ViewSonePage.java
src/main/java/net/pterodactylus/sone/web/WebInterface.java
src/main/java/net/pterodactylus/sone/web/ajax/BookmarkAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/CreatePostAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/CreateReplyAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/DeletePostAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/DeleteProfileFieldAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/DeleteReplyAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/DismissNotificationAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/DistrustAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/EditAlbumAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/EditImageAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/EditProfileFieldAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/FollowSoneAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetLikesAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetNotificationsAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetPostAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetReplyAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetStatusAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetTimesAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetTranslationPage.java
src/main/java/net/pterodactylus/sone/web/ajax/JsonPage.java
src/main/java/net/pterodactylus/sone/web/ajax/LikeAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/LockSoneAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/MarkAsKnownAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/MoveProfileFieldAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/TrustAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/UnbookmarkAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/UnfollowSoneAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/UnlikeAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/UnlockSoneAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/UntrustAjaxPage.java
src/main/java/net/pterodactylus/sone/web/page/FreenetPage.java
src/main/java/net/pterodactylus/sone/web/page/FreenetRequest.java
src/main/java/net/pterodactylus/sone/web/page/FreenetTemplatePage.java
src/main/java/net/pterodactylus/sone/web/page/PageToadlet.java
src/main/java/net/pterodactylus/sone/web/page/PageToadletFactory.java
src/main/resources/i18n/sone.de.properties
src/main/resources/i18n/sone.en.properties
src/main/resources/i18n/sone.es.properties
src/main/resources/i18n/sone.fr.properties
src/main/resources/i18n/sone.ja.properties
src/main/resources/i18n/sone.no.properties
src/main/resources/i18n/sone.pl.properties
src/main/resources/i18n/sone.ru.properties
src/main/resources/static/javascript/sone.js
src/main/resources/templates/deletePost.html
src/main/resources/templates/deleteReply.html
src/main/resources/templates/imageBrowser.html
src/main/resources/templates/insert/include/album.xml [deleted file]
src/main/resources/templates/notify/newVersionNotification.html
src/test/java/net/pterodactylus/sone/Matchers.java
src/test/java/net/pterodactylus/sone/TestAlbumBuilder.java
src/test/java/net/pterodactylus/sone/core/ConfigurationSoneParserTest.java
src/test/java/net/pterodactylus/sone/core/CoreTest.java
src/test/java/net/pterodactylus/sone/core/SoneParserTest.java
src/test/java/net/pterodactylus/sone/core/UpdateCheckerTest.java
src/test/java/net/pterodactylus/sone/database/memory/MemoryDatabaseTest.java
src/test/java/net/pterodactylus/sone/fcp/LockSoneCommandTest.java
src/test/java/net/pterodactylus/sone/fcp/UnlockSoneCommandTest.java
src/test/java/net/pterodactylus/sone/freenet/wot/DefaultIdentityTest.java
src/test/java/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentityTest.java
src/test/java/net/pterodactylus/sone/freenet/wot/Identities.java
src/test/java/net/pterodactylus/sone/freenet/wot/IdentityChangeDetectorTest.java
src/test/java/net/pterodactylus/sone/freenet/wot/IdentityChangeEventSenderTest.java
src/test/java/net/pterodactylus/sone/freenet/wot/IdentityLoaderTest.java
src/test/java/net/pterodactylus/sone/notify/ListNotificationFilterTest.java [new file with mode: 0644]
src/test/java/net/pterodactylus/sone/notify/ListNotificationTest.java [new file with mode: 0644]
src/test/java/net/pterodactylus/sone/notify/PostVisibilityFilterTest.java [new file with mode: 0644]
src/test/java/net/pterodactylus/sone/notify/ReplyVisibilityFilterTest.java [new file with mode: 0644]
src/test/java/net/pterodactylus/sone/template/AlbumAccessorTest.java
src/test/java/net/pterodactylus/sone/template/JavascriptFilterTest.java [new file with mode: 0644]
src/test/java/net/pterodactylus/sone/text/SoneTextParserTest.java
src/test/java/net/pterodactylus/sone/web/DeleteReplyPageTest.java [new file with mode: 0644]
src/test/java/net/pterodactylus/sone/web/NewPageTest.java [new file with mode: 0644]
src/test/java/net/pterodactylus/sone/web/UploadImagePageTest.java [new file with mode: 0644]
src/test/java/net/pterodactylus/sone/web/WebTestUtils.java [new file with mode: 0644]
src/test/java/net/pterodactylus/sone/web/ajax/GetTimesAjaxPageTest.java [new file with mode: 0644]

diff --git a/pom.xml b/pom.xml
index 2ca12c0..1799801 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -2,7 +2,7 @@
        <modelVersion>4.0.0</modelVersion>
        <groupId>net.pterodactylus</groupId>
        <artifactId>sone</artifactId>
-       <version>0.9.4</version>
+       <version>0.9.5</version>
        <dependencies>
                <dependency>
                        <groupId>net.pterodactylus</groupId>
                                <artifactId>maven-assembly-plugin</artifactId>
                                <version>2.2-beta-5</version>
                                <configuration>
+                                       <finalName>sone</finalName>
                                        <descriptorRefs>
                                                <descriptorRef>jar-with-dependencies</descriptorRef>
                                        </descriptorRefs>
                        <plugin>
                                <groupId>org.jacoco</groupId>
                                <artifactId>jacoco-maven-plugin</artifactId>
-                               <version>0.7.1.201405082137</version>
+                               <version>0.7.6.201602180812</version>
                                <executions>
                                        <execution>
                                                <id>default-prepare-agent</id>
                                        </execution>
                                </executions>
                        </plugin>
+                       <plugin>
+                               <groupId>org.pitest</groupId>
+                               <artifactId>pitest-maven</artifactId>
+                               <version>1.1.10</version>
+                               <configuration>
+                                       <targetClasses>
+                                               <param>net.pterodactylus.sone.*</param>
+                                       </targetClasses>
+                                       <targetTests>
+                                               <param>net.pterodactylus.sone.*</param>
+                                       </targetTests>
+                               </configuration>
+                       </plugin>
                </plugins>
        </build>
 </project>
index a29856b..37e0ed5 100644 (file)
@@ -201,8 +201,6 @@ public class ConfigurationSoneParser {
                        String albumDescription =
                                        getString(albumPrefix + "/Description", null);
                        String albumParentId = getString(albumPrefix + "/Parent", null);
-                       String albumImageId =
-                                       getString(albumPrefix + "/AlbumImage", null);
                        if ((albumTitle == null) || (albumDescription == null)) {
                                throw new InvalidAlbumFound();
                        }
@@ -213,7 +211,6 @@ public class ConfigurationSoneParser {
                                        .modify()
                                        .setTitle(albumTitle)
                                        .setDescription(albumDescription)
-                                       .setAlbumImage(albumImageId)
                                        .update();
                        if (albumParentId != null) {
                                Album parentAlbum = albums.get(albumParentId);
index 99a5654..3b958f1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Core.java - Copyright © 2010–2015 David Roden
+ * Sone - Core.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -204,14 +204,14 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         *            The database
         */
        @Inject
-       public Core(Configuration configuration, FreenetInterface freenetInterface, IdentityManager identityManager, WebOfTrustUpdater webOfTrustUpdater, EventBus eventBus, Database database) {
+       public Core(Configuration configuration, FreenetInterface freenetInterface, IdentityManager identityManager, UpdateChecker updateChecker, WebOfTrustUpdater webOfTrustUpdater, EventBus eventBus, Database database) {
                super("Sone Core");
                this.configuration = configuration;
                this.freenetInterface = freenetInterface;
                this.identityManager = identityManager;
                this.soneDownloader = new SoneDownloaderImpl(this, freenetInterface);
                this.imageInserter = new ImageInserter(freenetInterface, freenetInterface.new InsertTokenSupplier());
-               this.updateChecker = new UpdateChecker(eventBus, freenetInterface);
+               this.updateChecker = updateChecker;
                this.webOfTrustUpdater = webOfTrustUpdater;
                this.eventBus = eventBus;
                this.database = database;
@@ -1518,7 +1518,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                                configuration.getStringValue(albumPrefix + "/Title").setValue(album.getTitle());
                                configuration.getStringValue(albumPrefix + "/Description").setValue(album.getDescription());
                                configuration.getStringValue(albumPrefix + "/Parent").setValue(album.getParent().equals(sone.getRootAlbum()) ? null : album.getParent().getId());
-                               configuration.getStringValue(albumPrefix + "/AlbumImage").setValue(album.getAlbumImage() == null ? null : album.getAlbumImage().getId());
                        }
                        configuration.getStringValue(sonePrefix + "/Albums/" + albumCounter + "/ID").setValue(null);
 
@@ -1730,8 +1729,14 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        /* TODO - we don’t have the Sone anymore. should this happen? */
                        return;
                }
-               database.removeSone(sone.get());
+               for (PostReply postReply : sone.get().getReplies()) {
+                       eventBus.post(new PostReplyRemovedEvent(postReply));
+               }
+               for (Post post : sone.get().getPosts()) {
+                       eventBus.post(new PostRemovedEvent(post));
+               }
                eventBus.post(new SoneRemovedEvent(sone.get()));
+               database.removeSone(sone.get());
        }
 
        /**
index 219377d..a06b5b0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - FreenetInterface.java - Copyright © 2010–2015 David Roden
+ * Sone - FreenetInterface.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index d338161..f47b713 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageInserter.java - Copyright © 2011–2015 David Roden
+ * Sone - ImageInserter.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 9a53202..39b7b37 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Options.java - Copyright © 2010–2015 David Roden
+ * Sone - Options.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 8123764..456e7f2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Preferences.java - Copyright © 2013–2015 David Roden
+ * Sone - Preferences.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 6317abe..db08046 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneDownloaderImpl.java - Copyright © 2010–2015 David Roden
+ * Sone - SoneDownloaderImpl.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 246025d..e651646 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneException.java - Copyright © 2010–2015 David Roden
+ * Sone - SoneException.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index ffda655..879767e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneInsertException.java - Copyright © 2011–2015 David Roden
+ * Sone - SoneInsertException.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index e34f19d..46558ab 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneInserter.java - Copyright © 2010–2015 David Roden
+ * Sone - SoneInserter.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index ae1e2a5..fdbd9ae 100644 (file)
@@ -261,7 +261,6 @@ public class SoneParser {
                                String parentId = albumXml.getValue("parent", null);
                                String title = albumXml.getValue("title", null);
                                String description = albumXml.getValue("description", "");
-                               String albumImageId = albumXml.getValue("album-image", null);
                                if ((id == null) || (title == null)) {
                                        logger.log(Level.WARNING, String.format("Downloaded Sone %s contains invalid album!", sone));
                                        return null;
@@ -315,7 +314,6 @@ public class SoneParser {
                                                allImages.put(imageId, image);
                                        }
                                }
-                               album.modify().setAlbumImage(albumImageId).update();
                        }
                }
 
index 2ca4b4a..0731b1a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneRescuer.java - Copyright © 2011–2015 David Roden
+ * Sone - SoneRescuer.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index f62b4ec..1fbadbc 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneUri.java - Copyright © 2013–2015 David Roden
+ * Sone - SoneUri.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 146383a..e739a44 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UpdateChecker.java - Copyright © 2011–2015 David Roden
+ * Sone - UpdateChecker.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -28,6 +28,8 @@ import java.util.Properties;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import javax.inject.Singleton;
+
 import net.pterodactylus.sone.core.FreenetInterface.Fetched;
 import net.pterodactylus.sone.core.event.UpdateFoundEvent;
 import net.pterodactylus.sone.main.SonePlugin;
@@ -45,6 +47,7 @@ import freenet.support.api.Bucket;
  *
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
+@Singleton
 public class UpdateChecker {
 
        /** The logger. */
@@ -60,10 +63,11 @@ public class UpdateChecker {
        private FreenetURI currentUri;
 
        /** The latest known edition. */
-       private long latestEdition;
+       private long latestEdition = SonePlugin.getLatestEdition();
 
        /** The current latest known version. */
-       private Version currentLatestVersion = SonePlugin.VERSION;
+       private Version currentLatestVersion;
+       private final Version currentRunningVersion;
 
        /** The release date of the latest version. */
        private long latestVersionDate;
@@ -77,9 +81,11 @@ public class UpdateChecker {
         *            The freenet interface to use
         */
        @Inject
-       public UpdateChecker(EventBus eventBus, FreenetInterface freenetInterface) {
+       public UpdateChecker(EventBus eventBus, FreenetInterface freenetInterface, Version currentVersion) {
                this.eventBus = eventBus;
                this.freenetInterface = freenetInterface;
+               this.currentRunningVersion = currentVersion;
+               this.currentLatestVersion = currentVersion;
        }
 
        //
@@ -93,7 +99,7 @@ public class UpdateChecker {
         * @return {@code true} if a new version was found
         */
        public boolean hasLatestVersion() {
-               return currentLatestVersion.compareTo(SonePlugin.VERSION) > 0;
+               return currentLatestVersion.compareTo(currentRunningVersion) > 0;
        }
 
        /**
@@ -218,9 +224,22 @@ public class UpdateChecker {
                if (version.compareTo(currentLatestVersion) > 0) {
                        currentLatestVersion = version;
                        latestVersionDate = releaseTime;
-                       logger.log(Level.INFO, String.format("Found new version: %s (%tc)", version, new Date(releaseTime)));
-                       eventBus.post(new UpdateFoundEvent(version, releaseTime, edition));
+                       boolean disruptive = disruptiveVersionBetweenCurrentAndFound(properties);
+                       logger.log(Level.INFO, String.format("Found new version: %s (%tc%s)", version, new Date(releaseTime), disruptive ? ", disruptive" : ""));
+                       eventBus.post(new UpdateFoundEvent(version, releaseTime, edition, disruptive));
+               }
+       }
+
+       private boolean disruptiveVersionBetweenCurrentAndFound(Properties properties) {
+               for (String key : properties.stringPropertyNames()) {
+                       if (key.startsWith("DisruptiveVersion/")) {
+                               Version disruptiveVersion = Version.parse(key.substring("DisruptiveVersion/".length()));
+                               if (disruptiveVersion.compareTo(currentRunningVersion) > 0) {
+                                       return true;
+                               }
+                       }
                }
+               return false;
        }
 
 }
index 82b14ef..05d940f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - WebOfTrustUpdaterImpl.java - Copyright © 2013–2015 David Roden
+ * Sone - WebOfTrustUpdaterImpl.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index c9646ed..a8a0c6f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - ImageEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 2316217..63a561b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageInsertAbortedEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - ImageInsertAbortedEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index b09cd45..eaa3996 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageInsertFailedEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - ImageInsertFailedEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 06a506d..41d7ecc 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageInsertFinishedEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - ImageInsertFinishedEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index b0f0c68..876f2db 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageInsertStartedEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - ImageInsertStartedEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 112361a..72c7baf 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - MarkPostKnownEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - MarkPostKnownEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index d50a046..224bb31 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - MarkPostReplyKnownEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - MarkPostReplyKnownEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index eb1ea3f..ca6cd70 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - MarkSoneKnownEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - MarkSoneKnownEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index f77c462..ff81357 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - NewPostFoundEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - NewPostFoundEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 68c2d20..bda02b6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - NewPostReplyFoundEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - NewPostReplyFoundEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 623cf12..c110483 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - NewSoneFoundEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - NewSoneFoundEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 1b5cb68..78555b5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - PostEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 850f712..8240176 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostRemovedEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - PostRemovedEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index f312fcc..6ae51bc 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostReplyEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - PostReplyEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index e900de5..4f7cc8b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostReplyRemovedEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - PostReplyRemovedEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 324b230..848c1b8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - SoneEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index fa68c26..a20e15a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneInsertAbortedEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - SoneInsertAbortedEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 6c276b7..8b3f79e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneInsertedEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - SoneInsertedEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 4653317..4e2f155 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneInsertingEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - SoneInsertingEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 251759d..2703f16 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneLockedEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - SoneLockedEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 30bd317..ecba776 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneRemovedEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - SoneRemovedEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 17f8027..de2d875 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneUnlockedEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - SoneUnlockedEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index e8cb8e5..10adda5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UpdateFoundEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - UpdateFoundEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -26,61 +26,32 @@ import net.pterodactylus.util.version.Version;
  */
 public class UpdateFoundEvent {
 
-       /** The version that was found. */
        private final Version version;
-
-       /** The time the update was released. */
        private final long releaseTime;
-
-       /** The latest edition of the update page. */
        private final long latestEdition;
+       private final boolean disruptive;
 
-       /**
-        * Creates a new “update found” event.
-        *
-        * @param version
-        *            The version of the update
-        * @param releaseTime
-        *            The release time of the update
-        * @param latestEdition
-        *            The latest edition of the update page
-        */
-       public UpdateFoundEvent(Version version, long releaseTime, long latestEdition) {
+       public UpdateFoundEvent(Version version, long releaseTime, long latestEdition, boolean disruptive) {
                this.version = version;
                this.releaseTime = releaseTime;
                this.latestEdition = latestEdition;
+               this.disruptive = disruptive;
        }
 
-       //
-       // ACCESSORS
-       //
-
-       /**
-        * Returns the version of the update.
-        *
-        * @return The version of the update
-        */
        public Version version() {
                return version;
        }
 
-       /**
-        * Returns the release time of the update.
-        *
-        * @return The releae time of the update (in milliseconds since Jan 1, 1970
-        *         UTC)
-        */
        public long releaseTime() {
                return releaseTime;
        }
 
-       /**
-        * Returns the latest edition of the update page.
-        *
-        * @return The latest edition of the update page
-        */
        public long latestEdition() {
                return latestEdition;
        }
 
+       public boolean disruptive() {
+               return disruptive;
+       }
+
 }
index 698206d..5a02f19 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Album.java - Copyright © 2011–2015 David Roden
+ * Sone - Album.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -206,14 +206,6 @@ public interface Album extends Identified, Fingerprintable {
        Image moveImageDown(Image image);
 
        /**
-        * Returns the album image of this album, or {@code null} if no album image has
-        * been set.
-        *
-        * @return The image to show when this album is listed
-        */
-       Image getAlbumImage();
-
-       /**
         * Returns whether this album contains any other albums or images.
         *
         * @return {@code true} if this album is empty, {@code false} otherwise
@@ -288,8 +280,6 @@ public interface Album extends Identified, Fingerprintable {
 
                Modifier setDescription(String description);
 
-               Modifier setAlbumImage(String imageId);
-
                Album update() throws IllegalStateException;
 
                class AlbumTitleMustNotBeEmpty extends IllegalStateException { }
index e8f5082..650ef16 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Client.java - Copyright © 2010–2015 David Roden
+ * Sone - Client.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index df4e2d7..407aa8a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Fingerprintable.java - Copyright © 2011–2015 David Roden
+ * Sone - Fingerprintable.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index e748c7e..659a56f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Identified.java - Copyright © 2013–2015 David Roden
+ * Sone - Identified.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 6413481..55b0094 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Image.java - Copyright © 2011–2015 David Roden
+ * Sone - Image.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index d666eb7..990e20d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Post.java - Copyright © 2010–2015 David Roden
+ * Sone - Post.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 585b430..6821b7b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostReply.java - Copyright © 2010–2015 David Roden
+ * Sone - PostReply.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 55faf18..0f9b8ff 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Profile.java - Copyright © 2010–2015 David Roden
+ * Sone - Profile.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index c92bacb..d3068c4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Reply.java - Copyright © 2010–2015 David Roden
+ * Sone - Reply.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 072f2c7..68c8114 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Sone.java - Copyright © 2010–2015 David Roden
+ * Sone - Sone.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 9383133..ee6c35c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - TemporaryImage.java - Copyright © 2011–2015 David Roden
+ * Sone - TemporaryImage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 9be43b6..66a758a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - AbstractAlbumBuilder.java - Copyright © 2013–2015 David Roden
+ * Sone - AbstractAlbumBuilder.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index ad6d15f..2282c11 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - AbstractImageBuilder.java - Copyright © 2013–2015 David Roden
+ * Sone - AbstractImageBuilder.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 3c6e547..f33a446 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - AbstractPostBuilder.java - Copyright © 2013–2015 David Roden
+ * Sone - AbstractPostBuilder.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index be05424..10bdf76 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - AbstractPostReplyBuilder.java - Copyright © 2013–2015 David Roden
+ * Sone - AbstractPostReplyBuilder.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 71e1e53..c66debf 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - AbstractReplyBuilder.java - Copyright © 2013–2015 David Roden
+ * Sone - AbstractReplyBuilder.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 72ae5a5..3ec6b42 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - AlbumBuilderImpl.java - Copyright © 2013–2015 David Roden
+ * Sone - AlbumBuilderImpl.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 41668ab..991459a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - AlbumImpl.java - Copyright © 2011–2015 David Roden
+ * Sone - AlbumImpl.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -70,9 +70,6 @@ public class AlbumImpl implements Album {
        /** The description of this album. */
        private String description;
 
-       /** The ID of the album picture. */
-       private String albumImage;
-
        /** Creates a new album with a random ID. */
        public AlbumImpl(Sone sone) {
                this(sone, UUID.randomUUID().toString());
@@ -176,9 +173,6 @@ public class AlbumImpl implements Album {
                        image.getAlbum().removeImage(image);
                }
                image.setAlbum(this);
-               if (imageIds.isEmpty() && (albumImage == null)) {
-                       albumImage = image.getId();
-               }
                if (!imageIds.contains(image.getId())) {
                        imageIds.add(image.getId());
                        images.put(image.getId(), image);
@@ -192,13 +186,6 @@ public class AlbumImpl implements Album {
                checkArgument(image.getSone().equals(sone), "image must belong to the same Sone as this album");
                imageIds.remove(image.getId());
                images.remove(image.getId());
-               if (image.getId().equals(albumImage)) {
-                       if (images.isEmpty()) {
-                               albumImage = null;
-                       } else {
-                               albumImage = images.values().iterator().next().getId();
-                       }
-               }
        }
 
        @Override
@@ -232,14 +219,6 @@ public class AlbumImpl implements Album {
        }
 
        @Override
-       public Image getAlbumImage() {
-               if (albumImage == null) {
-                       return null;
-               }
-               return Optional.fromNullable(images.get(albumImage)).or(images.values().iterator().next());
-       }
-
-       @Override
        public boolean isEmpty() {
                return albums.isEmpty() && images.isEmpty();
        }
@@ -284,8 +263,6 @@ public class AlbumImpl implements Album {
 
                        private Optional<String> description = absent();
 
-                       private Optional<String> albumImage = absent();
-
                        @Override
                        public Modifier setTitle(String title) {
                                this.title = fromNullable(title);
@@ -299,12 +276,6 @@ public class AlbumImpl implements Album {
                        }
 
                        @Override
-                       public Modifier setAlbumImage(String imageId) {
-                               this.albumImage = fromNullable(imageId);
-                               return this;
-                       }
-
-                       @Override
                        public Album update() throws IllegalStateException {
                                if (title.isPresent() && title.get().trim().isEmpty()) {
                                        throw new AlbumTitleMustNotBeEmpty();
@@ -315,9 +286,6 @@ public class AlbumImpl implements Album {
                                if (description.isPresent()) {
                                        AlbumImpl.this.description = description.get();
                                }
-                               if (albumImage.isPresent()) {
-                                       AlbumImpl.this.albumImage = albumImage.get();
-                               }
                                return AlbumImpl.this;
                        }
                };
@@ -334,9 +302,6 @@ public class AlbumImpl implements Album {
                hash.putString("ID(").putString(id).putString(")");
                hash.putString("Title(").putString(title).putString(")");
                hash.putString("Description(").putString(description).putString(")");
-               if (albumImage != null) {
-                       hash.putString("AlbumImage(").putString(albumImage).putString(")");
-               }
 
                /* add nested albums. */
                hash.putString("Albums(");
index a0617b3..65b266d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DefaultPostBuilderFactory.java - Copyright © 2013–2015 David Roden
+ * Sone - DefaultPostBuilderFactory.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 1944ff9..bad7745 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DefaultPostReplyBuilderFactory.java - Copyright © 2013–2015 David Roden
+ * Sone - DefaultPostReplyBuilderFactory.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 04ac778..4f4f26b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageBuilderImpl.java - Copyright © 2013–2015 David Roden
+ * Sone - ImageBuilderImpl.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index ddf01c8..9f0fbbf 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageImpl.java - Copyright © 2011–2015 David Roden
+ * Sone - ImageImpl.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index e6c1b66..9fed167 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostBuilderImpl.java - Copyright © 2013–2015 David Roden
+ * Sone - PostBuilderImpl.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 365d28f..86f4098 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostImpl.java - Copyright © 2010–2015 David Roden
+ * Sone - PostImpl.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index a7abb66..30f32fc 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostReplyBuilderImpl.java - Copyright © 2013–2015 David Roden
+ * Sone - PostReplyBuilderImpl.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 136d6a0..5084c1c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostReplyImpl.java - Copyright © 2010–2015 David Roden
+ * Sone - PostReplyImpl.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 3416241..4105749 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ReplyImpl.java - Copyright © 2011–2015 David Roden
+ * Sone - ReplyImpl.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 5024be2..8a6dd80 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneImpl.java - Copyright © 2010–2015 David Roden
+ * Sone - SoneImpl.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index f0b19cb..2460306 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - AlbumBuilder.java - Copyright © 2013–2015 David Roden
+ * Sone - AlbumBuilder.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index ff5ab6d..07d7ae0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - AlbumBuilderFactory.java - Copyright © 2013–2015 David Roden
+ * Sone - AlbumBuilderFactory.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 7b37751..8e6fa45 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - AlbumDatabase.java - Copyright © 2013–2015 David Roden
+ * Sone - AlbumDatabase.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 83ce6dc..746f014 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - AlbumProvider.java - Copyright © 2013–2015 David Roden
+ * Sone - AlbumProvider.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 8c3c97b..2a9bfd0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - AlbumStore.java - Copyright © 2013–2015 David Roden
+ * Sone - AlbumStore.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index c08162c..db63ba3 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Database.java - Copyright © 2013–2015 David Roden
+ * Sone - Database.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index abd1080..bb98fec 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DatabaseException.java - Copyright © 2013–2015 David Roden
+ * Sone - DatabaseException.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 08b9897..7b5c9e0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageBuilder.java - Copyright © 2013–2015 David Roden
+ * Sone - ImageBuilder.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 6c40a22..900c989 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageBuilderFactory.java - Copyright © 2013–2015 David Roden
+ * Sone - ImageBuilderFactory.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 97e81cc..090aa3b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageDatabase.java - Copyright © 2013–2015 David Roden
+ * Sone - ImageDatabase.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index ecb1bd6..8ee3e5f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageProvider.java - Copyright © 2013–2015 David Roden
+ * Sone - ImageProvider.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 9c32542..97a7240 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageStore.java - Copyright © 2013–2015 David Roden
+ * Sone - ImageStore.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 3c1927f..45cc062 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostBuilder.java - Copyright © 2013–2015 David Roden
+ * Sone - PostBuilder.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 98513de..e5cee38 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostBuilderFactory.java - Copyright © 2013–2015 David Roden
+ * Sone - PostBuilderFactory.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 164224a..648c77e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostDatabase.java - Copyright © 2013–2015 David Roden
+ * Sone - PostDatabase.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 353a524..bea59b9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostProvider.java - Copyright © 2011–2015 David Roden
+ * Sone - PostProvider.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 82b0c0b..16569d9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostReplyBuilder.java - Copyright © 2013–2015 David Roden
+ * Sone - PostReplyBuilder.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 8f4dfe8..f85b18b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostReplyBuilderFactory.java - Copyright © 2013–2015 David Roden
+ * Sone - PostReplyBuilderFactory.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index c68b40c..f226d73 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostReplyDatabase.java - Copyright © 2013–2015 David Roden
+ * Sone - PostReplyDatabase.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index dc89826..e982599 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostReplyProvider.java - Copyright © 2013–2015 David Roden
+ * Sone - PostReplyProvider.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 939c100..7956a92 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostReplyStore.java - Copyright © 2013–2015 David Roden
+ * Sone - PostReplyStore.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index b4340b9..7c3e07f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostStore.java - Copyright © 2013–2015 David Roden
+ * Sone - PostStore.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 93cf6b3..21ba89c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ReplyBuilder.java - Copyright © 2013–2015 David Roden
+ * Sone - ReplyBuilder.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 1c685ab..a39ceff 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneProvider.java - Copyright © 2011–2015 David Roden
+ * Sone - SoneProvider.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index fa608e4..aa4a2a8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - MemoryDatabase.java - Copyright © 2013–2015 David Roden
+ * Sone - MemoryDatabase.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index d368fba..acf2374 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - MemoryPost.java - Copyright © 2010–2015 David Roden
+ * Sone - MemoryPost.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index d76f277..7125167 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - MemoryPostBuilder.java - Copyright © 2013–2015 David Roden
+ * Sone - MemoryPostBuilder.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index a48b017..714b84e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - MemoryPostReply.java - Copyright © 2013–2015 David Roden
+ * Sone - MemoryPostReply.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 2871b27..59e098c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - MemoryPostReplyBuilder.java - Copyright © 2013–2015 David Roden
+ * Sone - MemoryPostReplyBuilder.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 312bb9e..b2264a5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - AbstractSoneCommand.java - Copyright © 2011–2015 David Roden
+ * Sone - AbstractSoneCommand.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index cd596ce..6f1e82c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - CreatePostCommand.java - Copyright © 2011–2015 David Roden
+ * Sone - CreatePostCommand.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 65b0ac2..9369529 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - CreateReplyCommand.java - Copyright © 2011–2015 David Roden
+ * Sone - CreateReplyCommand.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index d411233..e68cbb5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DeletePostCommand.java - Copyright © 2011–2015 David Roden
+ * Sone - DeletePostCommand.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 8b2f5b7..00a5b97 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DeleteReplyCommand.java - Copyright © 2011–2015 David Roden
+ * Sone - DeleteReplyCommand.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 25cf4ae..440cb4c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - FcpInterface.java - Copyright © 2011–2015 David Roden
+ * Sone - FcpInterface.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index fdef446..8ac80cc 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetLocalSonesCommand.java - Copyright © 2011–2015 David Roden
+ * Sone - GetLocalSonesCommand.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 2211677..1e02dae 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetPostCommand.java - Copyright © 2011–2015 David Roden
+ * Sone - GetPostCommand.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 0851616..bbff5ed 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetPostFeedCommand.java - Copyright © 2011–2015 David Roden
+ * Sone - GetPostFeedCommand.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index baa7147..4059bf5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetPostsCommand.java - Copyright © 2011–2015 David Roden
+ * Sone - GetPostsCommand.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 2ad514f..f23be19 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetSoneCommand.java - Copyright © 2011–2015 David Roden
+ * Sone - GetSoneCommand.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 770cb22..97c8842 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetSonesCommand.java - Copyright © 2011–2015 David Roden
+ * Sone - GetSonesCommand.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index d8d6604..583b8e4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - LikePostCommand.java - Copyright © 2011–2015 David Roden
+ * Sone - LikePostCommand.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index edfa742..63d8893 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - LikeReplyCommand.java - Copyright © 2011–2015 David Roden
+ * Sone - LikeReplyCommand.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 287ec91..e05ac93 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - LockSoneCommand.java - Copyright © 2013–2015 David Roden
+ * Sone - LockSoneCommand.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 4870168..311a7ca 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UnlockSoneCommand.java - Copyright © 2013–2015 David Roden
+ * Sone - UnlockSoneCommand.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 9533aca..b887926 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - VersionCommand.java - Copyright © 2011–2015 David Roden
+ * Sone - VersionCommand.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index b547a53..3b4af2a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - L10nFilter.java - Copyright © 2010–2015 David Roden
+ * Sone - L10nFilter.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index dfc30f9..55e4024 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PluginStoreConfigurationBackend.java - Copyright © 2010–2015 David Roden
+ * Sone - PluginStoreConfigurationBackend.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 2c68f07..25088fc 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SimpleFieldSetBuilder.java - Copyright © 2011–2015 David Roden
+ * Sone - SimpleFieldSetBuilder.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index c516f0f..b39d905 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - AbstractCommand.java - Copyright © 2011–2015 David Roden
+ * Sone - AbstractCommand.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index d0f812a..bafa764 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Command.java - Copyright © 2011–2015 David Roden
+ * Sone - Command.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 34920f4..c28eef7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - FcpException.java - Copyright © 2011–2015 David Roden
+ * Sone - FcpException.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 46343a3..0090aa8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PluginConnector.java - Copyright © 2010–2015 David Roden
+ * Sone - PluginConnector.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 78e73cd..09b186e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PluginException.java - Copyright © 2010–2015 David Roden
+ * Sone - PluginException.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index feeb1ea..56a1d46 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ReceivedReplyEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - ReceivedReplyEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 4d5a3a8..13f9793 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Context.java - Copyright © 2014–2015 David Roden
+ * Sone - Context.java - Copyright © 2014–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 6313a1a..433516f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DefaultIdentity.java - Copyright © 2010–2015 David Roden
+ * Sone - DefaultIdentity.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 68e6f41..2f78943 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DefaultOwnIdentity.java - Copyright © 2010–2015 David Roden
+ * Sone - DefaultOwnIdentity.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index c6875e9..25ddf9c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Identity.java - Copyright © 2010–2015 David Roden
+ * Sone - Identity.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 9703891..cfce632 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IdentityChangeDetector.java - Copyright © 2013–2015 David Roden
+ * Sone - IdentityChangeDetector.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index d17efae..b3c3c05 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IdentityChangeEventSender.java - Copyright © 2013–2015 David Roden
+ * Sone - IdentityChangeEventSender.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index a3b4f6c..1a8cc49 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IdentityLoader.java - Copyright © 2013–2015 David Roden
+ * Sone - IdentityLoader.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 33b1ef6..02e91ce 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IdentityManagerImpl.java - Copyright © 2010–2015 David Roden
+ * Sone - IdentityManagerImpl.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 2f0ade9..8be0571 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - OwnIdentity.java - Copyright © 2010–2015 David Roden
+ * Sone - OwnIdentity.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 65c6dba..7b18888 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Trust.java - Copyright © 2010–2015 David Roden
+ * Sone - Trust.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index ca3a38c..5f7b8b7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - WebOfTrustConnector.java - Copyright © 2010–2015 David Roden
+ * Sone - WebOfTrustConnector.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index ae41613..c622b3c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - WebOfTrustException.java - Copyright © 2010–2015 David Roden
+ * Sone - WebOfTrustException.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 897c04b..323a9cf 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IdentityAddedEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - IdentityAddedEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 60abcb6..6289c8a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IdentityEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - IdentityEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index bdf2ff0..a586de0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IdentityRemovedEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - IdentityRemovedEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 0521f7e..3e9a0f7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IdentityUpdatedEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - IdentityUpdatedEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index f4bc97a..cdb787a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - OwnIdentityAddedEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - OwnIdentityAddedEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 56a7156..91ad281 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - OwnIdentityEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - OwnIdentityEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 0f8b201..5b6bd95 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - OwnIdentityRemovedEvent.java - Copyright © 2013–2015 David Roden
+ * Sone - OwnIdentityRemovedEvent.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 828d1af..bff0a49 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SonePlugin.java - Copyright © 2010–2015 David Roden
+ * Sone - SonePlugin.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -116,12 +116,12 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr
        }
 
        /** The version. */
-       public static final Version VERSION = new Version(0, 9, 4);
+       public static final Version VERSION = new Version(0, 9, 5);
 
        /** The current year at time of release. */
-       private static final int YEAR = 2015;
+       private static final int YEAR = 2016;
        private static final String SONE_HOMEPAGE = "USK@nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI,DuQSUZiI~agF8c-6tjsFFGuZ8eICrzWCILB60nT8KKo,AQACAAE/sone/";
-       private static final int LATEST_EDITION = 71;
+       private static final int LATEST_EDITION = 72;
 
        /** The logger. */
        private static final Logger logger = getLogger(SonePlugin.class.getName());
@@ -183,6 +183,10 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr
                return SONE_HOMEPAGE + LATEST_EDITION;
        }
 
+       public static long getLatestEdition() {
+               return LATEST_EDITION;
+       }
+
        //
        // FREDPLUGIN METHODS
        //
@@ -250,6 +254,7 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr
                                bind(Context.class).toInstance(context);
                                bind(getOptionalContextTypeLiteral()).toInstance(of(context));
                                bind(SonePlugin.class).toInstance(SonePlugin.this);
+                               bind(Version.class).toInstance(VERSION);
                                if (startConfiguration.getBooleanValue("Developer.LoadFromFilesystem").getValue(false)) {
                                        String path = startConfiguration.getStringValue("Developer.FilesystemPath").getValue(null);
                                        if (path != null) {
index 101b1fb..8ab8e4c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ListNotification.java - Copyright © 2010–2015 David Roden
+ * Sone - ListNotification.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -194,15 +194,7 @@ public class ListNotification<T> extends TemplateNotification {
                if (!key.equals(listNotification.key)) {
                        return false;
                }
-               if (elements.size() != listNotification.elements.size()) {
-                       return false;
-               }
-               for (int index = 0; index < elements.size(); ++index) {
-                       if (!elements.get(index).equals(listNotification.elements.get(index))) {
-                               return false;
-                       }
-               }
-               return true;
+               return elements.equals(listNotification.elements);
        }
 
 }
diff --git a/src/main/java/net/pterodactylus/sone/notify/ListNotificationFilter.java b/src/main/java/net/pterodactylus/sone/notify/ListNotificationFilter.java
new file mode 100644 (file)
index 0000000..4672e72
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * Sone - ListNotificationFilter.java - Copyright © 2010–2016 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.notify;
+
+import static com.google.common.collect.FluentIterable.from;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostReply;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.util.notify.Notification;
+
+import com.google.common.base.Optional;
+
+/**
+ * Filter for {@link ListNotification}s.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+@Singleton
+public class ListNotificationFilter {
+
+       private final PostVisibilityFilter postVisibilityFilter;
+       private final ReplyVisibilityFilter replyVisibilityFilter;
+
+       @Inject
+       public ListNotificationFilter(@Nonnull PostVisibilityFilter postVisibilityFilter, @Nonnull ReplyVisibilityFilter replyVisibilityFilter) {
+               this.postVisibilityFilter = postVisibilityFilter;
+               this.replyVisibilityFilter = replyVisibilityFilter;
+       }
+
+       /**
+        * Filters new-post and new-reply notifications in the given list of
+        * notifications. If {@code currentSone} is <code>null</code>, new-post and
+        * new-reply notifications are removed completely. If {@code currentSone} is
+        * not {@code null}, only posts that are posted by a friend Sone or the Sone
+        * itself, and replies that are replies to posts of friend Sones or the Sone
+        * itself will be retained in the notifications.
+        *
+        * @param notifications
+        *              The notifications to filter
+        * @param currentSone
+        *              The current Sone, or {@code null} if not logged in
+        * @return The filtered notifications
+        */
+       @SuppressWarnings("unchecked")
+       public List<Notification> filterNotifications(Collection<? extends Notification> notifications, Sone currentSone) {
+               List<Notification> filteredNotifications = new ArrayList<Notification>();
+               for (Notification notification : notifications) {
+                       if (notification.getId().equals("new-sone-notification")) {
+                               if ((currentSone != null) && !currentSone.getOptions().isShowNewSoneNotifications()) {
+                                       continue;
+                               }
+                               filteredNotifications.add(notification);
+                       } else if (notification.getId().equals("new-post-notification")) {
+                               if (currentSone == null) {
+                                       continue;
+                               }
+                               if (!currentSone.getOptions().isShowNewPostNotifications()) {
+                                       continue;
+                               }
+                               Optional<ListNotification<Post>> filteredNotification = filterPostNotification((ListNotification<Post>) notification, currentSone);
+                               if (filteredNotification.isPresent()) {
+                                       filteredNotifications.add(filteredNotification.get());
+                               }
+                       } else if (notification.getId().equals("new-reply-notification")) {
+                               if (currentSone == null) {
+                                       continue;
+                               }
+                               if (!currentSone.getOptions().isShowNewReplyNotifications()) {
+                                       continue;
+                               }
+                               Optional<ListNotification<PostReply>> filteredNotification =
+                                               filterNewReplyNotification((ListNotification<PostReply>) notification, currentSone);
+                               if (filteredNotification.isPresent()) {
+                                       filteredNotifications.add(filteredNotification.get());
+                               }
+                       } else if (notification.getId().equals("mention-notification")) {
+                               Optional<ListNotification<Post>> filteredNotification = filterPostNotification((ListNotification<Post>) notification, null);
+                               if (filteredNotification.isPresent()) {
+                                       filteredNotifications.add(filteredNotification.get());
+                               }
+                       } else {
+                               filteredNotifications.add(notification);
+                       }
+               }
+               return filteredNotifications;
+       }
+
+       /**
+        * Filters the posts of the given notification.
+        *
+        * @param postNotification
+        *              The post notification
+        * @param currentSone
+        *              The current Sone, or {@code null} if not logged in
+        * @return The filtered post notification, or {@link Optional#absent()} if the notification should be removed
+        */
+       @Nonnull
+       private Optional<ListNotification<Post>> filterPostNotification(@Nonnull ListNotification<Post> postNotification,
+                       @Nullable Sone currentSone) {
+               List<Post> newPosts = from(postNotification.getElements()).filter(postVisibilityFilter.isVisible(currentSone)).toList();
+               if (newPosts.isEmpty()) {
+                       return Optional.absent();
+               }
+               if (newPosts.size() == postNotification.getElements().size()) {
+                       return Optional.of(postNotification);
+               }
+               ListNotification<Post> filteredNotification = new ListNotification<Post>(postNotification);
+               filteredNotification.setElements(newPosts);
+               filteredNotification.setLastUpdateTime(postNotification.getLastUpdatedTime());
+               return Optional.of(filteredNotification);
+       }
+
+       /**
+        * Filters the new replies of the given notification. If {@code currentSone}
+        * is {@code null}, {@code null} is returned and the notification is
+        * subsequently removed. Otherwise only replies that are replies to posts
+        * that are posted by friend Sones of the given Sone are retained; all other
+        * replies are removed.
+        *
+        * @param newReplyNotification
+        *              The new-reply notification
+        * @param currentSone
+        *              The current Sone, or {@code null} if not logged in
+        * @return The filtered new-reply notification, or {@code null} if the
+        * notification should be removed
+        */
+       private Optional<ListNotification<PostReply>> filterNewReplyNotification(ListNotification<PostReply> newReplyNotification,
+                       @Nonnull Sone currentSone) {
+               List<PostReply> newReplies = from(newReplyNotification.getElements()).filter(replyVisibilityFilter.isVisible(currentSone)).toList();
+               if (newReplies.isEmpty()) {
+                       return Optional.absent();
+               }
+               if (newReplies.size() == newReplyNotification.getElements().size()) {
+                       return Optional.of(newReplyNotification);
+               }
+               ListNotification<PostReply> filteredNotification = new ListNotification<PostReply>(newReplyNotification);
+               filteredNotification.setElements(newReplies);
+               filteredNotification.setLastUpdateTime(newReplyNotification.getLastUpdatedTime());
+               return Optional.of(filteredNotification);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/notify/ListNotificationFilters.java b/src/main/java/net/pterodactylus/sone/notify/ListNotificationFilters.java
deleted file mode 100644 (file)
index 1d4aa68..0000000
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * Sone - ListNotificationFilters.java - Copyright © 2010–2015 David Roden
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.notify;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.PostReply;
-import net.pterodactylus.sone.data.Reply;
-import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.sone.freenet.wot.OwnIdentity;
-import net.pterodactylus.sone.freenet.wot.Trust;
-import net.pterodactylus.util.notify.Notification;
-
-import com.google.common.base.Optional;
-
-/**
- * Filter for {@link ListNotification}s.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class ListNotificationFilters {
-
-       /**
-        * Filters new-post and new-reply notifications in the given list of
-        * notifications. If {@code currentSone} is <code>null</code>, new-post and
-        * new-reply notifications are removed completely. If {@code currentSone} is
-        * not {@code null}, only posts that are posted by a friend Sone or the Sone
-        * itself, and replies that are replies to posts of friend Sones or the Sone
-        * itself will be retained in the notifications.
-        *
-        * @param notifications
-        *            The notifications to filter
-        * @param currentSone
-        *            The current Sone, or {@code null} if not logged in
-        * @return The filtered notifications
-        */
-       @SuppressWarnings("unchecked")
-       public static List<Notification> filterNotifications(Collection<? extends Notification> notifications, Sone currentSone) {
-               List<Notification> filteredNotifications = new ArrayList<Notification>();
-               for (Notification notification : notifications) {
-                       if (notification.getId().equals("new-sone-notification")) {
-                               if ((currentSone != null) && !currentSone.getOptions().isShowNewSoneNotifications()) {
-                                       continue;
-                               }
-                               filteredNotifications.add(notification);
-                       } else if (notification.getId().equals("new-post-notification")) {
-                               if ((currentSone != null) && !currentSone.getOptions().isShowNewPostNotifications()) {
-                                       continue;
-                               }
-                               ListNotification<Post> filteredNotification = filterNewPostNotification((ListNotification<Post>) notification, currentSone, true);
-                               if (filteredNotification != null) {
-                                       filteredNotifications.add(filteredNotification);
-                               }
-                       } else if (notification.getId().equals("new-reply-notification")) {
-                               if ((currentSone != null) && !currentSone.getOptions().isShowNewReplyNotifications()) {
-                                       continue;
-                               }
-                               ListNotification<PostReply> filteredNotification = filterNewReplyNotification((ListNotification<PostReply>) notification, currentSone);
-                               if (filteredNotification != null) {
-                                       filteredNotifications.add(filteredNotification);
-                               }
-                       } else if (notification.getId().equals("mention-notification")) {
-                               ListNotification<Post> filteredNotification = filterNewPostNotification((ListNotification<Post>) notification, null, false);
-                               if (filteredNotification != null) {
-                                       filteredNotifications.add(filteredNotification);
-                               }
-                       } else {
-                               filteredNotifications.add(notification);
-                       }
-               }
-               return filteredNotifications;
-       }
-
-       /**
-        * Filters the new posts of the given notification. If {@code currentSone}
-        * is {@code null} and {@code soneRequired} is {@code true}, {@code null} is
-        * returned and the notification is subsequently removed. Otherwise only
-        * posts that are posted by friend Sones of the given Sone are retained; all
-        * other posts are removed.
-        *
-        * @param newPostNotification
-        *            The new-post notification
-        * @param currentSone
-        *            The current Sone, or {@code null} if not logged in
-        * @param soneRequired
-        *            Whether a non-{@code null} Sone in {@code currentSone} is
-        *            required
-        * @return The filtered new-post notification, or {@code null} if the
-        *         notification should be removed
-        */
-       public static ListNotification<Post> filterNewPostNotification(ListNotification<Post> newPostNotification, Sone currentSone, boolean soneRequired) {
-               if (soneRequired && (currentSone == null)) {
-                       return null;
-               }
-               List<Post> newPosts = new ArrayList<Post>();
-               for (Post post : newPostNotification.getElements()) {
-                       if (isPostVisible(currentSone, post)) {
-                               newPosts.add(post);
-                       }
-               }
-               if (newPosts.isEmpty()) {
-                       return null;
-               }
-               if (newPosts.size() == newPostNotification.getElements().size()) {
-                       return newPostNotification;
-               }
-               ListNotification<Post> filteredNotification = new ListNotification<Post>(newPostNotification);
-               filteredNotification.setElements(newPosts);
-               filteredNotification.setLastUpdateTime(newPostNotification.getLastUpdatedTime());
-               return filteredNotification;
-       }
-
-       /**
-        * Filters the new replies of the given notification. If {@code currentSone}
-        * is {@code null}, {@code null} is returned and the notification is
-        * subsequently removed. Otherwise only replies that are replies to posts
-        * that are posted by friend Sones of the given Sone are retained; all other
-        * replies are removed.
-        *
-        * @param newReplyNotification
-        *            The new-reply notification
-        * @param currentSone
-        *            The current Sone, or {@code null} if not logged in
-        * @return The filtered new-reply notification, or {@code null} if the
-        *         notification should be removed
-        */
-       public static ListNotification<PostReply> filterNewReplyNotification(ListNotification<PostReply> newReplyNotification, Sone currentSone) {
-               if (currentSone == null) {
-                       return null;
-               }
-               List<PostReply> newReplies = new ArrayList<PostReply>();
-               for (PostReply reply : newReplyNotification.getElements()) {
-                       if (isReplyVisible(currentSone, reply)) {
-                               newReplies.add(reply);
-                       }
-               }
-               if (newReplies.isEmpty()) {
-                       return null;
-               }
-               if (newReplies.size() == newReplyNotification.getElements().size()) {
-                       return newReplyNotification;
-               }
-               ListNotification<PostReply> filteredNotification = new ListNotification<PostReply>(newReplyNotification);
-               filteredNotification.setElements(newReplies);
-               filteredNotification.setLastUpdateTime(newReplyNotification.getLastUpdatedTime());
-               return filteredNotification;
-       }
-
-       /**
-        * Filters the given posts, using {@link #isPostVisible(Sone, Post)} to
-        * decide whether a post should be contained in the returned list. If
-        * {@code currentSone} is not {@code null} it is used to filter out posts
-        * that are from Sones that are not followed or not trusted by the given
-        * Sone.
-        *
-        * @param posts
-        *            The posts to filter
-        * @param currentSone
-        *            The current Sone (may be {@code null})
-        * @return The filtered posts
-        */
-       public static List<Post> filterPosts(Collection<Post> posts, Sone currentSone) {
-               List<Post> filteredPosts = new ArrayList<Post>();
-               for (Post post : posts) {
-                       if (isPostVisible(currentSone, post)) {
-                               filteredPosts.add(post);
-                       }
-               }
-               return filteredPosts;
-       }
-
-       /**
-        * Checks whether a post is visible to the given Sone. A post is not
-        * considered visible if one of the following statements is true:
-        * <ul>
-        * <li>The post does not have a Sone.</li>
-        * <li>The post’s {@link Post#getTime() time} is in the future.</li>
-        * </ul>
-        * <p>
-        * If {@code post} is not {@code null} more checks are performed, and the
-        * post will be invisible if:
-        * </p>
-        * <ul>
-        * <li>The Sone of the post is not the given Sone, the given Sone does not
-        * follow the post’s Sone, and the given Sone is not the recipient of the
-        * post.</li>
-        * <li>The trust relationship between the two Sones can not be retrieved.</li>
-        * <li>The given Sone has explicitely assigned negative trust to the post’s
-        * Sone.</li>
-        * <li>The given Sone has not explicitely assigned negative trust to the
-        * post’s Sone but the implicit trust is negative.</li>
-        * </ul>
-        * If none of these statements is true the post is considered visible.
-        *
-        * @param sone
-        *            The Sone that checks for a post’s visibility (may be
-        *            {@code null} to skip Sone-specific checks, such as trust)
-        * @param post
-        *            The post to check for visibility
-        * @return {@code true} if the post is considered visible, {@code false}
-        *         otherwise
-        */
-       public static boolean isPostVisible(Sone sone, Post post) {
-               checkNotNull(post, "post must not be null");
-               if (!post.isLoaded()) {
-                       return false;
-               }
-               Sone postSone = post.getSone();
-               if (sone != null) {
-                       Trust trust = postSone.getIdentity().getTrust((OwnIdentity) sone.getIdentity());
-                       if (trust != null) {
-                               if ((trust.getExplicit() != null) && (trust.getExplicit() < 0)) {
-                                       return false;
-                               }
-                               if ((trust.getExplicit() == null) && (trust.getImplicit() != null) && (trust.getImplicit() < 0)) {
-                                       return false;
-                               }
-                       } else {
-                               /*
-                                * a null trust means that the trust updater has not yet
-                                * received a trust value for this relation. if we return false,
-                                * the post feed will stay empty until the trust updater has
-                                * received trust values. to prevent this we simply assume that
-                                * posts are visible if there is no trust.
-                                */
-                       }
-                       if ((!postSone.equals(sone)) && !sone.hasFriend(postSone.getId()) && !sone.getId().equals(post.getRecipientId().orNull())) {
-                               return false;
-                       }
-               }
-               if (post.getTime() > System.currentTimeMillis()) {
-                       return false;
-               }
-               return true;
-       }
-
-       /**
-        * Checks whether a reply is visible to the given Sone. A reply is not
-        * considered visible if one of the following statements is true:
-        * <ul>
-        * <li>The reply does not have a post.</li>
-        * <li>The reply’s post does not have a Sone.</li>
-        * <li>The Sone of the reply’s post is not the given Sone, the given Sone
-        * does not follow the reply’s post’s Sone, and the given Sone is not the
-        * recipient of the reply’s post.</li>
-        * <li>The trust relationship between the two Sones can not be retrieved.</li>
-        * <li>The given Sone has explicitely assigned negative trust to the post’s
-        * Sone.</li>
-        * <li>The given Sone has not explicitely assigned negative trust to the
-        * reply’s post’s Sone but the implicit trust is negative.</li>
-        * <li>The reply’s post’s {@link Post#getTime() time} is in the future.</li>
-        * <li>The reply’s {@link Reply#getTime() time} is in the future.</li>
-        * </ul>
-        * If none of these statements is true the reply is considered visible.
-        *
-        * @param sone
-        *            The Sone that checks for a post’s visibility (may be
-        *            {@code null} to skip Sone-specific checks, such as trust)
-        * @param reply
-        *            The reply to check for visibility
-        * @return {@code true} if the reply is considered visible, {@code false}
-        *         otherwise
-        */
-       public static boolean isReplyVisible(Sone sone, PostReply reply) {
-               checkNotNull(reply, "reply must not be null");
-               Optional<Post> post = reply.getPost();
-               if (!post.isPresent()) {
-                       return false;
-               }
-               if (!isPostVisible(sone, post.get())) {
-                       return false;
-               }
-               if (reply.getTime() > System.currentTimeMillis()) {
-                       return false;
-               }
-               return true;
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/notify/PostVisibilityFilter.java b/src/main/java/net/pterodactylus/sone/notify/PostVisibilityFilter.java
new file mode 100644 (file)
index 0000000..3582e3d
--- /dev/null
@@ -0,0 +1,97 @@
+package net.pterodactylus.sone.notify;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.inject.Singleton;
+
+import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.freenet.wot.OwnIdentity;
+import net.pterodactylus.sone.freenet.wot.Trust;
+import net.pterodactylus.util.notify.Notification;
+
+import com.google.common.base.Predicate;
+
+/**
+ * Filters {@link Notification}s involving {@link Post}s.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+@Singleton
+public class PostVisibilityFilter {
+
+       /**
+        * Checks whether a post is visible to the given Sone. A post is not
+        * considered visible if one of the following statements is true:
+        * <ul>
+        * <li>The post does not have a Sone.</li>
+        * <li>The post’s {@link Post#getTime() time} is in the future.</li>
+        * </ul>
+        * <p>
+        * If {@code post} is not {@code null} more checks are performed, and the
+        * post will be invisible if:
+        * </p>
+        * <ul>
+        * <li>The Sone of the post is not the given Sone, the given Sone does not
+        * follow the post’s Sone, and the given Sone is not the recipient of the
+        * post.</li>
+        * <li>The trust relationship between the two Sones can not be retrieved.</li>
+        * <li>The given Sone has explicitely assigned negative trust to the post’s
+        * Sone.</li>
+        * <li>The given Sone has not explicitely assigned negative trust to the
+        * post’s Sone but the implicit trust is negative.</li>
+        * </ul>
+        * If none of these statements is true the post is considered visible.
+        *
+        * @param sone
+        *              The Sone that checks for a post’s visibility (may be
+        *              {@code null} to skip Sone-specific checks, such as trust)
+        * @param post
+        *              The post to check for visibility
+        * @return {@code true} if the post is considered visible, {@code false}
+        * otherwise
+        */
+       boolean isPostVisible(@Nullable Sone sone, @Nonnull Post post) {
+               checkNotNull(post, "post must not be null");
+               if (!post.isLoaded()) {
+                       return false;
+               }
+               Sone postSone = post.getSone();
+               if (sone != null) {
+                       Trust trust = postSone.getIdentity().getTrust((OwnIdentity) sone.getIdentity());
+                       if (trust != null) {
+                               if ((trust.getExplicit() != null) && (trust.getExplicit() < 0)) {
+                                       return false;
+                               }
+                               if ((trust.getExplicit() == null) && (trust.getImplicit() != null) && (trust.getImplicit() < 0)) {
+                                       return false;
+                               }
+                       } else {
+                               /*
+                                * a null trust means that the trust updater has not yet
+                                * received a trust value for this relation. if we return false,
+                                * the post feed will stay empty until the trust updater has
+                                * received trust values. to prevent this we simply assume that
+                                * posts are visible if there is no trust.
+                                */
+                       }
+                       if ((!postSone.equals(sone)) && !sone.hasFriend(postSone.getId()) && !sone.getId().equals(post.getRecipientId().orNull())) {
+                               return false;
+                       }
+               }
+               return post.getTime() <= System.currentTimeMillis();
+       }
+
+       @Nonnull
+       public Predicate<Post> isVisible(@Nullable final Sone currentSone) {
+               return new Predicate<Post>() {
+                       @Override
+                       public boolean apply(@Nullable Post post) {
+                               return (post != null) && isPostVisible(currentSone, post);
+                       }
+               };
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/notify/ReplyVisibilityFilter.java b/src/main/java/net/pterodactylus/sone/notify/ReplyVisibilityFilter.java
new file mode 100644 (file)
index 0000000..fd13779
--- /dev/null
@@ -0,0 +1,72 @@
+package net.pterodactylus.sone.notify;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostReply;
+import net.pterodactylus.sone.data.Sone;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+
+/**
+ * Filter that checks a {@link PostReply} for visibility.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+@Singleton
+public class ReplyVisibilityFilter {
+
+       private final PostVisibilityFilter postVisibilityFilter;
+
+       @Inject
+       public ReplyVisibilityFilter(@Nonnull PostVisibilityFilter postVisibilityFilter) {
+               this.postVisibilityFilter = postVisibilityFilter;
+       }
+
+       /**
+        * Checks whether a reply is visible to the given Sone. A reply is not
+        * considered visible if one of the following statements is true:
+        * <ul>
+        * <li>The reply does not have a post.</li>
+        * <li>The reply’s post {@link PostVisibilityFilter#isPostVisible(Sone, Post) is not visible}.</li>
+        * <li>The reply’s {@link PostReply#getTime() time} is in the future.</li>
+        * </ul>
+        * If none of these statements is true the reply is considered visible.
+        *
+        * @param sone
+        *              The Sone that checks for a post’s visibility (may be
+        *              {@code null} to skip Sone-specific checks, such as trust)
+        * @param reply
+        *              The reply to check for visibility
+        * @return {@code true} if the reply is considered visible, {@code false}
+        * otherwise
+        */
+       boolean isReplyVisible(@Nullable Sone sone, @Nonnull PostReply reply) {
+               checkNotNull(reply, "reply must not be null");
+               Optional<Post> post = reply.getPost();
+               if (!post.isPresent()) {
+                       return false;
+               }
+               if (!postVisibilityFilter.isPostVisible(sone, post.get())) {
+                       return false;
+               }
+               return reply.getTime() <= System.currentTimeMillis();
+       }
+
+       @Nonnull
+       public Predicate<PostReply> isVisible(@Nullable final Sone currentSone) {
+               return new Predicate<PostReply>() {
+                       @Override
+                       public boolean apply(@Nullable PostReply postReply) {
+                               return (postReply != null) && isReplyVisible(currentSone, postReply);
+                       }
+               };
+       }
+
+}
index e19a89a..7d0ab90 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - AlbumAccessor.java - Copyright © 2011–2015 David Roden
+ * Sone - AlbumAccessor.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,8 +19,10 @@ package net.pterodactylus.sone.template;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Random;
 
 import net.pterodactylus.sone.data.Album;
+import net.pterodactylus.sone.data.Image;
 import net.pterodactylus.util.template.Accessor;
 import net.pterodactylus.util.template.ReflectionAccessor;
 import net.pterodactylus.util.template.TemplateContext;
@@ -34,6 +36,8 @@ import net.pterodactylus.util.template.TemplateContext;
  */
 public class AlbumAccessor extends ReflectionAccessor {
 
+       private final Random random = new Random();
+
        /**
         * {@inheritDoc}
         */
@@ -49,6 +53,9 @@ public class AlbumAccessor extends ReflectionAccessor {
                        }
                        backlinks.add(0, new Link("imageBrowser.html?sone=" + album.getSone().getId(), SoneAccessor.getNiceName(album.getSone())));
                        return backlinks;
+               } else if ("albumImage".equals(member)) {
+                       List<Image> images = album.getImages();
+                       return images.isEmpty() ? null : images.get(random.nextInt(images.size()));
                }
                return super.get(templateContext, object, member);
        }
index 5449fa5..0771abd 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - CollectionAccessor.java - Copyright © 2010–2015 David Roden
+ * Sone - CollectionAccessor.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 2b1f73c..5776b07 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - CssClassNameFilter.java - Copyright © 2010–2015 David Roden
+ * Sone - CssClassNameFilter.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index ae53e58..4c8f3d5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetPagePlugin.java - Copyright © 2010–2015 David Roden
+ * Sone - GetPagePlugin.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 3383a3b..3e7a261 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - HttpRequestAccessor.java - Copyright © 2011–2015 David Roden
+ * Sone - HttpRequestAccessor.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 3a49aed..74c1a77 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IdentityAccessor.java - Copyright © 2010–2015 David Roden
+ * Sone - IdentityAccessor.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 2c27933..903e26c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageAccessor.java - Copyright © 2011–2015 David Roden
+ * Sone - ImageAccessor.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index f560731..2f1526f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageLinkFilter.java - Copyright © 2011–2015 David Roden
+ * Sone - ImageLinkFilter.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 809c53a..595f40e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - JavascriptFilter.java - Copyright © 2011–2015 David Roden
+ * Sone - JavascriptFilter.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 109222e..479b3b2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ParserFilter.java - Copyright © 2011–2015 David Roden
+ * Sone - ParserFilter.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -20,7 +20,6 @@ package net.pterodactylus.sone.template;
 import static java.lang.String.valueOf;
 import static net.pterodactylus.sone.utils.NumberParsers.parseInt;
 
-import java.io.IOException;
 import java.io.StringReader;
 import java.io.StringWriter;
 import java.io.UnsupportedEncodingException;
@@ -101,41 +100,37 @@ public class ParserFilter implements Filter {
                FreenetRequest request = (FreenetRequest) templateContext.get("request");
                SoneTextParserContext context = new SoneTextParserContext(request, (Sone) sone);
                StringWriter parsedTextWriter = new StringWriter();
-               try {
-                       Iterable<Part> parts = soneTextParser.parse(context, new StringReader(text));
-                       if (length > -1) {
-                               int allPartsLength = 0;
-                               List<Part> shortenedParts = new ArrayList<Part>();
-                               for (Part part : parts) {
-                                       if (part instanceof PlainTextPart) {
-                                               String longText = ((PlainTextPart) part).getText();
-                                               if (allPartsLength < cutOffLength) {
-                                                       if ((allPartsLength + longText.length()) > cutOffLength) {
-                                                               shortenedParts.add(new PlainTextPart(longText.substring(0, cutOffLength - allPartsLength) + "…"));
-                                                       } else {
-                                                               shortenedParts.add(part);
-                                                       }
-                                               }
-                                               allPartsLength += longText.length();
-                                       } else if (part instanceof LinkPart) {
-                                               if (allPartsLength < cutOffLength) {
-                                                       shortenedParts.add(part);
-                                               }
-                                               allPartsLength += ((LinkPart) part).getText().length();
-                                       } else {
-                                               if (allPartsLength < cutOffLength) {
+               Iterable<Part> parts = soneTextParser.parse(text, context);
+               if (length > -1) {
+                       int allPartsLength = 0;
+                       List<Part> shortenedParts = new ArrayList<Part>();
+                       for (Part part : parts) {
+                               if (part instanceof PlainTextPart) {
+                                       String longText = part.getText();
+                                       if (allPartsLength < cutOffLength) {
+                                               if ((allPartsLength + longText.length()) > cutOffLength) {
+                                                       shortenedParts.add(new PlainTextPart(longText.substring(0, cutOffLength - allPartsLength) + "…"));
+                                               } else {
                                                        shortenedParts.add(part);
                                                }
                                        }
+                                       allPartsLength += longText.length();
+                               } else if (part instanceof LinkPart) {
+                                       if (allPartsLength < cutOffLength) {
+                                               shortenedParts.add(part);
+                                       }
+                                       allPartsLength += part.getText().length();
+                               } else {
+                                       if (allPartsLength < cutOffLength) {
+                                               shortenedParts.add(part);
+                                       }
                                }
-                               if (allPartsLength >= length) {
-                                       parts = shortenedParts;
-                               }
                        }
-                       render(parsedTextWriter, parts);
-               } catch (IOException ioe1) {
-                       /* no exceptions in a StringReader or StringWriter, ignore. */
+                       if (allPartsLength >= length) {
+                               parts = shortenedParts;
+                       }
                }
+               render(parsedTextWriter, parts);
                return parsedTextWriter.toString();
        }
 
@@ -252,26 +247,22 @@ public class ParserFilter implements Filter {
        private void render(Writer writer, PostPart postPart) {
                SoneTextParser parser = new SoneTextParser(core, core);
                SoneTextParserContext parserContext = new SoneTextParserContext(null, postPart.getPost().getSone());
-               try {
-                       Iterable<Part> parts = parser.parse(parserContext, new StringReader(postPart.getPost().getText()));
-                       StringBuilder excerpt = new StringBuilder();
-                       for (Part part : parts) {
-                               excerpt.append(part.getText());
-                               if (excerpt.length() > 20) {
-                                       int lastSpace = excerpt.lastIndexOf(" ", 20);
-                                       if (lastSpace > -1) {
-                                               excerpt.setLength(lastSpace);
-                                       } else {
-                                               excerpt.setLength(20);
-                                       }
-                                       excerpt.append("…");
-                                       break;
+               Iterable<Part> parts = parser.parse(postPart.getPost().getText(), parserContext);
+               StringBuilder excerpt = new StringBuilder();
+               for (Part part : parts) {
+                       excerpt.append(part.getText());
+                       if (excerpt.length() > 20) {
+                               int lastSpace = excerpt.lastIndexOf(" ", 20);
+                               if (lastSpace > -1) {
+                                       excerpt.setLength(lastSpace);
+                               } else {
+                                       excerpt.setLength(20);
                                }
+                               excerpt.append("…");
+                               break;
                        }
-                       renderLink(writer, "viewPost.html?post=" + postPart.getPost().getId(), excerpt.toString(), SoneAccessor.getNiceName(postPart.getPost().getSone()), "in-sone");
-               } catch (IOException ioe1) {
-                       /* StringReader shouldn’t throw. */
                }
+               renderLink(writer, "viewPost.html?post=" + postPart.getPost().getId(), excerpt.toString(), SoneAccessor.getNiceName(postPart.getPost().getSone()), "in-sone");
        }
 
        /**
index f04ea83..11cafea 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostAccessor.java - Copyright © 2010–2015 David Roden
+ * Sone - PostAccessor.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 93bfc59..980d960 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ProfileAccessor.java - Copyright © 2011–2015 David Roden
+ * Sone - ProfileAccessor.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index dc5ded0..a3253b0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ReplyAccessor.java - Copyright © 2010–2015 David Roden
+ * Sone - ReplyAccessor.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 709ffbc..9bb5b81 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ReplyGroupFilter.java - Copyright © 2010–2015 David Roden
+ * Sone - ReplyGroupFilter.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 211c4b4..2d6167a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - RequestChangeFilter.java - Copyright © 2010–2015 David Roden
+ * Sone - RequestChangeFilter.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 4bb78af..50fa272 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneAccessor.java - Copyright © 2010–2015 David Roden
+ * Sone - SoneAccessor.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 101cbb0..2104888 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SubstringFilter.java - Copyright © 2010–2015 David Roden
+ * Sone - SubstringFilter.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index f3dd827..fee047d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - TrustAccessor.java - Copyright © 2010–2015 David Roden
+ * Sone - TrustAccessor.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 005094e..2ca695b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UniqueElementFilter.java - Copyright © 2011–2015 David Roden
+ * Sone - UniqueElementFilter.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 7981d55..2c5a004 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UnknownDateFilter.java - Copyright © 2011–2015 David Roden
+ * Sone - UnknownDateFilter.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index b8fd240..829bf0f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - FreenetLinkPart.java - Copyright © 2011–2015 David Roden
+ * Sone - FreenetLinkPart.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 2e7d331..6764328 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - LinkPart.java - Copyright © 2011–2015 David Roden
+ * Sone - LinkPart.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index f5f8f1b..f87b852 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Parser.java - Copyright © 2010–2015 David Roden
+ * Sone - Parser.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,8 +17,9 @@
 
 package net.pterodactylus.sone.text;
 
-import java.io.IOException;
 import java.io.Reader;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
 /**
  * Interface for parsers that can create {@link Part}s from a text source
@@ -33,14 +34,13 @@ public interface Parser<C extends ParserContext> {
        /**
         * Create one or more {@link Part}s from the given text source.
         *
-        * @param context
-        *            The parser context (may be {@code null})
         * @param source
         *            The text source
+        * @param context
+        *            The parser context (may be {@code null})
         * @return The parsed parts
-        * @throws IOException
-        *             if an I/O error occurs
         */
-       public Iterable<Part> parse(C context, Reader source) throws IOException;
+       @Nonnull
+       Iterable<Part> parse(@Nonnull String source, @Nullable C context);
 
 }
index 9782008..7387967 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ParserContext.java - Copyright © 2010–2015 David Roden
+ * Sone - ParserContext.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -20,7 +20,7 @@ package net.pterodactylus.sone.text;
 /**
  * Context for the {@link Parser}. This interface needs to be implemented by
  * {@link Parser}s that need to provide more information than just the text to
- * parse to {@link Parser#parse(ParserContext, java.io.Reader)}.
+ * parse to {@link Parser#parse(String, ParserContext)}.
  *
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
index 8dee69b..e516f0a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Part.java - Copyright © 2010–2015 David Roden
+ * Sone - Part.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 75714f7..d1c89b3 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PartContainer.java - Copyright © 2010–2015 David Roden
+ * Sone - PartContainer.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 9090eb3..4f15a73 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PlainTextPart.java - Copyright © 2011–2015 David Roden
+ * Sone - PlainTextPart.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 6aae37c..68075a5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostPart.java - Copyright © 2011–2015 David Roden
+ * Sone - PostPart.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 3d3e666..cbafc61 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SonePart.java - Copyright © 2011–2015 David Roden
+ * Sone - SonePart.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index f338286..fb4e7cd 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneTextParser.java - Copyright © 2010–2015 David Roden
+ * Sone - SoneTextParser.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,13 +21,16 @@ import static java.util.logging.Logger.getLogger;
 
 import java.io.BufferedReader;
 import java.io.IOException;
-import java.io.Reader;
+import java.io.StringReader;
 import java.net.MalformedURLException;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.data.impl.IdOnlySone;
@@ -117,10 +120,11 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
        /**
         * {@inheritDoc}
         */
+       @Nonnull
        @Override
-       public Iterable<Part> parse(SoneTextParserContext context, Reader source) throws IOException {
+       public Iterable<Part> parse(@Nonnull String source, @Nullable SoneTextParserContext context) {
                PartContainer parts = new PartContainer();
-               BufferedReader bufferedReader = (source instanceof BufferedReader) ? (BufferedReader) source : new BufferedReader(source);
+               BufferedReader bufferedReader = new BufferedReader(new StringReader(source));
                try {
                        String line;
                        boolean lastLineEmpty = true;
@@ -271,10 +275,11 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                                }
                                lastLineEmpty = false;
                        }
+               } catch (IOException ioe1) {
+                       // a buffered reader around a string reader should never throw.
+                       throw new RuntimeException(ioe1);
                } finally {
-                       if (bufferedReader != source) {
-                               Closer.close(bufferedReader);
-                       }
+                       Closer.close(bufferedReader);
                }
                for (int partIndex = parts.size() - 1; partIndex >= 0; --partIndex) {
                        Part part = parts.getPart(partIndex);
index 3e7dc3f..8918e40 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneTextParserContext.java - Copyright © 2011–2015 David Roden
+ * Sone - SoneTextParserContext.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 5782a23..fa57f9c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - TextFilter.java - Copyright © 2011–2015 David Roden
+ * Sone - TextFilter.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index a0470c1..aac9594 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IntegerRangePredicate.java - Copyright © 2013–2015 David Roden
+ * Sone - IntegerRangePredicate.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 581df3e..76a475f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - AboutPage.java - Copyright © 2010–2015 David Roden
+ * Sone - AboutPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index afb6a29..be5fc67 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - BookmarkPage.java - Copyright © 2011–2015 David Roden
+ * Sone - BookmarkPage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index a86e044..404abb1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - BookmarksPage.java - Copyright © 2011–2015 David Roden
+ * Sone - BookmarksPage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 539fa6b..6ba14f0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - CreateAlbumPage.java - Copyright © 2011–2015 David Roden
+ * Sone - CreateAlbumPage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 46631ce..f486379 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - CreatePostPage.java - Copyright © 2010–2015 David Roden
+ * Sone - CreatePostPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 5098f2d..2383218 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - CreateReplyPage.java - Copyright © 2010–2015 David Roden
+ * Sone - CreateReplyPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 5ea2c1a..7ea74fe 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - CreateSonePage.java - Copyright © 2010–2015 David Roden
+ * Sone - CreateSonePage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index b3a7a06..7d2fa59 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DeleteAlbumPage.java - Copyright © 2011–2015 David Roden
+ * Sone - DeleteAlbumPage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 6934beb..eeb9e3c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DeleteImagePage.java - Copyright © 2011–2015 David Roden
+ * Sone - DeleteImagePage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 03a6933..517a07e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DeletePostPage.java - Copyright © 2010–2015 David Roden
+ * Sone - DeletePostPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 0184a2c..f036489 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DeleteProfileFieldPage.java - Copyright © 2011–2015 David Roden
+ * Sone - DeleteProfileFieldPage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 37b9fea..0cd6b4f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DeleteReplyPage.java - Copyright © 2010–2015 David Roden
+ * Sone - DeleteReplyPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -58,7 +58,7 @@ public class DeleteReplyPage extends SoneTemplatePage {
                Optional<PostReply> reply = webInterface.getCore().getPostReply(replyId);
                String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
                if (request.getMethod() == Method.POST) {
-                       if (!reply.get().getSone().isLocal()) {
+                       if (!reply.isPresent() || !reply.get().getSone().isLocal()) {
                                throw new RedirectException("noPermission.html");
                        }
                        if (request.getHttpRequest().isPartSet("confirmDelete")) {
index 90cc6cc..ce01771 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DeleteSonePage.java - Copyright © 2010–2015 David Roden
+ * Sone - DeleteSonePage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index d5bb6ea..80da869 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DismissNotificationPage.java - Copyright © 2010–2015 David Roden
+ * Sone - DismissNotificationPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -22,6 +22,8 @@ import net.pterodactylus.util.notify.Notification;
 import net.pterodactylus.util.template.Template;
 import net.pterodactylus.util.template.TemplateContext;
 
+import com.google.common.base.Optional;
+
 /**
  * Page that lets the user dismiss a notification.
  *
@@ -52,9 +54,9 @@ public class DismissNotificationPage extends SoneTemplatePage {
        protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException {
                super.processTemplate(request, templateContext);
                String notificationId = request.getHttpRequest().getPartAsStringFailsafe("notification", 36);
-               Notification notification = webInterface.getNotifications().getNotification(notificationId);
-               if ((notification != null) && notification.isDismissable()) {
-                       notification.dismiss();
+               Optional<Notification> notification = webInterface.getNotification(notificationId);
+               if (notification.isPresent() && notification.get().isDismissable()) {
+                       notification.get().dismiss();
                }
                String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
                throw new RedirectException(returnPage);
index 14411f9..c09608a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DistrustPage.java - Copyright © 2011–2015 David Roden
+ * Sone - DistrustPage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 5244bc5..6a08c3c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - EditAlbumPage.java - Copyright © 2011–2015 David Roden
+ * Sone - EditAlbumPage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -68,11 +68,6 @@ public class EditAlbumPage extends SoneTemplatePage {
                                webInterface.getCore().touchConfiguration();
                                throw new RedirectException("imageBrowser.html?album=" + album.getParent().getId());
                        }
-                       String albumImageId = request.getHttpRequest().getPartAsStringFailsafe("album-image", 36);
-                       if (webInterface.getCore().getImage(albumImageId, false) == null) {
-                               albumImageId = null;
-                       }
-                       album.modify().setAlbumImage(albumImageId).update();
                        String title = request.getHttpRequest().getPartAsStringFailsafe("title", 100).trim();
                        String description = request.getHttpRequest().getPartAsStringFailsafe("description", 1000).trim();
                        try {
index 9ae7ed6..b169161 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - EditImagePage.java - Copyright © 2010–2015 David Roden
+ * Sone - EditImagePage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 8f5e4ba..8cf18f2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - EditProfileFieldPage.java - Copyright © 2011–2015 David Roden
+ * Sone - EditProfileFieldPage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 5145358..bf76c26 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - EditProfilePage.java - Copyright © 2010–2015 David Roden
+ * Sone - EditProfilePage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index a83c46a..eef94df 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - FollowSonePage.java - Copyright © 2010–2015 David Roden
+ * Sone - FollowSonePage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 87e4fa2..ec38d0a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetImagePage.java - Copyright © 2011–2015 David Roden
+ * Sone - GetImagePage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 442cdc0..5da6e53 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageBrowserPage.java - Copyright © 2011–2015 David Roden
+ * Sone - ImageBrowserPage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index c5e80c4..e5d5b7d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IndexPage.java - Copyright © 2010–2015 David Roden
+ * Sone - IndexPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -26,14 +26,13 @@ import java.util.List;
 
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.sone.notify.ListNotificationFilters;
+import net.pterodactylus.sone.notify.PostVisibilityFilter;
 import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.util.collection.Pagination;
 import net.pterodactylus.util.template.Template;
 import net.pterodactylus.util.template.TemplateContext;
 
 import com.google.common.base.Optional;
-import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
 
 /**
@@ -44,14 +43,11 @@ import com.google.common.collect.Collections2;
  */
 public class IndexPage extends SoneTemplatePage {
 
-       /**
-        * @param template
-        *            The template to render
-        * @param webInterface
-        *            The Sone web interface
-        */
-       public IndexPage(Template template, WebInterface webInterface) {
+       private final PostVisibilityFilter postVisibilityFilter;
+
+       public IndexPage(Template template, WebInterface webInterface, PostVisibilityFilter postVisibilityFilter) {
                super("index.html", template, "Page.Index.Title", webInterface, true);
+               this.postVisibilityFilter = postVisibilityFilter;
        }
 
        //
@@ -81,14 +77,7 @@ public class IndexPage extends SoneTemplatePage {
                                }
                        }
                }
-               allPosts = Collections2.filter(allPosts, new Predicate<Post>() {
-
-                       @Override
-                       public boolean apply(Post post) {
-                               return ListNotificationFilters.isPostVisible(currentSone, post);
-                       }
-               });
-               allPosts = Collections2.filter(allPosts, Post.FUTURE_POSTS_FILTER);
+               allPosts = Collections2.filter(allPosts, postVisibilityFilter.isVisible(currentSone));
                List<Post> sortedPosts = new ArrayList<Post>(allPosts);
                Collections.sort(sortedPosts, Post.TIME_COMPARATOR);
                Pagination<Post> pagination = new Pagination<Post>(sortedPosts, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(parseInt(request.getHttpRequest().getParam("page"), 0));
index edb9980..d5fb9e2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - KnownSonesPage.java - Copyright © 2010–2015 David Roden
+ * Sone - KnownSonesPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 43d46a6..171a7be 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - LikePage.java - Copyright © 2010–2015 David Roden
+ * Sone - LikePage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index dc35b70..7881e9e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - LockSonePage.java - Copyright © 2010–2015 David Roden
+ * Sone - LockSonePage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 4b38a07..608f120 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - LoginPage.java - Copyright © 2010–2015 David Roden
+ * Sone - LoginPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index aa8f401..d47044c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - LogoutPage.java - Copyright © 2010–2015 David Roden
+ * Sone - LogoutPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 215ce93..39c5a43 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - MarkAsKnownPage.java - Copyright © 2011–2015 David Roden
+ * Sone - MarkAsKnownPage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 1757952..2b99adb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - NewPage.java - Copyright © 2013–2015 David Roden
+ * Sone - NewPage.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -25,11 +25,10 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
-import com.google.common.collect.Collections2;
-
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.PostReply;
-import net.pterodactylus.sone.notify.ListNotificationFilters;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.notify.PostVisibilityFilter;
 import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.util.collection.Pagination;
 import net.pterodactylus.util.template.Template;
@@ -37,8 +36,7 @@ import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * Page that displays all new posts and replies. The posts are filtered using
- * {@link ListNotificationFilters#filterPosts(java.util.Collection, net.pterodactylus.sone.data.Sone)}
- * and sorted by time.
+ * {@link PostVisibilityFilter#isPostVisible(Sone, Post)} and sorted by time.
  *
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
@@ -68,13 +66,13 @@ public class NewPage extends SoneTemplatePage {
                super.processTemplate(request, templateContext);
 
                /* collect new elements from notifications. */
-               Set<Post> posts = new HashSet<Post>(webInterface.getNewPosts());
-               for (PostReply reply : Collections2.filter(webInterface.getNewReplies(), PostReply.HAS_POST_FILTER)) {
+               Set<Post> posts = new HashSet<Post>(webInterface.getNewPosts(getCurrentSone(request.getToadletContext(), false)));
+               for (PostReply reply : webInterface.getNewReplies(getCurrentSone(request.getToadletContext(), false))) {
                        posts.add(reply.getPost().get());
                }
 
                /* filter and sort them. */
-               List<Post> sortedPosts = ListNotificationFilters.filterPosts(new ArrayList<Post>(posts), webInterface.getCurrentSone(request.getToadletContext(), false));
+               List<Post> sortedPosts = new ArrayList(posts);
                Collections.sort(sortedPosts, Post.TIME_COMPARATOR);
 
                /* paginate them. */
index dff7ecf..0bc44d5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - OptionsPage.java - Copyright © 2010–2015 David Roden
+ * Sone - OptionsPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 0c8f516..f89bbed 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ReloadingPage.java - Copyright © 2010–2015 David Roden
+ * Sone - ReloadingPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,6 +19,7 @@ package net.pterodactylus.sone.web;
 
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -72,8 +73,10 @@ public class ReloadingPage<REQ extends Request> implements Page<REQ> {
                String path = request.getUri().getPath();
                int lastSlash = path.lastIndexOf('/');
                String filename = path.substring(lastSlash + 1);
-               InputStream fileInputStream = new FileInputStream(new File(filesystemPath, filename));
-               if (fileInputStream == null) {
+               InputStream fileInputStream;
+               try {
+                       fileInputStream = new FileInputStream(new File(filesystemPath, filename));
+               } catch (FileNotFoundException fnfe1) {
                        return response.setStatusCode(404).setStatusText("Not found.");
                }
                OutputStream contentOutputStream = response.getContent();
index 8599a31..2383e0d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - RescuePage.java - Copyright © 2011–2015 David Roden
+ * Sone - RescuePage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 7ec7f9f..5b657d5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SearchPage.java - Copyright © 2010–2015 David Roden
+ * Sone - SearchPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 15810ca..b3e7a6a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneTemplatePage.java - Copyright © 2010–2015 David Roden
+ * Sone - SoneTemplatePage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,6 +19,7 @@ package net.pterodactylus.sone.web;
 
 import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -27,20 +28,19 @@ import java.util.Map;
 
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.main.SonePlugin;
-import net.pterodactylus.sone.notify.ListNotificationFilters;
 import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.sone.web.page.FreenetTemplatePage;
 import net.pterodactylus.util.notify.Notification;
 import net.pterodactylus.util.template.Template;
 import net.pterodactylus.util.template.TemplateContext;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-
 import freenet.clients.http.SessionManager.Session;
 import freenet.clients.http.ToadletContext;
 import freenet.support.api.HTTPRequest;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
 /**
  * Base page for the Sone web interface.
  *
@@ -65,21 +65,6 @@ public class SoneTemplatePage extends FreenetTemplatePage {
         *            The path of the page
         * @param template
         *            The template to render
-        * @param webInterface
-        *            The Sone web interface
-        */
-       public SoneTemplatePage(String path, Template template, WebInterface webInterface) {
-               this(path, template, null, webInterface, false);
-       }
-
-       /**
-        * Creates a new template page for Sone that does not require the user to be
-        * logged in.
-        *
-        * @param path
-        *            The path of the page
-        * @param template
-        *            The template to render
         * @param pageTitleKey
         *            The l10n key of the page title
         * @param webInterface
@@ -263,7 +248,7 @@ public class SoneTemplatePage extends FreenetTemplatePage {
                templateContext.set("latestEdition", webInterface.getCore().getUpdateChecker().getLatestEdition());
                templateContext.set("latestVersion", webInterface.getCore().getUpdateChecker().getLatestVersion());
                templateContext.set("latestVersionTime", webInterface.getCore().getUpdateChecker().getLatestVersionDate());
-               List<Notification> notifications = ListNotificationFilters.filterNotifications(webInterface.getNotifications().getNotifications(), currentSone);
+               List<Notification> notifications = new ArrayList<Notification>(webInterface.getNotifications(currentSone));
                Collections.sort(notifications, Notification.CREATED_TIME_SORTER);
                templateContext.set("notifications", notifications);
                templateContext.set("notificationHash", notifications.hashCode());
index cf67052..637bb26 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - TrustPage.java - Copyright © 2011–2015 David Roden
+ * Sone - TrustPage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 80b3ce6..51da550 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UnbookmarkPage.java - Copyright © 2011–2015 David Roden
+ * Sone - UnbookmarkPage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 1ec2d6f..d8ff899 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UnfollowSonePage.java - Copyright © 2010–2015 David Roden
+ * Sone - UnfollowSonePage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 057f7b3..ade8b89 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UnlikePage.java - Copyright © 2010–2015 David Roden
+ * Sone - UnlikePage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 0d4432f..f641361 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UnlockSonePage.java - Copyright © 2010–2015 David Roden
+ * Sone - UnlockSonePage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index a5b314f..9d51276 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UntrustPage.java - Copyright © 2011–2015 David Roden
+ * Sone - UntrustPage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 7621a6d..21c4272 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UploadImagePage.java - Copyright © 2011–2015 David Roden
+ * Sone - UploadImagePage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -91,7 +91,10 @@ public class UploadImagePage extends SoneTemplatePage {
                        if (!currentSone.equals(parent.getSone())) {
                                throw new RedirectException("noPermission.html");
                        }
-                       String name = request.getHttpRequest().getPartAsStringFailsafe("title", 200);
+                       String name = request.getHttpRequest().getPartAsStringFailsafe("title", 200).trim();
+                       if (name.length() == 0) {
+                               throw new RedirectException("emptyImageTitle.html");
+                       }
                        String description = request.getHttpRequest().getPartAsStringFailsafe("description", 4000);
                        HTTPUploadedFile uploadedFile = request.getHttpRequest().getUploadedFile("image");
                        Bucket fileBucket = uploadedFile.getData();
index 9ab6612..dca59e2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ViewPostPage.java - Copyright © 2010–2015 David Roden
+ * Sone - ViewPostPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 9b346c3..fe1e7db 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ViewSonePage.java - Copyright © 2010–2015 David Roden
+ * Sone - ViewSonePage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 4da2177..271111e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - WebInterface.java - Copyright © 2010–2015 David Roden
+ * Sone - WebInterface.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 
 package net.pterodactylus.sone.web;
 
+import static com.google.common.collect.FluentIterable.from;
 import static java.util.logging.Logger.getLogger;
 import static net.pterodactylus.util.template.TemplateParser.parse;
 
-import java.io.IOException;
 import java.io.StringReader;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -35,8 +35,9 @@ import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
-import java.util.logging.Level;
 import java.util.logging.Logger;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.core.event.ImageInsertAbortedEvent;
@@ -72,6 +73,9 @@ import net.pterodactylus.sone.main.Loaders;
 import net.pterodactylus.sone.main.ReparseFilter;
 import net.pterodactylus.sone.main.SonePlugin;
 import net.pterodactylus.sone.notify.ListNotification;
+import net.pterodactylus.sone.notify.ListNotificationFilter;
+import net.pterodactylus.sone.notify.PostVisibilityFilter;
+import net.pterodactylus.sone.notify.ReplyVisibilityFilter;
 import net.pterodactylus.sone.template.AlbumAccessor;
 import net.pterodactylus.sone.template.CollectionAccessor;
 import net.pterodactylus.sone.template.CssClassNameFilter;
@@ -147,11 +151,6 @@ import net.pterodactylus.util.template.XmlFilter;
 import net.pterodactylus.util.web.RedirectPage;
 import net.pterodactylus.util.web.TemplatePage;
 
-import com.google.common.collect.Collections2;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.eventbus.Subscribe;
-import com.google.inject.Inject;
-
 import freenet.clients.http.SessionManager;
 import freenet.clients.http.SessionManager.Session;
 import freenet.clients.http.ToadletContainer;
@@ -159,6 +158,12 @@ import freenet.clients.http.ToadletContext;
 import freenet.l10n.BaseL10n;
 import freenet.support.api.HTTPRequest;
 
+import com.google.common.base.Optional;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.eventbus.Subscribe;
+import com.google.inject.Inject;
+
 /**
  * Bundles functionality that a web interface of a Freenet plugin needs, e.g.
  * references to l10n helpers.
@@ -194,6 +199,10 @@ public class WebInterface {
        /** The parser filter. */
        private final ParserFilter parserFilter;
 
+       private final ListNotificationFilter listNotificationFilter;
+       private final PostVisibilityFilter postVisibilityFilter;
+       private final ReplyVisibilityFilter replyVisibilityFilter;
+
        /** The “new Sone” notification. */
        private final ListNotification<Sone> newSoneNotification;
 
@@ -243,9 +252,12 @@ public class WebInterface {
         *            The Sone plugin
         */
        @Inject
-       public WebInterface(SonePlugin sonePlugin, Loaders loaders) {
+       public WebInterface(SonePlugin sonePlugin, Loaders loaders, ListNotificationFilter listNotificationFilter, PostVisibilityFilter postVisibilityFilter, ReplyVisibilityFilter replyVisibilityFilter) {
                this.sonePlugin = sonePlugin;
                this.loaders = loaders;
+               this.listNotificationFilter = listNotificationFilter;
+               this.postVisibilityFilter = postVisibilityFilter;
+               this.replyVisibilityFilter = replyVisibilityFilter;
                formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
                soneTextParser = new SoneTextParser(getCore(), getCore());
 
@@ -454,6 +466,16 @@ public class WebInterface {
                return notificationManager;
        }
 
+       @Nonnull
+       public Optional<Notification> getNotification(@Nonnull String notificationId) {
+               return Optional.fromNullable(notificationManager.getNotification(notificationId));
+       }
+
+       @Nonnull
+       public Collection<Notification> getNotifications(@Nullable Sone currentSone) {
+               return listNotificationFilter.filterNotifications(notificationManager.getNotifications(), currentSone);
+       }
+
        /**
         * Returns the l10n helper of the node.
         *
@@ -491,6 +513,15 @@ public class WebInterface {
                return ImmutableSet.<Post> builder().addAll(newPostNotification.getElements()).addAll(localPostNotification.getElements()).build();
        }
 
+       @Nonnull
+       public Collection<Post> getNewPosts(@Nullable Sone currentSone) {
+               Set<Post> allNewPosts = ImmutableSet.<Post> builder()
+                               .addAll(newPostNotification.getElements())
+                               .addAll(localPostNotification.getElements())
+                               .build();
+               return from(allNewPosts).filter(postVisibilityFilter.isVisible(currentSone)).toSet();
+       }
+
        /**
         * Returns the replies that have been announced as new in the
         * {@link #newReplyNotification}.
@@ -501,6 +532,15 @@ public class WebInterface {
                return ImmutableSet.<PostReply> builder().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).build();
        }
 
+       @Nonnull
+       public Collection<PostReply> getNewReplies(@Nullable Sone currentSone) {
+               Set<PostReply> allNewReplies = ImmutableSet.<PostReply>builder()
+                               .addAll(newReplyNotification.getElements())
+                               .addAll(localReplyNotification.getElements())
+                               .build();
+               return from(allNewReplies).filter(replyVisibilityFilter.isVisible(currentSone)).toSet();
+       }
+
        /**
         * Sets whether the current start of the plugin is the first start. It is
         * considered a first start if the configuration file does not exist.
@@ -638,7 +678,7 @@ public class WebInterface {
 
                PageToadletFactory pageToadletFactory = new PageToadletFactory(sonePlugin.pluginRespirator().getHLSimpleClient(), "/Sone/");
                pageToadlets.add(pageToadletFactory.createPageToadlet(new RedirectPage<FreenetRequest>("", "index.html")));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new IndexPage(indexTemplate, this), "Index"));
+               pageToadlets.add(pageToadletFactory.createPageToadlet(new IndexPage(indexTemplate, this, postVisibilityFilter), "Index"));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new NewPage(newTemplate, this), "New"));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new CreateSonePage(createSoneTemplate, this), "CreateSone"));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new KnownSonesPage(knownSonesTemplate, this), "KnownSones"));
@@ -753,14 +793,10 @@ public class WebInterface {
        private Collection<Sone> getMentionedSones(String text) {
                /* we need no context to find mentioned Sones. */
                Set<Sone> mentionedSones = new HashSet<Sone>();
-               try {
-                       for (Part part : soneTextParser.parse(null, new StringReader(text))) {
-                               if (part instanceof SonePart) {
-                                       mentionedSones.add(((SonePart) part).getSone());
-                               }
+               for (Part part : soneTextParser.parse(text, null)) {
+                       if (part instanceof SonePart) {
+                               mentionedSones.add(((SonePart) part).getSone());
                        }
-               } catch (IOException ioe1) {
-                       logger.log(Level.WARNING, String.format("Could not parse post text: %s", text), ioe1);
                }
                return Collections2.filter(mentionedSones, Sone.LOCAL_SONE_FILTER);
        }
@@ -897,12 +933,6 @@ public class WebInterface {
        @Subscribe
        public void soneRemoved(SoneRemovedEvent soneRemovedEvent) {
                newSoneNotification.remove(soneRemovedEvent.sone());
-               for (Post post : soneRemovedEvent.sone().getPosts()) {
-                       removePost(post);
-               }
-               for (PostReply postReply : soneRemovedEvent.sone().getReplies()) {
-                       removeReply(postReply);
-               }
        }
 
        @Subscribe
@@ -1019,9 +1049,10 @@ public class WebInterface {
         */
        @Subscribe
        public void updateFound(UpdateFoundEvent updateFoundEvent) {
-               newVersionNotification.getTemplateContext().set("latestVersion", updateFoundEvent.version());
-               newVersionNotification.getTemplateContext().set("latestEdition", updateFoundEvent.latestEdition());
-               newVersionNotification.getTemplateContext().set("releaseTime", updateFoundEvent.releaseTime());
+               newVersionNotification.set("latestVersion", updateFoundEvent.version());
+               newVersionNotification.set("latestEdition", updateFoundEvent.latestEdition());
+               newVersionNotification.set("releaseTime", updateFoundEvent.releaseTime());
+               newVersionNotification.set("disruptive", updateFoundEvent.disruptive());
                notificationManager.addNotification(newVersionNotification);
        }
 
index dea9c3e..ce5276b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - BookmarkAjaxPage.java - Copyright © 2011–2015 David Roden
+ * Sone - BookmarkAjaxPage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 8af6d47..bf82328 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - CreatePostAjaxPage.java - Copyright © 2010–2015 David Roden
+ * Sone - CreatePostAjaxPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index b63c852..fe2ae85 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - CreateReplyAjaxPage.java - Copyright © 2010–2015 David Roden
+ * Sone - CreateReplyAjaxPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 6ed222b..cfc1415 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DeletePostAjaxPage.java - Copyright © 2010–2015 David Roden
+ * Sone - DeletePostAjaxPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 2ad3b29..7d937b5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DeleteProfileFieldAjaxPage.java - Copyright © 2011–2015 David Roden
+ * Sone - DeleteProfileFieldAjaxPage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index e4905f5..7b2c3c9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DeleteReplyAjaxPage.java - Copyright © 2010–2015 David Roden
+ * Sone - DeleteReplyAjaxPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 4e731e8..4661851 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DismissNotificationAjaxPage.java - Copyright © 2010–2015 David Roden
+ * Sone - DismissNotificationAjaxPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,6 +21,8 @@ import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.util.notify.Notification;
 
+import com.google.common.base.Optional;
+
 /**
  * AJAX page that lets the user dismiss a notification.
  *
@@ -44,14 +46,14 @@ public class DismissNotificationAjaxPage extends JsonPage {
        @Override
        protected JsonReturnObject createJsonObject(FreenetRequest request) {
                String notificationId = request.getHttpRequest().getParam("notification");
-               Notification notification = webInterface.getNotifications().getNotification(notificationId);
-               if (notification == null) {
+               Optional<Notification> notification = webInterface.getNotification(notificationId);
+               if (!notification.isPresent()) {
                        return createErrorJsonObject("invalid-notification-id");
                }
-               if (!notification.isDismissable()) {
+               if (!notification.get().isDismissable()) {
                        return createErrorJsonObject("not-dismissable");
                }
-               notification.dismiss();
+               notification.get().dismiss();
                return createSuccessJsonObject();
        }
 
index c99c1a5..26a1207 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DistrustAjaxPage.java - Copyright © 2011–2015 David Roden
+ * Sone - DistrustAjaxPage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 96f3d01..9817fd8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - EditAlbumAjaxPage.java - Copyright © 2011–2015 David Roden
+ * Sone - EditAlbumAjaxPage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index e11a2b2..6f67ccb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - EditImageAjaxPage.java - Copyright © 2011–2015 David Roden
+ * Sone - EditImageAjaxPage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 2a931c9..b81cf60 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - EditProfileFieldAjaxPage.java - Copyright © 2011–2015 David Roden
+ * Sone - EditProfileFieldAjaxPage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 115b27b..6a2d8ce 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - FollowSoneAjaxPage.java - Copyright © 2010–2015 David Roden
+ * Sone - FollowSoneAjaxPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 3f6c0d4..7bb75e6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetLikesAjaxPage.java - Copyright © 2010–2015 David Roden
+ * Sone - GetLikesAjaxPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index ee4683f..d0cba79 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetNotificationsAjaxPage.java - Copyright © 2011–2015 David Roden
+ * Sone - GetNotificationsAjaxPage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,13 +21,12 @@ import static com.fasterxml.jackson.databind.node.JsonNodeFactory.instance;
 
 import java.io.IOException;
 import java.io.StringWriter;
-import java.util.Collection;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.main.SonePlugin;
-import net.pterodactylus.sone.notify.ListNotificationFilters;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.util.notify.Notification;
@@ -81,14 +80,13 @@ public class GetNotificationsAjaxPage extends JsonPage {
        @Override
        protected JsonReturnObject createJsonObject(FreenetRequest request) {
                Sone currentSone = getCurrentSone(request.getToadletContext(), false);
-               Collection<Notification> notifications = webInterface.getNotifications().getNotifications();
-               List<Notification> filteredNotifications = ListNotificationFilters.filterNotifications(notifications, currentSone);
-               Collections.sort(filteredNotifications, Notification.CREATED_TIME_SORTER);
+               List<Notification> notifications = new ArrayList<Notification>(webInterface.getNotifications(currentSone));
+               Collections.sort(notifications, Notification.CREATED_TIME_SORTER);
                ArrayNode jsonNotifications = new ArrayNode(instance);
-               for (Notification notification : filteredNotifications) {
+               for (Notification notification : notifications) {
                        jsonNotifications.add(createJsonNotification(request, notification));
                }
-               return createSuccessJsonObject().put("notificationHash", filteredNotifications.hashCode()).put("notifications", jsonNotifications).put("options", createJsonOptions(currentSone));
+               return createSuccessJsonObject().put("notificationHash", notifications.hashCode()).put("notifications", jsonNotifications).put("options", createJsonOptions(currentSone));
        }
 
        //
index 78176cb..60e94ce 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetPostAjaxPage.java - Copyright © 2010–2015 David Roden
+ * Sone - GetPostAjaxPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 1445e98..c66f47a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetReplyAjaxPage.java - Copyright © 2010–2015 David Roden
+ * Sone - GetReplyAjaxPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 6c5c0de..2ca7c23 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetStatusAjaxPage.java - Copyright © 2010–2015 David Roden
+ * Sone - GetStatusAjaxPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@ import static com.fasterxml.jackson.databind.node.JsonNodeFactory.instance;
 
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
@@ -31,7 +32,8 @@ import java.util.Set;
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.sone.notify.ListNotificationFilters;
+import net.pterodactylus.sone.notify.PostVisibilityFilter;
+import net.pterodactylus.sone.notify.ReplyVisibilityFilter;
 import net.pterodactylus.sone.template.SoneAccessor;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
@@ -40,8 +42,6 @@ import net.pterodactylus.util.notify.Notification;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
-import com.google.common.base.Predicate;
-import com.google.common.collect.Collections2;
 
 /**
  * The “get status” AJAX handler returns all information that is necessary to
@@ -88,20 +88,11 @@ public class GetStatusAjaxPage extends JsonPage {
                        jsonSones.add(createJsonSone(sone));
                }
                /* load notifications. */
-               List<Notification> notifications = ListNotificationFilters.filterNotifications(webInterface.getNotifications().getNotifications(), currentSone);
+               List<Notification> notifications = new ArrayList<Notification>(webInterface.getNotifications(currentSone));
                Collections.sort(notifications, Notification.CREATED_TIME_SORTER);
                /* load new posts. */
-               Collection<Post> newPosts = webInterface.getNewPosts();
-               if (currentSone != null) {
-                       newPosts = Collections2.filter(newPosts, new Predicate<Post>() {
-
-                               @Override
-                               public boolean apply(Post post) {
-                                       return ListNotificationFilters.isPostVisible(currentSone, post);
-                               }
+               Collection<Post> newPosts = webInterface.getNewPosts(getCurrentSone(request.getToadletContext(), false));
 
-                       });
-               }
                ArrayNode jsonPosts = new ArrayNode(instance);
                for (Post post : newPosts) {
                        ObjectNode jsonPost = new ObjectNode(instance);
@@ -112,19 +103,8 @@ public class GetStatusAjaxPage extends JsonPage {
                        jsonPosts.add(jsonPost);
                }
                /* load new replies. */
-               Collection<PostReply> newReplies = webInterface.getNewReplies();
-               if (currentSone != null) {
-                       newReplies = Collections2.filter(newReplies, new Predicate<PostReply>() {
-
-                               @Override
-                               public boolean apply(PostReply reply) {
-                                       return ListNotificationFilters.isReplyVisible(currentSone, reply);
-                               }
+               Collection<PostReply> newReplies = webInterface.getNewReplies(getCurrentSone(request.getToadletContext(), false));
 
-                       });
-               }
-               /* remove replies to unknown posts. */
-               newReplies = Collections2.filter(newReplies, PostReply.HAS_POST_FILTER);
                ArrayNode jsonReplies = new ArrayNode(instance);
                for (PostReply reply : newReplies) {
                        ObjectNode jsonReply = new ObjectNode(instance);
index 97706a9..02fd0c1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetTimesAjaxPage.java - Copyright © 2010–2015 David Roden
+ * Sone - GetTimesAjaxPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify it under
  * the terms of the GNU General Public License as published by the Free Software
@@ -150,10 +150,10 @@ public class GetTimesAjaxPage extends JsonPage {
                String text;
                long refresh;
                if (age < 0) {
-                       text = webInterface.getL10n().getDefaultString("View.Time.InTheFuture");
+                       text = webInterface.getL10n().getString("View.Time.InTheFuture");
                        refresh = TimeUnit.MINUTES.toMillis(5);
                } else if (age < TimeUnit.SECONDS.toMillis(20)) {
-                       text = webInterface.getL10n().getDefaultString("View.Time.AFewSecondsAgo");
+                       text = webInterface.getL10n().getString("View.Time.AFewSecondsAgo");
                        refresh = TimeUnit.SECONDS.toMillis(10);
                } else if (age < TimeUnit.SECONDS.toMillis(45)) {
                        text = webInterface.getL10n().getString("View.Time.HalfAMinuteAgo");
index 2823177..53e3e67 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetTranslationPage.java - Copyright © 2010–2015 David Roden
+ * Sone - GetTranslationPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index a262742..289b5a4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - JsonPage.java - Copyright © 2010–2015 David Roden
+ * Sone - JsonPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index cf57c8e..f088acb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - LikeAjaxPage.java - Copyright © 2010–2015 David Roden
+ * Sone - LikeAjaxPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 3a9ea7e..7298bae 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - LockSoneAjaxPage.java - Copyright © 2010–2015 David Roden
+ * Sone - LockSoneAjaxPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 4727686..fff1975 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - MarkAsKnownAjaxPage.java - Copyright © 2011–2015 David Roden
+ * Sone - MarkAsKnownAjaxPage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 4283f72..e8377f9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - MoveProfileFieldAjaxPage.java - Copyright © 2011–2015 David Roden
+ * Sone - MoveProfileFieldAjaxPage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 1c0689f..a186d46 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - TrustAjaxPage.java - Copyright © 2011–2015 David Roden
+ * Sone - TrustAjaxPage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 769ebcb..94dd268 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UnbookmarkAjaxPage.java - Copyright © 2011–2015 David Roden
+ * Sone - UnbookmarkAjaxPage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 803464c..58af936 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UnfollowSoneAjaxPage.java - Copyright © 2010–2015 David Roden
+ * Sone - UnfollowSoneAjaxPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 6afe39b..2eaaa68 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UnlikeAjaxPage.java - Copyright © 2010–2015 David Roden
+ * Sone - UnlikeAjaxPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 513ca97..bf88371 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UnlockSoneAjaxPage.java - Copyright © 2010–2015 David Roden
+ * Sone - UnlockSoneAjaxPage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index cf8f1a7..644dfd6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UntrustAjaxPage.java - Copyright © 2011–2015 David Roden
+ * Sone - UntrustAjaxPage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 590ac48..813703c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - FreenetPage.java - Copyright © 2011–2015 David Roden
+ * Sone - FreenetPage.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 4a0b788..ecbc0bb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - FreenetRequest.java - Copyright © 2011–2015 David Roden
+ * Sone - FreenetRequest.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 7338149..4c20e1f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - FreenetTemplatePage.java - Copyright © 2010–2015 David Roden
+ * Sone - FreenetTemplatePage.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index c64f2d7..222aa72 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PageToadlet.java - Copyright © 2010–2015 David Roden
+ * Sone - PageToadlet.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 0816e89..da5fcde 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PageToadletFactory.java - Copyright © 2010–2015 David Roden
+ * Sone - PageToadletFactory.java - Copyright © 2010–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 6e871f9..62e9807 100644 (file)
@@ -455,6 +455,7 @@ Notification.SoneRescued.Text=Diese Sones wurden gerettet:
 Notification.SoneRescued.Text.RememberToUnlock=Bitte denken Sie daran, die Nachrichten und Antworten dieser Sone(s) zu kontrollieren und sie danach zu entsperren!
 Notification.LockedSones.Text=Diese Sones sind seit mehr als 5 Minuten gesperrt. Bitte überprüfen Sie, ob diese Sones wirklich gesperrt bleiben sollen:
 Notification.NewVersion.Text=Die Version {version} vom Sone-Plugin wurde gefunden. Sie können diese Version von USK@nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI,DuQSUZiI~agF8c-6tjsFFGuZ8eICrzWCILB60nT8KKo,AQACAAE/sone/{edition}​ herunter laden.
+Notification.NewVersion.Disruptive.Text=Es wird {em}dringend empfohlen{/em}, dass Sie auf diese neue Version updaten, denn es ist möglich, dass Ihnen mit Ihrer aktuellen Version ein Haufen Inhalte entgehen!
 Notification.InsertingImages.Text=Diese Bilder werden gerade nach Freenet hoch geladen:
 Notification.InsertedImages.Text=Diese Bilder wurden nach Freenet hoch geladen:
 Notification.ImageInsertFailed.Text=Diese Bilder konnten nicht nach Freenet hoch geladen werden:
index 40a12b1..ae57376 100644 (file)
@@ -455,6 +455,7 @@ Notification.SoneRescued.Text=The following Sones have been rescued:
 Notification.SoneRescued.Text.RememberToUnlock=Please remember to control the posts and replies you have given and don’t forget to unlock your Sones!
 Notification.LockedSones.Text=The following Sones have been locked for more than 5 minutes. Please check if you really want to keep these Sones locked:
 Notification.NewVersion.Text=Version {version} of the Sone plugin was found. Download it from USK@nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI,DuQSUZiI~agF8c-6tjsFFGuZ8eICrzWCILB60nT8KKo,AQACAAE/sone/{edition}​!
+Notification.NewVersion.Disruptive.Text=It is {em}highly recommended{/em} that you update to this version as it may be possible that you are missing a lot of content with your current version!
 Notification.InsertingImages.Text=The following images are being inserted:
 Notification.InsertedImages.Text=The following images have been inserted:
 Notification.ImageInsertFailed.Text=The following images could not be inserted:
index 368903f..ab992e4 100644 (file)
@@ -326,7 +326,7 @@ Page.Invalid.Text=Se ha realizado una acción inválida, o la acción era válid
 
 View.Search.Button.Search=Buscar
 
-View.CreateSone.Text.WotIdentityRequired=Para crear un Sone necesitas una identidad del {link} plugin Web of Trust{/link}.
+View.CreateSone.Text.WotIdentityRequired=Para crear un Sone necesitas una identidad del {link}plugin Web of Trust{/link}.
 View.CreateSone.Select.Default=Selecciona una identidad
 View.CreateSone.Text.NoIdentities=No tienes ninguna identidad de Web of Trust. Por favor, ve al {link}plugin Web of Trust{/link} y crea una identidad.
 View.CreateSone.Text.NoNonSoneIdentities=No tienes ninguna identidad de Web of Trust que no tenga ya un Sone. Usa alguna de las identidades restantes para crear un nuevo Sone o ve al {link}plugin Web of Trust{/link} para crear una nueva identidad.
@@ -455,6 +455,7 @@ Notification.SoneRescued.Text=Los siguientes Sone han sido rescatados:
 Notification.SoneRescued.Text.RememberToUnlock=Por favor, recuerda controlar las publicaciones y respuestas que has dado y no olvides desbloquear tus Sones!
 Notification.LockedSones.Text=Los siguientes Sone han estado bloqueados durante más de 5 minutos. Por favor, comprueva si realmente quieres mantenerlos bloqueados:
 Notification.NewVersion.Text=La versión {version} del plugin Sone fue encontrada. Descargala de USK@nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI,DuQSUZiI~agF8c-6tjsFFGuZ8eICrzWCILB60nT8KKo,AQACAAE/sone/{edition}​!
+Notification.NewVersion.Disruptive.Text=Es {em}muy recomendable{/em} que se actualice a la nueva version tan pronto como sea posible ya que pueden haber nuevas herramientas incompatibles con la actual!
 Notification.InsertingImages.Text=Las siguientes imágenes están siendo insertadas:
 Notification.InsertedImages.Text=Las siguientes imágenes han sido insertadas:
 Notification.ImageInsertFailed.Text=Las siguientes imágenes no han podido ser insertadas:
index df6f1ed..e068e49 100644 (file)
@@ -455,6 +455,7 @@ Notification.SoneRescued.Text=Les Sones suivants ont été sauvés:
 Notification.SoneRescued.Text.RememberToUnlock=Veuillez vous souvenir de contrôler les messages et réponses que vous avez donnés et n'oubliez pas de déverrouiller vos Sones!
 Notification.LockedSones.Text=Les Sones suivants ont été verrouillés pour une durée de plus de 5 minutes. Veuillez vérifier si vous voulez vraiment garder ces Sones bloqués:
 Notification.NewVersion.Text=La version ${version} du plugin Sone a été trouvée. Téléchargez la depuis USK@nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI,DuQSUZiI~agF8c-6tjsFFGuZ8eICrzWCILB60nT8KKo,AQACAAE/sone/{edition}​!
+Notification.NewVersion.Disruptive.Text=Il est {en}fortement recommandé{/en} que vous mettiez à jour vers cette version, car il est possible que vous manquiez beaucoup de contenu avec la version actuelle!
 Notification.InsertingImages.Text=Les images suivantes sont en cours d'insertion:
 Notification.InsertedImages.Text=Les images suivantes ont été insérées:
 Notification.ImageInsertFailed.Text=Les images suivantes ne peuvent être insérées:
index af7a295..d193e6f 100644 (file)
@@ -455,6 +455,7 @@ Notification.SoneRescued.Text=次のSoneが復帰しました:
 Notification.SoneRescued.Text.RememberToUnlock=投稿や返信を確認の上、ロックを解除するのを忘れないようにしてください。
 Notification.LockedSones.Text=次のSoneは5分以上ロック状態になっています。故意にロック中であるかを確認してください:
 Notification.NewVersion.Text=Soneの新しいバージョン{version}があります。USK@nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI,DuQSUZiI~agF8c-6tjsFFGuZ8eICrzWCILB60nT8KKo,AQACAAE/sone/{edition}​よりダウンロードしてください。
+Notification.NewVersion.Disruptive.Text=It is {em}highly recommended{/em} that you update to this version as it may be possible that you are missing a lot of content with your current version!
 Notification.InsertingImages.Text=次の画像がインサート中です:
 Notification.InsertedImages.Text=次の画像のインサートされました:
 Notification.ImageInsertFailed.Text=次の画像のインサートに失敗しました:
@@ -463,4 +464,4 @@ Notification.Mention.Text=次の投稿でメンションされています:
 Notification.SoneIsInserting.Text=あなたのSone sone://{0}は現在インサート中です。
 Notification.SoneIsInserted.Text=あなたのSone sone://{0}は{1,number}{1,choice,0#秒|1#秒|1<秒}でインサートされました。
 Notification.SoneInsertAborted.Text=あなたのSone sone://{0}のインサートに失敗しました。
-# 60, 100
+# 60, 100, 458
index 27a5a2e..99a950f 100644 (file)
@@ -455,6 +455,7 @@ Notification.SoneRescued.Text=De følgende Sonene har blitt reddet:
 Notification.SoneRescued.Text.RememberToUnlock=Husk å kontroller innlegg og svar du har publisert og ikke glem å lås opp dine Soner!
 Notification.LockedSones.Text=De følgende Sonene har vært låst i mer enn 5 minutter. Vennligst sjekk om du virkelig ønsker å beholde disse Sonene låst:
 Notification.NewVersion.Text=Utgave {version} av sone-tillegget ble funnet. Last det ned fra USK@nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI,DuQSUZiI~agF8c-6tjsFFGuZ8eICrzWCILB60nT8KKo,AQACAAE/sone/{edition}​!
+Notification.NewVersion.Disruptive.Text=It is {em}highly recommended{/em} that you update to this version as it may be possible that you are missing a lot of content with your current version!
 Notification.InsertingImages.Text=De følgende bildene blir lastet opp:
 Notification.InsertedImages.Text=De følgende bildene har blitt lastet opp:
 Notification.ImageInsertFailed.Text=De følgende bildene kunne ikke bli innsatt:
@@ -463,4 +464,4 @@ Notification.Mention.Text=Du har blitt nevnt i følgende innlegg:
 Notification.SoneIsInserting.Text=Your Sone sone://{0} is now being inserted.
 Notification.SoneIsInserted.Text=Your Sone sone://{0} has been inserted in {1,number} {1,choice,0#seconds|1#second|1<seconds}.
 Notification.SoneInsertAborted.Text=Your Sone sone://{0} could not be inserted.
-# 60, 100, 120-121, 308-310, 312-314, 463-465
+# 60, 100, 120-121, 308-310, 312-314, 458, 463-465
index 568e9e6..82db1b5 100644 (file)
@@ -455,6 +455,7 @@ Notification.SoneRescued.Text=Odzyskano następujące Sone:
 Notification.SoneRescued.Text.RememberToUnlock=Należy pamiętać o zarządzaniu napisanymi postami i odpowiedziami oraz nie zapomnieć o odblokowaniu swoich Sone!
 Notification.LockedSones.Text=Następujące Sone są zablokowane od ponad 5 minut. Sprawdź czy chcesz, żeby pozostały zablokowane.
 Notification.NewVersion.Text=Znaleziono wersję {version}wtyczki Sone. Ściągnij ją z USK@nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI,DuQSUZiI~agF8c-6tjsFFGuZ8eICrzWCILB60nT8KKo,AQACAAE/sone/{edition}!
+Notification.NewVersion.Disruptive.Text=It is {em}highly recommended{/em} that you update to this version as it may be possible that you are missing a lot of content with your current version!
 Notification.InsertingImages.Text=Ładowane są następujące obrazy:
 Notification.InsertedImages.Text=Załadowano nastepujące obrazy:
 Notification.ImageInsertFailed.Text=Nie można załadowac następujących obrazów:
@@ -463,3 +464,4 @@ Notification.Mention.Text=Zostałeś oznaczony w następujących postach:
 Notification.SoneIsInserting.Text=Twoje Sone sone://{0} jest w tej chili wysyłane.
 Notification.SoneIsInserted.Text=Twoje sone://{0} zostało wysłane w {1,number} {1,choice,0#seconds|1#second|1<seconds}.
 Notification.SoneInsertAborted.Text=Twoje Sone sone://{0} nie mogło zostać wysłane.
+# 458
index 7ef039d..85d82f1 100644 (file)
@@ -455,6 +455,7 @@ Notification.SoneRescued.Text=Следующие Sone были восстано
 Notification.SoneRescued.Text.RememberToUnlock=Пожалуйста, не забывайте контролировать сообщения и ответы, которые вы дали и не забывайте разблокировать ваши Sone!
 Notification.LockedSones.Text=Следующие Sone были заблокированы более 5 минут. Пожалуйста, проверьте, действительно ли вы хотите держать эти Sone заблокированными:
 Notification.NewVersion.Text=Версия {version} дополнения Sone была найдена. Загрузите ее из USK@nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI,DuQSUZiI~agF8c-6tjsFFGuZ8eICrzWCILB60nT8KKo,AQACAAE/sone/{edition}​!
+Notification.NewVersion.Disruptive.Text=It is {em}highly recommended{/em} that you update to this version as it may be possible that you are missing a lot of content with your current version!
 Notification.InsertingImages.Text=Следующие изображения выгружаются:
 Notification.InsertedImages.Text=Следующие изображения были выгружены:
 Notification.ImageInsertFailed.Text=Следующие изображения не могут быть выгружены:
@@ -463,4 +464,4 @@ Notification.Mention.Text=Вас упомянули в следующих соо
 Notification.SoneIsInserting.Text=Your Sone sone://{0} is now being inserted.
 Notification.SoneIsInserted.Text=Your Sone sone://{0} has been inserted in {1,number} {1,choice,0#seconds|1#second|1<seconds}.
 Notification.SoneInsertAborted.Text=Your Sone sone://{0} could not be inserted.
-# 60, 100, 120-121, 308-310, 312-314, 463-465
+# 60, 100, 120-121, 308-310, 312-314, 458, 463-465
index 07c5b3d..7ef585d 100644 (file)
@@ -718,6 +718,7 @@ function ajaxifyPost(postElement) {
                                        sone.find(".post#post-" + postId + " .create-reply .sender").hide();
                                        sone.find(".post#post-" + postId + " .create-reply .select-sender").show();
                                        sone.find(".post#post-" + postId + " .create-reply :input[name=sender]").val(getCurrentSoneId());
+                                       updateReplyTimes(replyId);
                                } else {
                                        alert(error);
                                }
index f6c57c8..1cba3f9 100644 (file)
@@ -7,7 +7,7 @@
        <form method="post">
                <input type="hidden" name="formPassword" value="<% formPassword|html>" />
                <input type="hidden" name="returnPage" value="<% returnPage|html>" />
-               <input type="hidden" name="post" value="<% post.id|html>" />
+               <input type="hidden" name="post" value="<% post|html>" />
                <button type="submit" name="confirmDelete" value="1"><%= Page.DeletePost.Button.Yes|l10n|html></button>
                <button type="submit" name="abortDelete" value="1"><%= Page.DeletePost.Button.No|l10n|html></button>
        </form>
index 1c0cf4e..23daf1c 100644 (file)
@@ -7,7 +7,7 @@
        <form method="post">
                <input type="hidden" name="formPassword" value="<% formPassword|html>" />
                <input type="hidden" name="returnPage" value="<% returnPage|html>" />
-               <input type="hidden" name="reply" value="<% reply.id|html>" />
+               <input type="hidden" name="reply" value="<% reply|html>" />
                <button type="submit" name="confirmDelete" value="1"><%= Page.DeleteReply.Button.Yes|l10n|html></button>
                <button type="submit" name="abortDelete" value="1"><%= Page.DeleteReply.Button.No|l10n|html></button>
        </form>
index 7dd4c29..644848a 100644 (file)
                                                <input type="hidden" name="formPassword" value="<%formPassword|html>" />
                                                <input type="hidden" name="album" value="<%album.id|html>" />
 
-                                               <%if ! album.images.empty>
-                                                       <div>
-                                                               <label for="album-image"><%= Page.ImageBrowser.Album.Label.AlbumImage|l10n|html></label>
-                                                               <select name="album-image">
-                                                                       <option disabled="disabled"><%= Page.ImageBrowser.Album.AlbumImage.Choose|l10n|html></option>
-                                                                       <%foreach album.images image>
-                                                                               <option value="<% image.id|html>"<%if album.albumImage.id|match value=image.id> selected="selected"<%/if>><% image.title|html></option>
-                                                                       <%/foreach>
-                                                               </select>
-                                                       </div>
-                                               <%/if>
                                                <div>
                                                        <label for="title"><%= Page.ImageBrowser.Album.Label.Title|l10n|html></label>
                                                        <input type="text" name="title" value="<%album.title|html>" />
diff --git a/src/main/resources/templates/insert/include/album.xml b/src/main/resources/templates/insert/include/album.xml
deleted file mode 100644 (file)
index cd845da..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-<album>
-       <id><% album.id|xml></id>
-       <name><% album.name|xml></name>
-       <description><% album.description|xml></description>
-       <albums>
-               <%foreach album.albums album>
-               <%include insert/include/album.xml>
-               <%/foreach>
-       </albums>
-       <images>
-               <%foreach album.images image>
-               <image>
-                       <id><% image.id|xml></id>
-                       <creation-time><% image.creationTime|xml></creation-time>
-                       <key><% image.key|xml></key>
-                       <width><% image.width|xml></width>
-                       <height><% image.height|xml></height>
-                       <title><% image.title|xml></title>
-                       <description><% image.description|xml></description>
-               </image>
-               <%/foreach>
-       </images>
-</album>
index 599a7e6..f35ecaf 100644 (file)
@@ -1 +1,4 @@
 <div class="text"><%= Notification.NewVersion.Text|l10n|replace needle=="{version}" replacement=latestVersion|replace needle=="{edition}" replacement=latestEdition|parse sone=="nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI"></div>
+<%if disruptive>
+       <div class="text"><%= Notification.NewVersion.Disruptive.Text|l10n|html|replace needle=="{em}" replacement=="<em>"|replace needle=="{/em}" replacement=="</em>"></div>
+<%/if>
index faf2633..823cdf9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Matchers.java - Copyright © 2013–2015 David Roden
+ * Sone - Matchers.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -109,8 +109,7 @@ public class Matchers {
 
        public static Matcher<Album> isAlbum(final String albumId,
                        final String parentAlbumId,
-                       final String title, final String albumDescription,
-                       final String imageId) {
+                       final String title, final String albumDescription) {
                return new TypeSafeDiagnosingMatcher<Album>() {
                        @Override
                        protected boolean matchesSafely(Album album,
@@ -146,22 +145,6 @@ public class Matchers {
                                                        .appendValue(album.getDescription());
                                        return false;
                                }
-                               if (imageId == null) {
-                                       if (album.getAlbumImage() != null) {
-                                               mismatchDescription.appendText("has album image");
-                                               return false;
-                                       }
-                               } else {
-                                       if (album.getAlbumImage() == null) {
-                                               mismatchDescription.appendText("has no album image");
-                                               return false;
-                                       }
-                                       if (!album.getAlbumImage().getId().equals(imageId)) {
-                                               mismatchDescription.appendText("has album image ")
-                                                               .appendValue(album.getAlbumImage().getId());
-                                               return false;
-                                       }
-                               }
                                return true;
                        }
 
@@ -177,12 +160,6 @@ public class Matchers {
                                description.appendText(", has title ").appendValue(title);
                                description.appendText(", has description ")
                                                .appendValue(albumDescription);
-                               if (imageId == null) {
-                                       description.appendText(", has no album image");
-                               } else {
-                                       description.appendText(", has album image ")
-                                                       .appendValue(imageId);
-                               }
                        }
                };
        }
index 9890a70..a499019 100644 (file)
@@ -46,17 +46,6 @@ public class TestAlbumBuilder implements AlbumBuilder {
                                return description;
                        }
                });
-               when(album.getAlbumImage()).thenAnswer(new Answer<Image>() {
-                       @Override
-                       public Image answer(InvocationOnMock invocation) {
-                               if (imageId == null) {
-                                       return null;
-                               }
-                               Image image = mock(Image.class);
-                               when(image.getId()).thenReturn(imageId);
-                               return image;
-                       }
-               });
                when(album.getAlbums()).thenReturn(albums);
                when(album.getImages()).thenReturn(images);
                doAnswer(new Answer<Void>() {
@@ -101,12 +90,6 @@ public class TestAlbumBuilder implements AlbumBuilder {
                        }
 
                        @Override
-                       public Modifier setAlbumImage(String imageId) {
-                               TestAlbumBuilder.this.imageId = imageId;
-                               return this;
-                       }
-
-                       @Override
                        public Album update() throws IllegalStateException {
                                return album;
                        }
index 7bbfae8..ec42a8c 100644 (file)
@@ -333,15 +333,15 @@ public class ConfigurationSoneParserTest {
                                                albumBuilderFactory);
                assertThat(topLevelAlbums, hasSize(2));
                Album firstAlbum = topLevelAlbums.get(0);
-               assertThat(firstAlbum, isAlbum("A1", null, "T1", "D1", "I1"));
+               assertThat(firstAlbum, isAlbum("A1", null, "T1", "D1"));
                assertThat(firstAlbum.getAlbums(), emptyIterable());
                assertThat(firstAlbum.getImages(), emptyIterable());
                Album secondAlbum = topLevelAlbums.get(1);
-               assertThat(secondAlbum, isAlbum("A2", null, "T2", "D2", null));
+               assertThat(secondAlbum, isAlbum("A2", null, "T2", "D2"));
                assertThat(secondAlbum.getAlbums(), hasSize(1));
                assertThat(secondAlbum.getImages(), emptyIterable());
                Album thirdAlbum = secondAlbum.getAlbums().get(0);
-               assertThat(thirdAlbum, isAlbum("A3", "A2", "T3", "D3", "I3"));
+               assertThat(thirdAlbum, isAlbum("A3", "A2", "T3", "D3"));
                assertThat(thirdAlbum.getAlbums(), emptyIterable());
                assertThat(thirdAlbum.getImages(), emptyIterable());
        }
index 263c2d0..8e83e21 100644 (file)
@@ -1,15 +1,36 @@
 package net.pterodactylus.sone.core;
 
+import static org.mockito.Matchers.argThat;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import net.pterodactylus.sone.core.Core.MarkPostKnown;
 import net.pterodactylus.sone.core.Core.MarkReplyKnown;
+import net.pterodactylus.sone.core.event.PostRemovedEvent;
+import net.pterodactylus.sone.core.event.PostReplyRemovedEvent;
+import net.pterodactylus.sone.core.event.SoneRemovedEvent;
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.PostReply;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.database.Database;
+import net.pterodactylus.sone.freenet.wot.Identity;
+import net.pterodactylus.sone.freenet.wot.IdentityManager;
+import net.pterodactylus.sone.freenet.wot.OwnIdentity;
+import net.pterodactylus.sone.freenet.wot.event.IdentityRemovedEvent;
+import net.pterodactylus.util.config.Configuration;
 
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.eventbus.EventBus;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
 import org.junit.Test;
+import org.mockito.InOrder;
 
 /**
  * Unit test for {@link Core} and its subclasses.
@@ -36,4 +57,108 @@ public class CoreTest {
                verify(core).markReplyKnown(eq(postReply));
        }
 
+       @Test
+       public void removingAnIdentitySendsRemovalEventsForAllSoneElements() {
+               // given
+               Configuration configuration = mock(Configuration.class);
+               FreenetInterface freenetInterface = mock(FreenetInterface.class);
+               IdentityManager identityManager = mock(IdentityManager.class);
+               SoneDownloader soneDownloader = mock(SoneDownloader.class);
+               ImageInserter imageInserter = mock(ImageInserter.class);
+               UpdateChecker updateChecker = mock(UpdateChecker.class);
+               WebOfTrustUpdater webOfTrustUpdater = mock(WebOfTrustUpdater.class);
+               EventBus eventBus = mock(EventBus.class);
+               Database database = mock(Database.class);
+               Core core = new Core(configuration, freenetInterface, identityManager, soneDownloader, imageInserter, updateChecker, webOfTrustUpdater, eventBus, database);
+               OwnIdentity ownIdentity = mock(OwnIdentity.class);
+               Identity identity = mock(Identity.class);
+               when(identity.getId()).thenReturn("sone-id");
+               Sone sone = mock(Sone.class);
+               when(database.getSone("sone-id")).thenReturn(Optional.of(sone));
+               PostReply postReply1 = mock(PostReply.class);
+               PostReply postReply2 = mock(PostReply.class);
+               when(sone.getReplies()).thenReturn(ImmutableSet.of(postReply1, postReply2));
+               Post post1 = mock(Post.class);
+               Post post2 = mock(Post.class);
+               when(sone.getPosts()).thenReturn(ImmutableList.of(post1, post2));
+
+               // when
+               core.identityRemoved(new IdentityRemovedEvent(ownIdentity, identity));
+
+               // then
+               InOrder inOrder = inOrder(eventBus, database);
+               inOrder.verify(eventBus).post(argThat(isPostReplyRemoved(postReply1)));
+               inOrder.verify(eventBus).post(argThat(isPostReplyRemoved(postReply2)));
+               inOrder.verify(eventBus).post(argThat(isPostRemoved(post1)));
+               inOrder.verify(eventBus).post(argThat(isPostRemoved(post2)));
+               inOrder.verify(eventBus).post(argThat(isSoneRemoved(sone)));
+               inOrder.verify(database).removeSone(sone);
+       }
+
+       private Matcher<Object> isPostRemoved(final Post post) {
+               return new TypeSafeDiagnosingMatcher<Object>() {
+                       @Override
+                       protected boolean matchesSafely(Object item, Description mismatchDescription) {
+                               if (!(item instanceof PostRemovedEvent)) {
+                                       mismatchDescription.appendText("is not PostRemovedEvent");
+                                       return false;
+                               }
+                               if (((PostRemovedEvent) item).post() != post) {
+                                       mismatchDescription.appendText("post is ").appendValue(((PostRemovedEvent) item).post());
+                                       return false;
+                               }
+                               return true;
+                       }
+
+                       @Override
+                       public void describeTo(Description description) {
+                               description.appendText("is PostRemovedEvent and post is ").appendValue(post);
+                       }
+               };
+       }
+
+       private Matcher<Object> isPostReplyRemoved(final PostReply postReply) {
+               return new TypeSafeDiagnosingMatcher<Object>() {
+                       @Override
+                       protected boolean matchesSafely(Object item, Description mismatchDescription) {
+                               if (!(item instanceof PostReplyRemovedEvent)) {
+                                       mismatchDescription.appendText("is not PostReplyRemovedEvent");
+                                       return false;
+                               }
+                               if (((PostReplyRemovedEvent) item).postReply() != postReply) {
+                                       mismatchDescription.appendText("post reply is ").appendValue(((PostReplyRemovedEvent) item).postReply());
+                                       return false;
+                               }
+                               return true;
+                       }
+
+                       @Override
+                       public void describeTo(Description description) {
+                               description.appendText("is PostReplyRemovedEvent and post is ").appendValue(postReply);
+                       }
+               };
+       }
+
+       private Matcher<Object> isSoneRemoved(final Sone sone) {
+               return new TypeSafeDiagnosingMatcher<Object>() {
+                       @Override
+                       protected boolean matchesSafely(Object item, Description mismatchDescription) {
+                               if (!(item instanceof SoneRemovedEvent)) {
+                                       mismatchDescription.appendText("is not SoneRemovedEvent");
+                                       return false;
+                               }
+                               if (((SoneRemovedEvent) item).sone() != sone) {
+                                       mismatchDescription.appendText("sone is ").appendValue(((SoneRemovedEvent) item).sone());
+                                       return false;
+                               }
+                               return true;
+                       }
+
+                       @Override
+                       public void describeTo(Description description) {
+                               description.appendText("is SoneRemovedEvent and sone is ").appendValue(sone);
+                       }
+               };
+       }
+
 }
index dc19195..2d8a5a0 100644 (file)
@@ -243,7 +243,6 @@ public class SoneParserTest {
        @Before
        public void setupAlbum() {
                final Album album = SoneParserTest.this.album;
-               when(album.getAlbumImage()).thenReturn(mock(Image.class));
                doAnswer(new Answer<Void>() {
                        @Override
                        public Void answer(InvocationOnMock invocation) {
@@ -273,7 +272,6 @@ public class SoneParserTest {
                final Modifier albumModifier = new Modifier() {
                        private String title = album.getTitle();
                        private String description = album.getDescription();
-                       private String imageId = album.getAlbumImage().getId();
 
                        @Override
                        public Modifier setTitle(String title) {
@@ -288,18 +286,9 @@ public class SoneParserTest {
                        }
 
                        @Override
-                       public Modifier setAlbumImage(String imageId) {
-                               this.imageId = imageId;
-                               return this;
-                       }
-
-                       @Override
                        public Album update() throws IllegalStateException {
                                when(album.getTitle()).thenReturn(title);
                                when(album.getDescription()).thenReturn(description);
-                               Image image = mock(Image.class);
-                               when(image.getId()).thenReturn(imageId);
-                               when(album.getAlbumImage()).thenReturn(image);
                                return album;
                        }
                };
index a5e3b2a..44819ee 100644 (file)
@@ -1,7 +1,6 @@
 package net.pterodactylus.sone.core;
 
 import static java.lang.Long.MAX_VALUE;
-import static net.pterodactylus.sone.main.SonePlugin.VERSION;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.instanceOf;
 import static org.hamcrest.Matchers.is;
@@ -44,7 +43,8 @@ public class UpdateCheckerTest {
 
        private final EventBus eventBus = mock(EventBus.class);
        private final FreenetInterface freenetInterface = mock(FreenetInterface.class);
-       private final UpdateChecker updateChecker = new UpdateChecker(eventBus, freenetInterface);
+       private final Version currentVersion = new Version(1, 0, 0);
+       private final UpdateChecker updateChecker = new UpdateChecker(eventBus, freenetInterface, currentVersion);
 
        @Before
        public void startUpdateChecker() {
@@ -54,7 +54,7 @@ public class UpdateCheckerTest {
        @Test
        public void newUpdateCheckerDoesNotHaveALatestVersion() {
                assertThat(updateChecker.hasLatestVersion(), is(false));
-               assertThat(updateChecker.getLatestVersion(), is(VERSION));
+               assertThat(updateChecker.getLatestVersion(), is(currentVersion));
        }
 
        @Test
@@ -87,23 +87,33 @@ public class UpdateCheckerTest {
                setupFetchResult(createFutureFetchResult());
                setupCallbackWithEdition(MAX_VALUE, true, false);
                verifyAFreenetUriIsFetched();
-               ArgumentCaptor<UpdateFoundEvent> updateFoundEvent = forClass(UpdateFoundEvent.class);
-               verify(eventBus, times(1)).post(updateFoundEvent.capture());
-               assertThat(updateFoundEvent.getValue().version(), is(new Version(99, 0, 0)));
-               assertThat(updateFoundEvent.getValue().releaseTime(), is(11865368297000L));
-               assertThat(updateChecker.getLatestVersion(), is(new Version(99, 0, 0)));
-               assertThat(updateChecker.getLatestVersionDate(), is(11865368297000L));
-               assertThat(updateChecker.hasLatestVersion(), is(true));
+               verifyEventIsFired(new Version(99, 0, 0), 11865368297000L, false);
+               verifyThatUpdateCheckerKnowsLatestVersion(new Version(99, 0, 0), 11865368297000L);
        }
 
        private FetchResult createFutureFetchResult() {
                ClientMetadata clientMetadata = new ClientMetadata("application/xml");
                Bucket fetched = new ArrayBucket(("# MapConfigurationBackendVersion=1\n" +
                                "CurrentVersion/Version: 99.0.0\n" +
-                               "CurrentVersion/ReleaseTime: 11865368297000").getBytes());
+                               "CurrentVersion/ReleaseTime: 11865368297000\n" +
+                               "DisruptiveVersion/0.1.2: true").getBytes());
                return new FetchResult(clientMetadata, fetched);
        }
 
+       private void verifyEventIsFired(Version version, long releaseTime, boolean disruptive) {
+               ArgumentCaptor<UpdateFoundEvent> updateFoundEvent = forClass(UpdateFoundEvent.class);
+               verify(eventBus, times(1)).post(updateFoundEvent.capture());
+               assertThat(updateFoundEvent.getValue().version(), is(version));
+               assertThat(updateFoundEvent.getValue().releaseTime(), is(releaseTime));
+               assertThat(updateFoundEvent.getValue().disruptive(), is(disruptive));
+       }
+
+       private void verifyThatUpdateCheckerKnowsLatestVersion(Version version, long releaseTime) {
+               assertThat(updateChecker.getLatestVersion(), is(version));
+               assertThat(updateChecker.getLatestVersionDate(), is(releaseTime));
+               assertThat(updateChecker.hasLatestVersion(), is(true));
+       }
+
        @Test
        public void callbackDoesNotStartIfNoNewEditionIsFound() {
                setupFetchResult(createPastFetchResult());
@@ -230,4 +240,22 @@ public class UpdateCheckerTest {
                return new FetchResult(clientMetadata, fetched);
        }
 
+       @Test
+       public void disruptiveVersionGetsNotification() {
+               setupFetchResult(createDisruptiveVersionFetchResult());
+               setupCallbackWithEdition(MAX_VALUE, true, false);
+               verifyAFreenetUriIsFetched();
+               verifyEventIsFired(new Version(1, 2, 3), 1289417883000L, true);
+               verifyThatUpdateCheckerKnowsLatestVersion(new Version(1, 2, 3), 1289417883000L);
+       }
+
+       private FetchResult createDisruptiveVersionFetchResult() {
+               ClientMetadata clientMetadata = new ClientMetadata("application/xml");
+               Bucket fetched = new ArrayBucket(("# MapConfigurationBackendVersion=1\n" +
+                               "CurrentVersion/Version: 1.2.3\n" +
+                               "CurrentVersion/ReleaseTime: 1289417883000\n" +
+                               "DisruptiveVersion/1.2.3: true").getBytes());
+               return new FetchResult(clientMetadata, fetched);
+       }
+
 }
index 5ee498c..a207132 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - MemoryDatabaseTest.java - Copyright © 2013–2015 David Roden
+ * Sone - MemoryDatabaseTest.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -131,7 +131,7 @@ public class MemoryDatabaseTest {
                                .update();
                Album secondAlbum = new TestAlbumBuilder().withId("album2").by(
                                sone).build().modify().setTitle("album2").setDescription(
-                               "album-description2").setAlbumImage("image1").update();
+                               "album-description2").update();
                Album thirdAlbum = new TestAlbumBuilder().withId("album3").by(
                                sone).build().modify().setTitle("album3").setDescription(
                                "album-description3").update();
@@ -192,14 +192,11 @@ public class MemoryDatabaseTest {
                assertThat(memoryDatabase.getPostReply("reply4").isPresent(),
                                is(false));
                assertThat(memoryDatabase.getAlbum("album1").get(),
-                               isAlbum("album1", null, "album1", "album-description1",
-                                               null));
+                               isAlbum("album1", null, "album1", "album-description1"));
                assertThat(memoryDatabase.getAlbum("album2").get(),
-                               isAlbum("album2", null, "album2", "album-description2",
-                                               "image1"));
+                               isAlbum("album2", null, "album2", "album-description2"));
                assertThat(memoryDatabase.getAlbum("album3").get(),
-                               isAlbum("album3", "album1", "album3", "album-description3",
-                                               null));
+                               isAlbum("album3", "album1", "album3", "album-description3"));
                assertThat(memoryDatabase.getAlbum("album4").isPresent(), is(false));
                assertThat(memoryDatabase.getImage("image1").get(),
                                isImage("image1", 1000L, "KSK@image1", "image1",
index b6c9cd7..d583661 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - LockSoneCommandTest.java - Copyright © 2013–2015 David Roden
+ * Sone - LockSoneCommandTest.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 4cabfd1..bd292dd 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UnlockSoneCommandTest.java - Copyright © 2013–2015 David Roden
+ * Sone - UnlockSoneCommandTest.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index b0e88ad..f11c7e3 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DefaultIdentityTest.java - Copyright © 2013–2015 David Roden
+ * Sone - DefaultIdentityTest.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 29c371e..c5dc840 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DefaultOwnIdentityTest.java - Copyright © 2013–2015 David Roden
+ * Sone - DefaultOwnIdentityTest.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 479bee6..9f92308 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Identities.java - Copyright © 2013–2015 David Roden
+ * Sone - Identities.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 6a4e3d2..3773cbe 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IdentityChangeDetectorTest.java - Copyright © 2013–2015 David Roden
+ * Sone - IdentityChangeDetectorTest.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index c88147c..f58c239 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IdentityChangeEventSenderTest.java - Copyright © 2013–2015 David Roden
+ * Sone - IdentityChangeEventSenderTest.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 692400a..a07af28 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IdentityLoaderTest.java - Copyright © 2013–2015 David Roden
+ * Sone - IdentityLoaderTest.java - Copyright © 2013–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/src/test/java/net/pterodactylus/sone/notify/ListNotificationFilterTest.java b/src/test/java/net/pterodactylus/sone/notify/ListNotificationFilterTest.java
new file mode 100644 (file)
index 0000000..b29b2c1
--- /dev/null
@@ -0,0 +1,281 @@
+package net.pterodactylus.sone.notify;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.emptyIterable;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostReply;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.data.SoneOptions;
+import net.pterodactylus.sone.freenet.wot.OwnIdentity;
+import net.pterodactylus.util.notify.Notification;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import org.hamcrest.Matchers;
+import org.junit.Test;
+
+/**
+ * Unit test for {@link ListNotificationFilterTest}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class ListNotificationFilterTest {
+
+       private static final String LOCAL_ID = "local-id";
+
+       private final PostVisibilityFilter postVisibilityFilter = mock(PostVisibilityFilter.class);
+       private final ReplyVisibilityFilter replyVisibilityFilter = mock(ReplyVisibilityFilter.class);
+       private final ListNotificationFilter listNotificationFilter = new ListNotificationFilter(postVisibilityFilter, replyVisibilityFilter);
+
+       private final Sone localSone = mock(Sone.class);
+       private final SoneOptions soneOptions = mock(SoneOptions.class);
+       private final OwnIdentity localIdentity = mock(OwnIdentity.class);
+       private final List<ListNotification<Post>> newPostNotifications = Arrays.asList(createNewPostNotification());
+       private final List<ListNotification<PostReply>> newReplyNotifications = Arrays.asList(createNewReplyNotification());
+       private final List<ListNotification<Post>> mentionNotifications = Arrays.asList(createMentionNotification());
+
+       public ListNotificationFilterTest() {
+               when(localSone.getId()).thenReturn(LOCAL_ID);
+               when(localSone.isLocal()).thenReturn(true);
+               when(localSone.getIdentity()).thenReturn(localIdentity);
+               when(localIdentity.getId()).thenReturn(LOCAL_ID);
+               when(localSone.getOptions()).thenReturn(soneOptions);
+       }
+
+       @Test
+       public void filterIsOnlyCreatedOnce() {
+           Injector injector = Guice.createInjector();
+               ListNotificationFilter firstFilter = injector.getInstance(ListNotificationFilter.class);
+               ListNotificationFilter secondFilter = injector.getInstance(ListNotificationFilter.class);
+               assertThat(firstFilter, sameInstance(secondFilter));
+       }
+
+       @Test
+       public void newSoneNotificationsAreNotRemovedIfNotLoggedIn() {
+               List<Notification> notifications = Arrays.asList(createNewSoneNotification());
+               List<Notification> filteredNotifications = listNotificationFilter.filterNotifications(notifications, null);
+               assertThat(filteredNotifications, contains(notifications.get(0)));
+       }
+
+       private Notification createNewSoneNotification() {
+               ListNotification<Sone> newSoneNotification = mock(ListNotification.class);
+               when(newSoneNotification.getId()).thenReturn("new-sone-notification");
+               return newSoneNotification;
+       }
+
+       @Test
+       public void newSoneNotificationsAreRemovedIfLoggedInAndNewSonesShouldNotBeShown() {
+               List<Notification> notifications = Arrays.asList(createNewSoneNotification());
+               List<Notification> filteredNotifications = listNotificationFilter.filterNotifications(notifications, localSone);
+               assertThat(filteredNotifications, emptyIterable());
+       }
+
+       @Test
+       public void newSoneNotificationsAreNotRemovedIfLoggedInAndNewSonesShouldBeShown() {
+               List<Notification> notifications = Arrays.asList(createNewSoneNotification());
+               when(soneOptions.isShowNewSoneNotifications()).thenReturn(true);
+               List<Notification> filteredNotifications = listNotificationFilter.filterNotifications(notifications, localSone);
+               assertThat(filteredNotifications, contains(notifications.get(0)));
+       }
+
+       private ListNotification<Post> createNewPostNotification() {
+               ListNotification<Post> newSoneNotification = mock(ListNotification.class);
+               when(newSoneNotification.getElements()).thenReturn(new ArrayList<Post>());
+               when(newSoneNotification.getId()).thenReturn("new-post-notification");
+               return newSoneNotification;
+       }
+
+       @Test
+       public void newPostNotificationIsNotShownIfOptionsSetAccordingly() {
+               List<ListNotification<Post>> notifications = Arrays.asList(createNewPostNotification());
+               List<Notification> filteredNotifications = listNotificationFilter.filterNotifications(notifications, localSone);
+               assertThat(filteredNotifications, hasSize(0));
+       }
+
+       private void activateNewPostNotifications() {
+               when(soneOptions.isShowNewPostNotifications()).thenReturn(true);
+       }
+
+       private boolean addPostToPostNotification(List<ListNotification<Post>> notifications) {
+               return notifications.get(0).getElements().add(mock(Post.class));
+       }
+
+       private void setPostVisibilityPredicate(Predicate<Post> value) {
+               when(postVisibilityFilter.isVisible(any(Sone.class))).thenReturn(value);
+       }
+
+       @Test
+       public void newPostNotificationIsNotShownIfNoNewPostsAreVisible() {
+               activateNewPostNotifications();
+               addPostToPostNotification(newPostNotifications);
+               setPostVisibilityPredicate(Predicates.<Post>alwaysFalse());
+               List<Notification> filteredNotifications = listNotificationFilter.filterNotifications(newPostNotifications, localSone);
+               assertThat(filteredNotifications, hasSize(0));
+       }
+
+       @Test
+       public void newPostNotificationIsShownIfNewPostsAreVisible() {
+               activateNewPostNotifications();
+               addPostToPostNotification(newPostNotifications);
+               setPostVisibilityPredicate(Predicates.<Post>alwaysTrue());
+               List<Notification> filteredNotifications = listNotificationFilter.filterNotifications(newPostNotifications, localSone);
+               assertThat(filteredNotifications, contains((Notification) newPostNotifications.get(0)));
+       }
+
+       @Test
+       public void newPostNotificationIsNotShownIfNewPostsAreVisibleButLocalSoneIsNull() {
+               activateNewPostNotifications();
+               addPostToPostNotification(newPostNotifications);
+               setPostVisibilityPredicate(Predicates.<Post>alwaysTrue());
+               List<Notification> filteredNotifications = listNotificationFilter.filterNotifications(newPostNotifications, null);
+               assertThat(filteredNotifications, Matchers.<Notification>emptyIterable());
+       }
+
+       @Test
+       public void newPostNotificationContainsOnlyVisiblePosts() {
+               activateNewPostNotifications();
+               addPostToPostNotification(newPostNotifications);
+               addPostToPostNotification(newPostNotifications);
+               setPostVisibilityPredicate(new Predicate<Post>() {
+                       @Override
+                       public boolean apply(@Nullable Post post) {
+                               return post.equals(newPostNotifications.get(0).getElements().get(1));
+                       }
+               });
+               List<Notification> filteredNotifications = listNotificationFilter.filterNotifications(newPostNotifications, localSone);
+               assertThat(filteredNotifications, hasSize(1));
+               assertThat(((ListNotification<Post>) filteredNotifications.get(0)).getElements().get(0), is(newPostNotifications.get(0).getElements().get(1)));
+       }
+
+       private ListNotification<PostReply> createNewReplyNotification() {
+               ListNotification<PostReply> newReplyNotifications = mock(ListNotification.class);
+               when(newReplyNotifications.getElements()).thenReturn(new ArrayList<PostReply>());
+               when(newReplyNotifications.getId()).thenReturn("new-reply-notification");
+               return newReplyNotifications;
+       }
+
+       private void activateNewReplyNotifications() {
+               when(soneOptions.isShowNewReplyNotifications()).thenReturn(true);
+       }
+
+       private void addReplyToNewReplyNotification(List<ListNotification<PostReply>> notifications) {
+               notifications.get(0).getElements().add(mock(PostReply.class));
+       }
+
+       private void setReplyVisibilityPredicate(Predicate<PostReply> value) {
+               when(replyVisibilityFilter.isVisible(any(Sone.class))).thenReturn(value);
+       }
+
+       @Test
+       public void newReplyNotificationContainsOnlyVisibleReplies() {
+               activateNewReplyNotifications();
+               addReplyToNewReplyNotification(newReplyNotifications);
+               addReplyToNewReplyNotification(newReplyNotifications);
+               setReplyVisibilityPredicate(new Predicate<PostReply>() {
+                       @Override
+                       public boolean apply(@Nullable PostReply postReply) {
+                               return postReply.equals(newReplyNotifications.get(0).getElements().get(1));
+                       }
+               });
+               List<Notification> filteredNotifications = listNotificationFilter.filterNotifications(newReplyNotifications, localSone);
+               assertThat(filteredNotifications, hasSize(1));
+               assertThat(((ListNotification<PostReply>) filteredNotifications.get(0)).getElements().get(0), is(newReplyNotifications.get(0).getElements().get(1)));
+       }
+
+       @Test
+       public void newReplyNotificationIsNotModifiedIfAllRepliesAreVisible() {
+               activateNewReplyNotifications();
+               addReplyToNewReplyNotification(newReplyNotifications);
+               addReplyToNewReplyNotification(newReplyNotifications);
+               setReplyVisibilityPredicate(Predicates.<PostReply>alwaysTrue());
+               List<Notification> filteredNotifications = listNotificationFilter.filterNotifications(newReplyNotifications, localSone);
+               assertThat(filteredNotifications, hasSize(1));
+               assertThat(filteredNotifications.get(0), is((Notification) newReplyNotifications.get(0)));
+               assertThat(((ListNotification<PostReply>) filteredNotifications.get(0)).getElements(), hasSize(2));
+       }
+
+       @Test
+       public void newReplyNotificationIsNotShownIfNoRepliesAreVisible() {
+               activateNewReplyNotifications();
+               addReplyToNewReplyNotification(newReplyNotifications);
+               addReplyToNewReplyNotification(newReplyNotifications);
+               setReplyVisibilityPredicate(Predicates.<PostReply>alwaysFalse());
+               List<Notification> filteredNotifications = listNotificationFilter.filterNotifications(newReplyNotifications, localSone);
+               assertThat(filteredNotifications, hasSize(0));
+       }
+
+       @Test
+       public void newReplyNotificationIsNotShownIfDeactivatedInOptions() {
+               addReplyToNewReplyNotification(newReplyNotifications);
+               addReplyToNewReplyNotification(newReplyNotifications);
+               setReplyVisibilityPredicate(Predicates.<PostReply>alwaysTrue());
+               List<Notification> filteredNotifications = listNotificationFilter.filterNotifications(newReplyNotifications, localSone);
+               assertThat(filteredNotifications, hasSize(0));
+       }
+
+       @Test
+       public void newReplyNotificationIsNotShownIfCurrentSoneIsNull() {
+               addReplyToNewReplyNotification(newReplyNotifications);
+               addReplyToNewReplyNotification(newReplyNotifications);
+               setReplyVisibilityPredicate(Predicates.<PostReply>alwaysTrue());
+               List<Notification> filteredNotifications = listNotificationFilter.filterNotifications(newReplyNotifications, null);
+               assertThat(filteredNotifications, hasSize(0));
+       }
+
+       private ListNotification<Post> createMentionNotification() {
+               ListNotification<Post> newSoneNotification = mock(ListNotification.class);
+               when(newSoneNotification.getElements()).thenReturn(new ArrayList<Post>());
+               when(newSoneNotification.getId()).thenReturn("mention-notification");
+               return newSoneNotification;
+       }
+
+       @Test
+       public void mentionNotificationContainsOnlyVisiblePosts() {
+               addPostToPostNotification(mentionNotifications);
+               addPostToPostNotification(mentionNotifications);
+               setPostVisibilityPredicate(new Predicate<Post>() {
+                       @Override
+                       public boolean apply(@Nullable Post post) {
+                               return post.equals(mentionNotifications.get(0).getElements().get(1));
+                       }
+               });
+               List<Notification> filteredNotifications = listNotificationFilter.filterNotifications(mentionNotifications, localSone);
+               assertThat(filteredNotifications, hasSize(1));
+               assertThat(((ListNotification<Post>) filteredNotifications.get(0)).getElements().get(0), is(mentionNotifications.get(0).getElements().get(1)));
+       }
+
+       @Test
+       public void mentionNotificationIsNotShownIfNoPostsAreVisible() {
+               addPostToPostNotification(mentionNotifications);
+               addPostToPostNotification(mentionNotifications);
+               setPostVisibilityPredicate(Predicates.<Post>alwaysFalse());
+               List<Notification> filteredNotifications = listNotificationFilter.filterNotifications(mentionNotifications, localSone);
+               assertThat(filteredNotifications, hasSize(0));
+       }
+
+       @Test
+       public void unfilterableNotificationIsNotFiltered() {
+               Notification notification = mock(Notification.class);
+               when(notification.getId()).thenReturn("random-notification");
+               List<Notification> notifications = Arrays.asList(notification);
+               List<Notification> filteredNotifications = listNotificationFilter.filterNotifications(notifications, null);
+               assertThat(filteredNotifications, contains(notification));
+       }
+
+}
diff --git a/src/test/java/net/pterodactylus/sone/notify/ListNotificationTest.java b/src/test/java/net/pterodactylus/sone/notify/ListNotificationTest.java
new file mode 100644 (file)
index 0000000..c354afa
--- /dev/null
@@ -0,0 +1,121 @@
+package net.pterodactylus.sone.notify;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.emptyIterable;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+
+import net.pterodactylus.util.notify.NotificationListener;
+import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
+
+import org.hamcrest.Matchers;
+import org.junit.Test;
+
+/**
+ * Unit test for {@link ListNotification}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class ListNotificationTest {
+
+       private static final String ID = "notification-id";
+       private static final String KEY = "element-key";
+       private static final String OTHER_KEY = "other-key";
+
+       private final Template template = mock(Template.class);
+       private final TemplateContext templateInitialContext = mock(TemplateContext.class);
+       private ListNotification<Object> listNotification;
+
+       public ListNotificationTest() {
+               when(template.getInitialContext()).thenReturn(templateInitialContext);
+               listNotification = new ListNotification<Object>(ID, KEY, template);
+       }
+
+       @Test
+       public void creatingAListNotificationSetsEmptyIterableOnElementKeyInTemplateContext() {
+               verify(templateInitialContext).set(eq(KEY), argThat(emptyIterable()));
+       }
+
+       @Test
+       public void newListNotificationHasNoElement() {
+               assertThat(listNotification.getElements(), emptyIterable());
+       }
+
+       @Test
+       public void newListNotificationIsEmpty() {
+               assertThat(listNotification.isEmpty(), is(true));
+       }
+
+       @Test
+       public void listNotificationRetainsSetElements() {
+               listNotification.setElements(Arrays.asList("a", "b", "c"));
+               assertThat(listNotification.getElements(), Matchers.<Object>contains("a", "b", "c"));
+       }
+
+       @Test
+       public void listNotificationRetainsAddedElements() {
+               listNotification.add("a");
+               listNotification.add("b");
+               listNotification.add("c");
+               assertThat(listNotification.getElements(), Matchers.<Object>contains("a", "b", "c"));
+       }
+
+       @Test
+       public void listNotificationRemovesCorrectElement() {
+               listNotification.setElements(Arrays.asList("a", "b", "c"));
+               listNotification.remove("b");
+               assertThat(listNotification.getElements(), Matchers.<Object>contains("a", "c"));
+       }
+
+       @Test
+       public void removingTheLastElementDismissesTheNotification() {
+               NotificationListener notificationListener = mock(NotificationListener.class);
+               listNotification.addNotificationListener(notificationListener);
+               listNotification.add("a");
+               listNotification.remove("a");
+               verify(notificationListener).notificationDismissed(listNotification);
+       }
+
+       @Test
+       public void dismissingTheListNotificationRemovesAllElements() {
+               listNotification.setElements(Arrays.asList("a", "b", "c"));
+               listNotification.dismiss();
+               assertThat(listNotification.getElements(), emptyIterable());
+       }
+
+       @Test
+       public void listNotificationWithDifferentElementsIsNotEqual() {
+               ListNotification secondNotification = new ListNotification(ID, KEY, template);
+               listNotification.add("a");
+               secondNotification.add("b");
+               assertThat(listNotification, not(is(secondNotification)));
+       }
+
+       @Test
+       public void listNotificationWithDifferentKeyIsNotEqual() {
+               ListNotification secondNotification = new ListNotification(ID, OTHER_KEY, template);
+               assertThat(listNotification, not(is(secondNotification)));
+       }
+
+       @Test
+       public void copiedNotificationsHaveTheSameHashCode() {
+               ListNotification secondNotification = new ListNotification(listNotification);
+               listNotification.add("a");
+               secondNotification.add("a");
+               assertThat(listNotification.hashCode(), is(secondNotification.hashCode()));
+       }
+
+       @Test
+       public void listNotificationIsNotEqualToOtherObjects() {
+           assertThat(listNotification, not(is(new Object())));
+       }
+
+}
diff --git a/src/test/java/net/pterodactylus/sone/notify/PostVisibilityFilterTest.java b/src/test/java/net/pterodactylus/sone/notify/PostVisibilityFilterTest.java
new file mode 100644 (file)
index 0000000..d49e59b
--- /dev/null
@@ -0,0 +1,203 @@
+package net.pterodactylus.sone.notify;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.freenet.wot.Identity;
+import net.pterodactylus.sone.freenet.wot.OwnIdentity;
+import net.pterodactylus.sone.freenet.wot.Trust;
+
+import com.google.common.base.Optional;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import org.junit.Test;
+
+/**
+ * Unit test for {@link PostVisibilityFilterTest}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class PostVisibilityFilterTest {
+
+       private static final String LOCAL_ID = "local-id";
+       private static final String REMOTE_ID = "remote-id";
+
+       private final PostVisibilityFilter postVisibilityFilter = new PostVisibilityFilter();
+
+       private final Sone localSone = mock(Sone.class);
+       private final OwnIdentity localIdentity = mock(OwnIdentity.class);
+       private final Post post = mock(Post.class);
+       private final Sone remoteSone = mock(Sone.class);
+       private final Identity remoteIdentity = mock(Identity.class);
+
+       public PostVisibilityFilterTest() {
+               when(localSone.getId()).thenReturn(LOCAL_ID);
+               when(localSone.isLocal()).thenReturn(true);
+               when(localSone.getIdentity()).thenReturn(localIdentity);
+               when(localIdentity.getId()).thenReturn(LOCAL_ID);
+               when(remoteSone.getId()).thenReturn(REMOTE_ID);
+               when(remoteSone.getIdentity()).thenReturn(remoteIdentity);
+               when(remoteIdentity.getId()).thenReturn(REMOTE_ID);
+               when(post.getRecipientId()).thenReturn(Optional.<String>absent());
+       }
+
+       @Test
+       public void postVisibilityFilterIsOnlyCreatedOnce() {
+               Injector injector = Guice.createInjector();
+               PostVisibilityFilter firstFilter = injector.getInstance(PostVisibilityFilter.class);
+               PostVisibilityFilter secondFilter = injector.getInstance(PostVisibilityFilter.class);
+               assertThat(firstFilter, sameInstance(secondFilter));
+       }
+
+       @Test
+       public void postIsNotVisibleIfItIsNotLoaded() {
+               assertThat(postVisibilityFilter.isPostVisible(localSone, post), is(false));
+       }
+
+       private static void makePostLoaded(Post post) {
+               when(post.isLoaded()).thenReturn(true);
+       }
+
+       @Test
+       public void loadedPostIsVisibleWithoutSone() {
+               makePostLoaded(post);
+               assertThat(postVisibilityFilter.isPostVisible(null, post), is(true));
+       }
+
+       private void makePostComeFromTheFuture() {
+               when(post.getTime()).thenReturn(System.currentTimeMillis() + 1000);
+       }
+
+       @Test
+       public void loadedPostFromTheFutureIsNotVisible() {
+               makePostLoaded(post);
+               makePostComeFromTheFuture();
+               assertThat(postVisibilityFilter.isPostVisible(null, post), is(false));
+       }
+
+       private void makePostFromRemoteSone() {
+               when(post.getSone()).thenReturn(remoteSone);
+       }
+
+       private void giveRemoteIdentityNegativeExplicitTrust() {
+               when(remoteIdentity.getTrust(localIdentity)).thenReturn(new Trust(-1, null, null));
+       }
+
+       @Test
+       public void loadedPostFromExplicitelyNotTrustedSoneIsNotVisible() {
+               makePostLoaded(post);
+               makePostFromRemoteSone();
+               giveRemoteIdentityNegativeExplicitTrust();
+               assertThat(postVisibilityFilter.isPostVisible(localSone, post), is(false));
+       }
+
+       private void giveRemoteIdentityNegativeImplicitTrust() {
+               when(remoteIdentity.getTrust(localIdentity)).thenReturn(new Trust(null, -1, null));
+       }
+
+       @Test
+       public void loadedPostFromImplicitelyUntrustedSoneIsNotVisible() {
+               makePostLoaded(post);
+               makePostFromRemoteSone();
+               giveRemoteIdentityNegativeImplicitTrust();
+               assertThat(postVisibilityFilter.isPostVisible(localSone, post), is(false));
+       }
+
+       private void makeLocalSoneFollowRemoteSone() {
+               when(localSone.hasFriend(REMOTE_ID)).thenReturn(true);
+       }
+
+       private void giveRemoteIdentityPositiveExplicitTrustButNegativeImplicitTrust() {
+               when(remoteIdentity.getTrust(localIdentity)).thenReturn(new Trust(1, -1, null));
+       }
+
+       @Test
+       public void loadedPostFromExplicitelyTrustedButImplicitelyUntrustedSoneIsVisible() {
+               makePostLoaded(post);
+               makePostFromRemoteSone();
+               makeLocalSoneFollowRemoteSone();
+               giveRemoteIdentityPositiveExplicitTrustButNegativeImplicitTrust();
+               assertThat(postVisibilityFilter.isPostVisible(localSone, post), is(true));
+       }
+
+       private void giveTheRemoteIdentityPositiveImplicitTrust() {
+               when(remoteIdentity.getTrust(localIdentity)).thenReturn(new Trust(null, 1, null));
+       }
+
+       @Test
+       public void loadedPostFromImplicitelyTrustedSoneIsVisible() {
+               makePostLoaded(post);
+               makePostFromRemoteSone();
+               makeLocalSoneFollowRemoteSone();
+               giveTheRemoteIdentityPositiveImplicitTrust();
+               assertThat(postVisibilityFilter.isPostVisible(localSone, post), is(true));
+       }
+
+       private void giveTheRemoteIdentityUnknownTrust() {
+               when(remoteIdentity.getTrust(localIdentity)).thenReturn(new Trust(null, null, null));
+       }
+
+       @Test
+       public void loadedPostFromSoneWithUnknownTrustIsVisible() {
+               makePostLoaded(post);
+               makePostFromRemoteSone();
+               makeLocalSoneFollowRemoteSone();
+               giveTheRemoteIdentityUnknownTrust();
+               assertThat(postVisibilityFilter.isPostVisible(localSone, post), is(true));
+       }
+
+       @Test
+       public void loadedPostFromUnfollowedRemoteSoneThatIsNotDirectedAtLocalSoneIsNotVisible() {
+               makePostLoaded(post);
+               makePostFromRemoteSone();
+               assertThat(postVisibilityFilter.isPostVisible(localSone, post), is(false));
+       }
+
+       private void makePostFromLocalSone() {
+               makePostLoaded(post);
+               when(post.getSone()).thenReturn(localSone);
+       }
+
+       @Test
+       public void loadedPostFromLocalSoneIsVisible() {
+               makePostFromLocalSone();
+               assertThat(postVisibilityFilter.isPostVisible(localSone, post), is(true));
+       }
+
+       @Test
+       public void loadedPostFromFollowedRemoteSoneThatIsNotDirectedAtLocalSoneIsVisible() {
+               makePostLoaded(post);
+               makePostFromRemoteSone();
+               makeLocalSoneFollowRemoteSone();
+               assertThat(postVisibilityFilter.isPostVisible(localSone, post), is(true));
+       }
+
+       private void makePostDirectedAtLocalId() {
+               when(post.getRecipientId()).thenReturn(Optional.of(LOCAL_ID));
+       }
+
+       @Test
+       public void loadedPostFromRemoteSoneThatIsDirectedAtLocalSoneIsVisible() {
+               makePostLoaded(post);
+               makePostFromRemoteSone();
+               makePostDirectedAtLocalId();
+               assertThat(postVisibilityFilter.isPostVisible(localSone, post), is(true));
+       }
+
+       @Test
+       public void predicateWillCorrectlyRecognizeVisiblePost() {
+               makePostFromLocalSone();
+               assertThat(postVisibilityFilter.isVisible(null).apply(post), is(true));
+       }
+
+       @Test
+       public void predicateWillCorrectlyRecognizeNotVisiblePost() {
+               assertThat(postVisibilityFilter.isVisible(null).apply(post), is(false));
+       }
+
+}
diff --git a/src/test/java/net/pterodactylus/sone/notify/ReplyVisibilityFilterTest.java b/src/test/java/net/pterodactylus/sone/notify/ReplyVisibilityFilterTest.java
new file mode 100644 (file)
index 0000000..7187847
--- /dev/null
@@ -0,0 +1,106 @@
+package net.pterodactylus.sone.notify;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostReply;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.freenet.wot.OwnIdentity;
+
+import com.google.common.base.Optional;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import org.junit.Test;
+
+/**
+ * Unit test for {@link ReplyVisibilityFilterTest}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class ReplyVisibilityFilterTest {
+
+       private static final String LOCAL_ID = "local-id";
+
+       private final PostVisibilityFilter postVisibilityFilter = mock(PostVisibilityFilter.class);
+       private final ReplyVisibilityFilter replyVisibilityFilter = new ReplyVisibilityFilter(postVisibilityFilter);
+
+       private final Sone localSone = mock(Sone.class);
+       private final OwnIdentity localIdentity = mock(OwnIdentity.class);
+       private final Post post = mock(Post.class);
+       private final PostReply postReply = mock(PostReply.class);
+
+       public ReplyVisibilityFilterTest() {
+               when(localSone.getId()).thenReturn(LOCAL_ID);
+               when(localSone.isLocal()).thenReturn(true);
+               when(localSone.getIdentity()).thenReturn(localIdentity);
+               when(post.getRecipientId()).thenReturn(Optional.<String>absent());
+       }
+
+       @Test
+       public void replyVisibilityFilterIsOnlyCreatedOnce() {
+               Injector injector = Guice.createInjector();
+               ReplyVisibilityFilter firstFilter = injector.getInstance(ReplyVisibilityFilter.class);
+               ReplyVisibilityFilter secondFilter = injector.getInstance(ReplyVisibilityFilter.class);
+               assertThat(firstFilter, sameInstance(secondFilter));
+       }
+
+       private void makePostPresent() {
+               when(postReply.getPost()).thenReturn(Optional.of(post));
+       }
+
+       @Test
+       public void replyIsNotVisibleIfPostIsNotVisible() {
+               makePostPresent();
+               assertThat(replyVisibilityFilter.isReplyVisible(localSone, postReply), is(false));
+       }
+
+       private void makePostAbsent() {
+               when(postReply.getPost()).thenReturn(Optional.<Post>absent());
+       }
+
+       @Test
+       public void replyIsNotVisibleIfPostIsNotPresent() {
+               makePostAbsent();
+               assertThat(replyVisibilityFilter.isReplyVisible(localSone, postReply), is(false));
+       }
+
+       private void makePostPresentAndVisible() {
+               makePostPresent();
+               when(postVisibilityFilter.isPostVisible(localSone, post)).thenReturn(true);
+       }
+
+       private void makeReplyComeFromFuture() {
+               when(postReply.getTime()).thenReturn(System.currentTimeMillis() + 1000);
+       }
+
+       @Test
+       public void replyIsNotVisibleIfItIsFromTheFuture() {
+               makePostPresentAndVisible();
+               makeReplyComeFromFuture();
+               assertThat(replyVisibilityFilter.isReplyVisible(localSone, postReply), is(false));
+       }
+
+       @Test
+       public void replyIsVisibleIfItIsNotFromTheFuture() {
+               makePostPresentAndVisible();
+               assertThat(replyVisibilityFilter.isReplyVisible(localSone, postReply), is(true));
+       }
+
+       @Test
+       public void predicateCorrectlyRecognizesVisibleReply() {
+               makePostPresentAndVisible();
+               assertThat(replyVisibilityFilter.isVisible(localSone).apply(postReply), is(true));
+       }
+
+       @Test
+       public void predicateCorrectlyRecognizesNotVisibleReply() {
+               makePostPresentAndVisible();
+               makeReplyComeFromFuture();
+               assertThat(replyVisibilityFilter.isVisible(localSone).apply(postReply), is(false));
+       }
+
+}
index afc2a54..aace929 100644 (file)
@@ -1,15 +1,22 @@
 package net.pterodactylus.sone.template;
 
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.allOf;
 import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
 import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
+import static org.hamcrest.Matchers.nullValue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 import net.pterodactylus.sone.TestUtil;
 import net.pterodactylus.sone.data.Album;
+import net.pterodactylus.sone.data.Image;
 import net.pterodactylus.sone.data.Profile;
 import net.pterodactylus.sone.data.Sone;
 
@@ -90,4 +97,25 @@ public class AlbumAccessorTest {
                };
        }
 
+       @Test
+       public void albumImageIsGeneratedRandomly() {
+               Image image = mock(Image.class);
+               List<Image> albumImages = Arrays.asList(mock(Image.class), image);
+               when(album.getImages()).thenReturn(albumImages);
+               int matchedImage = 0;
+               for (int i = 0; i < 1000; i++) {
+                       Image randomImage = (Image) albumAccessor.get(null, album, "albumImage");
+                       if (randomImage == image) {
+                               matchedImage++;
+                       }
+               }
+               assertThat(matchedImage, allOf(greaterThanOrEqualTo(250), lessThanOrEqualTo(750)));
+       }
+
+       @Test
+       public void albumImageIsNullIfThereAreNoImagesInAnAlbum() {
+               when(album.getImages()).thenReturn(Collections.<Image>emptyList());
+               assertThat(albumAccessor.get(null, album, "albumImage"), nullValue());
+       }
+
 }
diff --git a/src/test/java/net/pterodactylus/sone/template/JavascriptFilterTest.java b/src/test/java/net/pterodactylus/sone/template/JavascriptFilterTest.java
new file mode 100644 (file)
index 0000000..6718c5a
--- /dev/null
@@ -0,0 +1,56 @@
+package net.pterodactylus.sone.template;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+import net.pterodactylus.util.number.Hex;
+
+import org.junit.Test;
+
+/**
+ * Unit test for {@link JavascriptFilter}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class JavascriptFilterTest {
+
+       private final JavascriptFilter filter = new JavascriptFilter();
+
+       @Test
+       public void filterEscapesAllCharactersBelowSpace() {
+               String source = buildStringWithAllCharactersToEscape();
+               String target = buildStringWithEscapedCharacters();
+               assertThat((String) filter.format(null, source, null), is("\"" + target + "\""));
+       }
+
+       private String buildStringWithAllCharactersToEscape() {
+               StringBuilder stringBuilder = new StringBuilder();
+               for (int i = 0; i < 32; i++) {
+                       stringBuilder.append((char) i);
+               }
+               stringBuilder.append('"').append("\\").append("!");
+               return stringBuilder.toString();
+       }
+
+       private String buildStringWithEscapedCharacters() {
+               StringBuilder stringBuilder = new StringBuilder();
+               for (int i = 0; i < 32; i++) {
+                       switch (i) {
+                               case 9:
+                                       stringBuilder.append("\\t");
+                                       break;
+                               case 10:
+                                       stringBuilder.append("\\n");
+                                       break;
+                               case 13:
+                                       stringBuilder.append("\\r");
+                                       break;
+                               default:
+                                       stringBuilder.append("\\x").append(Hex.toHex(i, 2));
+                       }
+               }
+               stringBuilder.append("\\\"").append("\\\\").append("!");
+               return stringBuilder.toString();
+       }
+
+}
index 051c235..7a06dcf 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneTextParserTest.java - Copyright © 2011–2015 David Roden
+ * Sone - SoneTextParserTest.java - Copyright © 2011–2016 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -18,7 +18,6 @@
 package net.pterodactylus.sone.text;
 
 import java.io.IOException;
-import java.io.StringReader;
 import java.util.Arrays;
 import java.util.Collection;
 
@@ -53,17 +52,17 @@ public class SoneTextParserTest extends TestCase {
                Iterable<Part> parts;
 
                /* check basic operation. */
-               parts = soneTextParser.parse(null, new StringReader("Test."));
+               parts = soneTextParser.parse("Test.", null);
                assertNotNull("Parts", parts);
                assertEquals("Part Text", "Test.", convertText(parts, PlainTextPart.class));
 
                /* check empty lines at start and end. */
-               parts = soneTextParser.parse(null, new StringReader("\nTest.\n\n"));
+               parts = soneTextParser.parse("\nTest.\n\n", null);
                assertNotNull("Parts", parts);
                assertEquals("Part Text", "Test.", convertText(parts, PlainTextPart.class));
 
                /* check duplicate empty lines in the text. */
-               parts = soneTextParser.parse(null, new StringReader("\nTest.\n\n\nTest."));
+               parts = soneTextParser.parse("\nTest.\n\n\nTest.", null);
                assertNotNull("Parts", parts);
                assertEquals("Part Text", "Test.\n\nTest.", convertText(parts, PlainTextPart.class));
        }
@@ -80,17 +79,17 @@ public class SoneTextParserTest extends TestCase {
                Iterable<Part> parts;
 
                /* check basic links. */
-               parts = soneTextParser.parse(null, new StringReader("KSK@gpl.txt"));
+               parts = soneTextParser.parse("KSK@gpl.txt", null);
                assertNotNull("Parts", parts);
                assertEquals("Part Text", "[KSK@gpl.txt|gpl.txt|gpl.txt]", convertText(parts, FreenetLinkPart.class));
 
                /* check embedded links. */
-               parts = soneTextParser.parse(null, new StringReader("Link is KSK@gpl.txt\u200b."));
+               parts = soneTextParser.parse("Link is KSK@gpl.txt\u200b.", null);
                assertNotNull("Parts", parts);
                assertEquals("Part Text", "Link is [KSK@gpl.txt|gpl.txt|gpl.txt]\u200b.", convertText(parts, PlainTextPart.class, FreenetLinkPart.class));
 
                /* check embedded links and line breaks. */
-               parts = soneTextParser.parse(null, new StringReader("Link is KSK@gpl.txt\nKSK@test.dat\n"));
+               parts = soneTextParser.parse("Link is KSK@gpl.txt\nKSK@test.dat\n", null);
                assertNotNull("Parts", parts);
                assertEquals("Part Text", "Link is [KSK@gpl.txt|gpl.txt|gpl.txt]\n[KSK@test.dat|test.dat|test.dat]", convertText(parts, PlainTextPart.class, FreenetLinkPart.class));
        }
@@ -107,7 +106,7 @@ public class SoneTextParserTest extends TestCase {
                Iterable<Part> parts;
 
                /* check basic links. */
-               parts = soneTextParser.parse(null, new StringReader("Some text.\n\nLink to sone://DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU and stuff."));
+               parts = soneTextParser.parse("Some text.\n\nLink to sone://DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU and stuff.", null);
                assertNotNull("Parts", parts);
                assertEquals("Part Text", "Some text.\n\nLink to [Sone|DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU] and stuff.", convertText(parts, PlainTextPart.class, SonePart.class));
        }
@@ -125,7 +124,7 @@ public class SoneTextParserTest extends TestCase {
                Iterable<Part> parts;
 
                /* check empty http links. */
-               parts = soneTextParser.parse(null, new StringReader("Some text. Empty link: http:// – nice!"));
+               parts = soneTextParser.parse("Some text. Empty link: http:// – nice!", null);
                assertNotNull("Parts", parts);
                assertEquals("Part Text", "Some text. Empty link: http:// – nice!", convertText(parts, PlainTextPart.class));
        }
diff --git a/src/test/java/net/pterodactylus/sone/web/DeleteReplyPageTest.java b/src/test/java/net/pterodactylus/sone/web/DeleteReplyPageTest.java
new file mode 100644 (file)
index 0000000..80c8a2f
--- /dev/null
@@ -0,0 +1,65 @@
+package net.pterodactylus.sone.web;
+
+import static net.pterodactylus.sone.web.WebTestUtils.redirectsTo;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Collections;
+
+import net.pterodactylus.sone.data.PostReply;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.web.page.FreenetRequest;
+import net.pterodactylus.util.notify.Notification;
+import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
+import net.pterodactylus.util.web.Method;
+
+import freenet.support.api.HTTPRequest;
+
+import com.google.common.base.Optional;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mockito.Matchers;
+
+/**
+ * Unit test for {@link DeleteReplyPage}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class DeleteReplyPageTest {
+
+       @Rule
+       public final ExpectedException expectedException = ExpectedException.none();
+
+       private final Template template = new Template();
+       private final WebInterface webInterface = mock(WebInterface.class, RETURNS_DEEP_STUBS);
+       private final DeleteReplyPage page = new DeleteReplyPage(template, webInterface);
+       private final TemplateContext templateContext = new TemplateContext();
+       private final FreenetRequest freenetRequest = mock(FreenetRequest.class);
+       private final HTTPRequest httpRequest = mock(HTTPRequest.class);
+
+       @Before
+       public void setupWebInterface() {
+               when(webInterface.getNotifications(Matchers.any(Sone.class))).thenReturn(Collections.<Notification>emptyList());
+       }
+
+       @Before
+       public void setupHttpRequest() {
+               when(freenetRequest.getHttpRequest()).thenReturn(httpRequest);
+       }
+
+       @Test
+       public void tryingToDeleteAReplyWithAnInvalidIdResultsInNoPermissionPage() throws Exception {
+               when(freenetRequest.getMethod()).thenReturn(Method.POST);
+               when(httpRequest.getPartAsStringFailsafe(eq("reply"), anyInt())).thenReturn("id");
+               when(webInterface.getCore().getPostReply("id")).thenReturn(Optional.<PostReply>absent());
+               expectedException.expect(redirectsTo("noPermission.html"));
+               page.processTemplate(freenetRequest, templateContext);
+       }
+
+}
diff --git a/src/test/java/net/pterodactylus/sone/web/NewPageTest.java b/src/test/java/net/pterodactylus/sone/web/NewPageTest.java
new file mode 100644 (file)
index 0000000..b527e58
--- /dev/null
@@ -0,0 +1,74 @@
+package net.pterodactylus.sone.web;
+
+import static java.util.Arrays.asList;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Collections;
+import java.util.List;
+
+import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostReply;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.web.page.FreenetRequest;
+import net.pterodactylus.util.notify.Notification;
+import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
+
+import freenet.clients.http.ToadletContext;
+
+import com.google.common.base.Optional;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Unit test for {@link NewPage}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class NewPageTest {
+
+       private final Template template = mock(Template.class);
+       private final WebInterface webInterface = mock(WebInterface.class, RETURNS_DEEP_STUBS);
+       private final NewPage newPage = new NewPage(template, webInterface);
+       private final Sone currentSone = mock(Sone.class);
+       private final TemplateContext templateContext = new TemplateContext();
+       private final FreenetRequest freenetRequest = mock(FreenetRequest.class, RETURNS_DEEP_STUBS);
+
+       @Before
+       public void setupFreenetRequest() {
+               when(freenetRequest.getToadletContext()).thenReturn(mock(ToadletContext.class));
+       }
+
+       @Before
+       public void setupWebInterface() {
+               when(webInterface.getCore().getPreferences().getPostsPerPage()).thenReturn(5);
+               when(webInterface.getCurrentSone(any(ToadletContext.class), anyBoolean())).thenReturn(currentSone);
+               when(webInterface.getNotifications(any(Sone.class))).thenReturn(Collections.<Notification>emptyList());
+       }
+
+       @Test
+       public void postsAreNotDuplicatedWhenTheyComeFromBothNewPostsAndNewRepliesNotifications() throws Exception {
+               // given
+               Post extraPost = mock(Post.class);
+               List<Post> posts = asList(mock(Post.class), mock(Post.class));
+               List<PostReply> postReplies = asList(mock(PostReply.class), mock(PostReply.class));
+               when(postReplies.get(0).getPost()).thenReturn(Optional.of(posts.get(0)));
+               when(postReplies.get(1).getPost()).thenReturn(Optional.of(extraPost));
+               when(webInterface.getNewPosts(currentSone)).thenReturn(posts);
+               when(webInterface.getNewReplies(currentSone)).thenReturn(postReplies);
+
+               // when
+               newPage.processTemplate(freenetRequest, templateContext);
+
+               // then
+               List<Post> renderedPosts = templateContext.get("posts", List.class);
+               assertThat(renderedPosts, containsInAnyOrder(posts.get(0), posts.get(1), extraPost));
+       }
+
+}
diff --git a/src/test/java/net/pterodactylus/sone/web/UploadImagePageTest.java b/src/test/java/net/pterodactylus/sone/web/UploadImagePageTest.java
new file mode 100644 (file)
index 0000000..e74efbd
--- /dev/null
@@ -0,0 +1,73 @@
+package net.pterodactylus.sone.web;
+
+import static net.pterodactylus.sone.web.WebTestUtils.redirectsTo;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.net.URI;
+
+import net.pterodactylus.sone.core.Core;
+import net.pterodactylus.sone.core.UpdateChecker;
+import net.pterodactylus.sone.data.Album;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.web.page.FreenetRequest;
+import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
+import net.pterodactylus.util.web.Method;
+
+import freenet.clients.http.ToadletContext;
+import freenet.support.api.HTTPRequest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+/**
+ * Unit test for {@link UploadImagePageTest}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class UploadImagePageTest {
+
+       @Rule
+       public final ExpectedException expectedException = ExpectedException.none();
+
+       private final Template template = new Template();
+       private final WebInterface webInterface = mock(WebInterface.class);
+       private final UploadImagePage uploadImagePage = new UploadImagePage(template, webInterface);
+
+       private final TemplateContext templateContext = new TemplateContext();
+       private final HTTPRequest httpRequest = mock(HTTPRequest.class);
+       private final ToadletContext toadletContext = mock(ToadletContext.class);
+       private final Core core = mock(Core.class);
+       private final Sone currentSone = mock(Sone.class);
+       private final Album parentAlbum = mock(Album.class);
+
+       @Before
+       public void setupWebInterface() {
+               UpdateChecker updateChecker = mock(UpdateChecker.class);
+               when(core.getUpdateChecker()).thenReturn(updateChecker);
+               when(webInterface.getCore()).thenReturn(core);
+               when(webInterface.getCurrentSone(any(ToadletContext.class))).thenReturn(currentSone);
+       }
+
+       @Before
+       public void setupParentAlbum() {
+               when(core.getAlbum("parent-id")).thenReturn(parentAlbum);
+               when(parentAlbum.getSone()).thenReturn(currentSone);
+       }
+
+       @Test
+       public void uploadingAnImageWithoutTitleRedirectsToEmptyImageTitlePage() throws Exception {
+               FreenetRequest request = new FreenetRequest(new URI(""), Method.POST, httpRequest, toadletContext);
+               when(httpRequest.getPartAsStringFailsafe(eq("parent"), anyInt())).thenReturn("parent-id");
+               when(httpRequest.getPartAsStringFailsafe(eq("title"), anyInt())).thenReturn("  ");
+               expectedException.expect(redirectsTo("emptyImageTitle.html"));
+               uploadImagePage.processTemplate(request, templateContext);
+       }
+
+}
diff --git a/src/test/java/net/pterodactylus/sone/web/WebTestUtils.java b/src/test/java/net/pterodactylus/sone/web/WebTestUtils.java
new file mode 100644 (file)
index 0000000..d4e7596
--- /dev/null
@@ -0,0 +1,37 @@
+package net.pterodactylus.sone.web;
+
+import javax.annotation.Nonnull;
+
+import net.pterodactylus.sone.web.page.FreenetTemplatePage.RedirectException;
+
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+
+/**
+ * Utilities for testing the <code>web</code> package.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class WebTestUtils {
+
+       @Nonnull
+       public static Matcher<RedirectException> redirectsTo(@Nonnull final String page) {
+               return new TypeSafeDiagnosingMatcher<RedirectException>() {
+                       @Override
+                       protected boolean matchesSafely(RedirectException exception, Description mismatchDescription) {
+                               if (!exception.getTarget().equals(page)) {
+                                       mismatchDescription.appendText("target is ").appendValue(exception.getTarget());
+                                       return false;
+                               }
+                               return true;
+                       }
+
+                       @Override
+                       public void describeTo(Description description) {
+                               description.appendText("target is ").appendValue(page);
+                       }
+               };
+       }
+
+}
diff --git a/src/test/java/net/pterodactylus/sone/web/ajax/GetTimesAjaxPageTest.java b/src/test/java/net/pterodactylus/sone/web/ajax/GetTimesAjaxPageTest.java
new file mode 100644 (file)
index 0000000..e1ab857
--- /dev/null
@@ -0,0 +1,39 @@
+package net.pterodactylus.sone.web.ajax;
+
+import static java.lang.System.currentTimeMillis;
+import static net.pterodactylus.sone.web.ajax.GetTimesAjaxPage.getTime;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.when;
+
+import net.pterodactylus.sone.web.WebInterface;
+import net.pterodactylus.sone.web.ajax.GetTimesAjaxPage.Time;
+
+import org.junit.Test;
+import org.mockito.Mockito;
+
+/**
+ * Unit test for {@link GetTimesAjaxPage}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class GetTimesAjaxPageTest {
+
+       private final WebInterface webInterface = Mockito.mock(WebInterface.class, RETURNS_DEEP_STUBS);
+
+       @Test
+       public void timestampInTheFutureIsTranslatedCorrectly() {
+               when(webInterface.getL10n().getString("View.Time.InTheFuture")).thenReturn("in the future");
+               Time time = getTime(webInterface, currentTimeMillis() + 100);
+               assertThat(time.getText(), is("in the future"));
+       }
+
+       @Test
+       public void timestampAFewSecondsAgoIsTranslatedCorrectly() {
+               when(webInterface.getL10n().getString("View.Time.AFewSecondsAgo")).thenReturn("a few seconds ago");
+               Time time = getTime(webInterface, currentTimeMillis() - 1000);
+               assertThat(time.getText(), is("a few seconds ago"));
+       }
+
+}