🔀 Merge branch 'release-79' v79
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Wed, 26 Jun 2019 18:11:21 +0000 (20:11 +0200)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Wed, 26 Jun 2019 18:11:21 +0000 (20:11 +0200)
530 files changed:
.builds/jdk-1.8.yml [new file with mode: 0644]
.gitignore
Jenkinsfile [new file with mode: 0644]
build.gradle
gradle/wrapper/gradle-wrapper.properties
settings.gradle
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/PreferenceChangedEvent.kt [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/core/Preferences.java [deleted file]
src/main/java/net/pterodactylus/sone/core/PreferencesLoader.java
src/main/java/net/pterodactylus/sone/core/SoneChangeDetector.java [deleted file]
src/main/java/net/pterodactylus/sone/core/SoneDownloader.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/SoneModificationDetector.java
src/main/java/net/pterodactylus/sone/core/SoneParser.java
src/main/java/net/pterodactylus/sone/core/SoneRescuer.java
src/main/java/net/pterodactylus/sone/core/SoneUri.java
src/main/java/net/pterodactylus/sone/core/UpdateChecker.java
src/main/java/net/pterodactylus/sone/core/WebOfTrustUpdater.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/InsertionDelayChangedEvent.java [deleted file]
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 [deleted file]
src/main/java/net/pterodactylus/sone/core/event/NewPostReplyFoundEvent.java [deleted file]
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 [deleted file]
src/main/java/net/pterodactylus/sone/core/event/PostReplyEvent.java
src/main/java/net/pterodactylus/sone/core/event/PostReplyRemovedEvent.java [deleted file]
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 [deleted file]
src/main/java/net/pterodactylus/sone/data/Identified.java [deleted file]
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/SoneOptions.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/AbstractSoneBuilder.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 [deleted file]
src/main/java/net/pterodactylus/sone/data/impl/DefaultPostReplyBuilderFactory.java [deleted file]
src/main/java/net/pterodactylus/sone/data/impl/IdOnlySone.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 [deleted file]
src/main/java/net/pterodactylus/sone/data/impl/PostImpl.java [deleted file]
src/main/java/net/pterodactylus/sone/data/impl/PostReplyBuilderImpl.java [deleted file]
src/main/java/net/pterodactylus/sone/data/impl/PostReplyImpl.java [deleted file]
src/main/java/net/pterodactylus/sone/data/impl/ReplyImpl.java [deleted file]
src/main/java/net/pterodactylus/sone/data/impl/SoneImpl.java
src/main/java/net/pterodactylus/sone/database/AlbumBuilder.kt [deleted file]
src/main/java/net/pterodactylus/sone/database/AlbumBuilderFactory.kt [deleted file]
src/main/java/net/pterodactylus/sone/database/AlbumDatabase.kt [deleted file]
src/main/java/net/pterodactylus/sone/database/AlbumProvider.kt [deleted file]
src/main/java/net/pterodactylus/sone/database/AlbumStore.kt [deleted file]
src/main/java/net/pterodactylus/sone/database/DatabaseException.java
src/main/java/net/pterodactylus/sone/database/memory/ConfigurationLoader.java
src/main/java/net/pterodactylus/sone/database/memory/MemoryBookmarkDatabase.java
src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java [deleted file]
src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.kt [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/memory/MemoryFriendDatabase.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/database/memory/MemorySoneBuilder.java
src/main/java/net/pterodactylus/sone/fcp/AbstractSoneCommand.java [deleted file]
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/fcp/event/FcpInterfaceActivatedEvent.java
src/main/java/net/pterodactylus/sone/fcp/event/FcpInterfaceDeactivatedEvent.java
src/main/java/net/pterodactylus/sone/fcp/event/FullAccessRequiredChanged.java
src/main/java/net/pterodactylus/sone/freenet/Key.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/IdentityManager.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/DebugLoaders.java
src/main/java/net/pterodactylus/sone/main/DefaultLoaders.java
src/main/java/net/pterodactylus/sone/main/Loaders.java
src/main/java/net/pterodactylus/sone/main/ReparseFilter.java
src/main/java/net/pterodactylus/sone/main/SonePlugin.java
src/main/java/net/pterodactylus/sone/main/SonePlugin.kt [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/notify/ListNotification.java
src/main/java/net/pterodactylus/sone/notify/ListNotificationFilter.java
src/main/java/net/pterodactylus/sone/notify/PostVisibilityFilter.java
src/main/java/net/pterodactylus/sone/notify/ReplyVisibilityFilter.java
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/FilesystemTemplate.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/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/FreemailPart.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/PostPart.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/DefaultOption.java
src/main/java/net/pterodactylus/sone/utils/IntegerRangePredicate.java
src/main/java/net/pterodactylus/sone/utils/NumberParsers.java
src/main/java/net/pterodactylus/sone/utils/Option.java
src/main/java/net/pterodactylus/sone/web/AllPages.kt [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/web/WebInterface.java
src/main/java/net/pterodactylus/sone/web/page/FreenetPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/page/FreenetRequest.java [deleted file]
src/main/java/net/pterodactylus/sone/web/page/FreenetTemplatePage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/page/PageToadlet.java
src/main/java/net/pterodactylus/sone/web/page/PageToadletFactory.java [deleted file]
src/main/kotlin/net/pterodactylus/sone/core/Fetched.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/core/Preferences.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/core/SoneComparison.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/core/UpdatedSoneProcessor.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/core/event/InsertionDelayChangedEvent.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/core/event/NewPostFoundEvent.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/core/event/NewPostReplyFoundEvent.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/core/event/PostRemovedEvent.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/core/event/PostReplyRemovedEvent.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/data/Fingerprintable.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/data/Identified.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/AlbumBuilder.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/AlbumBuilderFactory.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/AlbumDatabase.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/AlbumProvider.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/AlbumStore.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/Database.kt
src/main/kotlin/net/pterodactylus/sone/database/FriendProvider.kt
src/main/kotlin/net/pterodactylus/sone/database/ImageBuilder.kt
src/main/kotlin/net/pterodactylus/sone/database/ImageBuilderFactory.kt
src/main/kotlin/net/pterodactylus/sone/database/ImageDatabase.kt
src/main/kotlin/net/pterodactylus/sone/database/ImageProvider.kt
src/main/kotlin/net/pterodactylus/sone/database/ImageStore.kt
src/main/kotlin/net/pterodactylus/sone/database/PostBuilder.kt
src/main/kotlin/net/pterodactylus/sone/database/PostBuilderFactory.kt
src/main/kotlin/net/pterodactylus/sone/database/PostDatabase.kt
src/main/kotlin/net/pterodactylus/sone/database/PostProvider.kt
src/main/kotlin/net/pterodactylus/sone/database/PostReplyBuilder.kt
src/main/kotlin/net/pterodactylus/sone/database/PostReplyBuilderFactory.kt
src/main/kotlin/net/pterodactylus/sone/database/PostReplyDatabase.kt
src/main/kotlin/net/pterodactylus/sone/database/PostReplyProvider.kt
src/main/kotlin/net/pterodactylus/sone/database/PostReplyStore.kt
src/main/kotlin/net/pterodactylus/sone/database/PostStore.kt
src/main/kotlin/net/pterodactylus/sone/database/ReplyBuilder.kt
src/main/kotlin/net/pterodactylus/sone/database/SoneProvider.kt
src/main/kotlin/net/pterodactylus/sone/fcp/AbstractSoneCommand.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/main/FreenetModule.kt
src/main/kotlin/net/pterodactylus/sone/main/SoneModuleCreator.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/template/LinkedElementRenderFilter.kt
src/main/kotlin/net/pterodactylus/sone/template/LinkedElementsFilter.kt
src/main/kotlin/net/pterodactylus/sone/template/RenderFilter.kt
src/main/kotlin/net/pterodactylus/sone/text/PlainTextPart.kt
src/main/kotlin/net/pterodactylus/sone/text/SoneTextParser.kt
src/main/kotlin/net/pterodactylus/sone/utils/Objects.kt
src/main/kotlin/net/pterodactylus/sone/utils/Pagination.kt
src/main/kotlin/net/pterodactylus/sone/web/PageToadletRegistry.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/WebInterfaceModule.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/BookmarkAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/CreatePostAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/CreateReplyAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/DeletePostAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/DeleteProfileFieldAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/DeleteReplyAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/DismissNotificationAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/DistrustAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/EditAlbumAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/EditImageAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/EditProfileFieldAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/FollowSoneAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/GetLikesAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/GetLinkedElementAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/GetNotificationsAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/GetPostAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/GetReplyAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/GetStatusAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/GetTimesAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/GetTranslationAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/JsonPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/LikeAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/LockSoneAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/LoggedInJsonPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/MarkAsKnownAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/MoveProfileFieldAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/TrustAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/UnbookmarkAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/UnfollowSoneAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/UnlikeAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/UnlockSoneAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/UntrustAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/page/Annotations.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/page/FreenetPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/page/FreenetRequest.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/page/FreenetTemplatePage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/page/PageMakerInteraction.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/page/PageMakerInteractionFactory.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/page/PageToadletFactory.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/page/SoneRequest.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/page/TemplateRenderer.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/AboutPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/BookmarkPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/BookmarksPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/CreateAlbumPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/CreatePostPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/CreateReplyPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/CreateSonePage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteAlbumPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteImagePage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/DeletePostPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteProfileFieldPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteReplyPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteSonePage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/DismissNotificationPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/DistrustPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/EditAlbumPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/EditImagePage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/EditProfileFieldPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/EditProfilePage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/ErrorPages.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/FollowSonePage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/GetImagePage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/ImageBrowserPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/IndexPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/KnownSonesPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/LikePage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/LockSonePage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/LoggedInPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/LoginPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/LogoutPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/MarkAsKnownPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/NewPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/OptionsPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/ReloadingPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/RescuePage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/SearchPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/SoneTemplatePage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/TrustPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/UnbookmarkPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/UnfollowSonePage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/UnlikePage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/UnlockSonePage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/UntrustPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/UploadImagePage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/ViewPostPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/ViewSonePage.kt
src/main/resources/i18n/sone.de.properties
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/FreenetInterfaceTest.java
src/test/java/net/pterodactylus/sone/core/ImageInserterTest.java [deleted file]
src/test/java/net/pterodactylus/sone/core/OptionsTest.java
src/test/java/net/pterodactylus/sone/core/PreferencesLoaderTest.java
src/test/java/net/pterodactylus/sone/core/PreferencesTest.java [deleted file]
src/test/java/net/pterodactylus/sone/core/SoneChangeDetectorTest.java [deleted file]
src/test/java/net/pterodactylus/sone/core/SoneDownloaderTest.java
src/test/java/net/pterodactylus/sone/core/SoneInserterTest.java
src/test/java/net/pterodactylus/sone/core/SoneModificationDetectorTest.java
src/test/java/net/pterodactylus/sone/core/SoneParserTest.java
src/test/java/net/pterodactylus/sone/core/SoneRescuerTest.java
src/test/java/net/pterodactylus/sone/core/SoneUriTest.java
src/test/java/net/pterodactylus/sone/core/UpdateCheckerTest.java
src/test/java/net/pterodactylus/sone/core/WebOfTrustUpdaterTest.java
src/test/java/net/pterodactylus/sone/data/ProfileTest.java [deleted file]
src/test/java/net/pterodactylus/sone/data/impl/AbstractSoneBuilderTest.java
src/test/java/net/pterodactylus/sone/data/impl/ImageImplTest.java
src/test/java/net/pterodactylus/sone/database/memory/ConfigurationLoaderTest.java [deleted file]
src/test/java/net/pterodactylus/sone/database/memory/MemoryBookmarkDatabaseTest.java
src/test/java/net/pterodactylus/sone/database/memory/MemoryDatabaseTest.java [deleted file]
src/test/java/net/pterodactylus/sone/freenet/KeyTest.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/freenet/wot/IdentityManagerTest.java
src/test/java/net/pterodactylus/sone/freenet/wot/event/IdentityEventTest.java
src/test/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityEventTest.java
src/test/java/net/pterodactylus/sone/main/DebugLoadersTest.java
src/test/java/net/pterodactylus/sone/main/DefaultLoadersTest.java
src/test/java/net/pterodactylus/sone/main/ReparseFilterTest.java
src/test/java/net/pterodactylus/sone/notify/ListNotificationFilterTest.java
src/test/java/net/pterodactylus/sone/notify/ListNotificationTest.java
src/test/java/net/pterodactylus/sone/notify/PostVisibilityFilterTest.java
src/test/java/net/pterodactylus/sone/notify/ReplyVisibilityFilterTest.java
src/test/java/net/pterodactylus/sone/template/AlbumAccessorTest.java
src/test/java/net/pterodactylus/sone/template/CollectionAccessorTest.java
src/test/java/net/pterodactylus/sone/template/CssClassNameFilterTest.java
src/test/java/net/pterodactylus/sone/template/FilesystemTemplateTest.java
src/test/java/net/pterodactylus/sone/template/GetPagePluginTest.java
src/test/java/net/pterodactylus/sone/template/HttpRequestAccessorTest.java
src/test/java/net/pterodactylus/sone/template/IdentityAccessorTest.java
src/test/java/net/pterodactylus/sone/template/ImageLinkFilterTest.java
src/test/java/net/pterodactylus/sone/template/JavascriptFilterTest.java
src/test/java/net/pterodactylus/sone/template/PostAccessorTest.java
src/test/java/net/pterodactylus/sone/test/Dirty.java
src/test/java/net/pterodactylus/sone/test/Matchers.java
src/test/java/net/pterodactylus/sone/test/TestAlbumBuilder.java
src/test/java/net/pterodactylus/sone/test/TestImageBuilder.java
src/test/java/net/pterodactylus/sone/test/TestPostBuilder.java
src/test/java/net/pterodactylus/sone/test/TestPostReplyBuilder.java
src/test/java/net/pterodactylus/sone/test/TestUtil.java
src/test/java/net/pterodactylus/sone/test/TestValue.java
src/test/java/net/pterodactylus/sone/text/FreemailPartTest.java
src/test/java/net/pterodactylus/sone/text/PostPartTest.java
src/test/java/net/pterodactylus/sone/text/SoneTextParserTest.java [deleted file]
src/test/java/net/pterodactylus/sone/text/TextFilterTest.java
src/test/java/net/pterodactylus/sone/utils/DefaultOptionTest.java
src/test/java/net/pterodactylus/sone/utils/IntegerRangePredicateTest.java
src/test/java/net/pterodactylus/sone/utils/NumberParsersTest.java
src/test/java/net/pterodactylus/sone/web/WebTestUtils.java
src/test/java/net/pterodactylus/sone/web/page/FreenetRequestTest.java [deleted file]
src/test/kotlin/net/pterodactylus/sone/core/ElementLoaderTest.kt
src/test/kotlin/net/pterodactylus/sone/core/ImageInserterTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/core/PreferencesTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/core/SoneComparisonTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/core/UpdatedSoneProcessorTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/data/ProfileTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/database/memory/ConfigurationLoaderTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/database/memory/MemoryDatabaseTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/database/memory/MemoryPostTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/fcp/CreatePostCommandTest.kt
src/test/kotlin/net/pterodactylus/sone/fcp/CreateReplyCommandTest.kt
src/test/kotlin/net/pterodactylus/sone/fcp/DeletePostCommandTest.kt
src/test/kotlin/net/pterodactylus/sone/fcp/DeleteReplyCommandTest.kt
src/test/kotlin/net/pterodactylus/sone/fcp/GetLocalSonesCommandTest.kt
src/test/kotlin/net/pterodactylus/sone/fcp/GetPostCommandTest.kt
src/test/kotlin/net/pterodactylus/sone/fcp/GetPostFeedCommandTest.kt
src/test/kotlin/net/pterodactylus/sone/fcp/GetPostsCommandTest.kt
src/test/kotlin/net/pterodactylus/sone/fcp/GetSoneCommandTest.kt
src/test/kotlin/net/pterodactylus/sone/fcp/GetSonesCommandTest.kt
src/test/kotlin/net/pterodactylus/sone/fcp/LikePostCommandTest.kt
src/test/kotlin/net/pterodactylus/sone/fcp/LikeReplyCommandTest.kt
src/test/kotlin/net/pterodactylus/sone/fcp/LockSoneCommandTest.kt
src/test/kotlin/net/pterodactylus/sone/fcp/SoneCommandTest.kt
src/test/kotlin/net/pterodactylus/sone/fcp/UnlockSoneCommandTest.kt
src/test/kotlin/net/pterodactylus/sone/fcp/VersionCommandTest.kt
src/test/kotlin/net/pterodactylus/sone/freenet/L10nFilterTest.kt
src/test/kotlin/net/pterodactylus/sone/main/FreenetModuleTest.kt
src/test/kotlin/net/pterodactylus/sone/main/SoneModuleCreatorTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/main/SonePluginTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/template/ImageAccessorTest.kt
src/test/kotlin/net/pterodactylus/sone/template/LinkedElementRenderFilterTest.kt
src/test/kotlin/net/pterodactylus/sone/template/ProfileAccessorTest.kt
src/test/kotlin/net/pterodactylus/sone/template/RenderFilterTest.kt
src/test/kotlin/net/pterodactylus/sone/template/ReplyAccessorTest.kt
src/test/kotlin/net/pterodactylus/sone/template/ReplyGroupFilterTest.kt
src/test/kotlin/net/pterodactylus/sone/template/SoneAccessorTest.kt
src/test/kotlin/net/pterodactylus/sone/test/Guice.kt
src/test/kotlin/net/pterodactylus/sone/test/Matchers.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/test/Mockotlin.kt
src/test/kotlin/net/pterodactylus/sone/test/OneByOneMatcher.kt
src/test/kotlin/net/pterodactylus/sone/test/TestUtils.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/text/SonePartTest.kt
src/test/kotlin/net/pterodactylus/sone/text/SoneTextParserTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/utils/ObjectsTest.kt
src/test/kotlin/net/pterodactylus/sone/utils/PaginationTest.kt
src/test/kotlin/net/pterodactylus/sone/web/AllPagesTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/PageToadletRegistryTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/WebInterfaceModuleTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/BookmarkAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/CreatePostAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/CreateReplyAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/DeletePostAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/DeleteProfileFieldAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/DeleteReplyAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/DismissNotificationAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/DistrustAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/EditAlbumAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/EditImageAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/EditProfileFieldAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/FollowSoneAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/GetLikesAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/GetLinkedElementAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/GetNotificationsAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/GetPostAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/GetReplyAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/GetStatusAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/GetTimesAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/GetTranslationAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/JsonPageBaseTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/LikeAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/LockSoneAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/LoggedInJsonPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/MarkAsKnownAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/MoveProfileFieldAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/TrustAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/UnbookmarkAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/UnfollowSoneAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/UnlikeAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/UnlockSoneAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/UntrustAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/page/FreenetRequestTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/page/FreenetTemplatePageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/page/PageMakerInteractionFactoryTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/page/PageMakerInteractionTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/page/PageToadletFactoryTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/page/SoneRequestTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/page/TemplateRendererTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/AboutPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/BookmarkPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/BookmarksPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/CreateAlbumPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/CreatePostPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/CreateReplyPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/CreateSonePageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteAlbumPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteImagePageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/DeletePostPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteProfileFieldPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteReplyPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteSonePageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/DismissNotificationPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/DistrustPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/EditAlbumPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/EditImagePageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/EditProfileFieldPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/EditProfilePageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/ErrorPagesTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/FollowSonePageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/GetImagePageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/ImageBrowserPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/IndexPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/KnownSonesPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/LikePageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/LockSonePageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/LoginPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/LogoutPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/MarkAsKnownPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/NewPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/OptionsPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/ReloadingPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/RescuePageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/SearchPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/SoneTemplatePageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/TrustPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/UnbookmarkPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/UnfollowSonePageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/UnlikePageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/UnlockSonePageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/UntrustPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/UploadImagePageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/ViewPostPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/ViewSonePageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/WebPageTest.kt
version.gradle

diff --git a/.builds/jdk-1.8.yml b/.builds/jdk-1.8.yml
new file mode 100644 (file)
index 0000000..5edd26d
--- /dev/null
@@ -0,0 +1,18 @@
+image: alpine/edge
+packages:
+  - openjdk8
+sources: 
+  - https://git.sr.ht/~bombe/sone
+tasks:
+  - clean: |
+      cd sone
+      ./gradlew clean
+  - build: |
+      cd sone
+      ./gradlew build
+  - test: |
+      cd sone
+      ./gradlew test
+  - jar: |
+      cd sone
+      ./gradlew fatJar
index 99b31c6..fce4db9 100644 (file)
@@ -1,2 +1,5 @@
 /target
 /src/generated
+
+/.gradle/
+/build/
diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644 (file)
index 0000000..6775f35
--- /dev/null
@@ -0,0 +1,39 @@
+pipeline {
+    agent any
+
+    options {
+        timestamps()
+    }
+
+    stages {
+        stage('Build') {
+            steps {
+                sh './gradlew clean classes testClasses'
+            }
+        }
+        stage('Test') {
+            steps {
+                sh './gradlew test'
+            }
+            post {
+                always {
+                    junit 'build/test-results/test/*.xml'
+                }
+            }
+        }
+        stage('Binary') {
+            steps {
+                sh './gradlew fatJar'
+                archiveArtifacts artifacts: 'build/libs/sone*-jar-with-dependencies.jar', fingerprint: true
+            }
+        }
+        stage('Reports') {
+            steps {
+                sh './gradlew jacocoTestReport findbugsMain countLines'
+                jacoco classPattern: 'build/classes/*/main', sourcePattern: '**/src/main/'
+                findbugs canComputeNew: false, defaultEncoding: '', excludePattern: '', healthy: '', includePattern: '', pattern: '**/findbugs/main.xml', unHealthy: ''
+                sloccountPublish encoding: '', pattern: 'build/reports/cloc/*.xml'
+            }
+        }
+    }
+}
index 8111c2a..01832fb 100644 (file)
@@ -1,21 +1,21 @@
 group = 'net.pterodactylus'
-version = '0.9.8'
+version = '79'
 
 buildscript {
-    ext.kotlinVersion = '1.2.0'
+    ext.kotlinVersion = '1.2.71'
     repositories {
         mavenCentral()
     }
     dependencies {
-        classpath group: 'info.solidsoft.gradle.pitest', name: 'gradle-pitest-plugin', version: '1.1.11'
+        classpath group: 'info.solidsoft.gradle.pitest', name: 'gradle-pitest-plugin', version: '1.4.0'
         classpath group: 'org.jetbrains.kotlin', name: 'kotlin-gradle-plugin', version: kotlinVersion
         classpath group: 'org.jetbrains.kotlin', name: 'kotlin-noarg', version: kotlinVersion
     }
 }
 
 repositories {
-     maven { url "http://maven.pterodactylus.net/" }
      mavenCentral()
+     maven { url "https://maven.pterodactylus.net/" }
 }
 
 apply plugin: 'java'
@@ -45,14 +45,14 @@ dependencies {
 
     compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib'
     compile group: 'net.pterodactylus', name: 'utils', version: '0.12.4'
-    compile group: 'com.google.inject', name: 'guice', version: '3.0'
-    compile group: 'com.google.guava', name: 'guava', version: '14.0.1'
+    compile group: 'com.google.inject', name: 'guice', version: '4.2.2'
+    compile group: 'com.google.guava', name: 'guava', version: '27.0.1-android'
     compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.1'
     compile group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.9.1'
     compile group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2'
     compile group: 'org.jsoup', name: 'jsoup', version: '1.10.2'
 
-    testCompile group: 'org.jetbrains.kotlin', name: 'kotlin-test'
+    testCompile group: 'org.jetbrains.kotlin', name: 'kotlin-test-junit'
     testCompile group: 'junit', name: 'junit', version: '4.11'
     testCompile group: 'org.mockito', name: 'mockito-core', version: '2.10.0'
     testCompile group: 'org.hamcrest', name: 'hamcrest-all', version: '1.3'
@@ -60,8 +60,12 @@ dependencies {
 
 apply from: 'version.gradle'
 
+test {
+    maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1
+}
+
 task fatJar(type: Jar) {
-    archiveName = project.name + '-jar-with-dependencies.jar'
+    archiveName = project.name.toLowerCase() + '-jar-with-dependencies.jar'
     from { (configurations.runtime - configurations.provided).collect { it.isDirectory() ? it : zipTree(it) } }
     manifest {
         attributes('Plugin-Main-Class': 'net.pterodactylus.sone.main.SonePlugin')
index 92781d4..c19a936 100644 (file)
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.5-all.zip
index e60d974..97902dc 100644 (file)
@@ -1 +1 @@
-rootProject.name = 'sone'
+rootProject.name = 'Sone'
index 37e0ed5..99c1c9d 100644 (file)
@@ -27,17 +27,15 @@ import net.pterodactylus.util.config.Configuration;
 
 /**
  * Parses a {@link Sone}’s data from a {@link Configuration}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class ConfigurationSoneParser {
 
        private final Configuration configuration;
        private final Sone sone;
        private final String sonePrefix;
-       private final Map<String, Album> albums = new HashMap<String, Album>();
-       private final List<Album> topLevelAlbums = new ArrayList<Album>();
-       private final Map<String, Image> images = new HashMap<String, Image>();
+       private final Map<String, Album> albums = new HashMap<>();
+       private final List<Album> topLevelAlbums = new ArrayList<>();
+       private final Map<String, Image> images = new HashMap<>();
 
        public ConfigurationSoneParser(Configuration configuration, Sone sone) {
                this.configuration = configuration;
@@ -86,7 +84,7 @@ public class ConfigurationSoneParser {
 
        public Set<Post> parsePosts(PostBuilderFactory postBuilderFactory)
        throws InvalidPostFound {
-               Set<Post> posts = new HashSet<Post>();
+               Set<Post> posts = new HashSet<>();
                while (true) {
                        String postPrefix = "/Posts/" + posts.size();
                        String postId = getString(postPrefix + "/ID", null);
@@ -123,7 +121,7 @@ public class ConfigurationSoneParser {
 
        public Set<PostReply> parsePostReplies(
                        PostReplyBuilderFactory postReplyBuilderFactory) {
-               Set<PostReply> replies = new HashSet<PostReply>();
+               Set<PostReply> replies = new HashSet<>();
                while (true) {
                        String replyPrefix = "/Replies/" + replies.size();
                        String replyId = getString(replyPrefix + "/ID", null);
@@ -149,7 +147,7 @@ public class ConfigurationSoneParser {
        }
 
        public Set<String> parseLikedPostIds() {
-               Set<String> likedPostIds = new HashSet<String>();
+               Set<String> likedPostIds = new HashSet<>();
                while (true) {
                        String likedPostId =
                                        getString("/Likes/Post/" + likedPostIds.size() + "/ID",
@@ -163,7 +161,7 @@ public class ConfigurationSoneParser {
        }
 
        public Set<String> parseLikedPostReplyIds() {
-               Set<String> likedPostReplyIds = new HashSet<String>();
+               Set<String> likedPostReplyIds = new HashSet<>();
                while (true) {
                        String likedReplyId = getString(
                                        "/Likes/Reply/" + likedPostReplyIds.size() + "/ID", null);
@@ -176,7 +174,7 @@ public class ConfigurationSoneParser {
        }
 
        public Set<String> parseFriends() {
-               Set<String> friends = new HashSet<String>();
+               Set<String> friends = new HashSet<>();
                while (true) {
                        String friendId =
                                        getString("/Friends/" + friends.size() + "/ID", null);
index 7384eda..b0df7a9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Core.java - Copyright © 2010–2016 David Roden
+ * Sone - Core.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -48,8 +48,6 @@ import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidImageFound;
 import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidParentAlbumFound;
 import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidPostFound;
 import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidPostReplyFound;
-import net.pterodactylus.sone.core.SoneChangeDetector.PostProcessor;
-import net.pterodactylus.sone.core.SoneChangeDetector.PostReplyProcessor;
 import net.pterodactylus.sone.core.event.ImageInsertFinishedEvent;
 import net.pterodactylus.sone.core.event.InsertionDelayChangedEvent;
 import net.pterodactylus.sone.core.event.MarkPostKnownEvent;
@@ -113,8 +111,6 @@ import kotlin.jvm.functions.Function1;
 
 /**
  * The Sone core.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 @Singleton
 public class Core extends AbstractService implements SoneProvider, PostProvider, PostReplyProvider {
@@ -158,23 +154,20 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
        /** The trust updater. */
        private final WebOfTrustUpdater webOfTrustUpdater;
 
-       /** The times Sones were followed. */
-       private final Map<String, Long> soneFollowingTimes = new HashMap<String, Long>();
-
        /** Locked local Sones. */
        /* synchronize on itself. */
-       private final Set<Sone> lockedSones = new HashSet<Sone>();
+       private final Set<Sone> lockedSones = new HashSet<>();
 
        /** Sone inserters. */
        /* synchronize access on this on sones. */
-       private final Map<Sone, SoneInserter> soneInserters = new HashMap<Sone, SoneInserter>();
+       private final Map<Sone, SoneInserter> soneInserters = new HashMap<>();
 
        /** Sone rescuers. */
        /* synchronize access on this on sones. */
-       private final Map<Sone, SoneRescuer> soneRescuers = new HashMap<Sone, SoneRescuer>();
+       private final Map<Sone, SoneRescuer> soneRescuers = new HashMap<>();
 
        /** All known Sones. */
-       private final Set<String> knownSones = new HashSet<String>();
+       private final Set<String> knownSones = new HashSet<>();
 
        /** The post database. */
        private final Database database;
@@ -183,7 +176,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
        private final Multimap<OwnIdentity, Identity> trustedIdentities = Multimaps.synchronizedSetMultimap(HashMultimap.<OwnIdentity, Identity>create());
 
        /** All temporary images. */
-       private final Map<String, TemporaryImage> temporaryImages = new HashMap<String, TemporaryImage>();
+       private final Map<String, TemporaryImage> temporaryImages = new HashMap<>();
 
        /** Ticker for threads that mark own elements as known. */
        private final ScheduledExecutorService localElementTicker = Executors.newScheduledThreadPool(1);
@@ -208,22 +201,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         *            The database
         */
        @Inject
-       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 = updateChecker;
-               this.webOfTrustUpdater = webOfTrustUpdater;
-               this.eventBus = eventBus;
-               this.database = database;
-               preferences = new Preferences(eventBus);
-       }
-
-       @VisibleForTesting
-       protected Core(Configuration configuration, FreenetInterface freenetInterface, IdentityManager identityManager, SoneDownloader soneDownloader, ImageInserter imageInserter, UpdateChecker updateChecker, WebOfTrustUpdater webOfTrustUpdater, EventBus eventBus, Database database) {
+       public Core(Configuration configuration, FreenetInterface freenetInterface, IdentityManager identityManager, SoneDownloader soneDownloader, ImageInserter imageInserter, UpdateChecker updateChecker, WebOfTrustUpdater webOfTrustUpdater, EventBus eventBus, Database database) {
                super("Sone Core");
                this.configuration = configuration;
                this.freenetInterface = freenetInterface;
@@ -401,20 +379,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
        }
 
        /**
-        * Returns the time when the given was first followed by any local Sone.
-        *
-        * @param sone
-        *            The Sone to get the time for
-        * @return The time (in milliseconds since Jan 1, 1970) the Sone has first
-        *         been followed, or {@link Long#MAX_VALUE}
-        */
-       public long getSoneFollowingTime(Sone sone) {
-               synchronized (soneFollowingTimes) {
-                       return Optional.fromNullable(soneFollowingTimes.get(sone.getId())).or(Long.MAX_VALUE);
-               }
-       }
-
-       /**
         * Returns a post builder.
         *
         * @return A new post builder
@@ -480,7 +444,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         * @return The Sones that like the given post
         */
        public Set<Sone> getLikes(Post post) {
-               Set<Sone> sones = new HashSet<Sone>();
+               Set<Sone> sones = new HashSet<>();
                for (Sone sone : getSones()) {
                        if (sone.getLikedPostIds().contains(post.getId())) {
                                sones.add(sone);
@@ -497,7 +461,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         * @return The Sones that like the given reply
         */
        public Set<Sone> getLikes(PostReply reply) {
-               Set<Sone> sones = new HashSet<Sone>();
+               Set<Sone> sones = new HashSet<>();
                for (Sone sone : getSones()) {
                        if (sone.getLikedReplyIds().contains(reply.getId())) {
                                sones.add(sone);
@@ -723,7 +687,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                }
                database.storeSone(sone);
                soneDownloader.addSone(sone);
-               soneDownloaders.execute(soneDownloader.fetchSoneWithUriAction(sone));
+               soneDownloaders.execute(soneDownloader.fetchSoneAsUskAction(sone));
                return sone;
        }
 
@@ -739,24 +703,20 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                checkNotNull(sone, "sone must not be null");
                checkNotNull(soneId, "soneId must not be null");
                database.addFriend(sone, soneId);
-               synchronized (soneFollowingTimes) {
-                       if (!soneFollowingTimes.containsKey(soneId)) {
-                               long now = System.currentTimeMillis();
-                               soneFollowingTimes.put(soneId, now);
-                               Sone followedSone = getSone(soneId);
-                               if (followedSone == null) {
-                                       return;
-                               }
-                               for (Post post : followedSone.getPosts()) {
-                                       if (post.getTime() < now) {
-                                               markPostKnown(post);
-                                       }
-                               }
-                               for (PostReply reply : followedSone.getReplies()) {
-                                       if (reply.getTime() < now) {
-                                               markReplyKnown(reply);
-                                       }
-                               }
+               @SuppressWarnings("ConstantConditions") // we just followed, this can’t be null.
+               long now = database.getFollowingTime(soneId);
+               Sone followedSone = getSone(soneId);
+               if (followedSone == null) {
+                       return;
+               }
+               for (Post post : followedSone.getPosts()) {
+                       if (post.getTime() < now) {
+                               markPostKnown(post);
+                       }
+               }
+               for (PostReply reply : followedSone.getReplies()) {
+                       if (reply.getTime() < now) {
+                               markReplyKnown(reply);
                        }
                }
                touchConfiguration();
@@ -774,15 +734,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                checkNotNull(sone, "sone must not be null");
                checkNotNull(soneId, "soneId must not be null");
                database.removeFriend(sone, soneId);
-               boolean unfollowedSoneStillFollowed = false;
-               for (Sone localSone : getLocalSones()) {
-                       unfollowedSoneStillFollowed |= localSone.hasFriend(soneId);
-               }
-               if (!unfollowedSoneStillFollowed) {
-                       synchronized (soneFollowingTimes) {
-                               soneFollowingTimes.remove(soneId);
-                       }
-               }
                touchConfiguration();
        }
 
@@ -898,44 +849,33 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                }
        }
 
-       private List<Object> collectEventsForChangesInSone(Sone oldSone,
-                       final Sone newSone) {
-               final List<Object> events = new ArrayList<Object>();
-               SoneChangeDetector soneChangeDetector = new SoneChangeDetector(
-                               oldSone);
-               soneChangeDetector.onNewPosts(new PostProcessor() {
-                       @Override
-                       public void processPost(Post post) {
-                               if (post.getTime() < getSoneFollowingTime(newSone)) {
-                                       post.setKnown(true);
-                               } else if (!post.isKnown()) {
-                                       events.add(new NewPostFoundEvent(post));
-                               }
-                       }
-               });
-               soneChangeDetector.onRemovedPosts(new PostProcessor() {
-                       @Override
-                       public void processPost(Post post) {
-                               events.add(new PostRemovedEvent(post));
+       private List<Object> collectEventsForChangesInSone(Sone oldSone, Sone newSone) {
+               List<Object> events = new ArrayList<>();
+               SoneComparison soneComparison = new SoneComparison(oldSone, newSone);
+               for (Post newPost : soneComparison.getNewPosts()) {
+                       if (newPost.getSone().equals(newSone)) {
+                               newPost.setKnown(true);
+                       } else if (newPost.getTime() < database.getFollowingTime(newSone.getId())) {
+                               newPost.setKnown(true);
+                       } else if (!newPost.isKnown()) {
+                               events.add(new NewPostFoundEvent(newPost));
                        }
-               });
-               soneChangeDetector.onNewPostReplies(new PostReplyProcessor() {
-                       @Override
-                       public void processPostReply(PostReply postReply) {
-                               if (postReply.getTime() < getSoneFollowingTime(newSone)) {
-                                       postReply.setKnown(true);
-                               } else if (!postReply.isKnown()) {
-                                       events.add(new NewPostReplyFoundEvent(postReply));
-                               }
-                       }
-               });
-               soneChangeDetector.onRemovedPostReplies(new PostReplyProcessor() {
-                       @Override
-                       public void processPostReply(PostReply postReply) {
-                               events.add(new PostReplyRemovedEvent(postReply));
+               }
+               for (Post post : soneComparison.getRemovedPosts()) {
+                       events.add(new PostRemovedEvent(post));
+               }
+               for (PostReply postReply : soneComparison.getNewPostReplies()) {
+                       if (postReply.getSone().equals(newSone)) {
+                               postReply.setKnown(true);
+                       } else if (postReply.getTime() < database.getFollowingTime(newSone.getId())) {
+                               postReply.setKnown(true);
+                       } else if (!postReply.isKnown()) {
+                               events.add(new NewPostReplyFoundEvent(postReply));
                        }
-               });
-               soneChangeDetector.detectChanges(newSone);
+               }
+               for (PostReply postReply : soneComparison.getRemovedPostReplies()) {
+                       events.add(new PostReplyRemovedEvent(postReply));
+               }
                return events;
        }
 
@@ -1388,7 +1328,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                identityManager.start();
                webOfTrustUpdater.init();
                webOfTrustUpdater.start();
-               database.start();
+               database.startAsync();
        }
 
        /**
@@ -1428,7 +1368,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        }
                }
                saveConfiguration();
-               database.stop();
+               database.stopAsync();
                webOfTrustUpdater.stop();
                updateChecker.stop();
                soneDownloader.stop();
@@ -1596,17 +1536,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                                configuration.getStringValue("KnownSone/" + soneCounter + "/ID").setValue(null);
                        }
 
-                       /* save Sone following times. */
-                       soneCounter = 0;
-                       synchronized (soneFollowingTimes) {
-                               for (Entry<String, Long> soneFollowingTime : soneFollowingTimes.entrySet()) {
-                                       configuration.getStringValue("SoneFollowingTimes/" + soneCounter + "/Sone").setValue(soneFollowingTime.getKey());
-                                       configuration.getLongValue("SoneFollowingTimes/" + soneCounter + "/Time").setValue(soneFollowingTime.getValue());
-                                       ++soneCounter;
-                               }
-                               configuration.getStringValue("SoneFollowingTimes/" + soneCounter + "/Sone").setValue(null);
-                       }
-
                        /* save known posts. */
                        database.save();
 
@@ -1641,20 +1570,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                                knownSones.add(knownSoneId);
                        }
                }
-
-               /* load Sone following times. */
-               soneCounter = 0;
-               while (true) {
-                       String soneId = configuration.getStringValue("SoneFollowingTimes/" + soneCounter + "/Sone").getValue(null);
-                       if (soneId == null) {
-                               break;
-                       }
-                       long time = configuration.getLongValue("SoneFollowingTimes/" + soneCounter + "/Time").getValue(Long.MAX_VALUE);
-                       synchronized (soneFollowingTimes) {
-                               soneFollowingTimes.put(soneId, time);
-                       }
-                       ++soneCounter;
-               }
        }
 
        /**
@@ -1720,7 +1635,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        }
                }
                soneDownloader.addSone(sone);
-               soneDownloaders.execute(soneDownloader.fetchSoneAction(sone));
+               soneDownloaders.execute(soneDownloader.fetchSoneAsSskAction(sone));
        }
 
        /**
index 946371b..1ed2024 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - FreenetInterface.java - Copyright © 2010–2016 David Roden
+ * Sone - FreenetInterface.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -32,6 +32,7 @@ import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import javax.annotation.Nonnull;
+import javax.inject.Inject;
 
 import net.pterodactylus.sone.core.event.ImageInsertAbortedEvent;
 import net.pterodactylus.sone.core.event.ImageInsertFailedEvent;
@@ -43,7 +44,6 @@ import net.pterodactylus.sone.data.TemporaryImage;
 
 import com.google.common.base.Function;
 import com.google.common.eventbus.EventBus;
-import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
 import freenet.client.ClientMetadata;
@@ -78,8 +78,6 @@ import freenet.support.io.ResumeFailedException;
 
 /**
  * Contains all necessary functionality for interacting with the Freenet node.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 @Singleton
 public class FreenetInterface {
@@ -95,9 +93,10 @@ public class FreenetInterface {
 
        /** The high-level client to use for requests. */
        private final HighLevelSimpleClient client;
+       private final RequestClient requestClient = new RequestClientBuilder().realTime().build();
 
        /** The USK callbacks. */
-       private final Map<String, USKCallback> soneUskCallbacks = new HashMap<String, USKCallback>();
+       private final Map<String, USKCallback> soneUskCallbacks = new HashMap<>();
 
        /** The not-Sone-related USK callbacks. */
        private final Map<FreenetURI, USKCallback> uriUskCallbacks = Collections.synchronizedMap(new HashMap<FreenetURI, USKCallback>());
@@ -258,7 +257,7 @@ public class FreenetInterface {
                try {
                        soneUskCallbacks.put(routingKey(requestUri), uskCallback);
                        node.clientCore.uskManager.subscribe(create(requestUri),
-                                       uskCallback, true, (RequestClient) client);
+                                       uskCallback, true, requestClient);
                } catch (MalformedURLException mue1) {
                        logger.log(WARNING, format("Could not subscribe USK “%s”!",
                                        requestUri), mue1);
@@ -271,8 +270,7 @@ public class FreenetInterface {
                        soneUskCallbacks.put(routingKey(requestUri), uskCallback);
                        node.clientCore
                                        .uskManager
-                                       .subscribe(create(requestUri), uskCallback, false,
-                                                       (RequestClient) client);
+                                       .subscribe(create(requestUri), uskCallback, false, requestClient);
                } catch (MalformedURLException mue1) {
                        logger.log(WARNING,
                                        format("Could not subscribe USK “%s”!", requestUri),
@@ -328,7 +326,7 @@ public class FreenetInterface {
 
                };
                try {
-                       node.clientCore.uskManager.subscribe(USK.create(uri), uskCallback, true, (RequestClient) client);
+                       node.clientCore.uskManager.subscribe(USK.create(uri), uskCallback, true, requestClient);
                        uriUskCallbacks.put(uri, uskCallback);
                } catch (MalformedURLException mue1) {
                        logger.log(Level.WARNING, String.format("Could not subscribe to USK: %s", uri), mue1);
@@ -355,59 +353,7 @@ public class FreenetInterface {
        }
 
        /**
-        * Container for a fetched URI and the {@link FetchResult}.
-        *
-        * @author <a href="mailto:d.roden@xplosion.de">David Roden</a>
-        */
-       public static class Fetched {
-
-               /** The fetched URI. */
-               private final FreenetURI freenetUri;
-
-               /** The fetch result. */
-               private final FetchResult fetchResult;
-
-               /**
-                * Creates a new fetched URI.
-                *
-                * @param freenetUri
-                *            The URI that was fetched
-                * @param fetchResult
-                *            The fetch result
-                */
-               public Fetched(FreenetURI freenetUri, FetchResult fetchResult) {
-                       this.freenetUri = freenetUri;
-                       this.fetchResult = fetchResult;
-               }
-
-               //
-               // ACCESSORS
-               //
-
-               /**
-                * Returns the fetched URI.
-                *
-                * @return The fetched URI
-                */
-               public FreenetURI getFreenetUri() {
-                       return freenetUri;
-               }
-
-               /**
-                * Returns the fetch result.
-                *
-                * @return The fetch result
-                */
-               public FetchResult getFetchResult() {
-                       return fetchResult;
-               }
-
-       }
-
-       /**
         * Callback for USK watcher events.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
        public static interface Callback {
 
@@ -435,7 +381,6 @@ public class FreenetInterface {
         * @see ImageInsertStartedEvent
         * @see ImageInsertFailedEvent
         * @see ImageInsertFinishedEvent
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
        public class InsertToken implements ClientPutCallback {
 
@@ -558,11 +503,18 @@ public class FreenetInterface {
 
        }
 
-       public class InsertTokenSupplier implements Function<Image, InsertToken> {
+       public static class InsertTokenSupplier implements Function<Image, InsertToken> {
+
+               private final FreenetInterface freenetInterface;
+
+               @Inject
+               public InsertTokenSupplier(FreenetInterface freenetInterface) {
+                       this.freenetInterface = freenetInterface;
+               }
 
                @Override
                public InsertToken apply(Image image) {
-                       return new InsertToken(image);
+                       return freenetInterface.new InsertToken(image);
                }
 
        }
index f47b713..fc76ca9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageInserter.java - Copyright © 2011–2016 David Roden
+ * Sone - ImageInserter.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -27,7 +27,10 @@ import java.util.Map;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import javax.inject.Inject;
+
 import net.pterodactylus.sone.core.FreenetInterface.InsertToken;
+import net.pterodactylus.sone.core.FreenetInterface.InsertTokenSupplier;
 import net.pterodactylus.sone.data.Image;
 import net.pterodactylus.sone.data.TemporaryImage;
 
@@ -38,8 +41,6 @@ import com.google.common.base.Function;
  * {@link FreenetInterface#insertImage(TemporaryImage, Image, InsertToken)} and
  * also tracks running inserts, giving the possibility to abort a running
  * insert.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class ImageInserter {
 
@@ -61,7 +62,8 @@ public class ImageInserter {
         * @param insertTokenSupplier
         *            The supplier for insert tokens
         */
-       public ImageInserter(FreenetInterface freenetInterface, Function<Image, InsertToken> insertTokenSupplier) {
+       @Inject
+       public ImageInserter(FreenetInterface freenetInterface, InsertTokenSupplier insertTokenSupplier) {
                this.freenetInterface = freenetInterface;
                this.insertTokenSupplier = insertTokenSupplier;
        }
index 39b7b37..af88dd8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Options.java - Copyright © 2010–2016 David Roden
+ * Sone - Options.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -27,8 +27,6 @@ import com.google.common.base.Predicate;
 
 /**
  * Stores various options that influence Sone’s behaviour.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class Options {
 
diff --git a/src/main/java/net/pterodactylus/sone/core/PreferenceChangedEvent.kt b/src/main/java/net/pterodactylus/sone/core/PreferenceChangedEvent.kt
new file mode 100644 (file)
index 0000000..2ebb62d
--- /dev/null
@@ -0,0 +1,3 @@
+package net.pterodactylus.sone.core
+
+data class PreferenceChangedEvent(val preferenceName: String, val newValue: Any)
diff --git a/src/main/java/net/pterodactylus/sone/core/Preferences.java b/src/main/java/net/pterodactylus/sone/core/Preferences.java
deleted file mode 100644 (file)
index 456e7f2..0000000
+++ /dev/null
@@ -1,434 +0,0 @@
-/*
- * 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
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.core;
-
-import static com.google.common.base.Predicates.equalTo;
-import static java.lang.Integer.MAX_VALUE;
-import static net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.ALWAYS;
-import static net.pterodactylus.sone.utils.IntegerRangePredicate.range;
-
-import net.pterodactylus.sone.core.event.InsertionDelayChangedEvent;
-import net.pterodactylus.sone.fcp.FcpInterface;
-import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired;
-import net.pterodactylus.sone.fcp.event.FcpInterfaceActivatedEvent;
-import net.pterodactylus.sone.fcp.event.FcpInterfaceDeactivatedEvent;
-import net.pterodactylus.sone.fcp.event.FullAccessRequiredChanged;
-import net.pterodactylus.sone.utils.DefaultOption;
-import net.pterodactylus.sone.utils.Option;
-import net.pterodactylus.util.config.Configuration;
-import net.pterodactylus.util.config.ConfigurationException;
-
-import com.google.common.base.Predicates;
-import com.google.common.eventbus.EventBus;
-
-/**
- * Convenience interface for external classes that want to access the core’s
- * configuration.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class Preferences {
-
-       private final EventBus eventBus;
-       private final Option<Integer> insertionDelay =
-                       new DefaultOption<Integer>(60, range(0, MAX_VALUE));
-       private final Option<Integer> postsPerPage =
-                       new DefaultOption<Integer>(10, range(1, MAX_VALUE));
-       private final Option<Integer> imagesPerPage =
-                       new DefaultOption<Integer>(9, range(1, MAX_VALUE));
-       private final Option<Integer> charactersPerPost =
-                       new DefaultOption<Integer>(400, Predicates.<Integer>or(
-                                       range(50, MAX_VALUE), equalTo(-1)));
-       private final Option<Integer> postCutOffLength =
-                       new DefaultOption<Integer>(200, range(50, MAX_VALUE));
-       private final Option<Boolean> requireFullAccess =
-                       new DefaultOption<Boolean>(false);
-       private final Option<Integer> positiveTrust =
-                       new DefaultOption<Integer>(75, range(0, 100));
-       private final Option<Integer> negativeTrust =
-                       new DefaultOption<Integer>(-25, range(-100, 100));
-       private final Option<String> trustComment =
-                       new DefaultOption<String>("Set from Sone Web Interface");
-       private final Option<Boolean> activateFcpInterface =
-                       new DefaultOption<Boolean>(false);
-       private final Option<FullAccessRequired> fcpFullAccessRequired =
-                       new DefaultOption<FullAccessRequired>(ALWAYS);
-
-       public Preferences(EventBus eventBus) {
-               this.eventBus = eventBus;
-       }
-
-       /**
-        * Returns the insertion delay.
-        *
-        * @return The insertion delay
-        */
-       public int getInsertionDelay() {
-               return insertionDelay.get();
-       }
-
-       /**
-        * Validates the given insertion delay.
-        *
-        * @param insertionDelay
-        *            The insertion delay to validate
-        * @return {@code true} if the given insertion delay was valid,
-        *         {@code false} otherwise
-        */
-       public boolean validateInsertionDelay(Integer insertionDelay) {
-               return this.insertionDelay.validate(insertionDelay);
-       }
-
-       /**
-        * Sets the insertion delay
-        *
-        * @param insertionDelay
-        *            The new insertion delay, or {@code null} to restore it to
-        *            the default value
-        * @return This preferences
-        */
-       public Preferences setInsertionDelay(Integer insertionDelay) {
-               this.insertionDelay.set(insertionDelay);
-               eventBus.post(new InsertionDelayChangedEvent(getInsertionDelay()));
-               return this;
-       }
-
-       /**
-        * Returns the number of posts to show per page.
-        *
-        * @return The number of posts to show per page
-        */
-       public int getPostsPerPage() {
-               return postsPerPage.get();
-       }
-
-       /**
-        * Validates the number of posts per page.
-        *
-        * @param postsPerPage
-        *            The number of posts per page
-        * @return {@code true} if the number of posts per page was valid,
-        *         {@code false} otherwise
-        */
-       public boolean validatePostsPerPage(Integer postsPerPage) {
-               return this.postsPerPage.validate(postsPerPage);
-       }
-
-       /**
-        * Sets the number of posts to show per page.
-        *
-        * @param postsPerPage
-        *            The number of posts to show per page
-        * @return This preferences object
-        */
-       public Preferences setPostsPerPage(Integer postsPerPage) {
-               this.postsPerPage.set(postsPerPage);
-               return this;
-       }
-
-       /**
-        * Returns the number of images to show per page.
-        *
-        * @return The number of images to show per page
-        */
-       public int getImagesPerPage() {
-               return imagesPerPage.get();
-       }
-
-       /**
-        * Validates the number of images per page.
-        *
-        * @param imagesPerPage
-        *            The number of images per page
-        * @return {@code true} if the number of images per page was valid,
-        *         {@code false} otherwise
-        */
-       public boolean validateImagesPerPage(Integer imagesPerPage) {
-               return this.imagesPerPage.validate(imagesPerPage);
-       }
-
-       /**
-        * Sets the number of images per page.
-        *
-        * @param imagesPerPage
-        *            The number of images per page
-        * @return This preferences object
-        */
-       public Preferences setImagesPerPage(Integer imagesPerPage) {
-               this.imagesPerPage.set(imagesPerPage);
-               return this;
-       }
-
-       /**
-        * Returns the number of characters per post, or <code>-1</code> if the
-        * posts should not be cut off.
-        *
-        * @return The numbers of characters per post
-        */
-       public int getCharactersPerPost() {
-               return charactersPerPost.get();
-       }
-
-       /**
-        * Validates the number of characters per post.
-        *
-        * @param charactersPerPost
-        *            The number of characters per post
-        * @return {@code true} if the number of characters per post was valid,
-        *         {@code false} otherwise
-        */
-       public boolean validateCharactersPerPost(Integer charactersPerPost) {
-               return this.charactersPerPost.validate(charactersPerPost);
-       }
-
-       /**
-        * Sets the number of characters per post.
-        *
-        * @param charactersPerPost
-        *            The number of characters per post, or <code>-1</code> to
-        *            not cut off the posts
-        * @return This preferences objects
-        */
-       public Preferences setCharactersPerPost(Integer charactersPerPost) {
-               this.charactersPerPost.set(charactersPerPost);
-               return this;
-       }
-
-       /**
-        * Returns the number of characters the shortened post should have.
-        *
-        * @return The number of characters of the snippet
-        */
-       public int getPostCutOffLength() {
-               return postCutOffLength.get();
-       }
-
-       /**
-        * Validates the number of characters after which to cut off the post.
-        *
-        * @param postCutOffLength
-        *            The number of characters of the snippet
-        * @return {@code true} if the number of characters of the snippet is
-        *         valid, {@code false} otherwise
-        */
-       public boolean validatePostCutOffLength(Integer postCutOffLength) {
-               return this.postCutOffLength.validate(postCutOffLength);
-       }
-
-       /**
-        * Sets the number of characters the shortened post should have.
-        *
-        * @param postCutOffLength
-        *            The number of characters of the snippet
-        * @return This preferences
-        */
-       public Preferences setPostCutOffLength(Integer postCutOffLength) {
-               this.postCutOffLength.set(postCutOffLength);
-               return this;
-       }
-
-       /**
-        * Returns whether Sone requires full access to be even visible.
-        *
-        * @return {@code true} if Sone requires full access, {@code false}
-        *         otherwise
-        */
-       public boolean isRequireFullAccess() {
-               return requireFullAccess.get();
-       }
-
-       /**
-        * Sets whether Sone requires full access to be even visible.
-        *
-        * @param requireFullAccess
-        *            {@code true} if Sone requires full access, {@code false}
-        *            otherwise
-        */
-       public void setRequireFullAccess(Boolean requireFullAccess) {
-               this.requireFullAccess.set(requireFullAccess);
-       }
-
-       /**
-        * Returns the positive trust.
-        *
-        * @return The positive trust
-        */
-       public int getPositiveTrust() {
-               return positiveTrust.get();
-       }
-
-       /**
-        * Validates the positive trust.
-        *
-        * @param positiveTrust
-        *            The positive trust to validate
-        * @return {@code true} if the positive trust was valid, {@code false}
-        *         otherwise
-        */
-       public boolean validatePositiveTrust(Integer positiveTrust) {
-               return this.positiveTrust.validate(positiveTrust);
-       }
-
-       /**
-        * Sets the positive trust.
-        *
-        * @param positiveTrust
-        *            The new positive trust, or {@code null} to restore it to
-        *            the default vlaue
-        * @return This preferences
-        */
-       public Preferences setPositiveTrust(Integer positiveTrust) {
-               this.positiveTrust.set(positiveTrust);
-               return this;
-       }
-
-       /**
-        * Returns the negative trust.
-        *
-        * @return The negative trust
-        */
-       public int getNegativeTrust() {
-               return negativeTrust.get();
-       }
-
-       /**
-        * Validates the negative trust.
-        *
-        * @param negativeTrust
-        *            The negative trust to validate
-        * @return {@code true} if the negative trust was valid, {@code false}
-        *         otherwise
-        */
-       public boolean validateNegativeTrust(Integer negativeTrust) {
-               return this.negativeTrust.validate(negativeTrust);
-       }
-
-       /**
-        * Sets the negative trust.
-        *
-        * @param negativeTrust
-        *            The negative trust, or {@code null} to restore it to the
-        *            default value
-        * @return The preferences
-        */
-       public Preferences setNegativeTrust(Integer negativeTrust) {
-               this.negativeTrust.set(negativeTrust);
-               return this;
-       }
-
-       /**
-        * Returns the trust comment. This is the comment that is set in the web
-        * of trust when a trust value is assigned to an identity.
-        *
-        * @return The trust comment
-        */
-       public String getTrustComment() {
-               return trustComment.get();
-       }
-
-       /**
-        * Sets the trust comment.
-        *
-        * @param trustComment
-        *            The trust comment, or {@code null} to restore it to the
-        *            default value
-        * @return This preferences
-        */
-       public Preferences setTrustComment(String trustComment) {
-               this.trustComment.set(trustComment);
-               return this;
-       }
-
-       /**
-        * Returns whether the {@link FcpInterface FCP interface} is currently
-        * active.
-        *
-        * @see FcpInterface#setActive(boolean)
-        * @return {@code true} if the FCP interface is currently active,
-        *         {@code false} otherwise
-        */
-       public boolean isFcpInterfaceActive() {
-               return activateFcpInterface.get();
-       }
-
-       /**
-        * Sets whether the {@link FcpInterface FCP interface} is currently
-        * active.
-        *
-        * @see FcpInterface#setActive(boolean)
-        * @param fcpInterfaceActive
-        *            {@code true} to activate the FCP interface, {@code false}
-        *            to deactivate the FCP interface
-        * @return This preferences object
-        */
-       public Preferences setFcpInterfaceActive(Boolean fcpInterfaceActive) {
-               this.activateFcpInterface.set(fcpInterfaceActive);
-               if (isFcpInterfaceActive()) {
-                       eventBus.post(new FcpInterfaceActivatedEvent());
-               } else {
-                       eventBus.post(new FcpInterfaceDeactivatedEvent());
-               }
-               return this;
-       }
-
-       /**
-        * Returns the action level for which full access to the FCP interface
-        * is required.
-        *
-        * @return The action level for which full access to the FCP interface
-        *         is required
-        */
-       public FullAccessRequired getFcpFullAccessRequired() {
-               return fcpFullAccessRequired.get();
-       }
-
-       /**
-        * Sets the action level for which full access to the FCP interface is
-        * required
-        *
-        * @param fcpFullAccessRequired
-        *            The action level
-        * @return This preferences
-        */
-       public Preferences setFcpFullAccessRequired(
-                       FullAccessRequired fcpFullAccessRequired) {
-               this.fcpFullAccessRequired.set(fcpFullAccessRequired);
-               eventBus.post(new FullAccessRequiredChanged(getFcpFullAccessRequired()));
-               return this;
-       }
-
-       public void saveTo(Configuration configuration) throws ConfigurationException {
-               configuration.getIntValue("Option/ConfigurationVersion").setValue(0);
-               configuration.getIntValue("Option/InsertionDelay").setValue(insertionDelay.getReal());
-               configuration.getIntValue("Option/PostsPerPage").setValue(postsPerPage.getReal());
-               configuration.getIntValue("Option/ImagesPerPage").setValue(imagesPerPage.getReal());
-               configuration.getIntValue("Option/CharactersPerPost").setValue(charactersPerPost.getReal());
-               configuration.getIntValue("Option/PostCutOffLength").setValue(postCutOffLength.getReal());
-               configuration.getBooleanValue("Option/RequireFullAccess").setValue(requireFullAccess.getReal());
-               configuration.getIntValue("Option/PositiveTrust").setValue(positiveTrust.getReal());
-               configuration.getIntValue("Option/NegativeTrust").setValue(negativeTrust.getReal());
-               configuration.getStringValue("Option/TrustComment").setValue(trustComment.getReal());
-               configuration.getBooleanValue("Option/ActivateFcpInterface").setValue(activateFcpInterface.getReal());
-               configuration.getIntValue("Option/FcpFullAccessRequired").setValue(toInt(fcpFullAccessRequired.getReal()));
-       }
-
-       private Integer toInt(FullAccessRequired fullAccessRequired) {
-               return (fullAccessRequired == null) ? null : fullAccessRequired.ordinal();
-       }
-
-}
index 0ae13ba..a730983 100644 (file)
@@ -7,8 +7,6 @@ import net.pterodactylus.util.config.ConfigurationException;
 /**
  * Loads preferences stored in a {@link Configuration} into a {@link
  * Preferences} object.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class PreferencesLoader {
 
@@ -33,31 +31,31 @@ public class PreferencesLoader {
        }
 
        private void loadInsertionDelay(Configuration configuration) {
-               preferences.setInsertionDelay(configuration.getIntValue(
+               preferences.setNewInsertionDelay(configuration.getIntValue(
                                "Option/InsertionDelay").getValue(null));
        }
 
        private void loadPostsPerPage(Configuration configuration) {
-               preferences.setPostsPerPage(
+               preferences.setNewPostsPerPage(
                                configuration.getIntValue("Option/PostsPerPage")
                                                .getValue(null));
        }
 
        private void loadImagesPerPage(Configuration configuration) {
-               preferences.setImagesPerPage(
+               preferences.setNewImagesPerPage(
                                configuration.getIntValue("Option/ImagesPerPage")
                                                .getValue(null));
        }
 
        private void loadCharactersPerPost(Configuration configuration) {
-               preferences.setCharactersPerPost(
+               preferences.setNewCharactersPerPost(
                                configuration.getIntValue("Option/CharactersPerPost")
                                                .getValue(null));
        }
 
        private void loadPostCutOffLength(Configuration configuration) {
                try {
-                       preferences.setPostCutOffLength(
+                       preferences.setNewPostCutOffLength(
                                        configuration.getIntValue("Option/PostCutOffLength")
                                                        .getValue(null));
                } catch (IllegalArgumentException iae1) {
@@ -66,38 +64,38 @@ public class PreferencesLoader {
        }
 
        private void loadRequireFullAccess(Configuration configuration) {
-               preferences.setRequireFullAccess(
+               preferences.setNewRequireFullAccess(
                                configuration.getBooleanValue("Option/RequireFullAccess")
                                                .getValue(null));
        }
 
        private void loadPositiveTrust(Configuration configuration) {
-               preferences.setPositiveTrust(
+               preferences.setNewPositiveTrust(
                                configuration.getIntValue("Option/PositiveTrust")
                                                .getValue(null));
        }
 
        private void loadNegativeTrust(Configuration configuration) {
-               preferences.setNegativeTrust(
+               preferences.setNewNegativeTrust(
                                configuration.getIntValue("Option/NegativeTrust")
                                                .getValue(null));
        }
 
        private void loadTrustComment(Configuration configuration) {
-               preferences.setTrustComment(
+               preferences.setNewTrustComment(
                                configuration.getStringValue("Option/TrustComment")
                                                .getValue(null));
        }
 
        private void loadFcpInterfaceActive(Configuration configuration) {
-               preferences.setFcpInterfaceActive(configuration.getBooleanValue(
+               preferences.setNewFcpInterfaceActive(configuration.getBooleanValue(
                                "Option/ActivateFcpInterface").getValue(null));
        }
 
        private void loadFcpFullAccessRequired(Configuration configuration) {
                Integer fullAccessRequiredInteger = configuration
                                .getIntValue("Option/FcpFullAccessRequired").getValue(null);
-               preferences.setFcpFullAccessRequired(
+               preferences.setNewFcpFullAccessRequired(
                                (fullAccessRequiredInteger == null) ? null :
                                                FullAccessRequired.values()[fullAccessRequiredInteger]);
        }
diff --git a/src/main/java/net/pterodactylus/sone/core/SoneChangeDetector.java b/src/main/java/net/pterodactylus/sone/core/SoneChangeDetector.java
deleted file mode 100644 (file)
index efcdc6e..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-package net.pterodactylus.sone.core;
-
-import static com.google.common.base.Optional.absent;
-import static com.google.common.base.Optional.fromNullable;
-import static com.google.common.collect.FluentIterable.from;
-
-import java.util.Collection;
-
-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;
-import com.google.common.collect.FluentIterable;
-
-/**
- * Compares the contents of two {@link Sone}s and fires events for new and
- * removed elements.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class SoneChangeDetector {
-
-       private final Sone oldSone;
-       private Optional<PostProcessor> newPostProcessor = absent();
-       private Optional<PostProcessor> removedPostProcessor = absent();
-       private Optional<PostReplyProcessor> newPostReplyProcessor = absent();
-       private Optional<PostReplyProcessor> removedPostReplyProcessor = absent();
-
-       public SoneChangeDetector(Sone oldSone) {
-               this.oldSone = oldSone;
-       }
-
-       public void onNewPosts(PostProcessor newPostProcessor) {
-               this.newPostProcessor = fromNullable(newPostProcessor);
-       }
-
-       public void onRemovedPosts(PostProcessor removedPostProcessor) {
-               this.removedPostProcessor = fromNullable(removedPostProcessor);
-       }
-
-       public void onNewPostReplies(PostReplyProcessor newPostReplyProcessor) {
-               this.newPostReplyProcessor = fromNullable(newPostReplyProcessor);
-       }
-
-       public void onRemovedPostReplies(
-                       PostReplyProcessor removedPostReplyProcessor) {
-               this.removedPostReplyProcessor = fromNullable(removedPostReplyProcessor);
-       }
-
-       public void detectChanges(Sone newSone) {
-               processPosts(from(newSone.getPosts()).filter(
-                               notContainedIn(oldSone.getPosts())), newPostProcessor);
-               processPosts(from(oldSone.getPosts()).filter(
-                               notContainedIn(newSone.getPosts())), removedPostProcessor);
-               processPostReplies(from(newSone.getReplies()).filter(
-                               notContainedIn(oldSone.getReplies())), newPostReplyProcessor);
-               processPostReplies(from(oldSone.getReplies()).filter(
-                               notContainedIn(newSone.getReplies())), removedPostReplyProcessor);
-       }
-
-       private void processPostReplies(FluentIterable<PostReply> postReplies,
-                       Optional<PostReplyProcessor> postReplyProcessor) {
-               for (PostReply postReply : postReplies) {
-                       notifyPostReplyProcessor(postReplyProcessor, postReply);
-               }
-       }
-
-       private void notifyPostReplyProcessor(
-                       Optional<PostReplyProcessor> postReplyProcessor,
-                       PostReply postReply) {
-               if (postReplyProcessor.isPresent()) {
-                       postReplyProcessor.get()
-                                       .processPostReply(postReply);
-               }
-       }
-
-       private void processPosts(FluentIterable<Post> posts,
-                       Optional<PostProcessor> newPostProcessor) {
-               for (Post post : posts) {
-                       notifyPostProcessor(newPostProcessor, post);
-               }
-       }
-
-       private void notifyPostProcessor(Optional<PostProcessor> postProcessor,
-                       Post newPost) {
-               if (postProcessor.isPresent()) {
-                       postProcessor.get().processPost(newPost);
-               }
-       }
-
-       private <T> Predicate<T> notContainedIn(final Collection<T> posts) {
-               return new Predicate<T>() {
-                       @Override
-                       public boolean apply(T element) {
-                               return !posts.contains(element);
-                       }
-               };
-       }
-
-       public interface PostProcessor {
-
-               void processPost(Post post);
-
-       }
-
-       public interface PostReplyProcessor {
-
-               void processPostReply(PostReply postReply);
-
-       }
-
-}
index be0be02..0ff66fd 100644 (file)
@@ -5,19 +5,19 @@ import net.pterodactylus.util.service.Service;
 
 import freenet.keys.FreenetURI;
 
+import com.google.inject.ImplementedBy;
+
 /**
  * Downloads and parses Sone and {@link Core#updateSone(Sone) updates the
  * core}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
+@ImplementedBy(SoneDownloaderImpl.class)
 public interface SoneDownloader extends Service {
 
        void addSone(Sone sone);
-       void fetchSone(Sone sone, FreenetURI soneUri);
        Sone fetchSone(Sone sone, FreenetURI soneUri, boolean fetchOnly);
 
-       Runnable fetchSoneWithUriAction(Sone sone);
-       Runnable fetchSoneAction(Sone sone);
+       Runnable fetchSoneAsUskAction(Sone sone);
+       Runnable fetchSoneAsSskAction(Sone sone);
 
 }
index db08046..ba1c632 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneDownloaderImpl.java - Copyright © 2010–2016 David Roden
+ * Sone - SoneDownloaderImpl.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -29,7 +29,8 @@ import java.util.Set;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import net.pterodactylus.sone.core.FreenetInterface.Fetched;
+import javax.inject.Inject;
+
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.data.Sone.SoneStatus;
 import net.pterodactylus.util.service.AbstractService;
@@ -41,15 +42,9 @@ import freenet.keys.FreenetURI;
 import freenet.keys.USK;
 import freenet.node.RequestStarter;
 import freenet.support.api.Bucket;
-import freenet.support.io.Closer;
-import com.db4o.ObjectContainer;
-
-import com.google.common.annotations.VisibleForTesting;
 
 /**
  * The Sone downloader is responsible for download Sones as they are updated.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class SoneDownloaderImpl extends AbstractService implements SoneDownloader {
 
@@ -60,40 +55,19 @@ public class SoneDownloaderImpl extends AbstractService implements SoneDownloade
        private static final int MAX_PROTOCOL_VERSION = 0;
 
        /** The core. */
-       private final Core core;
+       private final UpdatedSoneProcessor updatedSoneProcessor;
        private final SoneParser soneParser;
 
        /** The Freenet interface. */
        private final FreenetInterface freenetInterface;
 
        /** The sones to update. */
-       private final Set<Sone> sones = new HashSet<Sone>();
-
-       /**
-        * Creates a new Sone downloader.
-        *
-        * @param core
-        *              The core
-        * @param freenetInterface
-        *              The Freenet interface
-        */
-       public SoneDownloaderImpl(Core core, FreenetInterface freenetInterface) {
-               this(core, freenetInterface, new SoneParser(core));
-       }
+       private final Set<Sone> sones = new HashSet<>();
 
-       /**
-        * Creates a new Sone downloader.
-        *
-        * @param core
-        *              The core
-        * @param freenetInterface
-        *              The Freenet interface
-        * @param soneParser
-        */
-       @VisibleForTesting
-       SoneDownloaderImpl(Core core, FreenetInterface freenetInterface, SoneParser soneParser) {
+       @Inject
+       SoneDownloaderImpl(UpdatedSoneProcessor updatedSoneProcessor, FreenetInterface freenetInterface, SoneParser soneParser) {
                super("Sone Downloader", false);
-               this.core = core;
+               this.updatedSoneProcessor = updatedSoneProcessor;
                this.freenetInterface = freenetInterface;
                this.soneParser = soneParser;
        }
@@ -126,7 +100,7 @@ public class SoneDownloaderImpl extends AbstractService implements SoneDownloade
                                                sone, key, newKnownGood, newSlotToo));
                                if (edition > sone.getLatestEdition()) {
                                        sone.setLatestEdition(edition);
-                                       new Thread(fetchSoneAction(sone),
+                                       new Thread(fetchSoneAsSskAction(sone),
                                                        "Sone Downloader").start();
                                }
                        }
@@ -154,22 +128,8 @@ public class SoneDownloaderImpl extends AbstractService implements SoneDownloade
                return (currentTimeMillis() - sone.getTime()) < DAYS.toMillis(7);
        }
 
-       private void fetchSone(Sone sone) {
-               fetchSone(sone, sone.getRequestUri().sskForUSK());
-       }
-
-       /**
-        * Fetches the updated Sone. This method can be used to fetch a Sone from a
-        * specific URI.
-        *
-        * @param sone
-        *              The Sone to fetch
-        * @param soneUri
-        *              The URI to fetch the Sone from
-        */
-       @Override
-       public void fetchSone(Sone sone, FreenetURI soneUri) {
-               fetchSone(sone, soneUri, false);
+       private void fetchSoneAsSsk(Sone sone) {
+               fetchSone(sone, sone.getRequestUri().sskForUSK(), false);
        }
 
        /**
@@ -201,7 +161,7 @@ public class SoneDownloaderImpl extends AbstractService implements SoneDownloade
                        if (parsedSone != null) {
                                if (!fetchOnly) {
                                        parsedSone.setStatus((parsedSone.getTime() == 0) ? SoneStatus.unknown : SoneStatus.idle);
-                                       core.updateSone(parsedSone);
+                                       updatedSoneProcessor.updateSone(parsedSone);
                                        addSone(parsedSone);
                                }
                        }
@@ -231,6 +191,7 @@ public class SoneDownloaderImpl extends AbstractService implements SoneDownloade
                        Sone parsedSone = soneParser.parseSone(originalSone,
                                        soneInputStream);
                        if (parsedSone != null) {
+                               logger.log(Level.FINER, "Sone %s was successfully parsed.", parsedSone);
                                parsedSone.setLatestEdition(requestUri.getEdition());
                        }
                        return parsedSone;
@@ -244,21 +205,21 @@ public class SoneDownloaderImpl extends AbstractService implements SoneDownloade
        }
 
        @Override
-       public Runnable fetchSoneWithUriAction(final Sone sone) {
+       public Runnable fetchSoneAsUskAction(final Sone sone) {
                return new Runnable() {
                        @Override
                        public void run() {
-                               fetchSone(sone, sone.getRequestUri());
+                               fetchSone(sone, sone.getRequestUri(), false);
                        }
                };
        }
 
        @Override
-       public Runnable fetchSoneAction(final Sone sone) {
+       public Runnable fetchSoneAsSskAction(final Sone sone) {
                return new Runnable() {
                        @Override
                        public void run() {
-                               fetchSone(sone);
+                               fetchSoneAsSsk(sone);
                        }
                };
        }
index e651646..e460ee7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneException.java - Copyright © 2010–2016 David Roden
+ * Sone - SoneException.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,8 +19,6 @@ package net.pterodactylus.sone.core;
 
 /**
  * A Sone exception.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class SoneException extends Exception {
 
index 879767e..8a0ff53 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneInsertException.java - Copyright © 2011–2016 David Roden
+ * Sone - SoneInsertException.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,8 +19,6 @@ package net.pterodactylus.sone.core;
 
 /**
  * Exception that signals a problem with an insert.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class SoneInsertException extends SoneException {
 
index 3c32f7f..79a6259 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneInserter.java - Copyright © 2010–2016 David Roden
+ * Sone - SoneInserter.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -72,8 +72,6 @@ import freenet.support.io.ArrayBucket;
 
 /**
  * A Sone inserter is responsible for inserting a Sone if it has changed.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class SoneInserter extends AbstractService {
 
@@ -280,14 +278,12 @@ public class SoneInserter extends AbstractService {
         * Container for information that are required to insert a Sone. This
         * container merely exists to copy all relevant data without holding a lock
         * on the {@link Sone} object for too long.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
        @VisibleForTesting
        class InsertInformation implements Closeable {
 
                /** All properties of the Sone, copied for thread safety. */
-               private final Map<String, Object> soneProperties = new HashMap<String, Object>();
+               private final Map<String, Object> soneProperties = new HashMap<>();
                private final String fingerprint;
                private final ManifestCreator manifestCreator;
 
@@ -299,7 +295,7 @@ public class SoneInserter extends AbstractService {
                 */
                public InsertInformation(Sone sone) {
                        this.fingerprint = sone.getFingerprint();
-                       Map<String, Object> soneProperties = new HashMap<String, Object>();
+                       Map<String, Object> soneProperties = new HashMap<>();
                        soneProperties.put("id", sone.getId());
                        soneProperties.put("name", sone.getName());
                        soneProperties.put("time", currentTimeMillis());
@@ -307,8 +303,8 @@ public class SoneInserter extends AbstractService {
                        soneProperties.put("profile", sone.getProfile());
                        soneProperties.put("posts", Ordering.from(Post.NEWEST_FIRST).sortedCopy(sone.getPosts()));
                        soneProperties.put("replies", Ordering.from(Reply.TIME_COMPARATOR).reverse().sortedCopy(sone.getReplies()));
-                       soneProperties.put("likedPostIds", new HashSet<String>(sone.getLikedPostIds()));
-                       soneProperties.put("likedReplyIds", new HashSet<String>(sone.getLikedReplyIds()));
+                       soneProperties.put("likedPostIds", new HashSet<>(sone.getLikedPostIds()));
+                       soneProperties.put("likedReplyIds", new HashSet<>(sone.getLikedReplyIds()));
                        soneProperties.put("albums", FluentIterable.from(sone.getRootAlbum().getAlbums()).transformAndConcat(Album.FLATTENER).filter(NOT_EMPTY).toList());
                        manifestCreator = new ManifestCreator(core, soneProperties);
                }
@@ -332,7 +328,7 @@ public class SoneInserter extends AbstractService {
                 * @return The manifest entries for the Sone insert
                 */
                public HashMap<String, Object> generateManifestEntries() {
-                       HashMap<String, Object> manifestEntries = new HashMap<String, Object>();
+                       HashMap<String, Object> manifestEntries = new HashMap<>();
 
                        /* first, create an index.html. */
                        manifestEntries.put("index.html", manifestCreator.createManifestElement(
@@ -356,15 +352,13 @@ public class SoneInserter extends AbstractService {
 
        /**
         * Creates manifest elements for an insert by rendering a template.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
        @VisibleForTesting
        static class ManifestCreator implements Closeable {
 
                private final Core core;
                private final Map<String, Object> soneProperties;
-               private final Set<Bucket> buckets = new HashSet<Bucket>();
+               private final Set<Bucket> buckets = new HashSet<>();
 
                ManifestCreator(Core core, Map<String, Object> soneProperties) {
                        this.core = core;
index 810241a..92fa3dc 100644 (file)
@@ -18,8 +18,6 @@ import com.google.common.base.Ticker;
  * Class that detects {@link Sone} modifications (as per their {@link
  * Sone#getFingerprint() fingerprints} and determines when a modified Sone may
  * be inserted.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 class SoneModificationDetector {
 
@@ -83,8 +81,6 @@ class SoneModificationDetector {
        /**
         * Provider for a fingerprint and the information if a {@link Sone} is locked. This
         * prevents us from having to lug a Sone object around.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
        static interface LockableFingerprintProvider {
 
index fdbd9ae..1f30565 100644 (file)
@@ -14,6 +14,8 @@ import java.util.Set;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import javax.inject.Inject;
+
 import net.pterodactylus.sone.data.Album;
 import net.pterodactylus.sone.data.Client;
 import net.pterodactylus.sone.data.Image;
@@ -23,6 +25,7 @@ import net.pterodactylus.sone.data.Profile;
 import net.pterodactylus.sone.data.Profile.DuplicateField;
 import net.pterodactylus.sone.data.Profile.EmptyFieldName;
 import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.database.Database;
 import net.pterodactylus.sone.database.PostBuilder;
 import net.pterodactylus.sone.database.PostReplyBuilder;
 import net.pterodactylus.sone.database.SoneBuilder;
@@ -33,17 +36,16 @@ import org.w3c.dom.Document;
 
 /**
  * Parses a {@link Sone} from an XML {@link InputStream}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class SoneParser {
 
        private static final Logger logger = getLogger(SoneParser.class.getName());
        private static final int MAX_PROTOCOL_VERSION = 0;
-       private final Core core;
+       private final Database database;
 
-       public SoneParser(Core core) {
-               this.core = core;
+       @Inject
+       public SoneParser(Database database) {
+               this.database = database;
        }
 
        public Sone parseSone(Sone originalSone, InputStream soneInputStream) throws SoneException {
@@ -60,7 +62,7 @@ public class SoneParser {
                        return null;
                }
 
-               SoneBuilder soneBuilder = core.soneBuilder().from(originalSone.getIdentity());
+               SoneBuilder soneBuilder = database.newSoneBuilder().from(originalSone.getIdentity());
                if (originalSone.isLocal()) {
                        soneBuilder = soneBuilder.local();
                }
@@ -164,7 +166,7 @@ public class SoneParser {
 
                /* parse posts. */
                SimpleXML postsXml = soneXml.getNode("posts");
-               Set<Post> posts = new HashSet<Post>();
+               Set<Post> posts = new HashSet<>();
                if (postsXml == null) {
                        /* TODO - mark Sone as bad. */
                        logger.log(Level.WARNING, String.format("Downloaded Sone %s has no posts!", sone));
@@ -180,7 +182,7 @@ public class SoneParser {
                                        return null;
                                }
                                try {
-                                       PostBuilder postBuilder = core.postBuilder();
+                                       PostBuilder postBuilder = database.newPostBuilder();
                                        /* TODO - parse time correctly. */
                                        postBuilder.withId(postId).from(sone.getId()).withTime(Long.parseLong(postTime)).withText(postText);
                                        if ((postRecipientId != null) && (postRecipientId.length() == 43)) {
@@ -197,7 +199,7 @@ public class SoneParser {
 
                /* parse replies. */
                SimpleXML repliesXml = soneXml.getNode("replies");
-               Set<PostReply> replies = new HashSet<PostReply>();
+               Set<PostReply> replies = new HashSet<>();
                if (repliesXml == null) {
                        /* TODO - mark Sone as bad. */
                        logger.log(Level.WARNING, String.format("Downloaded Sone %s has no replies!", sone));
@@ -213,7 +215,7 @@ public class SoneParser {
                                        return null;
                                }
                                try {
-                                       PostReplyBuilder postReplyBuilder = core.postReplyBuilder();
+                                       PostReplyBuilder postReplyBuilder = database.newPostReplyBuilder();
                                        /* TODO - parse time correctly. */
                                        postReplyBuilder.withId(replyId).from(sone.getId()).to(replyPostId).withTime(Long.parseLong(replyTime)).withText(replyText);
                                        replies.add(postReplyBuilder.build());
@@ -227,7 +229,7 @@ public class SoneParser {
 
                /* parse liked post IDs. */
                SimpleXML likePostIdsXml = soneXml.getNode("post-likes");
-               Set<String> likedPostIds = new HashSet<String>();
+               Set<String> likedPostIds = new HashSet<>();
                if (likePostIdsXml == null) {
                        /* TODO - mark Sone as bad. */
                        logger.log(Level.WARNING, String.format("Downloaded Sone %s has no post likes!", sone));
@@ -240,7 +242,7 @@ public class SoneParser {
 
                /* parse liked reply IDs. */
                SimpleXML likeReplyIdsXml = soneXml.getNode("reply-likes");
-               Set<String> likedReplyIds = new HashSet<String>();
+               Set<String> likedReplyIds = new HashSet<>();
                if (likeReplyIdsXml == null) {
                        /* TODO - mark Sone as bad. */
                        logger.log(Level.WARNING, String.format("Downloaded Sone %s has no reply likes!", sone));
@@ -253,8 +255,8 @@ public class SoneParser {
 
                /* parse albums. */
                SimpleXML albumsXml = soneXml.getNode("albums");
-               Map<String, Image> allImages = new HashMap<String, Image>();
-               List<Album> topLevelAlbums = new ArrayList<Album>();
+               Map<String, Image> allImages = new HashMap<>();
+               List<Album> topLevelAlbums = new ArrayList<>();
                if (albumsXml != null) {
                        for (SimpleXML albumXml : albumsXml.getNodes("album")) {
                                String id = albumXml.getValue("id", null);
@@ -267,13 +269,13 @@ public class SoneParser {
                                }
                                Album parent = null;
                                if (parentId != null) {
-                                       parent = core.getAlbum(parentId);
+                                       parent = database.getAlbum(parentId);
                                        if (parent == null) {
                                                logger.log(Level.WARNING, String.format("Downloaded Sone %s has album with invalid parent!", sone));
                                                return null;
                                        }
                                }
-                               Album album = core.albumBuilder()
+                               Album album = database.newAlbumBuilder()
                                                .withId(id)
                                                .by(sone)
                                                .build()
@@ -307,7 +309,7 @@ public class SoneParser {
                                                        logger.log(Level.WARNING, String.format("Downloaded Sone %s contains image %s with invalid dimensions (%s, %s)!", sone, imageId, imageWidthString, imageHeightString));
                                                        return null;
                                                }
-                                               Image image = core.imageBuilder().withId(imageId).build().modify().setSone(sone).setKey(imageKey).setCreationTime(creationTime).update();
+                                               Image image = database.newImageBuilder().withId(imageId).build().modify().setSone(sone).setKey(imageKey).setCreationTime(creationTime).update();
                                                image = image.modify().setTitle(imageTitle).setDescription(imageDescription).update();
                                                image = image.modify().setWidth(imageWidth).setHeight(imageHeight).update();
                                                album.addImage(image);
index 0731b1a..2246ac9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneRescuer.java - Copyright © 2011–2016 David Roden
+ * Sone - SoneRescuer.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -24,8 +24,6 @@ import freenet.keys.FreenetURI;
 /**
  * The Sone rescuer downloads older editions of a Sone and updates the currently
  * stored Sone with it.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class SoneRescuer extends AbstractService {
 
index 1fbadbc..373077f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneUri.java - Copyright © 2013–2016 David Roden
+ * Sone - SoneUri.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -28,8 +28,6 @@ import freenet.keys.FreenetURI;
 /**
  * Helper class that creates {@link FreenetURI}s for Sone to insert to and
  * request from.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class SoneUri {
 
index e739a44..9f6963f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UpdateChecker.java - Copyright © 2011–2016 David Roden
+ * Sone - UpdateChecker.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -30,8 +30,8 @@ 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.PluginHomepage;
 import net.pterodactylus.sone.main.SonePlugin;
 import net.pterodactylus.util.io.Closer;
 import net.pterodactylus.util.version.Version;
@@ -44,8 +44,6 @@ import freenet.support.api.Bucket;
 
 /**
  * Watches the official Sone homepage for new releases.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 @Singleton
 public class UpdateChecker {
@@ -72,6 +70,8 @@ public class UpdateChecker {
        /** The release date of the latest version. */
        private long latestVersionDate;
 
+       private final PluginHomepage pluginHomepage;
+
        /**
         * Creates a new update checker.
         *
@@ -81,11 +81,12 @@ public class UpdateChecker {
         *            The freenet interface to use
         */
        @Inject
-       public UpdateChecker(EventBus eventBus, FreenetInterface freenetInterface, Version currentVersion) {
+       public UpdateChecker(EventBus eventBus, FreenetInterface freenetInterface, Version currentVersion, PluginHomepage pluginHomepage) {
                this.eventBus = eventBus;
                this.freenetInterface = freenetInterface;
                this.currentRunningVersion = currentVersion;
                this.currentLatestVersion = currentVersion;
+               this.pluginHomepage = pluginHomepage;
        }
 
        //
@@ -141,7 +142,7 @@ public class UpdateChecker {
         */
        public void start() {
                try {
-                       currentUri = new FreenetURI(SonePlugin.getHomepage());
+                       currentUri = new FreenetURI(pluginHomepage.getHomepage());
                } catch (MalformedURLException mue1) {
                        /* this can not really happen unless I screw up. */
                        logger.log(Level.SEVERE, "Sone Homepage URI invalid!", mue1);
index 908bd96..35c3203 100644 (file)
@@ -8,8 +8,6 @@ import com.google.inject.ImplementedBy;
 
 /**
  * Updates WebOfTrust identity data.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 @ImplementedBy(WebOfTrustUpdaterImpl.class)
 public interface WebOfTrustUpdater extends Service {
index 05d940f..809ca20 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - WebOfTrustUpdaterImpl.java - Copyright © 2013–2016 David Roden
+ * Sone - WebOfTrustUpdaterImpl.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -40,8 +40,6 @@ import com.google.inject.Singleton;
 /**
  * Updates WebOfTrust identity data in a background thread because communicating
  * with the WebOfTrust plugin can potentially last quite long.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 @Singleton
 public class WebOfTrustUpdaterImpl extends AbstractService implements WebOfTrustUpdater {
@@ -57,7 +55,7 @@ public class WebOfTrustUpdaterImpl extends AbstractService implements WebOfTrust
        private final WebOfTrustConnector webOfTrustConnector;
 
        /** The queue for jobs. */
-       private final BlockingQueue<WebOfTrustUpdateJob> updateJobs = new LinkedBlockingQueue<WebOfTrustUpdateJob>();
+       private final BlockingQueue<WebOfTrustUpdateJob> updateJobs = new LinkedBlockingQueue<>();
 
        /**
         * Creates a new trust updater.
@@ -229,8 +227,6 @@ public class WebOfTrustUpdaterImpl extends AbstractService implements WebOfTrust
 
        /**
         * Base class for WebOfTrust update jobs.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
        @VisibleForTesting
        class WebOfTrustUpdateJob implements Runnable {
@@ -302,8 +298,6 @@ public class WebOfTrustUpdaterImpl extends AbstractService implements WebOfTrust
 
        /**
         * Update job that sets the trust relation between two identities.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
        @VisibleForTesting
        class SetTrustJob extends WebOfTrustUpdateJob {
@@ -389,8 +383,6 @@ public class WebOfTrustUpdaterImpl extends AbstractService implements WebOfTrust
 
        /**
         * Base class for context updates of an {@link OwnIdentity}.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
        @VisibleForTesting
        class WebOfTrustContextUpdateJob extends WebOfTrustUpdateJob {
@@ -445,8 +437,6 @@ public class WebOfTrustUpdaterImpl extends AbstractService implements WebOfTrust
 
        /**
         * Job that adds a context to an {@link OwnIdentity}.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
        @VisibleForTesting
        class AddContextJob extends WebOfTrustContextUpdateJob {
@@ -481,8 +471,6 @@ public class WebOfTrustUpdaterImpl extends AbstractService implements WebOfTrust
 
        /**
         * Job that removes a context from an {@link OwnIdentity}.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
        @VisibleForTesting
        class RemoveContextJob extends WebOfTrustContextUpdateJob {
@@ -517,8 +505,6 @@ public class WebOfTrustUpdaterImpl extends AbstractService implements WebOfTrust
 
        /**
         * WebOfTrust update job that sets a property on an {@link OwnIdentity}.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
        @VisibleForTesting
        class SetPropertyJob extends WebOfTrustUpdateJob {
index a8a0c6f..6daef1a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageEvent.java - Copyright © 2013–2016 David Roden
+ * Sone - ImageEvent.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,8 +21,6 @@ import net.pterodactylus.sone.data.Image;
 
 /**
  * Base class for {@link Image} events.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public abstract class ImageEvent {
 
index 63a561b..2bfa3a2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageInsertAbortedEvent.java - Copyright © 2013–2016 David Roden
+ * Sone - ImageInsertAbortedEvent.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,8 +21,6 @@ import net.pterodactylus.sone.data.Image;
 
 /**
  * Event that signals that an {@link Image} insert is aborted.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class ImageInsertAbortedEvent extends ImageEvent {
 
index eaa3996..430c64b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageInsertFailedEvent.java - Copyright © 2013–2016 David Roden
+ * Sone - ImageInsertFailedEvent.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,8 +21,6 @@ import net.pterodactylus.sone.data.Image;
 
 /**
  * Event that signals that an {@link Image} insert has failed.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class ImageInsertFailedEvent extends ImageEvent {
 
index 41d7ecc..4419311 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageInsertFinishedEvent.java - Copyright © 2013–2016 David Roden
+ * Sone - ImageInsertFinishedEvent.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -22,8 +22,6 @@ import freenet.keys.FreenetURI;
 
 /**
  * Event that signals that an {@link Image} insert is finished.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class ImageInsertFinishedEvent extends ImageEvent {
 
index 876f2db..ac9e3e3 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageInsertStartedEvent.java - Copyright © 2013–2016 David Roden
+ * Sone - ImageInsertStartedEvent.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,8 +21,6 @@ import net.pterodactylus.sone.data.Image;
 
 /**
  * Event that signals that an {@link Image} is not being inserted.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class ImageInsertStartedEvent extends ImageEvent {
 
diff --git a/src/main/java/net/pterodactylus/sone/core/event/InsertionDelayChangedEvent.java b/src/main/java/net/pterodactylus/sone/core/event/InsertionDelayChangedEvent.java
deleted file mode 100644 (file)
index a3dc2ce..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-package net.pterodactylus.sone.core.event;
-
-import com.google.common.eventbus.EventBus;
-
-/**
- * Notifies interested {@link EventBus} clients that the Sone insertion delay
- * has changed.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class InsertionDelayChangedEvent {
-
-       private final int insertionDelay;
-
-       public InsertionDelayChangedEvent(int insertionDelay) {
-               this.insertionDelay = insertionDelay;
-       }
-
-       public int getInsertionDelay() {
-               return insertionDelay;
-       }
-
-}
index 72c7baf..7c8a6c3 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - MarkPostKnownEvent.java - Copyright © 2013–2016 David Roden
+ * Sone - MarkPostKnownEvent.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -22,8 +22,6 @@ import net.pterodactylus.sone.data.Post;
 /**
  * Event that signals that a {@link Post} has been marked as
  * {@link Post#isKnown() known}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class MarkPostKnownEvent extends PostEvent {
 
index 224bb31..4c0e2fc 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - MarkPostReplyKnownEvent.java - Copyright © 2013–2016 David Roden
+ * Sone - MarkPostReplyKnownEvent.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -22,8 +22,6 @@ import net.pterodactylus.sone.data.PostReply;
 /**
  * Event that signals that a {@link PostReply} has been marked as
  * {@link PostReply#isKnown() known}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class MarkPostReplyKnownEvent extends PostReplyEvent {
 
index ca6cd70..5bbdf5e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - MarkSoneKnownEvent.java - Copyright © 2013–2016 David Roden
+ * Sone - MarkSoneKnownEvent.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -22,8 +22,6 @@ import net.pterodactylus.sone.data.Sone;
 /**
  * Event that signals that a {@link Sone} has been marked as
  * {@link Sone#isKnown() known}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class MarkSoneKnownEvent extends SoneEvent {
 
diff --git a/src/main/java/net/pterodactylus/sone/core/event/NewPostFoundEvent.java b/src/main/java/net/pterodactylus/sone/core/event/NewPostFoundEvent.java
deleted file mode 100644 (file)
index ff81357..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.core.event;
-
-import net.pterodactylus.sone.data.Post;
-
-/**
- * Event that signals that a new post was found.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class NewPostFoundEvent extends PostEvent {
-
-       /**
-        * Creates a new “new post found” event.
-        *
-        * @param post
-        *            The post that was found
-        */
-       public NewPostFoundEvent(Post post) {
-               super(post);
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/core/event/NewPostReplyFoundEvent.java b/src/main/java/net/pterodactylus/sone/core/event/NewPostReplyFoundEvent.java
deleted file mode 100644 (file)
index bda02b6..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.core.event;
-
-import net.pterodactylus.sone.data.PostReply;
-
-/**
- * Event that signals that a new {@link PostReply} was found.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class NewPostReplyFoundEvent extends PostReplyEvent {
-
-       /**
-        * Creates a new “new post found” event.
-        *
-        * @param postReply
-        *            The post reply that was found
-        */
-       public NewPostReplyFoundEvent(PostReply postReply) {
-               super(postReply);
-       }
-
-}
index c110483..8fb17d2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - NewSoneFoundEvent.java - Copyright © 2013–2016 David Roden
+ * Sone - NewSoneFoundEvent.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,8 +21,6 @@ import net.pterodactylus.sone.data.Sone;
 
 /**
  * Event that signals that a new remote Sone was found.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class NewSoneFoundEvent extends SoneEvent {
 
index 78555b5..93a61c6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostEvent.java - Copyright © 2013–2016 David Roden
+ * Sone - PostEvent.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,8 +21,6 @@ import net.pterodactylus.sone.data.Post;
 
 /**
  * Base class for post events.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class PostEvent {
 
diff --git a/src/main/java/net/pterodactylus/sone/core/event/PostRemovedEvent.java b/src/main/java/net/pterodactylus/sone/core/event/PostRemovedEvent.java
deleted file mode 100644 (file)
index 8240176..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.core.event;
-
-import net.pterodactylus.sone.data.Post;
-
-/**
- * Event that signals that a {@link Post} was removed.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class PostRemovedEvent extends PostEvent {
-
-       /**
-        * Creates a new “post removed” event.
-        *
-        * @param post
-        *            The post that was removed
-        */
-       public PostRemovedEvent(Post post) {
-               super(post);
-       }
-
-}
index 6ae51bc..e31870b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostReplyEvent.java - Copyright © 2013–2016 David Roden
+ * Sone - PostReplyEvent.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,8 +21,6 @@ import net.pterodactylus.sone.data.PostReply;
 
 /**
  * Base class for {@link PostReply} events.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class PostReplyEvent {
 
diff --git a/src/main/java/net/pterodactylus/sone/core/event/PostReplyRemovedEvent.java b/src/main/java/net/pterodactylus/sone/core/event/PostReplyRemovedEvent.java
deleted file mode 100644 (file)
index 4f7cc8b..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.core.event;
-
-import net.pterodactylus.sone.data.PostReply;
-
-/**
- * Event that signals that a {@link PostReply} was removed.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class PostReplyRemovedEvent extends PostReplyEvent {
-
-       /**
-        * Creates a new “post reply removed” event.
-        *
-        * @param postReply
-        *            The post reply that was removed
-        */
-       public PostReplyRemovedEvent(PostReply postReply) {
-               super(postReply);
-       }
-
-}
index 848c1b8..b7497ff 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneEvent.java - Copyright © 2013–2016 David Roden
+ * Sone - SoneEvent.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,8 +21,6 @@ import net.pterodactylus.sone.data.Sone;
 
 /**
  * Base class for Sone events.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public abstract class SoneEvent {
 
index a20e15a..a8c9bb6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneInsertAbortedEvent.java - Copyright © 2013–2016 David Roden
+ * Sone - SoneInsertAbortedEvent.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,8 +21,6 @@ import net.pterodactylus.sone.data.Sone;
 
 /**
  * Event that signals that a {@link Sone} insert was aborted.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class SoneInsertAbortedEvent extends SoneEvent {
 
index 8b3f79e..53fa935 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneInsertedEvent.java - Copyright © 2013–2016 David Roden
+ * Sone - SoneInsertedEvent.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,8 +21,6 @@ import net.pterodactylus.sone.data.Sone;
 
 /**
  * Event that signals that a {@link Sone} was inserted.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class SoneInsertedEvent extends SoneEvent {
 
index 4e2f155..a9ef6fb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneInsertingEvent.java - Copyright © 2013–2016 David Roden
+ * Sone - SoneInsertingEvent.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,8 +21,6 @@ import net.pterodactylus.sone.data.Sone;
 
 /**
  * Event that signals that a {@link Sone} is now being inserted.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class SoneInsertingEvent extends SoneEvent {
 
index 2703f16..cdc9e3a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneLockedEvent.java - Copyright © 2013–2016 David Roden
+ * Sone - SoneLockedEvent.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -22,8 +22,6 @@ import net.pterodactylus.sone.data.Sone;
 /**
  * Event that signals that a {@link Sone} was locked. Only
  * {@link Sone#isLocal() local Sones} can be locked.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class SoneLockedEvent extends SoneEvent {
 
index ecba776..e46272d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneRemovedEvent.java - Copyright © 2013–2016 David Roden
+ * Sone - SoneRemovedEvent.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,8 +21,6 @@ import net.pterodactylus.sone.data.Sone;
 
 /**
  * Event that signals that a {@link Sone} was removed.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class SoneRemovedEvent extends SoneEvent {
 
index de2d875..106d32d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneUnlockedEvent.java - Copyright © 2013–2016 David Roden
+ * Sone - SoneUnlockedEvent.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -22,8 +22,6 @@ import net.pterodactylus.sone.data.Sone;
 /**
  * Event that signals that a {@link Sone} was unlocked. Only
  * {@link Sone#isLocal() local Sones} can be locked.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class SoneUnlockedEvent extends SoneEvent {
 
index 10adda5..2884090 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UpdateFoundEvent.java - Copyright © 2013–2016 David Roden
+ * Sone - UpdateFoundEvent.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,8 +21,6 @@ import net.pterodactylus.util.version.Version;
 
 /**
  * Event that signals that an update for Sone was found.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class UpdateFoundEvent {
 
index 5a02f19..c4af294 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Album.java - Copyright © 2011–2016 David Roden
+ * Sone - Album.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -22,7 +22,6 @@ import static java.util.Collections.emptyList;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.List;
 import javax.annotation.Nonnull;
 
@@ -33,20 +32,9 @@ import com.google.common.collect.ImmutableList;
 
 /**
  * Container for images that can also contain nested {@link Album}s.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public interface Album extends Identified, Fingerprintable {
 
-       /** Compares two {@link Album}s by {@link #getTitle()}. */
-       Comparator<Album> TITLE_COMPARATOR = new Comparator<Album>() {
-
-               @Override
-               public int compare(Album leftAlbum, Album rightAlbum) {
-                       return leftAlbum.getTitle().compareToIgnoreCase(rightAlbum.getTitle());
-               }
-       };
-
        /** Function that flattens the given album and all albums beneath it. */
        Function<Album, List<Album>> FLATTENER = new Function<Album, List<Album>>() {
 
@@ -56,7 +44,7 @@ public interface Album extends Identified, Fingerprintable {
                        if (album == null) {
                                return emptyList();
                        }
-                       List<Album> albums = new ArrayList<Album>();
+                       List<Album> albums = new ArrayList<>();
                        albums.add(album);
                        for (Album subAlbum : album.getAlbums()) {
                                albums.addAll(FluentIterable.from(ImmutableList.of(subAlbum)).transformAndConcat(FLATTENER).toList());
@@ -271,8 +259,6 @@ public interface Album extends Identified, Fingerprintable {
         * Allows modifying an album. Modifications are only performed once {@link
         * #update()} has succesfully returned a new album with the modifications
         * made.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
        interface Modifier {
 
index 650ef16..f23decf 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Client.java - Copyright © 2010–2016 David Roden
+ * Sone - Client.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -23,8 +23,6 @@ import com.google.common.base.Objects;
 
 /**
  * Container for the client information of a {@link Sone}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class Client {
 
diff --git a/src/main/java/net/pterodactylus/sone/data/Fingerprintable.java b/src/main/java/net/pterodactylus/sone/data/Fingerprintable.java
deleted file mode 100644 (file)
index 407aa8a..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.data;
-
-/**
- * Interface for objects that can create a fingerprint of themselves, e.g. to
- * detect modifications. The fingerprint should only contain original
- * information; derived information should not be included.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface Fingerprintable {
-
-       /**
-        * Returns the fingerprint of this object.
-        *
-        * @return The fingerprint of this object
-        */
-       public String getFingerprint();
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/data/Identified.java b/src/main/java/net/pterodactylus/sone/data/Identified.java
deleted file mode 100644 (file)
index 659a56f..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.data;
-
-import javax.annotation.Nonnull;
-
-import com.google.common.base.Function;
-import com.google.common.base.Optional;
-
-/**
- * Interface for all objects that expose an ID.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface Identified {
-
-       /** Function to extract the ID from an optional. */
-       public static final Function<Optional<? extends Identified>, Optional<String>> GET_ID = new Function<Optional<? extends Identified>, Optional<String>>() {
-
-               @Override
-               @Nonnull
-               public Optional<String> apply(Optional<? extends Identified> identified) {
-                       return (identified == null) ? Optional.<String>absent() : (identified.isPresent() ? Optional.of(identified.get().getId()) : Optional.<String>absent());
-               }
-       };
-
-       /**
-        * Returns the ID of this element.
-        *
-        * @return The ID of this element
-        */
-       public String getId();
-
-}
index 55b0094..93d0702 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Image.java - Copyright © 2011–2016 David Roden
+ * Sone - Image.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,8 +19,6 @@ package net.pterodactylus.sone.data;
 
 /**
  * Container for image metadata.
- *
- * @author <a href="mailto:d.roden@xplosion.de">David Roden</a>
  */
 public interface Image extends Identified, Fingerprintable {
 
index 5a1c2c5..b6648e2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Post.java - Copyright © 2010–2016 David Roden
+ * Sone - Post.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -27,8 +27,6 @@ import com.google.common.base.Predicate;
 /**
  * A post is a short message that a user writes in his Sone to let other users
  * know what is going on.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public interface Post extends Identified {
 
@@ -126,9 +124,6 @@ public interface Post extends Identified {
 
        /**
         * Shell for a post that has not yet been loaded.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’
-        *         Roden</a>
         */
        public static class EmptyPost implements Post {
 
index 6821b7b..dc4a903 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostReply.java - Copyright © 2010–2016 David Roden
+ * Sone - PostReply.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -23,8 +23,6 @@ import com.google.common.base.Predicate;
 /**
  * A reply is like a {@link Post} but can never be posted on its own, it always
  * refers to another {@link Post}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public interface PostReply extends Reply<PostReply> {
 
index a0ebeba..8ea09f7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Profile.java - Copyright © 2010–2016 David Roden
+ * Sone - Profile.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -20,6 +20,7 @@ package net.pterodactylus.sone.data;
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -35,8 +36,6 @@ import com.google.common.hash.Hashing;
 /**
  * A profile stores personal information about a {@link Sone}. All information
  * is optional and can be {@code null}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class Profile implements Fingerprintable {
 
@@ -128,7 +127,7 @@ public class Profile implements Fingerprintable {
         */
        @Nonnull
        public Profile setFirstName(@Nullable String firstName) {
-               this.firstName = firstName;
+               this.firstName = "".equals(firstName) ? null : firstName;
                return this;
        }
 
@@ -151,7 +150,7 @@ public class Profile implements Fingerprintable {
         */
        @Nonnull
        public Profile setMiddleName(@Nullable String middleName) {
-               this.middleName = middleName;
+               this.middleName = "".equals(middleName) ? null : middleName;
                return this;
        }
 
@@ -174,7 +173,7 @@ public class Profile implements Fingerprintable {
         */
        @Nonnull
        public Profile setLastName(@Nullable String lastName) {
-               this.lastName = lastName;
+               this.lastName = "".equals(lastName) ? null : lastName;
                return this;
        }
 
@@ -284,7 +283,7 @@ public class Profile implements Fingerprintable {
         */
        @Nonnull
        public List<Field> getFields() {
-               return new ArrayList<Field>(fields);
+               return new ArrayList<>(fields);
        }
 
        /**
@@ -431,42 +430,40 @@ public class Profile implements Fingerprintable {
        @Override
        public String getFingerprint() {
                Hasher hash = Hashing.sha256().newHasher();
-               hash.putString("Profile(");
+               hash.putString("Profile(", UTF_8);
                if (firstName != null) {
-                       hash.putString("FirstName(").putString(firstName).putString(")");
+                       hash.putString("FirstName(", UTF_8).putString(firstName, UTF_8).putString(")", UTF_8);
                }
                if (middleName != null) {
-                       hash.putString("MiddleName(").putString(middleName).putString(")");
+                       hash.putString("MiddleName(", UTF_8).putString(middleName, UTF_8).putString(")", UTF_8);
                }
                if (lastName != null) {
-                       hash.putString("LastName(").putString(lastName).putString(")");
+                       hash.putString("LastName(", UTF_8).putString(lastName, UTF_8).putString(")", UTF_8);
                }
                if (birthDay != null) {
-                       hash.putString("BirthDay(").putInt(birthDay).putString(")");
+                       hash.putString("BirthDay(", UTF_8).putInt(birthDay).putString(")", UTF_8);
                }
                if (birthMonth != null) {
-                       hash.putString("BirthMonth(").putInt(birthMonth).putString(")");
+                       hash.putString("BirthMonth(", UTF_8).putInt(birthMonth).putString(")", UTF_8);
                }
                if (birthYear != null) {
-                       hash.putString("BirthYear(").putInt(birthYear).putString(")");
+                       hash.putString("BirthYear(", UTF_8).putInt(birthYear).putString(")", UTF_8);
                }
                if (avatar != null) {
-                       hash.putString("Avatar(").putString(avatar).putString(")");
+                       hash.putString("Avatar(", UTF_8).putString(avatar, UTF_8).putString(")", UTF_8);
                }
-               hash.putString("ContactInformation(");
+               hash.putString("ContactInformation(", UTF_8);
                for (Field field : fields) {
-                       hash.putString(field.getName()).putString("(").putString(field.getValue()).putString(")");
+                       hash.putString(field.getName(), UTF_8).putString("(", UTF_8).putString(field.getValue(), UTF_8).putString(")", UTF_8);
                }
-               hash.putString(")");
-               hash.putString(")");
+               hash.putString(")", UTF_8);
+               hash.putString(")", UTF_8);
 
                return hash.hash().toString();
        }
 
        /**
         * Container for a profile field.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
        public class Field {
 
@@ -586,15 +583,11 @@ public class Profile implements Fingerprintable {
 
        /**
         * Exception that signals the addition of a field with an empty name.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
        public static class EmptyFieldName extends IllegalArgumentException { }
 
        /**
         * Exception that signals the addition of a field that already exists.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
        public static class DuplicateField extends IllegalArgumentException { }
 
index d3068c4..c596605 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Reply.java - Copyright © 2010–2016 David Roden
+ * Sone - Reply.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -26,7 +26,6 @@ import com.google.common.base.Predicate;
  *
  * @param <T>
  *            The type of the reply
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public interface Reply<T extends Reply<T>> extends Identified {
 
index b761c7b..273b73e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Sone.java - Copyright © 2010–2016 David Roden
+ * Sone - Sone.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -44,15 +44,11 @@ import com.google.common.primitives.Ints;
 /**
  * A Sone defines everything about a user: her profile, her status updates, her
  * replies, her likes and dislikes, etc.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
 
        /**
         * Enumeration for the possible states of a {@link Sone}.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
        public enum SoneStatus {
 
index 5f05447..d7089d9 100644 (file)
@@ -6,8 +6,6 @@ import javax.annotation.Nonnull;
 
 /**
  * All Sone-specific options.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public interface SoneOptions {
 
@@ -34,8 +32,6 @@ public interface SoneOptions {
 
        /**
         * Possible values for all options that are related to loading external content.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
        enum LoadExternalContent {
 
@@ -58,8 +54,6 @@ public interface SoneOptions {
 
        /**
         * {@link SoneOptions} implementation.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
        public class DefaultSoneOptions implements SoneOptions {
 
index ee6c35c..378a348 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - TemporaryImage.java - Copyright © 2011–2016 David Roden
+ * Sone - TemporaryImage.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -25,8 +25,6 @@ import java.util.UUID;
 /**
  * A temporary image stores an uploaded image in memory until it has been
  * inserted into Freenet and is subsequently loaded from there.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class TemporaryImage {
 
index 66a758a..460be4e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - AbstractAlbumBuilder.java - Copyright © 2013–2016 David Roden
+ * Sone - AbstractAlbumBuilder.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -25,8 +25,6 @@ import net.pterodactylus.sone.database.AlbumBuilder;
 /**
  * Abstract {@link AlbumBuilder} implementation. It stores the state of the new
  * album and performs validation, you only need to implement {@link #build()}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public abstract class AbstractAlbumBuilder implements AlbumBuilder {
 
index 2282c11..f545ae9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - AbstractImageBuilder.java - Copyright © 2013–2016 David Roden
+ * Sone - AbstractImageBuilder.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -23,8 +23,6 @@ import net.pterodactylus.sone.database.ImageBuilder;
 /**
  * Abstract {@link ImageBuilder} implementation. It stores the state of the new
  * album and performs validation, you only need to implement {@link #build()}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public abstract class AbstractImageBuilder implements ImageBuilder {
 
index 9e2c50a..cabe8d5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - AbstractPostBuilder.java - Copyright © 2013–2016 David Roden
+ * Sone - AbstractPostBuilder.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -26,8 +26,6 @@ import net.pterodactylus.sone.database.SoneProvider;
 /**
  * Abstract {@link PostBuilder} implementation. It stores the state of the new
  * post and performs validation, you only need to implement {@link #build()}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public abstract class AbstractPostBuilder implements PostBuilder {
 
index bd8090f..0fe124e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - AbstractPostReplyBuilder.java - Copyright © 2013–2016 David Roden
+ * Sone - AbstractPostReplyBuilder.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -25,8 +25,6 @@ import net.pterodactylus.sone.database.PostReplyBuilder;
  * Abstract {@link PostReplyBuilder} implementation. It stores the state of the
  * new post and performs validation, implementations only need to implement
  * {@link #build()}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public abstract class AbstractPostReplyBuilder extends AbstractReplyBuilder<PostReplyBuilder> implements PostReplyBuilder {
 
index c66debf..052ca2d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - AbstractReplyBuilder.java - Copyright © 2013–2016 David Roden
+ * Sone - AbstractReplyBuilder.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -24,7 +24,6 @@ import net.pterodactylus.sone.database.ReplyBuilder;
  *
  * @param <B>
  *            The interface implemented and exposed by the builder
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class AbstractReplyBuilder<B extends ReplyBuilder<B>> implements ReplyBuilder<B> {
 
index a214677..f6d091d 100644 (file)
@@ -8,8 +8,6 @@ import net.pterodactylus.sone.freenet.wot.OwnIdentity;
 
 /**
  * Abstract {@link SoneBuilder} implementation.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public abstract class AbstractSoneBuilder implements SoneBuilder {
 
index 3ec6b42..601daa8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - AlbumBuilderImpl.java - Copyright © 2013–2016 David Roden
+ * Sone - AlbumBuilderImpl.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -22,8 +22,6 @@ import net.pterodactylus.sone.database.AlbumBuilder;
 
 /**
  * {@link AlbumBuilder} implementation that creates {@link AlbumImpl} objects.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class AlbumBuilderImpl extends AbstractAlbumBuilder {
 
index 991459a..3ec362f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - AlbumImpl.java - Copyright © 2011–2016 David Roden
+ * Sone - AlbumImpl.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@ import static com.google.common.base.Optional.absent;
 import static com.google.common.base.Optional.fromNullable;
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -41,8 +42,6 @@ import com.google.common.hash.Hashing;
 
 /**
  * Container for images that can also contain nested {@link AlbumImpl}s.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class AlbumImpl implements Album {
 
@@ -53,13 +52,13 @@ public class AlbumImpl implements Album {
        private final Sone sone;
 
        /** Nested albums. */
-       private final List<Album> albums = new ArrayList<Album>();
+       private final List<Album> albums = new ArrayList<>();
 
        /** The image IDs in order. */
-       private final List<String> imageIds = new ArrayList<String>();
+       private final List<String> imageIds = new ArrayList<>();
 
        /** The images in this album. */
-       private final Map<String, Image> images = new HashMap<String, Image>();
+       private final Map<String, Image> images = new HashMap<>();
 
        /** The parent album. */
        private Album parent;
@@ -102,7 +101,7 @@ public class AlbumImpl implements Album {
 
        @Override
        public List<Album> getAlbums() {
-               return new ArrayList<Album>(albums);
+               return new ArrayList<>(albums);
        }
 
        @Override
@@ -154,7 +153,7 @@ public class AlbumImpl implements Album {
 
        @Override
        public List<Image> getImages() {
-               return new ArrayList<Image>(Collections2.filter(Collections2.transform(imageIds, new Function<String, Image>() {
+               return new ArrayList<>(Collections2.filter(Collections2.transform(imageIds, new Function<String, Image>() {
 
                        @Override
                        @SuppressWarnings("synthetic-access")
@@ -298,28 +297,28 @@ public class AlbumImpl implements Album {
        @Override
        public String getFingerprint() {
                Hasher hash = Hashing.sha256().newHasher();
-               hash.putString("Album(");
-               hash.putString("ID(").putString(id).putString(")");
-               hash.putString("Title(").putString(title).putString(")");
-               hash.putString("Description(").putString(description).putString(")");
+               hash.putString("Album(", UTF_8);
+               hash.putString("ID(", UTF_8).putString(id, UTF_8).putString(")", UTF_8);
+               hash.putString("Title(", UTF_8).putString(title, UTF_8).putString(")", UTF_8);
+               hash.putString("Description(", UTF_8).putString(description, UTF_8).putString(")", UTF_8);
 
                /* add nested albums. */
-               hash.putString("Albums(");
+               hash.putString("Albums(", UTF_8);
                for (Album album : albums) {
-                       hash.putString(album.getFingerprint());
+                       hash.putString(album.getFingerprint(), UTF_8);
                }
-               hash.putString(")");
+               hash.putString(")", UTF_8);
 
                /* add images. */
-               hash.putString("Images(");
+               hash.putString("Images(", UTF_8);
                for (Image image : getImages()) {
                        if (image.isInserted()) {
-                               hash.putString(image.getFingerprint());
+                               hash.putString(image.getFingerprint(), UTF_8);
                        }
                }
-               hash.putString(")");
+               hash.putString(")", UTF_8);
 
-               hash.putString(")");
+               hash.putString(")", UTF_8);
                return hash.hash().toString();
        }
 
diff --git a/src/main/java/net/pterodactylus/sone/data/impl/DefaultPostBuilderFactory.java b/src/main/java/net/pterodactylus/sone/data/impl/DefaultPostBuilderFactory.java
deleted file mode 100644 (file)
index 65b266d..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.data.impl;
-
-import net.pterodactylus.sone.database.PostBuilder;
-import net.pterodactylus.sone.database.PostBuilderFactory;
-import net.pterodactylus.sone.database.SoneProvider;
-
-import com.google.inject.Inject;
-
-/**
- * {@link PostBuilderFactory} implementation that creates
- * {@link PostBuilderImpl}s.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class DefaultPostBuilderFactory implements PostBuilderFactory {
-
-       /** The Sone provider. */
-       private final SoneProvider soneProvider;
-
-       /**
-        * Creates a new default post builder factory.
-        *
-        * @param soneProvider
-        *            The Sone provider
-        */
-       @Inject
-       public DefaultPostBuilderFactory(SoneProvider soneProvider) {
-               this.soneProvider = soneProvider;
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public PostBuilder newPostBuilder() {
-               return new PostBuilderImpl(soneProvider);
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/data/impl/DefaultPostReplyBuilderFactory.java b/src/main/java/net/pterodactylus/sone/data/impl/DefaultPostReplyBuilderFactory.java
deleted file mode 100644 (file)
index bad7745..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.data.impl;
-
-import net.pterodactylus.sone.database.PostProvider;
-import net.pterodactylus.sone.database.PostReplyBuilder;
-import net.pterodactylus.sone.database.PostReplyBuilderFactory;
-import net.pterodactylus.sone.database.SoneProvider;
-
-import com.google.inject.Inject;
-
-/**
- * {@link PostReplyBuilderFactory} that creates {@link PostReplyBuilderImpl}s.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class DefaultPostReplyBuilderFactory implements PostReplyBuilderFactory {
-
-       /** The Sone provider. */
-       private final SoneProvider soneProvider;
-
-       /** The post provider. */
-       private final PostProvider postProvider;
-
-       /**
-        * Creates a new default post reply builder factory.
-        *
-        * @param soneProvider
-        *            The Sone provider
-        * @param postProvider
-        *            The post provider
-        */
-       @Inject
-       public DefaultPostReplyBuilderFactory(SoneProvider soneProvider, PostProvider postProvider) {
-               this.soneProvider = soneProvider;
-               this.postProvider = postProvider;
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public PostReplyBuilder newPostReplyBuilder() {
-               return new PostReplyBuilderImpl(soneProvider, postProvider);
-       }
-
-}
index 79766b5..e06e5a7 100644 (file)
@@ -23,8 +23,6 @@ import com.google.common.base.Objects;
 /**
  * {@link Sone} implementation that only stores the ID of a Sone and returns
  * {@code null}, {@code 0}, or empty collections where appropriate.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class IdOnlySone implements Sone {
 
index 4f4f26b..b62cc40 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageBuilderImpl.java - Copyright © 2013–2016 David Roden
+ * Sone - ImageBuilderImpl.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -22,8 +22,6 @@ import net.pterodactylus.sone.database.ImageBuilder;
 
 /**
  * {@link ImageBuilder} implementation that creates {@link ImageImpl} objects.
- *
- * @author <a href="mailto:d.roden@xplosion.de">David Roden</a>
  */
 public class ImageBuilderImpl extends AbstractImageBuilder {
 
index 9f0fbbf..a54a8de 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageImpl.java - Copyright © 2011–2016 David Roden
+ * Sone - ImageImpl.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@ import static com.google.common.base.Optional.fromNullable;
 import static com.google.common.base.Optional.of;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import java.util.UUID;
 
@@ -34,8 +35,6 @@ import com.google.common.hash.Hashing;
 
 /**
  * Container for image metadata.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class ImageImpl implements Image {
 
@@ -248,11 +247,11 @@ public class ImageImpl implements Image {
        @Override
        public String getFingerprint() {
                Hasher hash = Hashing.sha256().newHasher();
-               hash.putString("Image(");
-               hash.putString("ID(").putString(id).putString(")");
-               hash.putString("Title(").putString(title).putString(")");
-               hash.putString("Description(").putString(description).putString(")");
-               hash.putString(")");
+               hash.putString("Image(", UTF_8);
+               hash.putString("ID(", UTF_8).putString(id, UTF_8).putString(")", UTF_8);
+               hash.putString("Title(", UTF_8).putString(title, UTF_8).putString(")", UTF_8);
+               hash.putString("Description(", UTF_8).putString(description, UTF_8).putString(")", UTF_8);
+               hash.putString(")", UTF_8);
                return hash.hash().toString();
        }
 
diff --git a/src/main/java/net/pterodactylus/sone/data/impl/PostBuilderImpl.java b/src/main/java/net/pterodactylus/sone/data/impl/PostBuilderImpl.java
deleted file mode 100644 (file)
index 9fed167..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.data.impl;
-
-import java.util.UUID;
-
-import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.database.PostBuilder;
-import net.pterodactylus.sone.database.SoneProvider;
-
-/**
- * {@link PostBuilder} implementation that creates {@link PostImpl} objects.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class PostBuilderImpl extends AbstractPostBuilder {
-
-       /**
-        * Creates a new post builder.
-        *
-        * @param soneProvider
-        *            The Sone provider
-        */
-       public PostBuilderImpl(SoneProvider soneProvider) {
-               super(soneProvider);
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public Post build() {
-               validate();
-               return new PostImpl(soneProvider, randomId ? UUID.randomUUID().toString() : id, senderId, recipientId, currentTime ? System.currentTimeMillis() : time, text);
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/data/impl/PostImpl.java b/src/main/java/net/pterodactylus/sone/data/impl/PostImpl.java
deleted file mode 100644 (file)
index 2b785f9..0000000
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * 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
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.data.impl;
-
-import static com.google.common.base.Optional.fromNullable;
-
-import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.sone.database.SoneProvider;
-
-import com.google.common.base.Optional;
-
-/**
- * A post is a short message that a user writes in his Sone to let other users
- * know what is going on.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class PostImpl implements Post {
-
-       /** The Sone provider. */
-       private final SoneProvider soneProvider;
-
-       /** The GUID of the post. */
-       private final String id;
-
-       /** The ID of the owning Sone. */
-       private final String soneId;
-
-       /** The ID of the recipient Sone. */
-       private final String recipientId;
-
-       /** The time of the post (in milliseconds since Jan 1, 1970 UTC). */
-       private final long time;
-
-       /** The text of the post. */
-       private final String text;
-
-       /** Whether the post is known. */
-       private volatile boolean known;
-
-       /**
-        * Creates a new post.
-        *
-        * @param soneProvider
-        *            The Sone provider
-        * @param id
-        *            The ID of the post
-        * @param soneId
-        *            The ID of the Sone this post belongs to
-        * @param recipientId
-        *            The ID of the recipient of the post
-        * @param time
-        *            The time of the post (in milliseconds since Jan 1, 1970 UTC)
-        * @param text
-        *            The text of the post
-        */
-       public PostImpl(SoneProvider soneProvider, String id, String soneId, String recipientId, long time, String text) {
-               this.soneProvider = soneProvider;
-               this.id = id;
-               this.soneId = soneId;
-               this.recipientId = recipientId;
-               this.time = time;
-               this.text = text;
-       }
-
-       //
-       // ACCESSORS
-       //
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public String getId() {
-               return id;
-       }
-
-       @Override
-       public boolean isLoaded() {
-               return true;
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public Sone getSone() {
-               return soneProvider.getSone(soneId);
-       }
-
-       /**
-        * {@inheritDocs}
-        */
-       @Override
-       public Optional<String> getRecipientId() {
-               return fromNullable(recipientId);
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public Optional<Sone> getRecipient() {
-               return fromNullable(soneProvider.getSone(recipientId));
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public long getTime() {
-               return time;
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public String getText() {
-               return text;
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public boolean isKnown() {
-               return known;
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public PostImpl setKnown(boolean known) {
-               this.known = known;
-               return this;
-       }
-
-       //
-       // OBJECT METHODS
-       //
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public int hashCode() {
-               return id.hashCode();
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public boolean equals(Object object) {
-               if (!(object instanceof PostImpl)) {
-                       return false;
-               }
-               PostImpl post = (PostImpl) object;
-               return post.id.equals(id);
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public String toString() {
-               return String.format("%s[id=%s,sone=%s,recipient=%s,time=%d,text=%s]", getClass().getName(), id, soneId, recipientId, time, text);
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/data/impl/PostReplyBuilderImpl.java b/src/main/java/net/pterodactylus/sone/data/impl/PostReplyBuilderImpl.java
deleted file mode 100644 (file)
index 7235bc9..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.data.impl;
-
-import static com.google.common.base.Preconditions.checkState;
-
-import java.util.UUID;
-
-import net.pterodactylus.sone.data.PostReply;
-import net.pterodactylus.sone.database.PostProvider;
-import net.pterodactylus.sone.database.PostReplyBuilder;
-import net.pterodactylus.sone.database.SoneProvider;
-
-/**
- * {@link PostReplyBuilder} implementation that creates {@link PostReplyImpl}
- * objects.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class PostReplyBuilderImpl extends AbstractPostReplyBuilder {
-
-       /** The Sone provider. */
-       private final SoneProvider soneProvider;
-
-       /** The post provider. */
-       private final PostProvider postProvider;
-
-       /**
-        * Creates a new post reply builder.
-        *
-        * @param soneProvider
-        *            The Sone provider
-        * @param postProvider
-        *            The post provider
-        */
-       public PostReplyBuilderImpl(SoneProvider soneProvider, PostProvider postProvider) {
-               this.soneProvider = soneProvider;
-               this.postProvider = postProvider;
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public PostReply build() {
-               checkState((randomId && (id == null)) || (!randomId && (id != null)), "either random ID nor custom ID must be set");
-               checkState(senderId != null, "sender must not be null");
-               checkState((currentTime && (time == 0)) || (!currentTime && (time >= 0)), "either current time or custom time must be set");
-               checkState((text != null) && !text.trim().isEmpty(), "text must not be empty");
-               checkState(postId != null, "post must not be null");
-
-               /* create new post reply. */
-               return new PostReplyImpl(soneProvider, postProvider, randomId ? UUID.randomUUID().toString() : id, senderId, currentTime ? System.currentTimeMillis() : time, text, postId);
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/data/impl/PostReplyImpl.java b/src/main/java/net/pterodactylus/sone/data/impl/PostReplyImpl.java
deleted file mode 100644 (file)
index 3571172..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * 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
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.data.impl;
-
-import static com.google.common.base.Optional.fromNullable;
-
-import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.PostReply;
-import net.pterodactylus.sone.database.PostProvider;
-import net.pterodactylus.sone.database.SoneProvider;
-
-import com.google.common.base.Optional;
-
-/**
- * Simple {@link PostReply} implementation.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class PostReplyImpl extends ReplyImpl<PostReply> implements PostReply {
-
-       /** The post provider. */
-       private final PostProvider postProvider;
-
-       /** The Post this reply refers to. */
-       private final String postId;
-
-       /**
-        * Creates a new reply.
-        *
-        * @param soneProvider
-        *            The Sone provider
-        * @param postProvider
-        *            The post provider
-        * @param id
-        *            The ID of the reply
-        * @param soneId
-        *            The ID of the Sone of the reply
-        * @param time
-        *            The time of the reply
-        * @param text
-        *            The text of the reply
-        * @param postId
-        *            The ID of the post this reply refers to
-        */
-       public PostReplyImpl(SoneProvider soneProvider, PostProvider postProvider, String id, String soneId, long time, String text, String postId) {
-               super(soneProvider, id, soneId, time, text);
-               this.postProvider = postProvider;
-               this.postId = postId;
-       }
-
-       //
-       // ACCESSORS
-       //
-
-       /**
-        * {@inheritDocs}
-        */
-       @Override
-       public String getPostId() {
-               return postId;
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public Optional<Post> getPost() {
-               return fromNullable(postProvider.getPost(postId));
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/data/impl/ReplyImpl.java b/src/main/java/net/pterodactylus/sone/data/impl/ReplyImpl.java
deleted file mode 100644 (file)
index 372c80a..0000000
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * 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
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.data.impl;
-
-import net.pterodactylus.sone.data.Reply;
-import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.sone.database.SoneProvider;
-
-/**
- * Abstract base class for all replies.
- *
- * @param <T>
- *            The type of the reply
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public abstract class ReplyImpl<T extends Reply<T>> implements Reply<T> {
-
-       /** The Sone provider. */
-       private final SoneProvider soneProvider;
-
-       /** The ID of the reply. */
-       private final String id;
-
-       /** The Sone that created this reply. */
-       private final String soneId;
-
-       /** The time of the reply. */
-       private final long time;
-
-       /** The text of the reply. */
-       private final String text;
-
-       /** Whether the reply is known. */
-       private volatile boolean known;
-
-       /**
-        * Creates a new reply.
-        *
-        * @param soneProvider
-        *            The Sone provider
-        * @param id
-        *            The ID of the reply
-        * @param soneId
-        *            The ID of the Sone of the reply
-        * @param time
-        *            The time of the reply
-        * @param text
-        *            The text of the reply
-        */
-       protected ReplyImpl(SoneProvider soneProvider, String id, String soneId, long time, String text) {
-               this.soneProvider = soneProvider;
-               this.id = id;
-               this.soneId = soneId;
-               this.time = time;
-               this.text = text;
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public String getId() {
-               return id;
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public Sone getSone() {
-               return soneProvider.getSone(soneId);
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public long getTime() {
-               return time;
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public String getText() {
-               return text;
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public boolean isKnown() {
-               return known;
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       @SuppressWarnings("unchecked")
-       public T setKnown(boolean known) {
-               this.known = known;
-               return (T) this;
-       }
-
-       //
-       // OBJECT METHODS
-       //
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public int hashCode() {
-               return id.hashCode();
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public boolean equals(Object object) {
-               if (!(object instanceof Reply<?>)) {
-                       return false;
-               }
-               Reply<?> reply = (Reply<?>) object;
-               return reply.getId().equals(id);
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public String toString() {
-               return String.format("%s[id=%s,sone=%s,time=%d,text=%s]", getClass().getName(), id, soneId, time, text);
-       }
-
-}
index 0494607..3366448 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneImpl.java - Copyright © 2010–2016 David Roden
+ * Sone - SoneImpl.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,6 +19,7 @@ package net.pterodactylus.sone.data.impl;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static java.lang.String.format;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.logging.Logger.getLogger;
 
 import java.net.MalformedURLException;
@@ -56,8 +57,6 @@ import com.google.common.hash.Hashing;
  * {@link Sone} implementation.
  * <p/>
  * Operations that modify the Sone need to synchronize on the Sone in question.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class SoneImpl implements Sone {
 
@@ -95,16 +94,16 @@ public class SoneImpl implements Sone {
        private volatile boolean known;
 
        /** All posts. */
-       private final Set<Post> posts = new CopyOnWriteArraySet<Post>();
+       private final Set<Post> posts = new CopyOnWriteArraySet<>();
 
        /** All replies. */
-       private final Set<PostReply> replies = new CopyOnWriteArraySet<PostReply>();
+       private final Set<PostReply> replies = new CopyOnWriteArraySet<>();
 
        /** The IDs of all liked posts. */
-       private final Set<String> likedPostIds = new CopyOnWriteArraySet<String>();
+       private final Set<String> likedPostIds = new CopyOnWriteArraySet<>();
 
        /** The IDs of all liked replies. */
-       private final Set<String> likedReplyIds = new CopyOnWriteArraySet<String>();
+       private final Set<String> likedReplyIds = new CopyOnWriteArraySet<>();
 
        /** The root album containing all albums. */
        private final Album rootAlbum = new AlbumImpl(this);
@@ -383,7 +382,7 @@ public class SoneImpl implements Sone {
        public List<Post> getPosts() {
                List<Post> sortedPosts;
                synchronized (this) {
-                       sortedPosts = new ArrayList<Post>(posts);
+                       sortedPosts = new ArrayList<>(posts);
                }
                Collections.sort(sortedPosts, Post.NEWEST_FIRST);
                return sortedPosts;
@@ -636,46 +635,46 @@ public class SoneImpl implements Sone {
        @Override
        public synchronized String getFingerprint() {
                Hasher hash = Hashing.sha256().newHasher();
-               hash.putString(profile.getFingerprint());
+               hash.putString(profile.getFingerprint(), UTF_8);
 
-               hash.putString("Posts(");
+               hash.putString("Posts(", UTF_8);
                for (Post post : getPosts()) {
-                       hash.putString("Post(").putString(post.getId()).putString(")");
+                       hash.putString("Post(", UTF_8).putString(post.getId(), UTF_8).putString(")", UTF_8);
                }
-               hash.putString(")");
+               hash.putString(")", UTF_8);
 
-               List<PostReply> replies = new ArrayList<PostReply>(getReplies());
+               List<PostReply> replies = new ArrayList<>(getReplies());
                Collections.sort(replies, Reply.TIME_COMPARATOR);
-               hash.putString("Replies(");
+               hash.putString("Replies(", UTF_8);
                for (PostReply reply : replies) {
-                       hash.putString("Reply(").putString(reply.getId()).putString(")");
+                       hash.putString("Reply(", UTF_8).putString(reply.getId(), UTF_8).putString(")", UTF_8);
                }
-               hash.putString(")");
+               hash.putString(")", UTF_8);
 
-               List<String> likedPostIds = new ArrayList<String>(getLikedPostIds());
+               List<String> likedPostIds = new ArrayList<>(getLikedPostIds());
                Collections.sort(likedPostIds);
-               hash.putString("LikedPosts(");
+               hash.putString("LikedPosts(", UTF_8);
                for (String likedPostId : likedPostIds) {
-                       hash.putString("Post(").putString(likedPostId).putString(")");
+                       hash.putString("Post(", UTF_8).putString(likedPostId, UTF_8).putString(")", UTF_8);
                }
-               hash.putString(")");
+               hash.putString(")", UTF_8);
 
-               List<String> likedReplyIds = new ArrayList<String>(getLikedReplyIds());
+               List<String> likedReplyIds = new ArrayList<>(getLikedReplyIds());
                Collections.sort(likedReplyIds);
-               hash.putString("LikedReplies(");
+               hash.putString("LikedReplies(", UTF_8);
                for (String likedReplyId : likedReplyIds) {
-                       hash.putString("Reply(").putString(likedReplyId).putString(")");
+                       hash.putString("Reply(", UTF_8).putString(likedReplyId, UTF_8).putString(")", UTF_8);
                }
-               hash.putString(")");
+               hash.putString(")", UTF_8);
 
-               hash.putString("Albums(");
+               hash.putString("Albums(", UTF_8);
                for (Album album : rootAlbum.getAlbums()) {
                        if (!Album.NOT_EMPTY.apply(album)) {
                                continue;
                        }
-                       hash.putString(album.getFingerprint());
+                       hash.putString(album.getFingerprint(), UTF_8);
                }
-               hash.putString(")");
+               hash.putString(")", UTF_8);
 
                return hash.hash().toString();
        }
diff --git a/src/main/java/net/pterodactylus/sone/database/AlbumBuilder.kt b/src/main/java/net/pterodactylus/sone/database/AlbumBuilder.kt
deleted file mode 100644 (file)
index ed441e0..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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
- * 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.database
-
-import net.pterodactylus.sone.data.Album
-import net.pterodactylus.sone.data.Sone
-
-/**
- * Builder for [Album] objects.
- */
-interface AlbumBuilder {
-
-       fun randomId(): AlbumBuilder
-       fun withId(id: String): AlbumBuilder
-
-       fun by(sone: Sone): AlbumBuilder
-
-       fun build(): Album
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/AlbumBuilderFactory.kt b/src/main/java/net/pterodactylus/sone/database/AlbumBuilderFactory.kt
deleted file mode 100644 (file)
index 6f854db..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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
- * 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.database
-
-/**
- * Factory for [AlbumBuilder]s.
- */
-interface AlbumBuilderFactory {
-
-       fun newAlbumBuilder(): AlbumBuilder
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/AlbumDatabase.kt b/src/main/java/net/pterodactylus/sone/database/AlbumDatabase.kt
deleted file mode 100644 (file)
index 69daa21..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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
- * 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.database
-
-/**
- * Combines an [AlbumProvider] and an [AlbumStore] into an album
- * database.
- */
-interface AlbumDatabase : AlbumProvider, AlbumBuilderFactory, AlbumStore
diff --git a/src/main/java/net/pterodactylus/sone/database/AlbumProvider.kt b/src/main/java/net/pterodactylus/sone/database/AlbumProvider.kt
deleted file mode 100644 (file)
index c0b09e8..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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
- * 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.database
-
-import net.pterodactylus.sone.data.Album
-
-import com.google.common.base.Optional
-
-/**
- * Interface for objects that can provide [Album]s by their ID.
- */
-interface AlbumProvider {
-
-       fun getAlbum(albumId: String): Album?
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/AlbumStore.kt b/src/main/java/net/pterodactylus/sone/database/AlbumStore.kt
deleted file mode 100644 (file)
index e2a31c7..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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
- * 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.database
-
-import net.pterodactylus.sone.data.Album
-
-/**
- * Interface for a store of albums.
- */
-interface AlbumStore {
-
-       fun storeAlbum(album: Album)
-       fun removeAlbum(album: Album)
-
-}
index bb98fec..9763ad6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DatabaseException.java - Copyright © 2013–2016 David Roden
+ * Sone - DatabaseException.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,8 +19,6 @@ package net.pterodactylus.sone.database;
 
 /**
  * Exception that signals a database error.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class DatabaseException extends Exception {
 
index 0f55575..268f1a2 100644 (file)
@@ -3,17 +3,21 @@ package net.pterodactylus.sone.database.memory;
 import static java.util.logging.Level.WARNING;
 
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.logging.Logger;
 
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
 import net.pterodactylus.util.config.Configuration;
 import net.pterodactylus.util.config.ConfigurationException;
 
 /**
  * Helper class for interacting with a {@link Configuration}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class ConfigurationLoader {
 
@@ -44,8 +48,52 @@ public class ConfigurationLoader {
                return loadIds("Bookmarks/Post");
        }
 
+       @Nullable
+       public synchronized Long getSoneFollowingTime(@Nonnull String soneId) {
+               return loadSoneFollowingTimes().get(soneId);
+       }
+
+       public synchronized void removeSoneFollowingTime(@Nonnull String soneId) {
+               Map<String, Long> soneFollowingTimes = loadSoneFollowingTimes();
+               soneFollowingTimes.remove(soneId);
+               storeSoneFollowingTimes(soneFollowingTimes);
+       }
+
+       public synchronized void setSoneFollowingTime(@Nonnull String soneId, long time) {
+               Map<String, Long> soneFollowingTimes = loadSoneFollowingTimes();
+               soneFollowingTimes.put(soneId, time);
+               storeSoneFollowingTimes(soneFollowingTimes);
+       }
+
+       private synchronized Map<String, Long> loadSoneFollowingTimes() {
+               Map<String, Long> soneFollowingTimes = new HashMap<>();
+               int soneCounter = 0;
+               while (true) {
+                       String soneId = configuration.getStringValue("SoneFollowingTimes/" + soneCounter + "/Sone").getValue(null);
+                       if (soneId == null) {
+                               break;
+                       }
+                       soneFollowingTimes.put(soneId, configuration.getLongValue("SoneFollowingTimes/" + soneCounter++ + "/Time").getValue(null));
+               }
+               return soneFollowingTimes;
+       }
+
+       private synchronized void storeSoneFollowingTimes(Map<String, Long> soneFollowingTimes) {
+               int soneCounter = 0;
+               try {
+                       for (Entry<String, Long> soneFollowingTime : soneFollowingTimes.entrySet()) {
+                               configuration.getStringValue("SoneFollowingTimes/" + soneCounter + "/Sone").setValue(soneFollowingTime.getKey());
+                               configuration.getLongValue("SoneFollowingTimes/" + soneCounter + "/Time").setValue(soneFollowingTime.getValue());
+                               ++soneCounter;
+                       }
+                       configuration.getStringValue("SoneFollowingTimes/" + soneCounter + "/Sone").setValue(null);
+               } catch (ConfigurationException ce1) {
+                       logger.log(WARNING, "Could not save Sone following times!", ce1);
+               }
+       }
+
        private Set<String> loadIds(String prefix) {
-               Set<String> ids = new HashSet<String>();
+               Set<String> ids = new HashSet<>();
                int idCounter = 0;
                while (true) {
                        String id = configuration
index 4c4bc3f..1a4e119 100644 (file)
@@ -16,15 +16,13 @@ import com.google.common.base.Function;
 
 /**
  * Memory-based {@link BookmarkDatabase} implementation.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class MemoryBookmarkDatabase implements BookmarkDatabase {
 
        private final ReadWriteLock lock = new ReentrantReadWriteLock();
        private final MemoryDatabase memoryDatabase;
        private final ConfigurationLoader configurationLoader;
-       private final Set<String> bookmarkedPosts = new HashSet<String>();
+       private final Set<String> bookmarkedPosts = new HashSet<>();
 
        public MemoryBookmarkDatabase(MemoryDatabase memoryDatabase,
                        ConfigurationLoader configurationLoader) {
diff --git a/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java b/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java
deleted file mode 100644 (file)
index 5e5021c..0000000
+++ /dev/null
@@ -1,762 +0,0 @@
-/*
- * 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
- * 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.database.memory;
-
-import static com.google.common.base.Optional.fromNullable;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Predicates.not;
-import static com.google.common.collect.FluentIterable.from;
-import static net.pterodactylus.sone.data.Reply.TIME_COMPARATOR;
-import static net.pterodactylus.sone.data.Sone.LOCAL_SONE_FILTER;
-import static net.pterodactylus.sone.data.Sone.toAllAlbums;
-import static net.pterodactylus.sone.data.Sone.toAllImages;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import net.pterodactylus.sone.data.Album;
-import net.pterodactylus.sone.data.Image;
-import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.PostReply;
-import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.sone.data.impl.AlbumBuilderImpl;
-import net.pterodactylus.sone.data.impl.ImageBuilderImpl;
-import net.pterodactylus.sone.database.AlbumBuilder;
-import net.pterodactylus.sone.database.Database;
-import net.pterodactylus.sone.database.DatabaseException;
-import net.pterodactylus.sone.database.ImageBuilder;
-import net.pterodactylus.sone.database.PostBuilder;
-import net.pterodactylus.sone.database.PostDatabase;
-import net.pterodactylus.sone.database.PostReplyBuilder;
-import net.pterodactylus.sone.database.SoneBuilder;
-import net.pterodactylus.sone.database.SoneProvider;
-import net.pterodactylus.util.config.Configuration;
-import net.pterodactylus.util.config.ConfigurationException;
-
-import com.google.common.base.Predicate;
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.SortedSetMultimap;
-import com.google.common.collect.TreeMultimap;
-import com.google.common.util.concurrent.AbstractService;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import kotlin.jvm.functions.Function1;
-
-/**
- * Memory-based {@link PostDatabase} implementation.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-@Singleton
-public class MemoryDatabase extends AbstractService implements Database {
-
-       /** The lock. */
-       private final ReadWriteLock lock = new ReentrantReadWriteLock();
-
-       /** The Sone provider. */
-       private final SoneProvider soneProvider;
-
-       /** The configuration. */
-       private final Configuration configuration;
-       private final ConfigurationLoader configurationLoader;
-
-       private final Map<String, Sone> allSones = new HashMap<String, Sone>();
-
-       /** All posts by their ID. */
-       private final Map<String, Post> allPosts = new HashMap<String, Post>();
-
-       /** All posts by their Sones. */
-       private final Multimap<String, Post> sonePosts = HashMultimap.create();
-
-       /** Whether posts are known. */
-       private final Set<String> knownPosts = new HashSet<String>();
-
-       /** All post replies by their ID. */
-       private final Map<String, PostReply> allPostReplies = new HashMap<String, PostReply>();
-
-       /** Replies sorted by Sone. */
-       private final SortedSetMultimap<String, PostReply> sonePostReplies = TreeMultimap.create(new Comparator<String>() {
-
-               @Override
-               public int compare(String leftString, String rightString) {
-                       return leftString.compareTo(rightString);
-               }
-       }, TIME_COMPARATOR);
-
-       /** Whether post replies are known. */
-       private final Set<String> knownPostReplies = new HashSet<String>();
-
-       private final Map<String, Album> allAlbums = new HashMap<String, Album>();
-       private final Multimap<String, Album> soneAlbums = HashMultimap.create();
-
-       private final Map<String, Image> allImages = new HashMap<String, Image>();
-       private final Multimap<String, Image> soneImages = HashMultimap.create();
-
-       private final MemoryBookmarkDatabase memoryBookmarkDatabase;
-       private final MemoryFriendDatabase memoryFriendDatabase;
-
-       /**
-        * Creates a new memory database.
-        *
-        * @param soneProvider
-        *              The Sone provider
-        * @param configuration
-        *              The configuration for loading and saving elements
-        */
-       @Inject
-       public MemoryDatabase(SoneProvider soneProvider, Configuration configuration) {
-               this.soneProvider = soneProvider;
-               this.configuration = configuration;
-               this.configurationLoader = new ConfigurationLoader(configuration);
-               memoryBookmarkDatabase =
-                               new MemoryBookmarkDatabase(this, configurationLoader);
-               memoryFriendDatabase = new MemoryFriendDatabase(configurationLoader);
-       }
-
-       //
-       // DATABASE METHODS
-       //
-
-       /**
-        * Saves the database.
-        *
-        * @throws DatabaseException
-        *              if an error occurs while saving
-        */
-       @Override
-       public void save() throws DatabaseException {
-               saveKnownPosts();
-               saveKnownPostReplies();
-       }
-
-       //
-       // SERVICE METHODS
-       //
-
-       /** {@inheritDocs} */
-       @Override
-       protected void doStart() {
-               memoryBookmarkDatabase.start();
-               loadKnownPosts();
-               loadKnownPostReplies();
-               notifyStarted();
-       }
-
-       /** {@inheritDocs} */
-       @Override
-       protected void doStop() {
-               try {
-                       memoryBookmarkDatabase.stop();
-                       save();
-                       notifyStopped();
-               } catch (DatabaseException de1) {
-                       notifyFailed(de1);
-               }
-       }
-
-       @Override
-       public SoneBuilder newSoneBuilder() {
-               return new MemorySoneBuilder(this);
-       }
-
-       @Override
-       public void storeSone(Sone sone) {
-               lock.writeLock().lock();
-               try {
-                       removeSone(sone);
-
-                       allSones.put(sone.getId(), sone);
-                       sonePosts.putAll(sone.getId(), sone.getPosts());
-                       for (Post post : sone.getPosts()) {
-                               allPosts.put(post.getId(), post);
-                       }
-                       sonePostReplies.putAll(sone.getId(), sone.getReplies());
-                       for (PostReply postReply : sone.getReplies()) {
-                               allPostReplies.put(postReply.getId(), postReply);
-                       }
-                       soneAlbums.putAll(sone.getId(), toAllAlbums.apply(sone));
-                       for (Album album : toAllAlbums.apply(sone)) {
-                               allAlbums.put(album.getId(), album);
-                       }
-                       soneImages.putAll(sone.getId(), toAllImages.apply(sone));
-                       for (Image image : toAllImages.apply(sone)) {
-                               allImages.put(image.getId(), image);
-                       }
-               } finally {
-                       lock.writeLock().unlock();
-               }
-       }
-
-       @Override
-       public void removeSone(Sone sone) {
-               lock.writeLock().lock();
-               try {
-                       allSones.remove(sone.getId());
-                       Collection<Post> removedPosts = sonePosts.removeAll(sone.getId());
-                       for (Post removedPost : removedPosts) {
-                               allPosts.remove(removedPost.getId());
-                       }
-                       Collection<PostReply> removedPostReplies =
-                                       sonePostReplies.removeAll(sone.getId());
-                       for (PostReply removedPostReply : removedPostReplies) {
-                               allPostReplies.remove(removedPostReply.getId());
-                       }
-                       Collection<Album> removedAlbums =
-                                       soneAlbums.removeAll(sone.getId());
-                       for (Album removedAlbum : removedAlbums) {
-                               allAlbums.remove(removedAlbum.getId());
-                       }
-                       Collection<Image> removedImages =
-                                       soneImages.removeAll(sone.getId());
-                       for (Image removedImage : removedImages) {
-                               allImages.remove(removedImage.getId());
-                       }
-               } finally {
-                       lock.writeLock().unlock();
-               }
-       }
-
-       @Nonnull
-       @Override
-       public Function1<String, Sone> getSoneLoader() {
-               return new Function1<String, Sone>() {
-                       @Override
-                       public Sone invoke(String soneId) {
-                               return getSone(soneId);
-                       }
-               };
-       }
-
-       @Override
-       public Sone getSone(String soneId) {
-               lock.readLock().lock();
-               try {
-                       return allSones.get(soneId);
-               } finally {
-                       lock.readLock().unlock();
-               }
-       }
-
-       @Override
-       public Collection<Sone> getSones() {
-               lock.readLock().lock();
-               try {
-                       return new HashSet<Sone>(allSones.values());
-               } finally {
-                       lock.readLock().unlock();
-               }
-       }
-
-       @Override
-       public Collection<Sone> getLocalSones() {
-               lock.readLock().lock();
-               try {
-                       return from(allSones.values()).filter(LOCAL_SONE_FILTER).toSet();
-               } finally {
-                       lock.readLock().unlock();
-               }
-       }
-
-       @Override
-       public Collection<Sone> getRemoteSones() {
-               lock.readLock().lock();
-               try {
-                       return from(allSones.values())
-                                       .filter(not(LOCAL_SONE_FILTER)) .toSet();
-               } finally {
-                       lock.readLock().unlock();
-               }
-       }
-
-       @Override
-       public Collection<String> getFriends(Sone localSone) {
-               if (!localSone.isLocal()) {
-                       return Collections.emptySet();
-               }
-               return memoryFriendDatabase.getFriends(localSone.getId());
-       }
-
-       @Override
-       public boolean isFriend(Sone localSone, String friendSoneId) {
-               if (!localSone.isLocal()) {
-                       return false;
-               }
-               return memoryFriendDatabase.isFriend(localSone.getId(), friendSoneId);
-       }
-
-       @Override
-       public void addFriend(Sone localSone, String friendSoneId) {
-               if (!localSone.isLocal()) {
-                       return;
-               }
-               memoryFriendDatabase.addFriend(localSone.getId(), friendSoneId);
-       }
-
-       @Override
-       public void removeFriend(Sone localSone, String friendSoneId) {
-               if (!localSone.isLocal()) {
-                       return;
-               }
-               memoryFriendDatabase.removeFriend(localSone.getId(), friendSoneId);
-       }
-
-       //
-       // POSTPROVIDER METHODS
-       //
-
-       @Nullable
-       @Override
-       public Post getPost(@Nonnull String postId) {
-               lock.readLock().lock();
-               try {
-                       return allPosts.get(postId);
-               } finally {
-                       lock.readLock().unlock();
-               }
-       }
-
-       /** {@inheritDocs} */
-       @Override
-       public Collection<Post> getPosts(String soneId) {
-               return new HashSet<Post>(getPostsFrom(soneId));
-       }
-
-       /** {@inheritDocs} */
-       @Override
-       public Collection<Post> getDirectedPosts(final String recipientId) {
-               lock.readLock().lock();
-               try {
-                       return from(sonePosts.values()).filter(new Predicate<Post>() {
-                               @Override
-                               public boolean apply(Post post) {
-                                       return post.getRecipientId().asSet().contains(recipientId);
-                               }
-                       }).toSet();
-               } finally {
-                       lock.readLock().unlock();
-               }
-       }
-
-       //
-       // POSTBUILDERFACTORY METHODS
-       //
-
-       /** {@inheritDocs} */
-       @Override
-       public PostBuilder newPostBuilder() {
-               return new MemoryPostBuilder(this, soneProvider);
-       }
-
-       //
-       // POSTSTORE METHODS
-       //
-
-       /** {@inheritDocs} */
-       @Override
-       public void storePost(Post post) {
-               checkNotNull(post, "post must not be null");
-               lock.writeLock().lock();
-               try {
-                       allPosts.put(post.getId(), post);
-                       getPostsFrom(post.getSone().getId()).add(post);
-               } finally {
-                       lock.writeLock().unlock();
-               }
-       }
-
-       /** {@inheritDocs} */
-       @Override
-       public void removePost(Post post) {
-               checkNotNull(post, "post must not be null");
-               lock.writeLock().lock();
-               try {
-                       allPosts.remove(post.getId());
-                       getPostsFrom(post.getSone().getId()).remove(post);
-                       post.getSone().removePost(post);
-               } finally {
-                       lock.writeLock().unlock();
-               }
-       }
-
-       //
-       // POSTREPLYPROVIDER METHODS
-       //
-
-       @Nullable
-       @Override
-       public PostReply getPostReply(String id) {
-               lock.readLock().lock();
-               try {
-                       return allPostReplies.get(id);
-               } finally {
-                       lock.readLock().unlock();
-               }
-       }
-
-       /** {@inheritDocs} */
-       @Override
-       public List<PostReply> getReplies(final String postId) {
-               lock.readLock().lock();
-               try {
-                       return from(allPostReplies.values())
-                                       .filter(new Predicate<PostReply>() {
-                                               @Override
-                                               public boolean apply(PostReply postReply) {
-                                                       return postReply.getPostId().equals(postId);
-                                               }
-                                       }).toSortedList(TIME_COMPARATOR);
-               } finally {
-                       lock.readLock().unlock();
-               }
-       }
-
-       //
-       // POSTREPLYBUILDERFACTORY METHODS
-       //
-
-       /** {@inheritDocs} */
-       @Override
-       public PostReplyBuilder newPostReplyBuilder() {
-               return new MemoryPostReplyBuilder(this, soneProvider);
-       }
-
-       //
-       // POSTREPLYSTORE METHODS
-       //
-
-       /** {@inheritDocs} */
-       @Override
-       public void storePostReply(PostReply postReply) {
-               lock.writeLock().lock();
-               try {
-                       allPostReplies.put(postReply.getId(), postReply);
-               } finally {
-                       lock.writeLock().unlock();
-               }
-       }
-
-       /** {@inheritDocs} */
-       @Override
-       public void removePostReply(PostReply postReply) {
-               lock.writeLock().lock();
-               try {
-                       allPostReplies.remove(postReply.getId());
-               } finally {
-                       lock.writeLock().unlock();
-               }
-       }
-
-       //
-       // ALBUMPROVDER METHODS
-       //
-
-       @Nullable
-       @Override
-       public Album getAlbum(@Nonnull String albumId) {
-               lock.readLock().lock();
-               try {
-                       return allAlbums.get(albumId);
-               } finally {
-                       lock.readLock().unlock();
-               }
-       }
-
-       //
-       // ALBUMBUILDERFACTORY METHODS
-       //
-
-       @Override
-       public AlbumBuilder newAlbumBuilder() {
-               return new AlbumBuilderImpl();
-       }
-
-       //
-       // ALBUMSTORE METHODS
-       //
-
-       @Override
-       public void storeAlbum(Album album) {
-               lock.writeLock().lock();
-               try {
-                       allAlbums.put(album.getId(), album);
-                       soneAlbums.put(album.getSone().getId(), album);
-               } finally {
-                       lock.writeLock().unlock();
-               }
-       }
-
-       @Override
-       public void removeAlbum(Album album) {
-               lock.writeLock().lock();
-               try {
-                       allAlbums.remove(album.getId());
-                       soneAlbums.remove(album.getSone().getId(), album);
-               } finally {
-                       lock.writeLock().unlock();
-               }
-       }
-
-       //
-       // IMAGEPROVIDER METHODS
-       //
-
-       @Nullable
-       @Override
-       public Image getImage(@Nonnull String imageId) {
-               lock.readLock().lock();
-               try {
-                       return allImages.get(imageId);
-               } finally {
-                       lock.readLock().unlock();
-               }
-       }
-
-       //
-       // IMAGEBUILDERFACTORY METHODS
-       //
-
-       @Override
-       public ImageBuilder newImageBuilder() {
-               return new ImageBuilderImpl();
-       }
-
-       //
-       // IMAGESTORE METHODS
-       //
-
-       @Override
-       public void storeImage(Image image) {
-               lock.writeLock().lock();
-               try {
-                       allImages.put(image.getId(), image);
-                       soneImages.put(image.getSone().getId(), image);
-               } finally {
-                       lock.writeLock().unlock();
-               }
-       }
-
-       @Override
-       public void removeImage(Image image) {
-               lock.writeLock().lock();
-               try {
-                       allImages.remove(image.getId());
-                       soneImages.remove(image.getSone().getId(), image);
-               } finally {
-                       lock.writeLock().unlock();
-               }
-       }
-
-       @Override
-       public void bookmarkPost(Post post) {
-               memoryBookmarkDatabase.bookmarkPost(post);
-       }
-
-       @Override
-       public void unbookmarkPost(Post post) {
-               memoryBookmarkDatabase.unbookmarkPost(post);
-       }
-
-       @Override
-       public boolean isPostBookmarked(Post post) {
-               return memoryBookmarkDatabase.isPostBookmarked(post);
-       }
-
-       @Override
-       public Set<Post> getBookmarkedPosts() {
-               return memoryBookmarkDatabase.getBookmarkedPosts();
-       }
-
-       //
-       // PACKAGE-PRIVATE METHODS
-       //
-
-       /**
-        * Returns whether the given post is known.
-        *
-        * @param post
-        *              The post
-        * @return {@code true} if the post is known, {@code false} otherwise
-        */
-       boolean isPostKnown(Post post) {
-               lock.readLock().lock();
-               try {
-                       return knownPosts.contains(post.getId());
-               } finally {
-                       lock.readLock().unlock();
-               }
-       }
-
-       /**
-        * Sets whether the given post is known.
-        *
-        * @param post
-        *              The post
-        * @param known
-        *              {@code true} if the post is known, {@code false} otherwise
-        */
-       void setPostKnown(Post post, boolean known) {
-               lock.writeLock().lock();
-               try {
-                       if (known) {
-                               knownPosts.add(post.getId());
-                       } else {
-                               knownPosts.remove(post.getId());
-                       }
-               } finally {
-                       lock.writeLock().unlock();
-               }
-       }
-
-       /**
-        * Returns whether the given post reply is known.
-        *
-        * @param postReply
-        *              The post reply
-        * @return {@code true} if the given post reply is known, {@code false}
-        *         otherwise
-        */
-       boolean isPostReplyKnown(PostReply postReply) {
-               lock.readLock().lock();
-               try {
-                       return knownPostReplies.contains(postReply.getId());
-               } finally {
-                       lock.readLock().unlock();
-               }
-       }
-
-       /**
-        * Sets whether the given post reply is known.
-        *
-        * @param postReply
-        *              The post reply
-        * @param known
-        *              {@code true} if the post reply is known, {@code false} otherwise
-        */
-       void setPostReplyKnown(PostReply postReply, boolean known) {
-               lock.writeLock().lock();
-               try {
-                       if (known) {
-                               knownPostReplies.add(postReply.getId());
-                       } else {
-                               knownPostReplies.remove(postReply.getId());
-                       }
-               } finally {
-                       lock.writeLock().unlock();
-               }
-       }
-
-       //
-       // PRIVATE METHODS
-       //
-
-       /**
-        * Gets all posts for the given Sone, creating a new collection if there is
-        * none yet.
-        *
-        * @param soneId
-        *              The ID of the Sone to get the posts for
-        * @return All posts
-        */
-       private Collection<Post> getPostsFrom(String soneId) {
-               lock.readLock().lock();
-               try {
-                       return sonePosts.get(soneId);
-               } finally {
-                       lock.readLock().unlock();
-               }
-       }
-
-       /** Loads the known posts. */
-       private void loadKnownPosts() {
-               Set<String> knownPosts = configurationLoader.loadKnownPosts();
-               lock.writeLock().lock();
-               try {
-                       this.knownPosts.clear();
-                       this.knownPosts.addAll(knownPosts);
-               } finally {
-                       lock.writeLock().unlock();
-               }
-       }
-
-       /**
-        * Saves the known posts to the configuration.
-        *
-        * @throws DatabaseException
-        *              if a configuration error occurs
-        */
-       private void saveKnownPosts() throws DatabaseException {
-               lock.readLock().lock();
-               try {
-                       int postCounter = 0;
-                       for (String knownPostId : knownPosts) {
-                               configuration.getStringValue("KnownPosts/" + postCounter++ + "/ID").setValue(knownPostId);
-                       }
-                       configuration.getStringValue("KnownPosts/" + postCounter + "/ID").setValue(null);
-               } catch (ConfigurationException ce1) {
-                       throw new DatabaseException("Could not save database.", ce1);
-               } finally {
-                       lock.readLock().unlock();
-               }
-       }
-
-       /** Loads the known post replies. */
-       private void loadKnownPostReplies() {
-               Set<String> knownPostReplies = configurationLoader.loadKnownPostReplies();
-               lock.writeLock().lock();
-               try {
-                       this.knownPostReplies.clear();
-                       this.knownPostReplies.addAll(knownPostReplies);
-               } finally {
-                       lock.writeLock().unlock();
-               }
-       }
-
-       /**
-        * Saves the known post replies to the configuration.
-        *
-        * @throws DatabaseException
-        *              if a configuration error occurs
-        */
-       private void saveKnownPostReplies() throws DatabaseException {
-               lock.readLock().lock();
-               try {
-                       int replyCounter = 0;
-                       for (String knownReplyId : knownPostReplies) {
-                               configuration.getStringValue("KnownReplies/" + replyCounter++ + "/ID").setValue(knownReplyId);
-                       }
-                       configuration.getStringValue("KnownReplies/" + replyCounter + "/ID").setValue(null);
-               } catch (ConfigurationException ce1) {
-                       throw new DatabaseException("Could not save database.", ce1);
-               } finally {
-                       lock.readLock().unlock();
-               }
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.kt b/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.kt
new file mode 100644 (file)
index 0000000..6fce7e0
--- /dev/null
@@ -0,0 +1,345 @@
+/*
+ * Sone - MemoryDatabase.kt - Copyright © 2013–2019 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.database.memory
+
+import com.google.common.base.Preconditions.checkNotNull
+import com.google.common.collect.HashMultimap
+import com.google.common.collect.Multimap
+import com.google.common.collect.TreeMultimap
+import com.google.common.util.concurrent.AbstractService
+import com.google.inject.Inject
+import com.google.inject.Singleton
+import net.pterodactylus.sone.data.Album
+import net.pterodactylus.sone.data.Image
+import net.pterodactylus.sone.data.Post
+import net.pterodactylus.sone.data.PostReply
+import net.pterodactylus.sone.data.Reply.TIME_COMPARATOR
+import net.pterodactylus.sone.data.Sone
+import net.pterodactylus.sone.data.Sone.toAllAlbums
+import net.pterodactylus.sone.data.Sone.toAllImages
+import net.pterodactylus.sone.data.impl.AlbumBuilderImpl
+import net.pterodactylus.sone.data.impl.ImageBuilderImpl
+import net.pterodactylus.sone.database.AlbumBuilder
+import net.pterodactylus.sone.database.Database
+import net.pterodactylus.sone.database.DatabaseException
+import net.pterodactylus.sone.database.ImageBuilder
+import net.pterodactylus.sone.database.PostBuilder
+import net.pterodactylus.sone.database.PostDatabase
+import net.pterodactylus.sone.database.PostReplyBuilder
+import net.pterodactylus.sone.utils.unit
+import net.pterodactylus.util.config.Configuration
+import net.pterodactylus.util.config.ConfigurationException
+import java.util.concurrent.locks.ReentrantReadWriteLock
+import kotlin.concurrent.withLock
+
+/**
+ * Memory-based [PostDatabase] implementation.
+ */
+@Singleton
+class MemoryDatabase @Inject constructor(private val configuration: Configuration) : AbstractService(), Database {
+
+       private val lock = ReentrantReadWriteLock()
+       private val readLock by lazy { lock.readLock()!! }
+       private val writeLock by lazy { lock.writeLock()!! }
+       private val configurationLoader = ConfigurationLoader(configuration)
+       private val allSones = mutableMapOf<String, Sone>()
+       private val allPosts = mutableMapOf<String, Post>()
+       private val sonePosts: Multimap<String, Post> = HashMultimap.create<String, Post>()
+       private val knownPosts = mutableSetOf<String>()
+       private val allPostReplies = mutableMapOf<String, PostReply>()
+       private val sonePostReplies: Multimap<String, PostReply> = TreeMultimap.create<String, PostReply>(Comparator { leftString, rightString -> leftString.compareTo(rightString) }, TIME_COMPARATOR)
+       private val knownPostReplies = mutableSetOf<String>()
+       private val allAlbums = mutableMapOf<String, Album>()
+       private val soneAlbums: Multimap<String, Album> = HashMultimap.create<String, Album>()
+       private val allImages = mutableMapOf<String, Image>()
+       private val soneImages: Multimap<String, Image> = HashMultimap.create<String, Image>()
+       private val memoryBookmarkDatabase = MemoryBookmarkDatabase(this, configurationLoader)
+       private val memoryFriendDatabase = MemoryFriendDatabase(configurationLoader)
+
+       override val soneLoader get() = this::getSone
+
+       override val sones get() = readLock.withLock { allSones.values.toSet() }
+
+       override val localSones get() = readLock.withLock { allSones.values.filter(Sone::isLocal) }
+
+       override val remoteSones get() = readLock.withLock { allSones.values.filterNot(Sone::isLocal) }
+
+       override val bookmarkedPosts get() = memoryBookmarkDatabase.bookmarkedPosts
+
+       override fun save() {
+               saveKnownPosts()
+               saveKnownPostReplies()
+       }
+
+       override fun doStart() {
+               memoryBookmarkDatabase.start()
+               loadKnownPosts()
+               loadKnownPostReplies()
+               notifyStarted()
+       }
+
+       override fun doStop() {
+               try {
+                       memoryBookmarkDatabase.stop()
+                       save()
+                       notifyStopped()
+               } catch (de1: DatabaseException) {
+                       notifyFailed(de1)
+               }
+       }
+
+       override fun newSoneBuilder() = MemorySoneBuilder(this)
+
+       override fun storeSone(sone: Sone) {
+               writeLock.withLock {
+                       removeSone(sone)
+
+                       allSones[sone.id] = sone
+                       sonePosts.putAll(sone.id, sone.posts)
+                       for (post in sone.posts) {
+                               allPosts[post.id] = post
+                       }
+                       sonePostReplies.putAll(sone.id, sone.replies)
+                       for (postReply in sone.replies) {
+                               allPostReplies[postReply.id] = postReply
+                       }
+                       soneAlbums.putAll(sone.id, toAllAlbums.apply(sone)!!)
+                       for (album in toAllAlbums.apply(sone)!!) {
+                               allAlbums[album.id] = album
+                       }
+                       soneImages.putAll(sone.id, toAllImages.apply(sone)!!)
+                       for (image in toAllImages.apply(sone)!!) {
+                               allImages[image.id] = image
+                       }
+               }
+       }
+
+       override fun removeSone(sone: Sone) {
+               writeLock.withLock {
+                       allSones.remove(sone.id)
+                       val removedPosts = sonePosts.removeAll(sone.id)
+                       for (removedPost in removedPosts) {
+                               allPosts.remove(removedPost.id)
+                       }
+                       val removedPostReplies = sonePostReplies.removeAll(sone.id)
+                       for (removedPostReply in removedPostReplies) {
+                               allPostReplies.remove(removedPostReply.id)
+                       }
+                       val removedAlbums = soneAlbums.removeAll(sone.id)
+                       for (removedAlbum in removedAlbums) {
+                               allAlbums.remove(removedAlbum.id)
+                       }
+                       val removedImages = soneImages.removeAll(sone.id)
+                       for (removedImage in removedImages) {
+                               allImages.remove(removedImage.id)
+                       }
+               }
+       }
+
+       override fun getSone(soneId: String) = readLock.withLock { allSones[soneId] }
+
+       override fun getFriends(localSone: Sone): Collection<String> =
+                       if (!localSone.isLocal) {
+                               emptySet()
+                       } else {
+                               memoryFriendDatabase.getFriends(localSone.id)
+                       }
+
+       override fun isFriend(localSone: Sone, friendSoneId: String) =
+                       if (!localSone.isLocal) {
+                               false
+                       } else {
+                               memoryFriendDatabase.isFriend(localSone.id, friendSoneId)
+                       }
+
+       override fun addFriend(localSone: Sone, friendSoneId: String) {
+               if (!localSone.isLocal) {
+                       return
+               }
+               memoryFriendDatabase.addFriend(localSone.id, friendSoneId)
+       }
+
+       override fun removeFriend(localSone: Sone, friendSoneId: String) {
+               if (!localSone.isLocal) {
+                       return
+               }
+               memoryFriendDatabase.removeFriend(localSone.id, friendSoneId)
+       }
+
+       override fun getFollowingTime(friendSoneId: String) =
+                       memoryFriendDatabase.getFollowingTime(friendSoneId)
+
+       override fun getPost(postId: String) =
+                       readLock.withLock { allPosts[postId] }
+
+       override fun getPosts(soneId: String): Collection<Post> =
+                       sonePosts[soneId].toSet()
+
+       override fun getDirectedPosts(recipientId: String) =
+                       readLock.withLock {
+                               allPosts.values.filter {
+                                       it.recipientId.orNull() == recipientId
+                               }
+                       }
+
+       override fun newPostBuilder(): PostBuilder = MemoryPostBuilder(this, this)
+
+       override fun storePost(post: Post) {
+               checkNotNull(post, "post must not be null")
+               writeLock.withLock {
+                       allPosts[post.id] = post
+                       sonePosts[post.sone.id].add(post)
+               }
+       }
+
+       override fun removePost(post: Post) {
+               checkNotNull(post, "post must not be null")
+               writeLock.withLock {
+                       allPosts.remove(post.id)
+                       sonePosts[post.sone.id].remove(post)
+                       post.sone.removePost(post)
+               }
+       }
+
+       override fun getPostReply(id: String) = readLock.withLock { allPostReplies[id] }
+
+       override fun getReplies(postId: String) =
+                       readLock.withLock {
+                               allPostReplies.values
+                                               .filter { it.postId == postId }
+                                               .sortedWith(TIME_COMPARATOR)
+                       }
+
+       override fun newPostReplyBuilder(): PostReplyBuilder =
+                       MemoryPostReplyBuilder(this, this)
+
+       override fun storePostReply(postReply: PostReply) =
+                       writeLock.withLock {
+                               allPostReplies[postReply.id] = postReply
+                       }
+
+       override fun removePostReply(postReply: PostReply) =
+                       writeLock.withLock {
+                               allPostReplies.remove(postReply.id)
+                       }.unit
+
+       override fun getAlbum(albumId: String) = readLock.withLock { allAlbums[albumId] }
+
+       override fun newAlbumBuilder(): AlbumBuilder = AlbumBuilderImpl()
+
+       override fun storeAlbum(album: Album) =
+                       writeLock.withLock {
+                               allAlbums[album.id] = album
+                               soneAlbums.put(album.sone.id, album)
+                       }.unit
+
+       override fun removeAlbum(album: Album) =
+                       writeLock.withLock {
+                               allAlbums.remove(album.id)
+                               soneAlbums.remove(album.sone.id, album)
+                       }.unit
+
+       override fun getImage(imageId: String) = readLock.withLock { allImages[imageId] }
+
+       override fun newImageBuilder(): ImageBuilder = ImageBuilderImpl()
+
+       override fun storeImage(image: Image): Unit =
+                       writeLock.withLock {
+                               allImages[image.id] = image
+                               soneImages.put(image.sone.id, image)
+                       }
+
+       override fun removeImage(image: Image): Unit =
+                       writeLock.withLock {
+                               allImages.remove(image.id)
+                               soneImages.remove(image.sone.id, image)
+                       }
+
+       override fun bookmarkPost(post: Post) =
+                       memoryBookmarkDatabase.bookmarkPost(post)
+
+       override fun unbookmarkPost(post: Post) =
+                       memoryBookmarkDatabase.unbookmarkPost(post)
+
+       override fun isPostBookmarked(post: Post) =
+                       memoryBookmarkDatabase.isPostBookmarked(post)
+
+       protected fun isPostKnown(post: Post) = readLock.withLock { post.id in knownPosts }
+
+       fun setPostKnown(post: Post, known: Boolean): Unit =
+                       writeLock.withLock {
+                               if (known)
+                                       knownPosts.add(post.id)
+                               else
+                                       knownPosts.remove(post.id)
+                               saveKnownPosts()
+                       }
+
+       protected fun isPostReplyKnown(postReply: PostReply) = readLock.withLock { postReply.id in knownPostReplies }
+
+       fun setPostReplyKnown(postReply: PostReply, known: Boolean): Unit =
+                       writeLock.withLock {
+                               if (known)
+                                       knownPostReplies.add(postReply.id)
+                               else
+                                       knownPostReplies.remove(postReply.id)
+                               saveKnownPostReplies()
+                       }
+
+       private fun loadKnownPosts() =
+                       configurationLoader.loadKnownPosts()
+                                       .let {
+                                               writeLock.withLock {
+                                                       knownPosts.clear()
+                                                       knownPosts.addAll(it)
+                                               }
+                                       }
+
+       private fun saveKnownPosts() =
+                       try {
+                               readLock.withLock {
+                                       knownPosts.forEachIndexed { index, knownPostId ->
+                                               configuration.getStringValue("KnownPosts/$index/ID").value = knownPostId
+                                       }
+                                       configuration.getStringValue("KnownPosts/${knownPosts.size}/ID").value = null
+                               }
+                       } catch (ce1: ConfigurationException) {
+                               throw DatabaseException("Could not save database.", ce1)
+                       }
+
+       private fun loadKnownPostReplies(): Unit =
+                       configurationLoader.loadKnownPostReplies().let { knownPostReplies ->
+                               writeLock.withLock {
+                                       this.knownPostReplies.clear()
+                                       this.knownPostReplies.addAll(knownPostReplies)
+                               }
+                       }
+
+       private fun saveKnownPostReplies() =
+                       try {
+                               readLock.withLock {
+                                       knownPostReplies.forEachIndexed { index, knownPostReply ->
+                                               configuration.getStringValue("KnownReplies/$index/ID").value = knownPostReply
+                                       }
+                                       configuration.getStringValue("KnownReplies/${knownPostReplies.size}/ID").value = null
+                               }
+                       } catch (ce1: ConfigurationException) {
+                               throw DatabaseException("Could not save database.", ce1)
+                       }
+
+}
index 0be8738..6661be0 100644 (file)
@@ -4,13 +4,14 @@ import java.util.Collection;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
 import com.google.common.collect.HashMultimap;
 import com.google.common.collect.Multimap;
 
 /**
  * In-memory implementation of friend-related functionality.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 class MemoryFriendDatabase {
 
@@ -48,6 +49,9 @@ class MemoryFriendDatabase {
                try {
                        if (soneFriends.put(localSoneId, friendSoneId)) {
                                configurationLoader.saveFriends(localSoneId, soneFriends.get(localSoneId));
+                               if (configurationLoader.getSoneFollowingTime(friendSoneId) == null) {
+                                       configurationLoader.setSoneFollowingTime(friendSoneId, System.currentTimeMillis());
+                               }
                        }
                } finally {
                        lock.writeLock().unlock();
@@ -60,12 +64,24 @@ class MemoryFriendDatabase {
                try {
                        if (soneFriends.remove(localSoneId, friendSoneId)) {
                                configurationLoader.saveFriends(localSoneId, soneFriends.get(localSoneId));
+                               boolean unfollowedSoneStillFollowed = false;
+                               for (String soneId : soneFriends.keys()) {
+                                       unfollowedSoneStillFollowed |= getFriends(soneId).contains(friendSoneId);
+                               }
+                               if (!unfollowedSoneStillFollowed) {
+                                       configurationLoader.removeSoneFollowingTime(friendSoneId);
+                               }
                        }
                } finally {
                        lock.writeLock().unlock();
                }
        }
 
+       @Nullable
+       Long getFollowingTime(@Nonnull String soneId) {
+               return configurationLoader.getSoneFollowingTime(soneId);
+       }
+
        private void loadFriends(String localSoneId) {
                lock.writeLock().lock();
                try {
index 724c627..30fc2b0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - MemoryPost.java - Copyright © 2010–2016 David Roden
+ * Sone - MemoryPost.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -30,8 +30,6 @@ import com.google.common.base.Optional;
 /**
  * A post is a short message that a user writes in his Sone to let other users
  * know what is going on.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 class MemoryPost implements Post {
 
@@ -122,7 +120,7 @@ class MemoryPost implements Post {
         */
        @Override
        public Optional<Sone> getRecipient() {
-               return fromNullable(soneProvider.getSone(recipientId));
+               return recipientId == null ? Optional.<Sone>absent() : fromNullable(soneProvider.getSone(recipientId));
        }
 
        /**
index 7125167..dea3449 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - MemoryPostBuilder.java - Copyright © 2013–2016 David Roden
+ * Sone - MemoryPostBuilder.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -26,8 +26,6 @@ import net.pterodactylus.sone.database.SoneProvider;
 
 /**
  * {@link PostBuilder} implementation that creates a {@link MemoryPost}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 class MemoryPostBuilder extends AbstractPostBuilder {
 
index 687c28a..40f0775 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - MemoryPostReply.java - Copyright © 2013–2016 David Roden
+ * Sone - MemoryPostReply.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -28,8 +28,6 @@ import com.google.common.base.Optional;
 
 /**
  * Memory-based {@link PostReply} implementation.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 class MemoryPostReply implements PostReply {
 
index 59e098c..3bcfe33 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - MemoryPostReplyBuilder.java - Copyright © 2013–2016 David Roden
+ * Sone - MemoryPostReplyBuilder.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -27,8 +27,6 @@ import net.pterodactylus.sone.database.SoneProvider;
 /**
  * {@link PostReplyBuilder} implementation that creates {@link MemoryPostReply}
  * objects.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 class MemoryPostReplyBuilder extends AbstractPostReplyBuilder {
 
index 49531a1..14997dd 100644 (file)
@@ -7,8 +7,6 @@ import net.pterodactylus.sone.database.Database;
 
 /**
  * Memory-based {@link AbstractSoneBuilder} implementation.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class MemorySoneBuilder extends AbstractSoneBuilder {
 
diff --git a/src/main/java/net/pterodactylus/sone/fcp/AbstractSoneCommand.java b/src/main/java/net/pterodactylus/sone/fcp/AbstractSoneCommand.java
deleted file mode 100644 (file)
index 9942af7..0000000
+++ /dev/null
@@ -1,404 +0,0 @@
-/*
- * 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
- * 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.fcp;
-
-import java.util.Collection;
-import java.util.List;
-
-import net.pterodactylus.sone.core.Core;
-import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.PostReply;
-import net.pterodactylus.sone.data.Profile;
-import net.pterodactylus.sone.data.Profile.Field;
-import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.sone.freenet.SimpleFieldSetBuilder;
-import net.pterodactylus.sone.freenet.fcp.AbstractCommand;
-import net.pterodactylus.sone.freenet.fcp.Command;
-import net.pterodactylus.sone.freenet.fcp.FcpException;
-import net.pterodactylus.sone.template.SoneAccessor;
-
-import freenet.node.FSParseException;
-import freenet.support.SimpleFieldSet;
-
-import com.google.common.base.Optional;
-
-/**
- * Abstract base implementation of a {@link Command} with Sone-related helper
- * methods.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public abstract class AbstractSoneCommand extends AbstractCommand {
-
-       /** The Sone core. */
-       private final Core core;
-
-       /** Whether this command needs write access. */
-       private final boolean writeAccess;
-
-       /**
-        * Creates a new abstract Sone FCP command.
-        *
-        * @param core
-        *            The Sone core
-        */
-       protected AbstractSoneCommand(Core core) {
-               this(core, false);
-       }
-
-       /**
-        * Creates a new abstract Sone FCP command.
-        *
-        * @param core
-        *            The Sone core
-        * @param writeAccess
-        *            {@code true} if this command requires write access,
-        *            {@code false} otherwise
-        */
-       protected AbstractSoneCommand(Core core, boolean writeAccess) {
-               this.core = core;
-               this.writeAccess = writeAccess;
-       }
-
-       //
-       // ACCESSORS
-       //
-
-       /**
-        * Returns the Sone core.
-        *
-        * @return The Sone core
-        */
-       protected Core getCore() {
-               return core;
-       }
-
-       /**
-        * Returns whether this command requires write access.
-        *
-        * @return {@code true} if this command require write access, {@code false}
-        *         otherwise
-        */
-       public boolean requiresWriteAccess() {
-               return writeAccess;
-       }
-
-       //
-       // PROTECTED METHODS
-       //
-
-       /**
-        * Encodes text in a way that makes it possible for the text to be stored in
-        * a {@link SimpleFieldSet}. Backslashes, CR, and LF are prepended with a
-        * backslash.
-        *
-        * @param text
-        *            The text to encode
-        * @return The encoded text
-        */
-       protected static String encodeString(String text) {
-               return text.replaceAll("\\\\", "\\\\\\\\").replaceAll("\n", "\\\\n").replaceAll("\r", "\\\\r");
-       }
-
-       /**
-        * Returns a Sone whose ID is a parameter in the given simple field set.
-        *
-        * @param simpleFieldSet
-        *            The simple field set containing the ID of the Sone
-        * @param parameterName
-        *            The name under which the Sone ID is stored in the simple field
-        *            set
-        * @param localOnly
-        *            {@code true} to only return local Sones, {@code false} to
-        *            return any Sones
-        * @return The Sone
-        * @throws FcpException
-        *             if there is no Sone ID stored under the given parameter name,
-        *             or if the Sone ID is invalid
-        */
-       protected Sone getSone(SimpleFieldSet simpleFieldSet, String parameterName, boolean localOnly) throws FcpException {
-               return getSone(simpleFieldSet, parameterName, localOnly, true).get();
-       }
-
-       /**
-        * Returns a Sone whose ID is a parameter in the given simple field set.
-        *
-        * @param simpleFieldSet
-        *            The simple field set containing the ID of the Sone
-        * @param parameterName
-        *            The name under which the Sone ID is stored in the simple field
-        *            set
-        * @param localOnly
-        *            {@code true} to only return local Sones, {@code false} to
-        *            return any Sones
-        * @param mandatory
-        *            {@code true} if a valid Sone ID is required, {@code false}
-        *            otherwise
-        * @return The Sone, or {@code null} if {@code mandatory} is {@code false}
-        *         and the Sone ID is invalid
-        * @throws FcpException
-        *             if there is no Sone ID stored under the given parameter name,
-        *             or if {@code mandatory} is {@code true} and the Sone ID is
-        *             invalid
-        */
-       protected Optional<Sone> getSone(SimpleFieldSet simpleFieldSet, String parameterName, boolean localOnly, boolean mandatory) throws FcpException {
-               String soneId = simpleFieldSet.get(parameterName);
-               if (mandatory && (soneId == null)) {
-                       throw new FcpException("Could not load Sone ID from “" + parameterName + "”.");
-               }
-               Sone sone = core.getSone(soneId);
-               if ((mandatory && (sone == null)) || ((sone != null) && localOnly && !sone.isLocal())) {
-                       throw new FcpException("Could not load Sone from “" + soneId + "”.");
-               }
-               return Optional.fromNullable(sone);
-       }
-
-       /**
-        * Returns a post whose ID is a parameter in the given simple field set.
-        *
-        * @param simpleFieldSet
-        *            The simple field set containing the ID of the post
-        * @param parameterName
-        *            The name under which the post ID is stored in the simple field
-        *            set
-        * @return The post
-        * @throws FcpException
-        *             if there is no post ID stored under the given parameter name,
-        *             or if the post ID is invalid
-        */
-       protected Post getPost(SimpleFieldSet simpleFieldSet, String parameterName) throws FcpException {
-               try {
-                       String postId = simpleFieldSet.getString(parameterName);
-                       Post post = core.getPost(postId);
-                       if (post == null) {
-                               throw new FcpException("Could not load post from “" + postId + "”.");
-                       }
-                       return post;
-               } catch (FSParseException fspe1) {
-                       throw new FcpException("Could not post ID from “" + parameterName + "”.", fspe1);
-               }
-       }
-
-       /**
-        * Returns a reply whose ID is a parameter in the given simple field set.
-        *
-        * @param simpleFieldSet
-        *            The simple field set containing the ID of the reply
-        * @param parameterName
-        *            The name under which the reply ID is stored in the simple
-        *            field set
-        * @return The reply
-        * @throws FcpException
-        *             if there is no reply ID stored under the given parameter
-        *             name, or if the reply ID is invalid
-        */
-       protected PostReply getReply(SimpleFieldSet simpleFieldSet, String parameterName) throws FcpException {
-               try {
-                       String replyId = simpleFieldSet.getString(parameterName);
-                       PostReply reply = core.getPostReply(replyId);
-                       if (reply == null) {
-                               throw new FcpException("Could not load reply from “" + replyId + "”.");
-                       }
-                       return reply;
-               } catch (FSParseException fspe1) {
-                       throw new FcpException("Could not reply ID from “" + parameterName + "”.", fspe1);
-               }
-       }
-
-       /**
-        * Creates a simple field set from the given Sone, including {@link Profile}
-        * information.
-        *
-        * @param sone
-        *            The Sone to encode
-        * @param prefix
-        *            The prefix for the field names (may be empty but not {@code
-        *            null})
-        * @param localSone
-        *            An optional local Sone that is used for Sone-specific data,
-        *            such as if the Sone is followed by the local Sone
-        * @return The simple field set containing the given Sone
-        */
-       protected static SimpleFieldSet encodeSone(Sone sone, String prefix, Optional<Sone> localSone) {
-               SimpleFieldSetBuilder soneBuilder = new SimpleFieldSetBuilder();
-
-               soneBuilder.put(prefix + "ID", sone.getId());
-               soneBuilder.put(prefix + "Name", sone.getName());
-               soneBuilder.put(prefix + "NiceName", SoneAccessor.getNiceName(sone));
-               soneBuilder.put(prefix + "LastUpdated", sone.getTime());
-               if (localSone.isPresent()) {
-                       soneBuilder.put(prefix + "Followed", String.valueOf(localSone.get().hasFriend(sone.getId())));
-               }
-               Profile profile = sone.getProfile();
-               soneBuilder.put(prefix + "Field.Count", profile.getFields().size());
-               int fieldIndex = 0;
-               for (Field field : profile.getFields()) {
-                       soneBuilder.put(prefix + "Field." + fieldIndex + ".Name", field.getName());
-                       soneBuilder.put(prefix + "Field." + fieldIndex + ".Value", field.getValue());
-                       ++fieldIndex;
-               }
-
-               return soneBuilder.get();
-       }
-
-       /**
-        * Creates a simple field set from the given collection of Sones.
-        *
-        * @param sones
-        *            The Sones to encode
-        * @param prefix
-        *            The prefix for the field names (may be empty but not
-        *            {@code null})
-        * @return The simple field set containing the given Sones
-        */
-       protected static SimpleFieldSet encodeSones(Collection<? extends Sone> sones, String prefix) {
-               SimpleFieldSetBuilder soneBuilder = new SimpleFieldSetBuilder();
-
-               int soneIndex = 0;
-               soneBuilder.put(prefix + "Count", sones.size());
-               for (Sone sone : sones) {
-                       String sonePrefix = prefix + soneIndex++ + ".";
-                       soneBuilder.put(encodeSone(sone, sonePrefix, Optional.<Sone>absent()));
-               }
-
-               return soneBuilder.get();
-       }
-
-       /**
-        * Creates a simple field set from the given post.
-        *
-        * @param post
-        *            The post to encode
-        * @param prefix
-        *            The prefix for the field names (may be empty but not
-        *            {@code null})
-        * @param includeReplies
-        *            {@code true} to include replies, {@code false} to not include
-        *            replies
-        * @return The simple field set containing the post
-        */
-       protected SimpleFieldSet encodePost(Post post, String prefix, boolean includeReplies) {
-               SimpleFieldSetBuilder postBuilder = new SimpleFieldSetBuilder();
-
-               postBuilder.put(prefix + "ID", post.getId());
-               postBuilder.put(prefix + "Sone", post.getSone().getId());
-               if (post.getRecipientId().isPresent()) {
-                       postBuilder.put(prefix + "Recipient", post.getRecipientId().get());
-               }
-               postBuilder.put(prefix + "Time", post.getTime());
-               postBuilder.put(prefix + "Text", encodeString(post.getText()));
-               postBuilder.put(encodeLikes(core.getLikes(post), prefix + "Likes."));
-
-               if (includeReplies) {
-                       List<PostReply> replies = core.getReplies(post.getId());
-                       postBuilder.put(encodeReplies(replies, prefix));
-               }
-
-               return postBuilder.get();
-       }
-
-       /**
-        * Creates a simple field set from the given collection of posts.
-        *
-        * @param posts
-        *            The posts to encode
-        * @param prefix
-        *            The prefix for the field names (may be empty but not
-        *            {@code null})
-        * @param includeReplies
-        *            {@code true} to include the replies, {@code false} to not
-        *            include the replies
-        * @return The simple field set containing the posts
-        */
-       protected SimpleFieldSet encodePosts(Collection<? extends Post> posts, String prefix, boolean includeReplies) {
-               SimpleFieldSetBuilder postBuilder = new SimpleFieldSetBuilder();
-
-               int postIndex = 0;
-               postBuilder.put(prefix + "Count", posts.size());
-               for (Post post : posts) {
-                       String postPrefix = prefix + postIndex++;
-                       postBuilder.put(encodePost(post, postPrefix + ".", includeReplies));
-               }
-
-               return postBuilder.get();
-       }
-
-       /**
-        * Creates a simple field set from the given collection of replies.
-        *
-        * @param replies
-        *            The replies to encode
-        * @param prefix
-        *            The prefix for the field names (may be empty, but not
-        *            {@code null})
-        * @return The simple field set containing the replies
-        */
-       protected SimpleFieldSet encodeReplies(Collection<? extends PostReply> replies, String prefix) {
-               SimpleFieldSetBuilder replyBuilder = new SimpleFieldSetBuilder();
-
-               int replyIndex = 0;
-               replyBuilder.put(prefix + "Replies.Count", replies.size());
-               for (PostReply reply : replies) {
-                       String replyPrefix = prefix + "Replies." + replyIndex++ + ".";
-                       replyBuilder.put(replyPrefix + "ID", reply.getId());
-                       replyBuilder.put(replyPrefix + "Sone", reply.getSone().getId());
-                       replyBuilder.put(replyPrefix + "Time", reply.getTime());
-                       replyBuilder.put(replyPrefix + "Text", encodeString(reply.getText()));
-                       replyBuilder.put(encodeLikes(core.getLikes(reply), replyPrefix + "Likes."));
-               }
-
-               return replyBuilder.get();
-       }
-
-       /**
-        * Creates a simple field set from the given collection of Sones that like
-        * an element.
-        *
-        * @param likes
-        *            The liking Sones
-        * @param prefix
-        *            The prefix for the field names (may be empty but not
-        *            {@code null})
-        * @return The simple field set containing the likes
-        */
-       protected static SimpleFieldSet encodeLikes(Collection<? extends Sone> likes, String prefix) {
-               SimpleFieldSetBuilder likesBuilder = new SimpleFieldSetBuilder();
-
-               int likeIndex = 0;
-               likesBuilder.put(prefix + "Count", likes.size());
-               for (Sone sone : likes) {
-                       String sonePrefix = prefix + likeIndex++ + ".";
-                       likesBuilder.put(sonePrefix + "ID", sone.getId());
-               }
-
-               return likesBuilder.get();
-       }
-
-       //
-       // OBJECT METHODS
-       //
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public String toString() {
-               return getClass().getName() + "[writeAccess=" + writeAccess + "]";
-       }
-
-}
index a3f709e..2c8456d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - CreatePostCommand.java - Copyright © 2011–2016 David Roden
+ * Sone - CreatePostCommand.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -30,7 +30,6 @@ import freenet.support.SimpleFieldSet;
  * FCP command that creates a new {@link Post}.
  *
  * @see Core#createPost(Sone, Optional, String)
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class CreatePostCommand extends AbstractSoneCommand {
 
index d268a07..9f17940 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - CreateReplyCommand.java - Copyright © 2011–2016 David Roden
+ * Sone - CreateReplyCommand.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -30,7 +30,6 @@ import freenet.support.SimpleFieldSet;
  * FCP command that creates a new {@link Reply}.
  *
  * @see Core#createReply(Sone, Post, String)
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class CreateReplyCommand extends AbstractSoneCommand {
 
index b5ace1d..c93029f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DeletePostCommand.java - Copyright © 2011–2016 David Roden
+ * Sone - DeletePostCommand.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -27,7 +27,6 @@ import freenet.support.SimpleFieldSet;
  * FCP command that deletes a {@link Post}.
  *
  * @see Core#deletePost(Post)
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class DeletePostCommand extends AbstractSoneCommand {
 
index d75603e..4531f93 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DeleteReplyCommand.java - Copyright © 2011–2016 David Roden
+ * Sone - DeleteReplyCommand.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -27,7 +27,6 @@ import freenet.support.SimpleFieldSet;
  * FCP command that deletes a {@link PostReply}.
  *
  * @see Core#deleteReply(PostReply)
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class DeleteReplyCommand extends AbstractSoneCommand {
 
index 6f28518..0b321f4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - FcpInterface.java - Copyright © 2011–2016 David Roden
+ * Sone - FcpInterface.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -54,16 +54,12 @@ import com.google.inject.Inject;
 /**
  * Implementation of an FCP interface for other clients or plugins to
  * communicate with Sone.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 @Singleton
 public class FcpInterface {
 
        /**
         * The action level that full access for the FCP connection is required.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
        public enum FullAccessRequired {
 
@@ -85,7 +81,7 @@ public class FcpInterface {
        private final AtomicBoolean active = new AtomicBoolean();
 
        /** What function full access is required for. */
-       private final AtomicReference<FullAccessRequired> fullAccessRequired = new AtomicReference<FullAccessRequired>(FullAccessRequired.ALWAYS);
+       private final AtomicReference<FullAccessRequired> fullAccessRequired = new AtomicReference<>(FullAccessRequired.ALWAYS);
 
        /** All available FCP commands. */
        private final Map<String, AbstractSoneCommand> commands;
index a55a81c..1427fe7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetLocalSonesCommand.java - Copyright © 2011–2016 David Roden
+ * Sone - GetLocalSonesCommand.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 
 package net.pterodactylus.sone.fcp;
 
+import static net.pterodactylus.sone.fcp.AbstractSoneCommandKt.encodeSones;
+
 import net.pterodactylus.sone.core.Core;
 import freenet.support.SimpleFieldSet;
 
 /**
  * Implements the “GetLocalSones” FCP command that returns the list of local
  * Sones to the sender.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class GetLocalSonesCommand extends AbstractSoneCommand {
 
index a6f1ae8..d1dc643 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetPostCommand.java - Copyright © 2011–2016 David Roden
+ * Sone - GetPostCommand.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -24,8 +24,6 @@ import freenet.support.SimpleFieldSet;
 
 /**
  * The “GetPost” FCP command returns a single {@link Post} to an FCP client.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class GetPostCommand extends AbstractSoneCommand {
 
index 4ee15e3..bf80dc2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetPostFeedCommand.java - Copyright © 2011–2016 David Roden
+ * Sone - GetPostFeedCommand.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -35,8 +35,6 @@ import freenet.support.SimpleFieldSet;
 /**
  * Implementation of an FCP interface for other clients or plugins to
  * communicate with Sone.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class GetPostFeedCommand extends AbstractSoneCommand {
 
@@ -59,7 +57,7 @@ public class GetPostFeedCommand extends AbstractSoneCommand {
                int startPost = getInt(parameters, "StartPost", 0);
                int maxPosts = getInt(parameters, "MaxPosts", -1);
 
-               Collection<Post> allPosts = new HashSet<Post>();
+               Collection<Post> allPosts = new HashSet<>();
                allPosts.addAll(sone.getPosts());
                for (String friendSoneId : sone.getFriends()) {
                        Sone friendSone = getCore().getSone(friendSoneId);
@@ -71,7 +69,7 @@ public class GetPostFeedCommand extends AbstractSoneCommand {
                allPosts.addAll(getCore().getDirectedPosts(sone.getId()));
                allPosts = Collections2.filter(allPosts, Post.FUTURE_POSTS_FILTER);
 
-               List<Post> sortedPosts = new ArrayList<Post>(allPosts);
+               List<Post> sortedPosts = new ArrayList<>(allPosts);
                Collections.sort(sortedPosts, Post.NEWEST_FIRST);
 
                if (sortedPosts.size() < startPost) {
index b9932a6..9cc131b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetPostsCommand.java - Copyright © 2011–2016 David Roden
+ * Sone - GetPostsCommand.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -29,8 +29,6 @@ import freenet.support.SimpleFieldSet;
 /**
  * Implements the “GetPosts” FCP command that returns the list of posts a Sone
  * made.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class GetPostsCommand extends AbstractSoneCommand {
 
index 9d0e0b7..a4b936b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetSoneCommand.java - Copyright © 2011–2016 David Roden
+ * Sone - GetSoneCommand.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.fcp;
 
+import static net.pterodactylus.sone.fcp.AbstractSoneCommandKt.encodeSone;
+
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Profile;
 import net.pterodactylus.sone.data.Sone;
@@ -29,8 +31,6 @@ import freenet.support.SimpleFieldSet;
 /**
  * Implements the “GetSone“ FCP command which returns {@link Profile}
  * information about a {@link Sone}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class GetSoneCommand extends AbstractSoneCommand {
 
index fa805e1..ec43143 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetSonesCommand.java - Copyright © 2011–2016 David Roden
+ * Sone - GetSonesCommand.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.fcp;
 
+import static net.pterodactylus.sone.fcp.AbstractSoneCommandKt.encodeSones;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -27,8 +29,6 @@ import freenet.support.SimpleFieldSet;
 
 /**
  * Implements the “GetSones” FCP command that returns the list of known Sones.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class GetSonesCommand extends AbstractSoneCommand {
 
@@ -49,7 +49,7 @@ public class GetSonesCommand extends AbstractSoneCommand {
        public Response execute(SimpleFieldSet parameters) {
                int startSone = getInt(parameters, "StartSone", 0);
                int maxSones = getInt(parameters, "MaxSones", -1);
-               List<Sone> sones = new ArrayList<Sone>(getCore().getSones());
+               List<Sone> sones = new ArrayList<>(getCore().getSones());
                if (sones.size() < startSone) {
                        return new Response("Sones", encodeSones(Collections.<Sone> emptyList(), "Sones."));
                }
index a2fdd18..edd5a43 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - LikePostCommand.java - Copyright © 2011–2016 David Roden
+ * Sone - LikePostCommand.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -26,8 +26,6 @@ import freenet.support.SimpleFieldSet;
 
 /**
  * Implements the “LikePost” FCP command which allows the user to like a post.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class LikePostCommand extends AbstractSoneCommand {
 
index 6fba11b..800e43c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - LikeReplyCommand.java - Copyright © 2011–2016 David Roden
+ * Sone - LikeReplyCommand.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -26,8 +26,6 @@ import freenet.support.SimpleFieldSet;
 
 /**
  * Implements the “LikeReply” FCP command which allows the user to like a reply.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class LikeReplyCommand extends AbstractSoneCommand {
 
index f39a18c..691bc4b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - LockSoneCommand.java - Copyright © 2013–2016 David Roden
+ * Sone - LockSoneCommand.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -30,8 +30,6 @@ import com.google.common.base.Optional;
  * Implements the “LockSone” FCP command. If a valid local Sone was given as
  * parameter “Sone,” this command will always lock the Sone and reply with
  * “SoneLocked.”
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class LockSoneCommand extends AbstractSoneCommand {
 
index bc78441..ca5a59e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UnlockSoneCommand.java - Copyright © 2013–2016 David Roden
+ * Sone - UnlockSoneCommand.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -30,8 +30,6 @@ import com.google.common.base.Optional;
  * Implements the “UnlockSone” FCP command. If a valid local Sone was given as
  * parameter “Sone,” this command will always unlock the Sone and reply with
  * “SoneUnlocked.”
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class UnlockSoneCommand extends AbstractSoneCommand {
 
index 5f50b4d..7acea2d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - VersionCommand.java - Copyright © 2011–2016 David Roden
+ * Sone - VersionCommand.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -24,8 +24,6 @@ import freenet.support.SimpleFieldSet;
 
 /**
  * Returns version information about the Sone plugin.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class VersionCommand extends AbstractSoneCommand {
 
index 56b658a..f45ac53 100644 (file)
@@ -5,8 +5,6 @@ import net.pterodactylus.sone.fcp.FcpInterface;
 /**
  * Event that signals that the {@link FcpInterface} was activated in the
  * configuration.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class FcpInterfaceActivatedEvent {
 
index b97ef76..216960b 100644 (file)
@@ -5,8 +5,6 @@ import net.pterodactylus.sone.fcp.FcpInterface;
 /**
  * Event that signals that the {@link FcpInterface} was deactivated in the
  * configuration.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class FcpInterfaceDeactivatedEvent {
 
index 9638880..86c20a3 100644 (file)
@@ -6,8 +6,6 @@ import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired;
 /**
  * Event that signals that the {@link FcpInterface}’s {@link
  * FullAccessRequired} parameter was changed in the configuration.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class FullAccessRequiredChanged {
 
index f21e2f6..6811642 100644 (file)
@@ -10,8 +10,6 @@ import com.google.common.annotations.VisibleForTesting;
 /**
  * Encapsulates the parts of a {@link FreenetURI} that do not change while
  * being converted from SSK to USK and/or back.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class Key {
 
index 7ca9a5c..d667811 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - L10nFilter.java - Copyright © 2010–2016 David Roden
+ * Sone - L10nFilter.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -25,29 +25,21 @@ import java.util.Map;
 
 import javax.annotation.Nonnull;
 
-import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.util.template.Filter;
 import net.pterodactylus.util.template.TemplateContext;
 
+import freenet.l10n.BaseL10n;
+
 /**
  * {@link Filter} implementation replaces {@link String} values with their
  * translated equivalents.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class L10nFilter implements Filter {
 
-       /** The web interface. */
-       private final WebInterface webInterface;
+       private final BaseL10n l10n;
 
-       /**
-        * Creates a new L10n filter.
-        *
-        * @param webInterface
-        *            The Sone web interface
-        */
-       public L10nFilter(WebInterface webInterface) {
-               this.webInterface = webInterface;
+       public L10nFilter(BaseL10n l10n) {
+               this.l10n = l10n;
        }
 
        /**
@@ -58,9 +50,9 @@ public class L10nFilter implements Filter {
                List<Object> parameterValues = getParameters(data, parameters);
                String text = getText(data);
                if (parameterValues.isEmpty()) {
-                       return webInterface.getL10n().getString(text);
+                       return l10n.getString(text);
                }
-               return new MessageFormat(webInterface.getL10n().getString(text), new Locale(webInterface.getL10n().getSelectedLanguage().shortCode)).format(parameterValues.toArray());
+               return new MessageFormat(l10n.getString(text), new Locale(l10n.getSelectedLanguage().shortCode)).format(parameterValues.toArray());
        }
 
        @Nonnull
@@ -73,7 +65,7 @@ public class L10nFilter implements Filter {
                if (data instanceof L10nText) {
                        return ((L10nText) data).getParameters();
                }
-               List<Object> parameterValues = new ArrayList<Object>();
+               List<Object> parameterValues = new ArrayList<>();
                int parameterIndex = 0;
                while (parameters.containsKey(String.valueOf(parameterIndex))) {
                        Object value = parameters.get(String.valueOf(parameterIndex));
index 55e4024..cce544f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PluginStoreConfigurationBackend.java - Copyright © 2010–2016 David Roden
+ * Sone - PluginStoreConfigurationBackend.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -31,8 +31,6 @@ import freenet.pluginmanager.PluginStore;
 
 /**
  * Backend for a {@link Configuration} that is based on a {@link PluginStore}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class PluginStoreConfigurationBackend implements ExtendedConfigurationBackend {
 
index 25088fc..48eb4bf 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SimpleFieldSetBuilder.java - Copyright © 2011–2016 David Roden
+ * Sone - SimpleFieldSetBuilder.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -23,8 +23,6 @@ import freenet.support.SimpleFieldSet;
 
 /**
  * Helper class to construct {@link SimpleFieldSet} objects in a single call.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class SimpleFieldSetBuilder {
 
index b39d905..3ea4804 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - AbstractCommand.java - Copyright © 2011–2016 David Roden
+ * Sone - AbstractCommand.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -23,8 +23,6 @@ import freenet.support.SimpleFieldSet;
 /**
  * Basic implementation of a {@link Command} with various helper methods to
  * simplify processing of input parameters.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public abstract class AbstractCommand implements Command {
 
index 17f8370..63fc8eb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Command.java - Copyright © 2011–2016 David Roden
+ * Sone - Command.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -23,8 +23,6 @@ import freenet.support.SimpleFieldSet;
 /**
  * Implementation of an FCP interface for other clients or plugins to
  * communicate with Sone.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public interface Command {
 
@@ -42,8 +40,6 @@ public interface Command {
 
        /**
         * The access type of the request.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
        public static enum AccessType {
 
@@ -60,8 +56,6 @@ public interface Command {
 
        /**
         * Interface for command replies.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
        public static class Response {
 
@@ -98,8 +92,6 @@ public interface Command {
        /**
         * Response implementation that can return an error message and an optional
         * error code.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
        public class ErrorResponse extends Response {
 
index c28eef7..0e156a1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - FcpException.java - Copyright © 2011–2016 David Roden
+ * Sone - FcpException.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,8 +19,6 @@ package net.pterodactylus.sone.freenet.fcp;
 
 /**
  * Base exception for FCP communication.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class FcpException extends Exception {
 
index 0090aa8..fbaabb3 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PluginConnector.java - Copyright © 2010–2016 David Roden
+ * Sone - PluginConnector.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -33,8 +33,6 @@ import freenet.support.api.Bucket;
 /**
  * Interface for talking to other plugins. Other plugins are identified by their
  * name and a unique connection identifier.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 @Singleton
 public class PluginConnector implements FredPluginTalker {
index 09b186e..e263a60 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PluginException.java - Copyright © 2010–2016 David Roden
+ * Sone - PluginException.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,8 +21,6 @@ import net.pterodactylus.sone.freenet.wot.WebOfTrustException;
 
 /**
  * Exception that signals an error when communicating with a plugin.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class PluginException extends WebOfTrustException {
 
index 56a1d46..ce2ba7f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ReceivedReplyEvent.java - Copyright © 2013–2016 David Roden
+ * Sone - ReceivedReplyEvent.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -23,8 +23,6 @@ import freenet.support.api.Bucket;
 
 /**
  * Event that signals that a plugin reply was received.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class ReceivedReplyEvent {
 
index 13f9793..b386bdb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Context.java - Copyright © 2014–2016 David Roden
+ * Sone - Context.java - Copyright © 2014–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -24,8 +24,6 @@ import com.google.common.base.Function;
 /**
  * Custom container for the Web of Trust context. This allows easier
  * configuration of dependency injection.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class Context {
 
index 433516f..0d92d61 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DefaultIdentity.java - Copyright © 2010–2016 David Roden
+ * Sone - DefaultIdentity.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -26,8 +26,6 @@ import java.util.Set;
 
 /**
  * A Web of Trust identity.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class DefaultIdentity implements Identity {
 
index 2f78943..e2e3a74 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DefaultOwnIdentity.java - Copyright © 2010–2016 David Roden
+ * Sone - DefaultOwnIdentity.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -22,8 +22,6 @@ import static com.google.common.base.Preconditions.checkNotNull;
 /**
  * An own identity is an identity that the owner of the node has full control
  * over.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class DefaultOwnIdentity extends DefaultIdentity implements OwnIdentity {
 
index 25ddf9c..a99aac0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Identity.java - Copyright © 2010–2016 David Roden
+ * Sone - Identity.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -28,8 +28,6 @@ import com.google.common.base.Function;
  * Interface for web of trust identities, defining all functions that can be
  * performed on an identity. An identity is only a container for identity data
  * and will not perform any updating in the WebOfTrust plugin itself.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public interface Identity {
 
index f014de5..8b28011 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IdentityChangeDetector.java - Copyright © 2013–2016 David Roden
+ * Sone - IdentityChangeDetector.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -38,8 +38,6 @@ import com.google.common.collect.ImmutableMap;
  * added and removed identities, and for identities that exist in both list
  * their contexts and properties are checked for added, removed, or (in case of
  * properties) changed values.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class IdentityChangeDetector {
 
index b3c3c05..fd57c38 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IdentityChangeEventSender.java - Copyright © 2013–2016 David Roden
+ * Sone - IdentityChangeEventSender.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -33,7 +33,6 @@ import com.google.common.eventbus.EventBus;
  * Detects changes in {@link Identity}s trusted my multiple {@link
  * OwnIdentity}s.
  *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  * @see IdentityChangeDetector
  */
 public class IdentityChangeEventSender {
index 1a8cc49..f16df1c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IdentityLoader.java - Copyright © 2013–2016 David Roden
+ * Sone - IdentityLoader.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -33,8 +33,6 @@ import com.google.inject.Inject;
 
 /**
  * Loads {@link OwnIdentity}s and the {@link Identity}s they trust.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class IdentityLoader {
 
@@ -57,7 +55,7 @@ public class IdentityLoader {
        }
 
        private Map<OwnIdentity, Collection<Identity>> loadTrustedIdentitiesForOwnIdentities(Collection<OwnIdentity> ownIdentities) throws PluginException {
-               Map<OwnIdentity, Collection<Identity>> currentIdentities = new HashMap<OwnIdentity, Collection<Identity>>();
+               Map<OwnIdentity, Collection<Identity>> currentIdentities = new HashMap<>();
 
                for (OwnIdentity ownIdentity : ownIdentities) {
                        if (identityDoesNotHaveTheCorrectContext(ownIdentity)) {
index d3ba606..c0f6f1b 100644 (file)
@@ -10,8 +10,6 @@ import com.google.inject.ImplementedBy;
 /**
  * Connects to a {@link WebOfTrustConnector} and sends identity events to an
  * {@link EventBus}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 @ImplementedBy(IdentityManagerImpl.class)
 public interface IdentityManager extends Service {
index 02e91ce..6f46465 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IdentityManagerImpl.java - Copyright © 2010–2016 David Roden
+ * Sone - IdentityManagerImpl.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -43,8 +43,6 @@ import com.google.inject.Singleton;
  * It is also responsible for polling identities from the Web of Trust plugin
  * and sending events to the {@link EventBus} when {@link Identity}s and
  * {@link OwnIdentity}s are discovered or disappearing.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 @Singleton
 public class IdentityManagerImpl extends AbstractService implements IdentityManager {
@@ -109,7 +107,7 @@ public class IdentityManagerImpl extends AbstractService implements IdentityMana
        @Override
        public Set<OwnIdentity> getAllOwnIdentities() {
                synchronized (currentOwnIdentities) {
-                       return new HashSet<OwnIdentity>(currentOwnIdentities);
+                       return new HashSet<>(currentOwnIdentities);
                }
        }
 
@@ -122,7 +120,7 @@ public class IdentityManagerImpl extends AbstractService implements IdentityMana
         */
        @Override
        protected void serviceRun() {
-               Map<OwnIdentity, Collection<Identity>> oldIdentities = new HashMap<OwnIdentity, Collection<Identity>>();
+               Map<OwnIdentity, Collection<Identity>> oldIdentities = new HashMap<>();
 
                while (!shouldStop()) {
                        try {
index 8be0571..500f2c7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - OwnIdentity.java - Copyright © 2010–2016 David Roden
+ * Sone - OwnIdentity.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -20,8 +20,6 @@ package net.pterodactylus.sone.freenet.wot;
 
 /**
  * Defines a local identity, an own identity.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public interface OwnIdentity extends Identity {
 
index 7b18888..2da00f6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Trust.java - Copyright © 2010–2016 David Roden
+ * Sone - Trust.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -23,8 +23,6 @@ import com.google.common.base.Objects;
 
 /**
  * Container class for trust in the web of trust.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class Trust {
 
index 5f7b8b7..c1dbef9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - WebOfTrustConnector.java - Copyright © 2010–2016 David Roden
+ * Sone - WebOfTrustConnector.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -43,8 +43,6 @@ import freenet.support.api.Bucket;
 
 /**
  * Connector for the Web of Trust plugin.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 @Singleton
 public class WebOfTrustConnector {
@@ -98,7 +96,7 @@ public class WebOfTrustConnector {
                Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetOwnIdentities").get());
                SimpleFieldSet fields = reply.getFields();
                int ownIdentityCounter = -1;
-               Set<OwnIdentity> ownIdentities = new HashSet<OwnIdentity>();
+               Set<OwnIdentity> ownIdentities = new HashSet<>();
                while (true) {
                        String id = fields.get("Identity" + ++ownIdentityCounter);
                        if (id == null) {
@@ -144,7 +142,7 @@ public class WebOfTrustConnector {
        public Set<Identity> loadTrustedIdentities(OwnIdentity ownIdentity, Optional<String> context) throws PluginException {
                Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetIdentitiesByScore").put("Truster", ownIdentity.getId()).put("Selection", "+").put("Context", context.or("")).put("WantTrustValues", "true").get());
                SimpleFieldSet fields = reply.getFields();
-               Set<Identity> identities = new HashSet<Identity>();
+               Set<Identity> identities = new HashSet<>();
                int identityCounter = -1;
                while (true) {
                        String id = fields.get("Identity" + ++identityCounter);
@@ -331,7 +329,7 @@ public class WebOfTrustConnector {
         * @return The parsed contexts
         */
        private static Set<String> parseContexts(String prefix, SimpleFieldSet fields) {
-               Set<String> contexts = new HashSet<String>();
+               Set<String> contexts = new HashSet<>();
                int contextCounter = -1;
                while (true) {
                        String context = fields.get(prefix + "Context" + ++contextCounter);
@@ -353,7 +351,7 @@ public class WebOfTrustConnector {
         * @return The parsed properties
         */
        private static Map<String, String> parseProperties(String prefix, SimpleFieldSet fields) {
-               Map<String, String> properties = new HashMap<String, String>();
+               Map<String, String> properties = new HashMap<>();
                int propertiesCounter = -1;
                while (true) {
                        String propertyName = fields.get(prefix + "Property" + ++propertiesCounter + ".Name");
@@ -443,8 +441,6 @@ public class WebOfTrustConnector {
 
        /**
         * Container for the data of the reply from a plugin.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
        private static class Reply {
 
@@ -502,8 +498,6 @@ public class WebOfTrustConnector {
 
        /**
         * Helper method to create {@link SimpleFieldSet}s with terser code.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
        private static class SimpleFieldSetConstructor {
 
@@ -578,8 +572,6 @@ public class WebOfTrustConnector {
        /**
         * Container for identifying plugins. Plugins are identified by their plugin
         * name and their unique identifier.
-        *
-        * @author <a href="mailto:d.roden@xplosion.de">David Roden</a>
         */
        private static class PluginIdentifier {
 
index c622b3c..954fe04 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - WebOfTrustException.java - Copyright © 2010–2016 David Roden
+ * Sone - WebOfTrustException.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -20,8 +20,6 @@ package net.pterodactylus.sone.freenet.wot;
 /**
  * Exception that signals an error processing web of trust identities, mostly
  * when communicating with the web of trust plugin.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class WebOfTrustException extends Exception {
 
index 323a9cf..2d7ab32 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IdentityAddedEvent.java - Copyright © 2013–2016 David Roden
+ * Sone - IdentityAddedEvent.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -22,8 +22,6 @@ import net.pterodactylus.sone.freenet.wot.OwnIdentity;
 
 /**
  * Event that signals that an {@link Identity} was added.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class IdentityAddedEvent extends IdentityEvent {
 
index 6289c8a..8a23ae1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IdentityEvent.java - Copyright © 2013–2016 David Roden
+ * Sone - IdentityEvent.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -22,8 +22,6 @@ import net.pterodactylus.sone.freenet.wot.OwnIdentity;
 
 /**
  * Base class for {@link Identity} events.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public abstract class IdentityEvent {
 
index a586de0..655015e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IdentityRemovedEvent.java - Copyright © 2013–2016 David Roden
+ * Sone - IdentityRemovedEvent.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -22,8 +22,6 @@ import net.pterodactylus.sone.freenet.wot.OwnIdentity;
 
 /**
  * Event that signals that an {@link Identity} was removed.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class IdentityRemovedEvent extends IdentityEvent {
 
index 3e9a0f7..a71ad5b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IdentityUpdatedEvent.java - Copyright © 2013–2016 David Roden
+ * Sone - IdentityUpdatedEvent.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -22,8 +22,6 @@ import net.pterodactylus.sone.freenet.wot.OwnIdentity;
 
 /**
  * Event that signals that an {@link Identity} was updated.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class IdentityUpdatedEvent extends IdentityEvent {
 
index cdb787a..fc34848 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - OwnIdentityAddedEvent.java - Copyright © 2013–2016 David Roden
+ * Sone - OwnIdentityAddedEvent.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,8 +21,6 @@ import net.pterodactylus.sone.freenet.wot.OwnIdentity;
 
 /**
  * Event that signals that an {@link OwnIdentity} was added.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class OwnIdentityAddedEvent extends OwnIdentityEvent {
 
index 91ad281..9e88d20 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - OwnIdentityEvent.java - Copyright © 2013–2016 David Roden
+ * Sone - OwnIdentityEvent.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,8 +21,6 @@ import net.pterodactylus.sone.freenet.wot.OwnIdentity;
 
 /**
  * Base class for {@link OwnIdentity} events.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public abstract class OwnIdentityEvent {
 
index 5b6bd95..73761b0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - OwnIdentityRemovedEvent.java - Copyright © 2013–2016 David Roden
+ * Sone - OwnIdentityRemovedEvent.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,8 +21,6 @@ import net.pterodactylus.sone.freenet.wot.OwnIdentity;
 
 /**
  * Event that signals that an {@link OwnIdentity} was removed.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class OwnIdentityRemovedEvent extends OwnIdentityEvent {
 
index d0168ae..c42b056 100644 (file)
@@ -12,8 +12,6 @@ import net.pterodactylus.util.web.Request;
 
 /**
  * {@link Loaders} implementation that loads all resources from the filesystem.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class DebugLoaders implements Loaders {
 
@@ -30,7 +28,7 @@ public class DebugLoaders implements Loaders {
 
        @Override
        public <REQ extends Request> Page<REQ> loadStaticPage(String basePath, String prefix, String mimeType) {
-               return new ReloadingPage<REQ>(basePath, new File(filesystemPath, prefix).getAbsolutePath(), mimeType);
+               return new ReloadingPage<>(basePath, new File(filesystemPath, prefix).getAbsolutePath(), mimeType);
        }
 
        @Override
index e94e655..72d8d19 100644 (file)
@@ -18,8 +18,6 @@ import net.pterodactylus.util.web.StaticPage;
 
 /**
  * Default {@link Loaders} implementation that loads resources from the classpath.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class DefaultLoaders implements Loaders {
 
index 34ee1b1..8ee5132 100644 (file)
@@ -9,8 +9,6 @@ import com.google.inject.ImplementedBy;
 
 /**
  * Defines loaders for resources that can be loaded from various locations.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 @ImplementedBy(DefaultLoaders.class)
 public interface Loaders {
index acad00b..4f7c5cb 100644 (file)
@@ -12,8 +12,6 @@ import net.pterodactylus.util.template.TemplateParser;
 
 /**
  * Takes the input and parses it as a new {@link Template}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class ReparseFilter implements Filter {
 
index 5cf708f..649ebf6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SonePlugin.java - Copyright © 2010–2016 David Roden
+ * Sone - SonePlugin.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -30,12 +30,15 @@ import javax.inject.Singleton;
 
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.database.Database;
+import net.pterodactylus.sone.database.PostProvider;
+import net.pterodactylus.sone.database.SoneProvider;
 import net.pterodactylus.sone.database.memory.MemoryDatabase;
 import net.pterodactylus.sone.fcp.FcpInterface;
 import net.pterodactylus.sone.freenet.PluginStoreConfigurationBackend;
 import net.pterodactylus.sone.freenet.wot.Context;
 import net.pterodactylus.sone.freenet.wot.WebOfTrustConnector;
 import net.pterodactylus.sone.web.WebInterface;
+import net.pterodactylus.sone.web.WebInterfaceModule;
 import net.pterodactylus.util.config.Configuration;
 import net.pterodactylus.util.config.ConfigurationException;
 import net.pterodactylus.util.config.MapConfigurationBackend;
@@ -49,6 +52,7 @@ import com.google.common.eventbus.EventBus;
 import com.google.inject.AbstractModule;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
+import com.google.inject.Module;
 import com.google.inject.TypeLiteral;
 import com.google.inject.matcher.Matchers;
 import com.google.inject.spi.InjectionListener;
@@ -56,6 +60,7 @@ import com.google.inject.spi.TypeEncounter;
 import com.google.inject.spi.TypeListener;
 
 import freenet.client.async.PersistenceDisabledException;
+import freenet.l10n.BaseL10n;
 import freenet.l10n.BaseL10n.LANGUAGE;
 import freenet.l10n.PluginL10n;
 import freenet.pluginmanager.FredPlugin;
@@ -72,8 +77,6 @@ import freenet.support.api.Bucket;
 /**
  * This class interfaces with Freenet. It is the class that is loaded by the
  * node and starts up the whole Sone system.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, FredPluginBaseL10n, FredPluginThreadless, FredPluginVersioned {
 
@@ -119,9 +122,9 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr
        }
 
        /** The current year at time of release. */
-       private static final int YEAR = 2017;
+       private static final int YEAR = 2019;
        private static final String SONE_HOMEPAGE = "USK@nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI,DuQSUZiI~agF8c-6tjsFFGuZ8eICrzWCILB60nT8KKo,AQACAAE/sone/";
-       private static final int LATEST_EDITION = 77;
+       private static final int LATEST_EDITION = 78;
 
        /** The logger. */
        private static final Logger logger = getLogger(SonePlugin.class.getName());
@@ -180,11 +183,11 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr
                return (version == null) ? "unknown" : version.getNice();
        }
 
-       public static int getYear() {
+       public int getYear() {
                return YEAR;
        }
 
-       public static String getHomepage() {
+       public String getHomepage() {
                return SONE_HOMEPAGE + LATEST_EDITION;
        }
 
@@ -257,6 +260,9 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr
                                bind(PluginYear.class).toInstance(new PluginYear(getYear()));
                                bind(PluginHomepage.class).toInstance(new PluginHomepage(getHomepage()));
                                bind(Database.class).to(MemoryDatabase.class).in(Singleton.class);
+                               bind(BaseL10n.class).toInstance(l10n.getBase());
+                               bind(SoneProvider.class).to(Core.class).in(Singleton.class);
+                               bind(PostProvider.class).to(Core.class).in(Singleton.class);
                                if (startConfiguration.getBooleanValue("Developer.LoadFromFilesystem").getValue(false)) {
                                        String path = startConfiguration.getStringValue("Developer.FilesystemPath").getValue(null);
                                        if (path != null) {
@@ -284,7 +290,8 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr
                        }
 
                };
-               Injector injector = Guice.createInjector(freenetModule, soneModule);
+               Module webInterfaceModule = new WebInterfaceModule();
+               Injector injector = Guice.createInjector(freenetModule, soneModule, webInterfaceModule);
                core = injector.getInstance(Core.class);
 
                /* create web of trust connector. */
@@ -410,46 +417,4 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr
                return getPluginVersion();
        }
 
-       public static class PluginVersion {
-
-               private final String version;
-
-               public PluginVersion(String version) {
-                       this.version = version;
-               }
-
-               public String getVersion() {
-                       return version;
-               }
-
-       }
-
-       public static class PluginYear {
-
-               private final int year;
-
-               public PluginYear(int year) {
-                       this.year = year;
-               }
-
-               public int getYear() {
-                       return year;
-               }
-
-       }
-
-       public static class PluginHomepage {
-
-               private final String homepage;
-
-               public PluginHomepage(String homepage) {
-                       this.homepage = homepage;
-               }
-
-               public String getHomepage() {
-                       return homepage;
-               }
-
-       }
-
 }
diff --git a/src/main/java/net/pterodactylus/sone/main/SonePlugin.kt b/src/main/java/net/pterodactylus/sone/main/SonePlugin.kt
new file mode 100644 (file)
index 0000000..5e0b2c1
--- /dev/null
@@ -0,0 +1,7 @@
+package net.pterodactylus.sone.main
+
+data class PluginVersion(val version: String)
+
+data class PluginYear(val year: Int)
+
+data class PluginHomepage(val homepage: String)
index 8ab8e4c..6a7b086 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ListNotification.java - Copyright © 2010–2016 David Roden
+ * Sone - ListNotification.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -30,7 +30,6 @@ import net.pterodactylus.util.template.Template;
  *
  * @param <T>
  *            The type of the items
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class ListNotification<T> extends TemplateNotification {
 
@@ -38,7 +37,7 @@ public class ListNotification<T> extends TemplateNotification {
        private final String key;
 
        /** The list of new elements. */
-       private final List<T> elements = new CopyOnWriteArrayList<T>();
+       private final List<T> elements = new CopyOnWriteArrayList<>();
 
        /**
         * Creates a new list notification.
@@ -97,7 +96,7 @@ public class ListNotification<T> extends TemplateNotification {
         * @return The current list of elements
         */
        public List<T> getElements() {
-               return new ArrayList<T>(elements);
+               return new ArrayList<>(elements);
        }
 
        /**
index 4672e72..50a1087 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ListNotificationFilter.java - Copyright © 2010–2016 David Roden
+ * Sone - ListNotificationFilter.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -36,8 +36,6 @@ 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 {
@@ -67,7 +65,7 @@ public class ListNotificationFilter {
         */
        @SuppressWarnings("unchecked")
        public List<Notification> filterNotifications(Collection<? extends Notification> notifications, Sone currentSone) {
-               List<Notification> filteredNotifications = new ArrayList<Notification>();
+               List<Notification> filteredNotifications = new ArrayList<>();
                for (Notification notification : notifications) {
                        if (notification.getId().equals("new-sone-notification")) {
                                if ((currentSone != null) && !currentSone.getOptions().isShowNewSoneNotifications()) {
@@ -128,7 +126,7 @@ public class ListNotificationFilter {
                if (newPosts.size() == postNotification.getElements().size()) {
                        return Optional.of(postNotification);
                }
-               ListNotification<Post> filteredNotification = new ListNotification<Post>(postNotification);
+               ListNotification<Post> filteredNotification = new ListNotification<>(postNotification);
                filteredNotification.setElements(newPosts);
                filteredNotification.setLastUpdateTime(postNotification.getLastUpdatedTime());
                return Optional.of(filteredNotification);
@@ -157,7 +155,7 @@ public class ListNotificationFilter {
                if (newReplies.size() == newReplyNotification.getElements().size()) {
                        return Optional.of(newReplyNotification);
                }
-               ListNotification<PostReply> filteredNotification = new ListNotification<PostReply>(newReplyNotification);
+               ListNotification<PostReply> filteredNotification = new ListNotification<>(newReplyNotification);
                filteredNotification.setElements(newReplies);
                filteredNotification.setLastUpdateTime(newReplyNotification.getLastUpdatedTime());
                return Optional.of(filteredNotification);
index 3582e3d..d29d537 100644 (file)
@@ -16,8 +16,6 @@ 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 {
index fd13779..5e3e16d 100644 (file)
@@ -16,8 +16,6 @@ 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 {
index 7d0ab90..19b600e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - AlbumAccessor.java - Copyright © 2011–2016 David Roden
+ * Sone - AlbumAccessor.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -31,8 +31,6 @@ import net.pterodactylus.util.template.TemplateContext;
  * {@link Accessor} implementation for {@link Album}s. A property named
  * “backlinks” is added, it returns links to all parents and the owner Sone of
  * an album.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class AlbumAccessor extends ReflectionAccessor {
 
@@ -45,7 +43,7 @@ public class AlbumAccessor extends ReflectionAccessor {
        public Object get(TemplateContext templateContext, Object object, String member) {
                Album album = (Album) object;
                if ("backlinks".equals(member)) {
-                       List<Link> backlinks = new ArrayList<Link>();
+                       List<Link> backlinks = new ArrayList<>();
                        Album currentAlbum = album;
                        while (!currentAlbum.isRoot()) {
                                backlinks.add(0, new Link("imageBrowser.html?album=" + currentAlbum.getId(), currentAlbum.getTitle()));
@@ -62,8 +60,6 @@ public class AlbumAccessor extends ReflectionAccessor {
 
        /**
         * Container for links.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
        private static class Link {
 
index 0771abd..8ce97ad 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - CollectionAccessor.java - Copyright © 2010–2016 David Roden
+ * Sone - CollectionAccessor.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -35,8 +35,6 @@ import net.pterodactylus.util.template.TemplateContext;
  * <dt>Returns the nice names of all {@link Sone}s in the collection, sorted
  * ascending by their nice names.</dt>
  * </dl>
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class CollectionAccessor extends ReflectionAccessor {
 
@@ -47,7 +45,7 @@ public class CollectionAccessor extends ReflectionAccessor {
        public Object get(TemplateContext templateContext, Object object, String member) {
                Collection<?> collection = (Collection<?>) object;
                if (member.equals("soneNames")) {
-                       List<Sone> sones = new ArrayList<Sone>();
+                       List<Sone> sones = new ArrayList<>();
                        for (Object sone : collection) {
                                if (!(sone instanceof Sone)) {
                                        continue;
index 5776b07..1ff4f04 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - CssClassNameFilter.java - Copyright © 2010–2016 David Roden
+ * Sone - CssClassNameFilter.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -26,8 +26,6 @@ import net.pterodactylus.util.template.TemplateContext;
  * Converts the {@link String} {@link String#valueOf(Object) representation} of
  * an object to a valid CSS class name by converting all characters that are not
  * US-ASCII letters or numbers to an underscore.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class CssClassNameFilter implements Filter {
 
index ad753a7..7c7da4c 100644 (file)
@@ -22,15 +22,13 @@ import com.google.common.base.Charsets;
 
 /**
  * {@link Template} implementation that can be reloaded from the filesystem.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class FilesystemTemplate extends Template {
 
        private final String filename;
-       private final AtomicReference<LastLoadedTemplate> lastTemplate = new AtomicReference<LastLoadedTemplate>();
+       private final AtomicReference<LastLoadedTemplate> lastTemplate = new AtomicReference<>();
        private final TemplateContext initialContext = new TemplateContext();
-       private final List<Part> parts = new ArrayList<Part>();
+       private final List<Part> parts = new ArrayList<>();
 
        public FilesystemTemplate(String filename) {
                this.filename = filename;
@@ -120,8 +118,6 @@ public class FilesystemTemplate extends Template {
 
        /**
         * Exception that signals that a template file could not be found.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
        public static class TemplateFileNotFoundException extends RuntimeException {
 
index 4c8f3d5..172b4b2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetPagePlugin.java - Copyright © 2010–2016 David Roden
+ * Sone - GetPagePlugin.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -27,8 +27,6 @@ import net.pterodactylus.util.web.Request;
 /**
  * Extracts a page number from a {@link Request}’s parameters and stores it in
  * the {@link TemplateContext}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class GetPagePlugin implements Plugin {
 
index 3e7a261..0e6fda0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - HttpRequestAccessor.java - Copyright © 2011–2016 David Roden
+ * Sone - HttpRequestAccessor.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -27,7 +27,6 @@ import freenet.support.api.HTTPRequest;
  * {@link HTTPRequest}s.
  *
  * @see HTTPRequest#getHeader(String)
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class HttpRequestAccessor extends ReflectionAccessor {
 
index 7766af3..ed139de 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IdentityAccessor.java - Copyright © 2010–2016 David Roden
+ * Sone - IdentityAccessor.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -32,8 +32,6 @@ import net.pterodactylus.util.template.TemplateContext;
 /**
  * {@link Accessor} implementation that adds a “uniqueNickname” member to an
  * {@link Identity}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 @Singleton
 public class IdentityAccessor extends ReflectionAccessor {
index 903e26c..78c3aeb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageAccessor.java - Copyright © 2011–2016 David Roden
+ * Sone - ImageAccessor.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -32,8 +32,6 @@ import net.pterodactylus.util.template.TemplateContext;
  * <li>{@code next}: returns the next image in the image’s album, or {@code
  * null} if the image is the last image of its album.</li>
  * </ul>
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class ImageAccessor extends ReflectionAccessor {
 
index 511baf8..fc4b803 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageLinkFilter.java - Copyright © 2011–2016 David Roden
+ * Sone - ImageLinkFilter.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -28,6 +28,7 @@ import java.util.Map;
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Image;
 import net.pterodactylus.util.template.Filter;
+import net.pterodactylus.util.template.HtmlFilter;
 import net.pterodactylus.util.template.Template;
 import net.pterodactylus.util.template.TemplateContext;
 import net.pterodactylus.util.template.TemplateContextFactory;
@@ -39,8 +40,6 @@ import com.google.common.base.Optional;
 /**
  * Template filter that turns an {@link Image} into an HTML &lt;img&gt; tag,
  * using some parameters to influence parameters of the image.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class ImageLinkFilter implements Filter {
 
@@ -51,19 +50,12 @@ public class ImageLinkFilter implements Filter {
        private final Core core;
 
        /** The template context factory. */
-       private final TemplateContextFactory templateContextFactory;
+       private final TemplateContextFactory templateContextFactory = new TemplateContextFactory();
 
-       /**
-        * Creates a new image link filter.
-        *
-        * @param core
-        *            The core
-        * @param templateContextFactory
-        *            The template context factory
-        */
-       public ImageLinkFilter(Core core, TemplateContextFactory templateContextFactory) {
+       public ImageLinkFilter(Core core) {
                this.core = core;
-               this.templateContextFactory = templateContextFactory;
+               templateContextFactory.addFilter("html", new HtmlFilter());
+               templateContextFactory.addFilter("css", new CssClassNameFilter());
        }
 
        /**
index 595f40e..8dd2b04 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - JavascriptFilter.java - Copyright © 2011–2016 David Roden
+ * Sone - JavascriptFilter.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -28,8 +28,6 @@ import com.google.common.io.BaseEncoding;
  * Escapes double quotes, backslashes, carriage returns and line feeds, and
  * additionally encloses a given string with double quotes to make it possible
  * to use a string in Javascript.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class JavascriptFilter implements Filter {
 
index 11cafea..4025c7b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostAccessor.java - Copyright © 2010–2016 David Roden
+ * Sone - PostAccessor.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -32,8 +32,6 @@ import com.google.common.collect.Collections2;
  * <dd>replies</dd>
  * <dt>All replies to this post, sorted by time, oldest first</dt>
  * </dl>
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class PostAccessor extends ReflectionAccessor {
 
index fafba86..a152038 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ProfileAccessor.java - Copyright © 2011–2016 David Roden
+ * Sone - ProfileAccessor.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -31,8 +31,6 @@ import net.pterodactylus.util.template.TemplateContext;
  * {@link Accessor} for {@link Profile} objects that overwrites the original
  * “avatar” member to include checks for whether the custom avatar should
  * actually be shown.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class ProfileAccessor extends ReflectionAccessor {
 
index a3253b0..c6dea31 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ReplyAccessor.java - Copyright © 2010–2016 David Roden
+ * Sone - ReplyAccessor.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -28,8 +28,6 @@ import net.pterodactylus.util.template.TemplateContext;
 /**
  * {@link Accessor} implementation that adds a couple of properties to
  * {@link Reply}s.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class ReplyAccessor extends ReflectionAccessor {
 
index da12ca8..c093e6f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ReplyGroupFilter.java - Copyright © 2010–2016 David Roden
+ * Sone - ReplyGroupFilter.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -34,8 +34,6 @@ import net.pterodactylus.util.template.TemplateContext;
  * {@link Filter} implementation that groups replies by the post the are in
  * reply to, returning a map with the post as key and the list of replies as
  * values.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class ReplyGroupFilter implements Filter {
 
index 2d6167a..d004dfc 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - RequestChangeFilter.java - Copyright © 2010–2016 David Roden
+ * Sone - RequestChangeFilter.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -34,8 +34,6 @@ import net.pterodactylus.util.template.TemplateContext;
  * This filter expects a {@link FreenetRequest} as input and outputs a
  * {@link URI} that is modified by the parameters. The name of the parameter is
  * handed in as “name”, the new value is stored in “value”.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class RequestChangeFilter implements Filter {
 
@@ -48,7 +46,7 @@ public class RequestChangeFilter implements Filter {
                String name = String.valueOf(parameters.get("name"));
                String value = String.valueOf(parameters.get("value"));
 
-               Map<String, String> values = new HashMap<String, String>();
+               Map<String, String> values = new HashMap<>();
                Collection<String> parameterNames = request.getHttpRequest().getParameterNames();
                for (String parameterName : parameterNames) {
                        values.put(parameterName, request.getHttpRequest().getParam(parameterName));
index 5f4ab0c..a8cbe57 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneAccessor.java - Copyright © 2010–2016 David Roden
+ * Sone - SoneAccessor.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -51,8 +51,6 @@ import net.pterodactylus.util.template.TemplateContext;
  * <dd>Will return {@code true} if the sone in question is the currently logged
  * in Sone.</dd>
  * </dl>
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class SoneAccessor extends ReflectionAccessor {
 
index f14d0ff..355b6a9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SubstringFilter.java - Copyright © 2010–2016 David Roden
+ * Sone - SubstringFilter.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -28,8 +28,6 @@ import net.pterodactylus.util.template.TemplateContext;
  * “start” and “length.” “length” is optional and defaults to “the rest of the
  * string.” “start” starts at {@code 0} and can be negative to denote starting
  * at the end of the string.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class SubstringFilter implements Filter {
 
index 32650f1..26a128b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - TrustAccessor.java - Copyright © 2010–2016 David Roden
+ * Sone - TrustAccessor.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -30,8 +30,6 @@ import net.pterodactylus.util.template.TemplateContext;
  * <dd>{@link Boolean} that indicates whether this trust relationship has an
  * explicit value assigned to it.</dd>
  * </dl>
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class TrustAccessor extends ReflectionAccessor {
 
index 2ca695b..6fb584e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UniqueElementFilter.java - Copyright © 2011–2016 David Roden
+ * Sone - UniqueElementFilter.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -27,8 +27,6 @@ import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * Filter that reduces a collection to a {@link Set}, removing duplicates.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class UniqueElementFilter implements Filter {
 
@@ -40,7 +38,7 @@ public class UniqueElementFilter implements Filter {
                if (!(data instanceof Collection<?>)) {
                        return data;
                }
-               return new HashSet<Object>((Collection<?>) data);
+               return new HashSet<>((Collection<?>) data);
        }
 
 }
index 2c5a004..0d4872b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UnknownDateFilter.java - Copyright © 2011–2016 David Roden
+ * Sone - UnknownDateFilter.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -26,8 +26,6 @@ import freenet.l10n.BaseL10n;
 /**
  * {@link Filter} implementation that replaces a {@link Long} with a value of
  * {@code 0} by a {@link String} from an {@link BaseL10n l10n handler}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class UnknownDateFilter implements Filter {
 
index 69e6bfe..d45d99a 100644 (file)
@@ -2,8 +2,6 @@ package net.pterodactylus.sone.text;
 
 /**
  * {@link Part} implementation that holds a freemail address.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class FreemailPart implements Part {
 
index f87b852..0daf30c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Parser.java - Copyright © 2010–2016 David Roden
+ * Sone - Parser.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -27,7 +27,6 @@ import javax.annotation.Nullable;
  *
  * @param <C>
  *            The type of the parser context
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public interface Parser<C extends ParserContext> {
 
index 7387967..056fcc1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ParserContext.java - Copyright © 2010–2016 David Roden
+ * Sone - ParserContext.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,8 +21,6 @@ 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(String, ParserContext)}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public interface ParserContext {
 
index e70448f..de957c5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostPart.java - Copyright © 2011–2016 David Roden
+ * Sone - PostPart.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -25,8 +25,6 @@ import net.pterodactylus.sone.data.Post;
 
 /**
  * {@link Part} implementation that stores a reference to a {@link Post}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class PostPart implements Part {
 
index fba81ab..13a7a26 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneTextParserContext.java - Copyright © 2011–2016 David Roden
+ * Sone - SoneTextParserContext.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -24,8 +24,6 @@ import net.pterodactylus.sone.web.page.FreenetRequest;
  * {@link ParserContext} implementation for the {@link SoneTextParser}. It
  * stores the {@link Sone} that provided the parsed text so that certain links
  * can be marked in a different way.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class SoneTextParserContext implements ParserContext {
 
index fa57f9c..0703f32 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - TextFilter.java - Copyright © 2011–2016 David Roden
+ * Sone - TextFilter.java - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -23,8 +23,6 @@ package net.pterodactylus.sone.text;
  * will be converted to “KSK@gpl.txt”. This will only work for links that point
  * to the same address Sone is accessed by, so if you access Sone using
  * localhost:8888, links to 127.0.0.1:8888 will not be removed.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class TextFilter {
 
index 0939f21..d9acaeb 100644 (file)
@@ -7,7 +7,6 @@ import com.google.common.base.Predicate;
  *
  * @param <T>
  *            The type of the option
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class DefaultOption<T> implements Option<T> {
 
index aac9594..1fd7527 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IntegerRangePredicate.java - Copyright © 2013–2016 David Roden
+ * Sone - IntegerRangePredicate.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -23,8 +23,6 @@ import com.google.common.base.Predicate;
  * {@link Predicate} that verifies that an {@link Integer} value is not
  * {@code null} and is between a lower and an upper bound. Both bounds are
  * inclusive.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class IntegerRangePredicate implements Predicate<Integer> {
 
index ce4da2f..471fc26 100644 (file)
@@ -8,8 +8,6 @@ import com.google.common.primitives.Longs;
 
 /**
  * Parses numbers from strings.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class NumberParsers {
 
index 9149c07..eca96f5 100644 (file)
@@ -5,7 +5,6 @@ package net.pterodactylus.sone.utils;
  *
  * @param <T>
  *            The type of the option
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public interface Option<T> {
 
diff --git a/src/main/java/net/pterodactylus/sone/web/AllPages.kt b/src/main/java/net/pterodactylus/sone/web/AllPages.kt
new file mode 100644 (file)
index 0000000..9eccccf
--- /dev/null
@@ -0,0 +1,55 @@
+package net.pterodactylus.sone.web
+
+import net.pterodactylus.sone.web.pages.*
+import javax.inject.Inject
+
+/**
+ * Container for all web pages. This uses field injection because there are way too many pages
+ * to sensibly use constructor injection.
+ */
+class AllPages {
+
+       @Inject lateinit var aboutPage: AboutPage
+       @Inject lateinit var bookmarkPage: BookmarkPage
+       @Inject lateinit var bookmarksPage: BookmarksPage
+       @Inject lateinit var createAlbumPage: CreateAlbumPage
+       @Inject lateinit var createPostPage: CreatePostPage
+       @Inject lateinit var createReplyPage: CreateReplyPage
+       @Inject lateinit var createSonePage: CreateSonePage
+       @Inject lateinit var deleteAlbumPage: DeleteAlbumPage
+       @Inject lateinit var deleteImagePage: DeleteImagePage
+       @Inject lateinit var deletePostPage: DeletePostPage
+       @Inject lateinit var deleteProfileFieldPage: DeleteProfileFieldPage
+       @Inject lateinit var deleteReplyPage: DeleteReplyPage
+       @Inject lateinit var deleteSonePage: DeleteSonePage
+       @Inject lateinit var dismissNotificationPage: DismissNotificationPage
+       @Inject lateinit var distrustPage: DistrustPage
+       @Inject lateinit var editAlbumPage: EditAlbumPage
+       @Inject lateinit var editImagePage: EditImagePage
+       @Inject lateinit var editProfileFieldPage: EditProfileFieldPage
+       @Inject lateinit var editProfilePage: EditProfilePage
+       @Inject lateinit var followSonePage: FollowSonePage
+       @Inject lateinit var getImagePage: GetImagePage
+       @Inject lateinit var imageBrowserPage: ImageBrowserPage
+       @Inject lateinit var indexPage: IndexPage
+       @Inject lateinit var knownSonesPage: KnownSonesPage
+       @Inject lateinit var likePage: LikePage
+       @Inject lateinit var lockSonePage: LockSonePage
+       @Inject lateinit var loginPage: LoginPage
+       @Inject lateinit var logoutPage: LogoutPage
+       @Inject lateinit var markAsKnownPage: MarkAsKnownPage
+       @Inject lateinit var newPage: NewPage
+       @Inject lateinit var optionsPage: OptionsPage
+       @Inject lateinit var rescuePage: RescuePage
+       @Inject lateinit var searchPage: SearchPage
+       @Inject lateinit var trustPage: TrustPage
+       @Inject lateinit var unbookmarkPage: UnbookmarkPage
+       @Inject lateinit var unfollowSonePage: UnfollowSonePage
+       @Inject lateinit var unlikePage: UnlikePage
+       @Inject lateinit var unlockSonePage: UnlockSonePage
+       @Inject lateinit var untrustPage: UntrustPage
+       @Inject lateinit var uploadImagePage: UploadImagePage
+       @Inject lateinit var viewPostPage: ViewPostPage
+       @Inject lateinit var viewSonePage: ViewSonePage
+
+}
index 6401b43..3d131c8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - WebInterface.java - Copyright © 2010–2016 David Roden
+ * Sone - WebInterface.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -22,12 +22,10 @@ import static java.util.logging.Logger.getLogger;
 import static net.pterodactylus.util.template.TemplateParser.parse;
 
 import java.io.StringReader;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.TimeZone;
@@ -61,49 +59,24 @@ import net.pterodactylus.sone.core.event.SoneLockedEvent;
 import net.pterodactylus.sone.core.event.SoneRemovedEvent;
 import net.pterodactylus.sone.core.event.SoneUnlockedEvent;
 import net.pterodactylus.sone.core.event.UpdateFoundEvent;
-import net.pterodactylus.sone.data.Album;
 import net.pterodactylus.sone.data.Image;
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.PostReply;
-import net.pterodactylus.sone.data.Profile;
-import net.pterodactylus.sone.data.Reply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.freenet.L10nFilter;
-import net.pterodactylus.sone.freenet.wot.Identity;
-import net.pterodactylus.sone.freenet.wot.Trust;
 import net.pterodactylus.sone.main.Loaders;
-import net.pterodactylus.sone.main.ReparseFilter;
+import net.pterodactylus.sone.main.PluginHomepage;
+import net.pterodactylus.sone.main.PluginVersion;
+import net.pterodactylus.sone.main.PluginYear;
 import net.pterodactylus.sone.main.SonePlugin;
-import net.pterodactylus.sone.main.SonePlugin.PluginHomepage;
-import net.pterodactylus.sone.main.SonePlugin.PluginVersion;
-import net.pterodactylus.sone.main.SonePlugin.PluginYear;
 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;
-import net.pterodactylus.sone.template.HttpRequestAccessor;
-import net.pterodactylus.sone.template.IdentityAccessor;
-import net.pterodactylus.sone.template.ImageAccessor;
-import net.pterodactylus.sone.template.ImageLinkFilter;
-import net.pterodactylus.sone.template.JavascriptFilter;
 import net.pterodactylus.sone.template.LinkedElementRenderFilter;
-import net.pterodactylus.sone.template.LinkedElementsFilter;
 import net.pterodactylus.sone.template.ParserFilter;
-import net.pterodactylus.sone.template.PostAccessor;
-import net.pterodactylus.sone.template.ProfileAccessor;
 import net.pterodactylus.sone.template.RenderFilter;
-import net.pterodactylus.sone.template.ReplyAccessor;
-import net.pterodactylus.sone.template.ReplyGroupFilter;
-import net.pterodactylus.sone.template.RequestChangeFilter;
 import net.pterodactylus.sone.template.ShortenFilter;
-import net.pterodactylus.sone.template.SoneAccessor;
-import net.pterodactylus.sone.template.SubstringFilter;
-import net.pterodactylus.sone.template.TrustAccessor;
-import net.pterodactylus.sone.template.UniqueElementFilter;
-import net.pterodactylus.sone.template.UnknownDateFilter;
 import net.pterodactylus.sone.text.Part;
 import net.pterodactylus.sone.text.SonePart;
 import net.pterodactylus.sone.text.SoneTextParser;
@@ -139,8 +112,7 @@ import net.pterodactylus.sone.web.ajax.UnlikeAjaxPage;
 import net.pterodactylus.sone.web.ajax.UnlockSoneAjaxPage;
 import net.pterodactylus.sone.web.ajax.UntrustAjaxPage;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.sone.web.page.PageToadlet;
-import net.pterodactylus.sone.web.page.PageToadletFactory;
+import net.pterodactylus.sone.web.page.TemplateRenderer;
 import net.pterodactylus.sone.web.pages.AboutPage;
 import net.pterodactylus.sone.web.pages.BookmarkPage;
 import net.pterodactylus.sone.web.pages.BookmarksPage;
@@ -160,10 +132,13 @@ import net.pterodactylus.sone.web.pages.EditAlbumPage;
 import net.pterodactylus.sone.web.pages.EditImagePage;
 import net.pterodactylus.sone.web.pages.EditProfileFieldPage;
 import net.pterodactylus.sone.web.pages.EditProfilePage;
+import net.pterodactylus.sone.web.pages.EmptyAlbumTitlePage;
+import net.pterodactylus.sone.web.pages.EmptyImageTitlePage;
 import net.pterodactylus.sone.web.pages.FollowSonePage;
 import net.pterodactylus.sone.web.pages.GetImagePage;
 import net.pterodactylus.sone.web.pages.ImageBrowserPage;
 import net.pterodactylus.sone.web.pages.IndexPage;
+import net.pterodactylus.sone.web.pages.InvalidPage;
 import net.pterodactylus.sone.web.pages.KnownSonesPage;
 import net.pterodactylus.sone.web.pages.LikePage;
 import net.pterodactylus.sone.web.pages.LockSonePage;
@@ -171,6 +146,7 @@ import net.pterodactylus.sone.web.pages.LoginPage;
 import net.pterodactylus.sone.web.pages.LogoutPage;
 import net.pterodactylus.sone.web.pages.MarkAsKnownPage;
 import net.pterodactylus.sone.web.pages.NewPage;
+import net.pterodactylus.sone.web.pages.NoPermissionPage;
 import net.pterodactylus.sone.web.pages.OptionsPage;
 import net.pterodactylus.sone.web.pages.RescuePage;
 import net.pterodactylus.sone.web.pages.SearchPage;
@@ -187,30 +163,15 @@ import net.pterodactylus.sone.web.pages.ViewSonePage;
 import net.pterodactylus.util.notify.Notification;
 import net.pterodactylus.util.notify.NotificationManager;
 import net.pterodactylus.util.notify.TemplateNotification;
-import net.pterodactylus.util.template.CollectionSortFilter;
-import net.pterodactylus.util.template.ContainsFilter;
-import net.pterodactylus.util.template.DateFilter;
-import net.pterodactylus.util.template.FormatFilter;
-import net.pterodactylus.util.template.HtmlFilter;
-import net.pterodactylus.util.template.MatchFilter;
-import net.pterodactylus.util.template.ModFilter;
-import net.pterodactylus.util.template.PaginationFilter;
-import net.pterodactylus.util.template.ReflectionAccessor;
-import net.pterodactylus.util.template.ReplaceFilter;
-import net.pterodactylus.util.template.StoreFilter;
 import net.pterodactylus.util.template.Template;
 import net.pterodactylus.util.template.TemplateContextFactory;
-import net.pterodactylus.util.template.TemplateProvider;
-import net.pterodactylus.util.template.XmlFilter;
 import net.pterodactylus.util.web.RedirectPage;
 import net.pterodactylus.util.web.TemplatePage;
 
 import freenet.clients.http.SessionManager;
 import freenet.clients.http.SessionManager.Session;
-import freenet.clients.http.ToadletContainer;
 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;
@@ -221,8 +182,6 @@ import com.google.inject.Inject;
 /**
  * Bundles functionality that a web interface of a Freenet plugin needs, e.g.
  * references to l10n helpers.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class WebInterface implements SessionProvider {
 
@@ -238,14 +197,12 @@ public class WebInterface implements SessionProvider {
        /** The Sone plugin. */
        private final SonePlugin sonePlugin;
 
-       /** The registered toadlets. */
-       private final List<PageToadlet> pageToadlets = new ArrayList<PageToadlet>();
-
        /** The form password. */
        private final String formPassword;
 
        /** The template context factory. */
        private final TemplateContextFactory templateContextFactory;
+       private final TemplateRenderer templateRenderer;
 
        /** The Sone text parser. */
        private final SoneTextParser soneTextParser;
@@ -262,7 +219,9 @@ public class WebInterface implements SessionProvider {
        private final ElementLoader elementLoader;
        private final LinkedElementRenderFilter linkedElementRenderFilter;
        private final TimeTextConverter timeTextConverter = new TimeTextConverter();
-       private final L10nFilter l10nFilter = new L10nFilter(this);
+       private final L10nFilter l10nFilter;
+
+       private final PageToadletRegistry pageToadletRegistry;
 
        /** The “new Sone” notification. */
        private final ListNotification<Sone> newSoneNotification;
@@ -283,7 +242,7 @@ public class WebInterface implements SessionProvider {
        private final ListNotification<Post> mentionNotification;
 
        /** Notifications for sone inserts. */
-       private final Map<Sone, TemplateNotification> soneInsertNotifications = new HashMap<Sone, TemplateNotification>();
+       private final Map<Sone, TemplateNotification> soneInsertNotifications = new HashMap<>();
 
        /** Sone locked notification ticker objects. */
        private final Map<Sone, ScheduledFuture<?>> lockedSonesTickerObjects = Collections.synchronizedMap(new HashMap<Sone, ScheduledFuture<?>>());
@@ -306,99 +265,68 @@ public class WebInterface implements SessionProvider {
        /** Scheduled executor for time-based notifications. */
        private final ScheduledExecutorService ticker = Executors.newScheduledThreadPool(1);
 
-       /**
-        * Creates a new web interface.
-        *
-        * @param sonePlugin
-        *            The Sone plugin
-        */
        @Inject
-       public WebInterface(SonePlugin sonePlugin, Loaders loaders, ListNotificationFilter listNotificationFilter, PostVisibilityFilter postVisibilityFilter, ReplyVisibilityFilter replyVisibilityFilter, ElementLoader elementLoader) {
+       public WebInterface(SonePlugin sonePlugin, Loaders loaders, ListNotificationFilter listNotificationFilter,
+                       PostVisibilityFilter postVisibilityFilter, ReplyVisibilityFilter replyVisibilityFilter,
+                       ElementLoader elementLoader, TemplateContextFactory templateContextFactory,
+                       TemplateRenderer templateRenderer,
+                       ParserFilter parserFilter, ShortenFilter shortenFilter,
+                       RenderFilter renderFilter,
+                       LinkedElementRenderFilter linkedElementRenderFilter,
+                       PageToadletRegistry pageToadletRegistry) {
                this.sonePlugin = sonePlugin;
                this.loaders = loaders;
                this.listNotificationFilter = listNotificationFilter;
                this.postVisibilityFilter = postVisibilityFilter;
                this.replyVisibilityFilter = replyVisibilityFilter;
                this.elementLoader = elementLoader;
+               this.templateRenderer = templateRenderer;
+               this.parserFilter = parserFilter;
+               this.shortenFilter = shortenFilter;
+               this.renderFilter = renderFilter;
+               this.linkedElementRenderFilter = linkedElementRenderFilter;
+               this.pageToadletRegistry = pageToadletRegistry;
                formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
                soneTextParser = new SoneTextParser(getCore(), getCore());
+               l10nFilter = new L10nFilter(getL10n());
 
-               templateContextFactory = new TemplateContextFactory();
-               templateContextFactory.addAccessor(Object.class, new ReflectionAccessor());
-               templateContextFactory.addAccessor(Collection.class, new CollectionAccessor());
-               templateContextFactory.addAccessor(Sone.class, new SoneAccessor(getCore(), new TimeTextConverter()));
-               templateContextFactory.addAccessor(Post.class, new PostAccessor(getCore()));
-               templateContextFactory.addAccessor(Reply.class, new ReplyAccessor(getCore()));
-               templateContextFactory.addAccessor(Album.class, new AlbumAccessor());
-               templateContextFactory.addAccessor(Image.class, new ImageAccessor());
-               templateContextFactory.addAccessor(Identity.class, new IdentityAccessor(getCore()));
-               templateContextFactory.addAccessor(Trust.class, new TrustAccessor());
-               templateContextFactory.addAccessor(HTTPRequest.class, new HttpRequestAccessor());
-               templateContextFactory.addAccessor(Profile.class, new ProfileAccessor(getCore()));
-               templateContextFactory.addFilter("date", new DateFilter());
-               templateContextFactory.addFilter("html", new HtmlFilter());
-               templateContextFactory.addFilter("replace", new ReplaceFilter());
-               templateContextFactory.addFilter("store", new StoreFilter());
-               templateContextFactory.addFilter("l10n", new L10nFilter(this));
-               templateContextFactory.addFilter("substring", new SubstringFilter());
-               templateContextFactory.addFilter("xml", new XmlFilter());
-               templateContextFactory.addFilter("change", new RequestChangeFilter());
-               templateContextFactory.addFilter("match", new MatchFilter());
-               templateContextFactory.addFilter("css", new CssClassNameFilter());
-               templateContextFactory.addFilter("js", new JavascriptFilter());
-               templateContextFactory.addFilter("parse", parserFilter = new ParserFilter(getCore(), soneTextParser));
-               templateContextFactory.addFilter("shorten", shortenFilter = new ShortenFilter());
-               templateContextFactory.addFilter("render", renderFilter = new RenderFilter(getCore(), templateContextFactory));
-               templateContextFactory.addFilter("linked-elements", new LinkedElementsFilter(elementLoader));
-               templateContextFactory.addFilter("render-linked-element", linkedElementRenderFilter = new LinkedElementRenderFilter(templateContextFactory));
-               templateContextFactory.addFilter("reparse", new ReparseFilter());
-               templateContextFactory.addFilter("unknown", new UnknownDateFilter(getL10n(), "View.Sone.Text.UnknownDate"));
-               templateContextFactory.addFilter("format", new FormatFilter());
-               templateContextFactory.addFilter("sort", new CollectionSortFilter());
-               templateContextFactory.addFilter("image-link", new ImageLinkFilter(getCore(), templateContextFactory));
-               templateContextFactory.addFilter("replyGroup", new ReplyGroupFilter());
-               templateContextFactory.addFilter("in", new ContainsFilter());
-               templateContextFactory.addFilter("unique", new UniqueElementFilter());
-               templateContextFactory.addFilter("mod", new ModFilter());
-               templateContextFactory.addFilter("paginate", new PaginationFilter());
-               templateContextFactory.addProvider(TemplateProvider.TEMPLATE_CONTEXT_PROVIDER);
-               templateContextFactory.addProvider(loaders.getTemplateProvider());
+               this.templateContextFactory = templateContextFactory;
                templateContextFactory.addTemplateObject("webInterface", this);
                templateContextFactory.addTemplateObject("formPassword", formPassword);
 
                /* create notifications. */
                Template newSoneNotificationTemplate = loaders.loadTemplate("/templates/notify/newSoneNotification.html");
-               newSoneNotification = new ListNotification<Sone>("new-sone-notification", "sones", newSoneNotificationTemplate, false);
+               newSoneNotification = new ListNotification<>("new-sone-notification", "sones", newSoneNotificationTemplate, false);
 
                Template newPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html");
-               newPostNotification = new ListNotification<Post>("new-post-notification", "posts", newPostNotificationTemplate, false);
+               newPostNotification = new ListNotification<>("new-post-notification", "posts", newPostNotificationTemplate, false);
 
                Template localPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html");
-               localPostNotification = new ListNotification<Post>("local-post-notification", "posts", localPostNotificationTemplate, false);
+               localPostNotification = new ListNotification<>("local-post-notification", "posts", localPostNotificationTemplate, false);
 
                Template newReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
-               newReplyNotification = new ListNotification<PostReply>("new-reply-notification", "replies", newReplyNotificationTemplate, false);
+               newReplyNotification = new ListNotification<>("new-reply-notification", "replies", newReplyNotificationTemplate, false);
 
                Template localReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
-               localReplyNotification = new ListNotification<PostReply>("local-reply-notification", "replies", localReplyNotificationTemplate, false);
+               localReplyNotification = new ListNotification<>("local-reply-notification", "replies", localReplyNotificationTemplate, false);
 
                Template mentionNotificationTemplate = loaders.loadTemplate("/templates/notify/mentionNotification.html");
-               mentionNotification = new ListNotification<Post>("mention-notification", "posts", mentionNotificationTemplate, false);
+               mentionNotification = new ListNotification<>("mention-notification", "posts", mentionNotificationTemplate, false);
 
                Template lockedSonesTemplate = loaders.loadTemplate("/templates/notify/lockedSonesNotification.html");
-               lockedSonesNotification = new ListNotification<Sone>("sones-locked-notification", "sones", lockedSonesTemplate);
+               lockedSonesNotification = new ListNotification<>("sones-locked-notification", "sones", lockedSonesTemplate);
 
                Template newVersionTemplate = loaders.loadTemplate("/templates/notify/newVersionNotification.html");
                newVersionNotification = new TemplateNotification("new-version-notification", newVersionTemplate);
 
                Template insertingImagesTemplate = loaders.loadTemplate("/templates/notify/inserting-images-notification.html");
-               insertingImagesNotification = new ListNotification<Image>("inserting-images-notification", "images", insertingImagesTemplate);
+               insertingImagesNotification = new ListNotification<>("inserting-images-notification", "images", insertingImagesTemplate);
 
                Template insertedImagesTemplate = loaders.loadTemplate("/templates/notify/inserted-images-notification.html");
-               insertedImagesNotification = new ListNotification<Image>("inserted-images-notification", "images", insertedImagesTemplate);
+               insertedImagesNotification = new ListNotification<>("inserted-images-notification", "images", insertedImagesTemplate);
 
                Template imageInsertFailedTemplate = loaders.loadTemplate("/templates/notify/image-insert-failed-notification.html");
-               imageInsertFailedNotification = new ListNotification<Image>("image-insert-failed-notification", "images", imageInsertFailedTemplate);
+               imageInsertFailedNotification = new ListNotification<>("image-insert-failed-notification", "images", imageInsertFailedTemplate);
        }
 
        //
@@ -669,7 +597,7 @@ public class WebInterface implements SessionProvider {
         * Stops the web interface and unregisters all toadlets.
         */
        public void stop() {
-               unregisterToadlets();
+               pageToadletRegistry.unregisterToadlets();
                ticker.shutdownNow();
        }
 
@@ -681,143 +609,93 @@ public class WebInterface implements SessionProvider {
         * Register all toadlets.
         */
        private void registerToadlets() {
-               Template emptyTemplate = parse(new StringReader(""));
-               Template loginTemplate = loaders.loadTemplate("/templates/login.html");
-               Template indexTemplate = loaders.loadTemplate("/templates/index.html");
-               Template newTemplate = loaders.loadTemplate("/templates/new.html");
-               Template knownSonesTemplate = loaders.loadTemplate("/templates/knownSones.html");
-               Template createSoneTemplate = loaders.loadTemplate("/templates/createSone.html");
-               Template createPostTemplate = loaders.loadTemplate("/templates/createPost.html");
-               Template createReplyTemplate = loaders.loadTemplate("/templates/createReply.html");
-               Template bookmarksTemplate = loaders.loadTemplate("/templates/bookmarks.html");
-               Template searchTemplate = loaders.loadTemplate("/templates/search.html");
-               Template editProfileTemplate = loaders.loadTemplate("/templates/editProfile.html");
-               Template editProfileFieldTemplate = loaders.loadTemplate("/templates/editProfileField.html");
-               Template deleteProfileFieldTemplate = loaders.loadTemplate("/templates/deleteProfileField.html");
-               Template viewSoneTemplate = loaders.loadTemplate("/templates/viewSone.html");
-               Template viewPostTemplate = loaders.loadTemplate("/templates/viewPost.html");
-               Template deletePostTemplate = loaders.loadTemplate("/templates/deletePost.html");
-               Template deleteReplyTemplate = loaders.loadTemplate("/templates/deleteReply.html");
-               Template deleteSoneTemplate = loaders.loadTemplate("/templates/deleteSone.html");
-               Template imageBrowserTemplate = loaders.loadTemplate("/templates/imageBrowser.html");
-               Template createAlbumTemplate = loaders.loadTemplate("/templates/createAlbum.html");
-               Template deleteAlbumTemplate = loaders.loadTemplate("/templates/deleteAlbum.html");
-               Template deleteImageTemplate = loaders.loadTemplate("/templates/deleteImage.html");
-               Template noPermissionTemplate = loaders.loadTemplate("/templates/noPermission.html");
-               Template emptyImageTitleTemplate = loaders.loadTemplate("/templates/emptyImageTitle.html");
-               Template emptyAlbumTitleTemplate = loaders.loadTemplate("/templates/emptyAlbumTitle.html");
-               Template optionsTemplate = loaders.loadTemplate("/templates/options.html");
-               Template rescueTemplate = loaders.loadTemplate("/templates/rescue.html");
-               Template aboutTemplate = loaders.loadTemplate("/templates/about.html");
-               Template invalidTemplate = loaders.loadTemplate("/templates/invalid.html");
                Template postTemplate = loaders.loadTemplate("/templates/include/viewPost.html");
                Template replyTemplate = loaders.loadTemplate("/templates/include/viewReply.html");
                Template openSearchTemplate = loaders.loadTemplate("/templates/xml/OpenSearch.xml");
 
-               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, 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"));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new EditProfilePage(editProfileTemplate, this), "EditProfile"));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new EditProfileFieldPage(editProfileFieldTemplate, this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new DeleteProfileFieldPage(deleteProfileFieldTemplate, this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new CreatePostPage(createPostTemplate, this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new CreateReplyPage(createReplyTemplate, this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new ViewSonePage(viewSoneTemplate, this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new ViewPostPage(viewPostTemplate, this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new LikePage(emptyTemplate, this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new UnlikePage(emptyTemplate, this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new DeletePostPage(deletePostTemplate, this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new DeleteReplyPage(deleteReplyTemplate, this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new LockSonePage(emptyTemplate, this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new UnlockSonePage(emptyTemplate, this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new FollowSonePage(emptyTemplate, this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new UnfollowSonePage(emptyTemplate, this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new ImageBrowserPage(imageBrowserTemplate, this), "ImageBrowser"));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new CreateAlbumPage(createAlbumTemplate, this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new EditAlbumPage(emptyTemplate, this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new DeleteAlbumPage(deleteAlbumTemplate, this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new UploadImagePage(invalidTemplate, this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new EditImagePage(emptyTemplate, this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new DeleteImagePage(deleteImageTemplate, this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new TrustPage(emptyTemplate, this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new DistrustPage(emptyTemplate, this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new UntrustPage(emptyTemplate, this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new MarkAsKnownPage(emptyTemplate, this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new BookmarkPage(emptyTemplate, this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new UnbookmarkPage(emptyTemplate, this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new BookmarksPage(bookmarksTemplate, this), "Bookmarks"));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new SearchPage(searchTemplate, this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new DeleteSonePage(deleteSoneTemplate, this), "DeleteSone"));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new LoginPage(loginTemplate, this), "Login"));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new LogoutPage(emptyTemplate, this), "Logout"));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new OptionsPage(optionsTemplate, this), "Options"));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new RescuePage(rescueTemplate, this), "Rescue"));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new AboutPage(aboutTemplate, this, new PluginVersion(SonePlugin.getPluginVersion()), new PluginYear(SonePlugin.getYear()), new PluginHomepage(SonePlugin.getHomepage())), "About"));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new SoneTemplatePage("noPermission.html", noPermissionTemplate, "Page.NoPermission.Title", this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new SoneTemplatePage("emptyImageTitle.html", emptyImageTitleTemplate, "Page.EmptyImageTitle.Title", this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new SoneTemplatePage("emptyAlbumTitle.html", emptyAlbumTitleTemplate, "Page.EmptyAlbumTitle.Title", this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new DismissNotificationPage(emptyTemplate, this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new SoneTemplatePage("invalid.html", invalidTemplate, "Page.Invalid.Title", this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(loaders.<FreenetRequest>loadStaticPage("css/", "/static/css/", "text/css")));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(loaders.<FreenetRequest>loadStaticPage("javascript/", "/static/javascript/", "text/javascript")));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(loaders.<FreenetRequest>loadStaticPage("images/", "/static/images/", "image/png")));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new TemplatePage<FreenetRequest>("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new GetImagePage(this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new GetTranslationAjaxPage(this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new GetStatusAjaxPage(this, elementLoader, timeTextConverter, l10nFilter, TimeZone.getDefault())));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new GetNotificationsAjaxPage(this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new DismissNotificationAjaxPage(this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new CreatePostAjaxPage(this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new CreateReplyAjaxPage(this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new GetReplyAjaxPage(this, replyTemplate)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new GetPostAjaxPage(this, postTemplate)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new GetLinkedElementAjaxPage(this, elementLoader, linkedElementRenderFilter)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new GetTimesAjaxPage(this, timeTextConverter, l10nFilter, TimeZone.getDefault())));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new MarkAsKnownAjaxPage(this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new DeletePostAjaxPage(this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new DeleteReplyAjaxPage(this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new LockSoneAjaxPage(this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new UnlockSoneAjaxPage(this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new FollowSoneAjaxPage(this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new UnfollowSoneAjaxPage(this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new EditAlbumAjaxPage(this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new EditImageAjaxPage(this, parserFilter, shortenFilter, renderFilter)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new TrustAjaxPage(this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new DistrustAjaxPage(this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new UntrustAjaxPage(this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new LikeAjaxPage(this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new UnlikeAjaxPage(this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new GetLikesAjaxPage(this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new BookmarkAjaxPage(this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new UnbookmarkAjaxPage(this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new EditProfileFieldAjaxPage(this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new DeleteProfileFieldAjaxPage(this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new MoveProfileFieldAjaxPage(this)));
-
-               ToadletContainer toadletContainer = sonePlugin.pluginRespirator().getToadletContainer();
-               toadletContainer.getPageMaker().addNavigationCategory("/Sone/index.html", "Navigation.Menu.Sone.Name", "Navigation.Menu.Sone.Tooltip", sonePlugin);
-               for (PageToadlet toadlet : pageToadlets) {
-                       String menuName = toadlet.getMenuName();
-                       if (menuName != null) {
-                               toadletContainer.register(toadlet, "Navigation.Menu.Sone.Name", toadlet.path(), true, "Navigation.Menu.Sone.Item." + menuName + ".Name", "Navigation.Menu.Sone.Item." + menuName + ".Tooltip", false, toadlet);
-                       } else {
-                               toadletContainer.register(toadlet, null, toadlet.path(), true, false);
-                       }
-               }
-       }
-
-       /**
-        * Unregisters all toadlets.
-        */
-       private void unregisterToadlets() {
-               ToadletContainer toadletContainer = sonePlugin.pluginRespirator().getToadletContainer();
-               for (PageToadlet pageToadlet : pageToadlets) {
-                       toadletContainer.unregister(pageToadlet);
-               }
-               toadletContainer.getPageMaker().removeNavigationCategory("Navigation.Menu.Sone.Name");
+               pageToadletRegistry.addPage(new RedirectPage<FreenetRequest>("", "index.html"));
+               pageToadletRegistry.addPage(new IndexPage(this, loaders, templateRenderer, postVisibilityFilter));
+               pageToadletRegistry.addPage(new NewPage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new CreateSonePage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new KnownSonesPage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new EditProfilePage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new EditProfileFieldPage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new DeleteProfileFieldPage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new CreatePostPage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new CreateReplyPage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new ViewSonePage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new ViewPostPage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new LikePage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new UnlikePage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new DeletePostPage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new DeleteReplyPage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new LockSonePage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new UnlockSonePage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new FollowSonePage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new UnfollowSonePage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new ImageBrowserPage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new CreateAlbumPage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new EditAlbumPage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new DeleteAlbumPage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new UploadImagePage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new EditImagePage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new DeleteImagePage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new TrustPage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new DistrustPage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new UntrustPage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new MarkAsKnownPage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new BookmarkPage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new UnbookmarkPage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new BookmarksPage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new SearchPage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new DeleteSonePage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new LoginPage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new LogoutPage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new OptionsPage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new RescuePage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new AboutPage(this, loaders, templateRenderer, new PluginVersion(SonePlugin.getPluginVersion()), new PluginYear(sonePlugin.getYear()), new PluginHomepage(sonePlugin.getHomepage())));
+               pageToadletRegistry.addPage(new InvalidPage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new NoPermissionPage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new EmptyImageTitlePage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new EmptyAlbumTitlePage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(new DismissNotificationPage(this, loaders, templateRenderer));
+               pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("css/", "/static/css/", "text/css"));
+               pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("javascript/", "/static/javascript/", "text/javascript"));
+               pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("images/", "/static/images/", "image/png"));
+               pageToadletRegistry.addPage(new TemplatePage<FreenetRequest>("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate));
+               pageToadletRegistry.addPage(new GetImagePage(this));
+               pageToadletRegistry.addPage(new GetTranslationAjaxPage(this));
+               pageToadletRegistry.addPage(new GetStatusAjaxPage(this, elementLoader, timeTextConverter, l10nFilter, TimeZone.getDefault()));
+               pageToadletRegistry.addPage(new GetNotificationsAjaxPage(this));
+               pageToadletRegistry.addPage(new DismissNotificationAjaxPage(this));
+               pageToadletRegistry.addPage(new CreatePostAjaxPage(this));
+               pageToadletRegistry.addPage(new CreateReplyAjaxPage(this));
+               pageToadletRegistry.addPage(new GetReplyAjaxPage(this, replyTemplate));
+               pageToadletRegistry.addPage(new GetPostAjaxPage(this, postTemplate));
+               pageToadletRegistry.addPage(new GetLinkedElementAjaxPage(this, elementLoader, linkedElementRenderFilter));
+               pageToadletRegistry.addPage(new GetTimesAjaxPage(this, timeTextConverter, l10nFilter, TimeZone.getDefault()));
+               pageToadletRegistry.addPage(new MarkAsKnownAjaxPage(this));
+               pageToadletRegistry.addPage(new DeletePostAjaxPage(this));
+               pageToadletRegistry.addPage(new DeleteReplyAjaxPage(this));
+               pageToadletRegistry.addPage(new LockSoneAjaxPage(this));
+               pageToadletRegistry.addPage(new UnlockSoneAjaxPage(this));
+               pageToadletRegistry.addPage(new FollowSoneAjaxPage(this));
+               pageToadletRegistry.addPage(new UnfollowSoneAjaxPage(this));
+               pageToadletRegistry.addPage(new EditAlbumAjaxPage(this));
+               pageToadletRegistry.addPage(new EditImageAjaxPage(this, parserFilter, shortenFilter, renderFilter));
+               pageToadletRegistry.addPage(new TrustAjaxPage(this));
+               pageToadletRegistry.addPage(new DistrustAjaxPage(this));
+               pageToadletRegistry.addPage(new UntrustAjaxPage(this));
+               pageToadletRegistry.addPage(new LikeAjaxPage(this));
+               pageToadletRegistry.addPage(new UnlikeAjaxPage(this));
+               pageToadletRegistry.addPage(new GetLikesAjaxPage(this));
+               pageToadletRegistry.addPage(new BookmarkAjaxPage(this));
+               pageToadletRegistry.addPage(new UnbookmarkAjaxPage(this));
+               pageToadletRegistry.addPage(new EditProfileFieldAjaxPage(this));
+               pageToadletRegistry.addPage(new DeleteProfileFieldAjaxPage(this));
+               pageToadletRegistry.addPage(new MoveProfileFieldAjaxPage(this));
+
+               pageToadletRegistry.registerToadlets();
        }
 
        /**
@@ -831,7 +709,7 @@ public class WebInterface implements SessionProvider {
         */
        private Collection<Sone> getMentionedSones(String text) {
                /* we need no context to find mentioned Sones. */
-               Set<Sone> mentionedSones = new HashSet<Sone>();
+               Set<Sone> mentionedSones = new HashSet<>();
                for (Part part : soneTextParser.parse(text, null)) {
                        if (part instanceof SonePart) {
                                mentionedSones.add(((SonePart) part).getSone());
diff --git a/src/main/java/net/pterodactylus/sone/web/page/FreenetPage.java b/src/main/java/net/pterodactylus/sone/web/page/FreenetPage.java
deleted file mode 100644 (file)
index 813703c..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.web.page;
-
-import java.net.URI;
-
-import net.pterodactylus.util.web.Page;
-
-/**
- * Freenet-specific {@link Page} extension that adds the capability to allow a
- * link to a page to be unharmed by Freenet’s content filter.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface FreenetPage extends Page<FreenetRequest> {
-
-       /**
-        * Returns whether the given should be excepted from being filtered.
-        *
-        * @param link
-        *            The link to check
-        * @return {@code true} if the link should not be filtered, {@code false} if
-        *         it should be filtered
-        */
-       public boolean isLinkExcepted(URI link);
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/web/page/FreenetRequest.java b/src/main/java/net/pterodactylus/sone/web/page/FreenetRequest.java
deleted file mode 100644 (file)
index ecbc0bb..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.web.page;
-
-import java.net.URI;
-
-import net.pterodactylus.util.web.Method;
-import net.pterodactylus.util.web.Request;
-import freenet.clients.http.ToadletContext;
-import freenet.support.api.HTTPRequest;
-
-/**
- * Encapsulates all Freenet-specific properties of a request.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class FreenetRequest extends Request {
-
-       /** The underlying HTTP request from Freenet. */
-       private final HTTPRequest httpRequest;
-
-       /** The toadlet context. */
-       private final ToadletContext toadletContext;
-
-       /**
-        * Creates a new freenet request.
-        *
-        * @param uri
-        *            The URI that is being accessed
-        * @param method
-        *            The method used to access this page
-        * @param httpRequest
-        *            The underlying HTTP request from Freenet
-        * @param toadletContext
-        *            The toadlet context
-        */
-       public FreenetRequest(URI uri, Method method, HTTPRequest httpRequest, ToadletContext toadletContext) {
-               super(uri, method);
-               this.httpRequest = httpRequest;
-               this.toadletContext = toadletContext;
-       }
-
-       //
-       // ACCESSORS
-       //
-
-       /**
-        * Returns the underlying HTTP request from Freenet.
-        *
-        * @return The underlying HTTP request from Freenet
-        */
-       public HTTPRequest getHttpRequest() {
-               return httpRequest;
-       }
-
-       /**
-        * Returns the toadlet context.
-        *
-        * @return The toadlet context
-        */
-       public ToadletContext getToadletContext() {
-               return toadletContext;
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/web/page/FreenetTemplatePage.java b/src/main/java/net/pterodactylus/sone/web/page/FreenetTemplatePage.java
deleted file mode 100644 (file)
index 9cfd026..0000000
+++ /dev/null
@@ -1,323 +0,0 @@
-/*
- * 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
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.web.page;
-
-import static java.lang.String.format;
-import static java.util.logging.Logger.getLogger;
-
-import java.io.IOException;
-import java.io.StringWriter;
-import java.net.URI;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import net.pterodactylus.util.template.Template;
-import net.pterodactylus.util.template.TemplateContext;
-import net.pterodactylus.util.template.TemplateContextFactory;
-import net.pterodactylus.util.web.Method;
-import net.pterodactylus.util.web.Page;
-import net.pterodactylus.util.web.RedirectResponse;
-import net.pterodactylus.util.web.Response;
-import freenet.clients.http.LinkEnabledCallback;
-import freenet.clients.http.PageMaker;
-import freenet.clients.http.PageNode;
-import freenet.clients.http.ToadletContext;
-import freenet.support.HTMLNode;
-
-/**
- * Base class for all {@link Page}s that are rendered with {@link Template}s and
- * fit into Freenet’s web interface.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class FreenetTemplatePage implements FreenetPage, LinkEnabledCallback {
-
-       /** The logger. */
-       private static final Logger logger = getLogger(FreenetTemplatePage.class.getName());
-
-       /** The path of the page. */
-       private final String path;
-
-       /** The template context factory. */
-       private final TemplateContextFactory templateContextFactory;
-
-       /** The template to render. */
-       private final Template template;
-
-       /** Where to redirect for invalid form passwords. */
-       private final String invalidFormPasswordRedirectTarget;
-
-       /**
-        * Creates a new template page.
-        *
-        * @param path
-        *            The path of the page
-        * @param templateContextFactory
-        *            The template context factory
-        * @param template
-        *            The template to render
-        * @param invalidFormPasswordRedirectTarget
-        *            The target to redirect to if a POST request does not contain
-        *            the correct form password
-        */
-       public FreenetTemplatePage(String path, TemplateContextFactory templateContextFactory, Template template, String invalidFormPasswordRedirectTarget) {
-               this.path = path;
-               this.templateContextFactory = templateContextFactory;
-               this.template = template;
-               this.invalidFormPasswordRedirectTarget = invalidFormPasswordRedirectTarget;
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public String getPath() {
-               return path;
-       }
-
-       /**
-        * Returns the title of the page.
-        *
-        * @param request
-        *            The request to serve
-        * @return The title of the page
-        */
-       @SuppressWarnings("static-method")
-       protected String getPageTitle(FreenetRequest request) {
-               return null;
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public boolean isPrefixPage() {
-               return false;
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public final Response handleRequest(FreenetRequest request, Response response) throws IOException {
-               String redirectTarget = getRedirectTarget(request);
-               if (redirectTarget != null) {
-                       return new RedirectResponse(redirectTarget);
-               }
-
-               if (isFullAccessOnly() && !request.getToadletContext().isAllowedFullAccess()) {
-                       return response.setStatusCode(401).setStatusText("Not authorized").setContentType("text/html");
-               }
-               ToadletContext toadletContext = request.getToadletContext();
-               if (request.getMethod() == Method.POST) {
-                       /* require form password. */
-                       String formPassword = request.getHttpRequest().getPartAsStringFailsafe("formPassword", 32);
-                       if (!formPassword.equals(toadletContext.getContainer().getFormPassword())) {
-                               return new RedirectResponse(invalidFormPasswordRedirectTarget);
-                       }
-               }
-               PageMaker pageMaker = toadletContext.getPageMaker();
-               PageNode pageNode = pageMaker.getPageNode(getPageTitle(request), toadletContext);
-               for (String styleSheet : getStyleSheets()) {
-                       pageNode.addCustomStyleSheet(styleSheet);
-               }
-               for (Map<String, String> linkNodeParameters : getAdditionalLinkNodes(request)) {
-                       HTMLNode linkNode = pageNode.headNode.addChild("link");
-                       for (Entry<String, String> parameter : linkNodeParameters.entrySet()) {
-                               linkNode.addAttribute(parameter.getKey(), parameter.getValue());
-                       }
-               }
-               String shortcutIcon = getShortcutIcon();
-               if (shortcutIcon != null) {
-                       pageNode.addForwardLink("icon", shortcutIcon);
-               }
-
-               TemplateContext templateContext = templateContextFactory.createTemplateContext();
-               templateContext.mergeContext(template.getInitialContext());
-               try {
-                       long start = System.nanoTime();
-                       processTemplate(request, templateContext);
-                       long finish = System.nanoTime();
-                       logger.log(Level.FINEST, format("Template was rendered in %.2fms.", (finish - start) / 1000000.0));
-               } catch (RedirectException re1) {
-                       return new RedirectResponse(re1.getTarget());
-               }
-
-               StringWriter stringWriter = new StringWriter();
-               template.render(templateContext, stringWriter);
-               pageNode.content.addChild("%", stringWriter.toString());
-
-               postProcess(request, templateContext);
-
-               return response.setStatusCode(200).setStatusText("OK").setContentType("text/html").write(pageNode.outer.generate());
-       }
-
-       /**
-        * Can be overridden to return a custom set of style sheets that are to be
-        * included in the page’s header.
-        *
-        * @return Additional style sheets to load
-        */
-       @SuppressWarnings("static-method")
-       protected Collection<String> getStyleSheets() {
-               return Collections.emptySet();
-       }
-
-       /**
-        * Returns the name of the shortcut icon to include in the page’s header.
-        *
-        * @return The URL of the shortcut icon, or {@code null} for no icon
-        */
-       @SuppressWarnings("static-method")
-       protected String getShortcutIcon() {
-               return null;
-       }
-
-       /**
-        * Can be overridden when extending classes need to set variables in the
-        * template before it is rendered.
-        *
-        * @param request
-        *            The request that is rendered
-        * @param templateContext
-        *            The template context to set variables in
-        * @throws RedirectException
-        *             if the processing page wants to redirect after processing
-        */
-       protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException {
-               /* do nothing. */
-       }
-
-       /**
-        * This method will be called after
-        * {@link #processTemplate(FreenetRequest, TemplateContext)} has processed
-        * the template and the template was rendered. This method will not be
-        * called if {@link #processTemplate(FreenetRequest, TemplateContext)}
-        * throws a {@link RedirectException}!
-        *
-        * @param request
-        *            The request being processed
-        * @param templateContext
-        *            The template context that supplied the rendered data
-        */
-       protected void postProcess(FreenetRequest request, TemplateContext templateContext) {
-               /* do nothing. */
-       }
-
-       /**
-        * Can be overridden to redirect the user to a different page, in case a log
-        * in is required, or something else is wrong.
-        *
-        * @param request
-        *            The request that is processed
-        * @return The URL to redirect to, or {@code null} to not redirect
-        */
-       @SuppressWarnings("static-method")
-       protected String getRedirectTarget(FreenetRequest request) {
-               return null;
-       }
-
-       /**
-        * Returns additional &lt;link&gt; nodes for the HTML’s &lt;head&gt; node.
-        *
-        * @param request
-        *            The request for which to return the link nodes
-        * @return All link nodes that should be added to the HTML head
-        */
-       @SuppressWarnings("static-method")
-       protected List<Map<String, String>> getAdditionalLinkNodes(FreenetRequest request) {
-               return Collections.emptyList();
-       }
-
-       /**
-        * Returns whether this page should only be allowed for requests from hosts
-        * with full access.
-        *
-        * @return {@code true} if this page should only be allowed for hosts with
-        *         full access, {@code false} to allow this page for any host
-        */
-       @SuppressWarnings("static-method")
-       protected boolean isFullAccessOnly() {
-               return false;
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public boolean isLinkExcepted(URI link) {
-               return false;
-       }
-
-       //
-       // INTERFACE LinkEnabledCallback
-       //
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public boolean isEnabled(ToadletContext toadletContext) {
-               return !isFullAccessOnly();
-       }
-
-       /**
-        * Exception that can be thrown to signal that a subclassed {@link Page}
-        * wants to redirect the user during the
-        * {@link FreenetTemplatePage#processTemplate(FreenetRequest, TemplateContext)}
-        * method call.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
-        */
-       public static class RedirectException extends Exception {
-
-               /** The target to redirect to. */
-               private final String target;
-
-               /**
-                * Creates a new redirect exception.
-                *
-                * @param target
-                *            The target of the redirect
-                */
-               public RedirectException(String target) {
-                       this.target = target;
-               }
-
-               /**
-                * Returns the target to redirect to.
-                *
-                * @return The target to redirect to
-                */
-               public String getTarget() {
-                       return target;
-               }
-
-               @Override
-               public String toString() {
-                       return format("RedirectException{target='%s'}", target);
-               }
-
-       }
-
-}
index 17a1bab..2b22377 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PageToadlet.java - Copyright © 2010–2016 David Roden
+ * Sone - PageToadlet.java - Copyright © 2010–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -30,19 +30,21 @@ import net.pterodactylus.util.web.Response;
 import freenet.client.HighLevelSimpleClient;
 import freenet.clients.http.LinkEnabledCallback;
 import freenet.clients.http.LinkFilterExceptedToadlet;
+import freenet.clients.http.SessionManager;
 import freenet.clients.http.Toadlet;
 import freenet.clients.http.ToadletContext;
 import freenet.clients.http.ToadletContextClosedException;
+import freenet.l10n.NodeL10n;
 import freenet.support.MultiValueTable;
 import freenet.support.api.HTTPRequest;
 
 /**
  * {@link Toadlet} implementation that is wrapped around a {@link Page}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class PageToadlet extends Toadlet implements LinkEnabledCallback, LinkFilterExceptedToadlet {
 
+       private final SessionManager sessionManager;
+
        /** The name of the menu item. */
        private final String menuName;
 
@@ -65,8 +67,9 @@ public class PageToadlet extends Toadlet implements LinkEnabledCallback, LinkFil
         *            Prefix that is prepended to all {@link Page#getPath()} return
         *            values
         */
-       protected PageToadlet(HighLevelSimpleClient highLevelSimpleClient, String menuName, Page<FreenetRequest> page, String pathPrefix) {
+       protected PageToadlet(HighLevelSimpleClient highLevelSimpleClient, SessionManager sessionManager, String menuName, Page<FreenetRequest> page, String pathPrefix) {
                super(highLevelSimpleClient);
+               this.sessionManager = sessionManager;
                this.menuName = menuName;
                this.page = page;
                this.pathPrefix = pathPrefix;
@@ -104,7 +107,7 @@ public class PageToadlet extends Toadlet implements LinkEnabledCallback, LinkFil
         *             if the toadlet context is closed
         */
        public void handleMethodGET(URI uri, HTTPRequest httpRequest, ToadletContext toadletContext) throws IOException, ToadletContextClosedException {
-               handleRequest(new FreenetRequest(uri, Method.GET, httpRequest, toadletContext));
+               handleRequest(new FreenetRequest(uri, Method.GET, httpRequest, toadletContext, NodeL10n.getBase(), sessionManager));
        }
 
        /**
@@ -122,7 +125,7 @@ public class PageToadlet extends Toadlet implements LinkEnabledCallback, LinkFil
         *             if the toadlet context is closed
         */
        public void handleMethodPOST(URI uri, HTTPRequest httpRequest, ToadletContext toadletContext) throws IOException, ToadletContextClosedException {
-               handleRequest(new FreenetRequest(uri, Method.POST, httpRequest, toadletContext));
+               handleRequest(new FreenetRequest(uri, Method.POST, httpRequest, toadletContext, NodeL10n.getBase(), sessionManager));
        }
 
        /**
@@ -147,7 +150,7 @@ public class PageToadlet extends Toadlet implements LinkEnabledCallback, LinkFil
                try (AutoCloseableBucket pageBucket = new AutoCloseableBucket(pageRequest.getToadletContext().getBucketFactory().makeBucket(-1));
                     OutputStream pageBucketOutputStream = pageBucket.getBucket().getOutputStream()) {
                        Response pageResponse = page.handleRequest(pageRequest, new Response(pageBucketOutputStream));
-                       MultiValueTable<String, String> headers = new MultiValueTable<String, String>();
+                       MultiValueTable<String, String> headers = new MultiValueTable<>();
                        if (pageResponse.getHeaders() != null) {
                                for (Header header : pageResponse.getHeaders()) {
                                        for (String value : header) {
diff --git a/src/main/java/net/pterodactylus/sone/web/page/PageToadletFactory.java b/src/main/java/net/pterodactylus/sone/web/page/PageToadletFactory.java
deleted file mode 100644 (file)
index da5fcde..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * 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
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.web.page;
-
-import net.pterodactylus.util.web.Page;
-import freenet.client.HighLevelSimpleClient;
-
-/**
- * Factory that creates {@link PageToadlet}s using a given
- * {@link HighLevelSimpleClient}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class PageToadletFactory {
-
-       /** The client to use when creating the toadlets. */
-       private final HighLevelSimpleClient highLevelSimpleClient;
-
-       /** The prefix for all pages’ paths. */
-       private final String pathPrefix;
-
-       /**
-        * Creates a new {@link PageToadlet} factory.
-        *
-        * @param highLevelSimpleClient
-        *            The client to use when creating the toadlets
-        * @param pathPrefix
-        *            The path that is prepended to all pages’ paths
-        */
-       public PageToadletFactory(HighLevelSimpleClient highLevelSimpleClient, String pathPrefix) {
-               this.highLevelSimpleClient = highLevelSimpleClient;
-               this.pathPrefix = pathPrefix;
-       }
-
-       /**
-        * Creates a {@link PageToadlet} that wraps the given page and does not
-        * appear in the node’s menu.
-        *
-        * @param page
-        *            The page to wrap
-        * @return The toadlet wrapped around the page
-        */
-       public PageToadlet createPageToadlet(Page<FreenetRequest> page) {
-               return createPageToadlet(page, null);
-       }
-
-       /**
-        * Creates a {@link PageToadlet} that wraps the given page and appears in
-        * the node’s menu under the given name.
-        *
-        * @param page
-        *            The page to wrap
-        * @param menuName
-        *            The name of the menu item
-        * @return The toadlet wrapped around the page
-        */
-       public PageToadlet createPageToadlet(Page<FreenetRequest> page, String menuName) {
-               return new PageToadlet(highLevelSimpleClient, menuName, page, pathPrefix);
-       }
-
-}
diff --git a/src/main/kotlin/net/pterodactylus/sone/core/Fetched.kt b/src/main/kotlin/net/pterodactylus/sone/core/Fetched.kt
new file mode 100644 (file)
index 0000000..1f48e3e
--- /dev/null
@@ -0,0 +1,9 @@
+package net.pterodactylus.sone.core
+
+import freenet.client.FetchResult
+import freenet.keys.FreenetURI
+
+/**
+ * Container for a fetched URI and the [FetchResult].
+ */
+data class Fetched(val freenetUri: FreenetURI, val fetchResult: FetchResult)
diff --git a/src/main/kotlin/net/pterodactylus/sone/core/Preferences.kt b/src/main/kotlin/net/pterodactylus/sone/core/Preferences.kt
new file mode 100644 (file)
index 0000000..783552e
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * Sone - Preferences.kt - Copyright © 2013–2019 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core
+
+import com.google.common.base.Predicates.*
+import com.google.common.eventbus.*
+import net.pterodactylus.sone.core.event.*
+import net.pterodactylus.sone.fcp.FcpInterface.*
+import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.*
+import net.pterodactylus.sone.fcp.event.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.utils.IntegerRangePredicate.*
+import net.pterodactylus.util.config.*
+import java.lang.Integer.*
+
+/**
+ * Convenience interface for external classes that want to access the core’s
+ * configuration.
+ */
+class Preferences(private val eventBus: EventBus) {
+
+       private val _insertionDelay = DefaultOption(60, range(0, MAX_VALUE))
+       val insertionDelay: Int get() = _insertionDelay.get()
+       var newInsertionDelay: Int?
+               get() = unsupported
+               set(value) {
+                       _insertionDelay.set(value)
+                       eventBus.post(InsertionDelayChangedEvent(insertionDelay))
+                       eventBus.post(PreferenceChangedEvent("InsertionDelay", insertionDelay))
+               }
+
+       private val _postsPerPage = DefaultOption(10, range(1, MAX_VALUE))
+       val postsPerPage: Int get() = _postsPerPage.get()
+       var newPostsPerPage: Int?
+               get() = unsupported
+               set(value) {
+                       _postsPerPage.set(value)
+                       eventBus.post(PreferenceChangedEvent("PostsPerPage", postsPerPage))
+               }
+
+       private val _imagesPerPage = DefaultOption(9, range(1, MAX_VALUE))
+       val imagesPerPage: Int get() = _imagesPerPage.get()
+       var newImagesPerPage: Int?
+               get() = unsupported
+               set (value: Int?) = _imagesPerPage.set(value)
+
+       private val _charactersPerPost = DefaultOption(400, or(range(50, MAX_VALUE), equalTo(-1)))
+       val charactersPerPost: Int get() = _charactersPerPost.get()
+       var newCharactersPerPost: Int?
+               get() = unsupported
+               set(value) = _charactersPerPost.set(value)
+
+       private val _postCutOffLength = DefaultOption(200, range(50, MAX_VALUE))
+       val postCutOffLength: Int get() = _postCutOffLength.get()
+       var newPostCutOffLength: Int?
+               get() = unsupported
+               set(value) = _postCutOffLength.set(value)
+
+       private val _requireFullAccess = DefaultOption(false)
+       val requireFullAccess: Boolean get() = _requireFullAccess.get()
+       var newRequireFullAccess: Boolean?
+               get() = unsupported
+               set(value) = _requireFullAccess.set(value)
+
+       private val _positiveTrust = DefaultOption(75, range(0, 100))
+       val positiveTrust: Int get() = _positiveTrust.get()
+       var newPositiveTrust: Int?
+               get() = unsupported
+               set(value) = _positiveTrust.set(value)
+
+       private val _negativeTrust = DefaultOption(-25, range(-100, 100))
+       val negativeTrust: Int get() = _negativeTrust.get()
+       var newNegativeTrust: Int?
+               get() = unsupported
+               set(value) = _negativeTrust.set(value)
+
+       private val _trustComment = DefaultOption("Set from Sone Web Interface")
+       val trustComment: String get() = _trustComment.get()
+       var newTrustComment: String?
+               get() = unsupported
+               set(value) = _trustComment.set(value)
+
+       private val _fcpInterfaceActive = DefaultOption(false)
+       val fcpInterfaceActive: Boolean get() = _fcpInterfaceActive.get()
+       var newFcpInterfaceActive: Boolean?
+               get() = unsupported
+               set(value) {
+                       _fcpInterfaceActive.set(value)
+                       when (value) {
+                               true -> eventBus.post(FcpInterfaceActivatedEvent())
+                               else -> eventBus.post(FcpInterfaceDeactivatedEvent())
+                       }
+               }
+
+       private val _fcpFullAccessRequired = DefaultOption(ALWAYS)
+       val fcpFullAccessRequired: FullAccessRequired get() = _fcpFullAccessRequired.get()
+       var newFcpFullAccessRequired: FullAccessRequired?
+               get() = unsupported
+               set(value) {
+                       _fcpFullAccessRequired.set(value)
+                       eventBus.post(FullAccessRequiredChanged(fcpFullAccessRequired))
+               }
+
+       @Throws(ConfigurationException::class)
+       fun saveTo(configuration: Configuration) {
+               configuration.getIntValue("Option/ConfigurationVersion").value = 0
+               configuration.getIntValue("Option/InsertionDelay").value = _insertionDelay.real
+               configuration.getIntValue("Option/PostsPerPage").value = _postsPerPage.real
+               configuration.getIntValue("Option/ImagesPerPage").value = _imagesPerPage.real
+               configuration.getIntValue("Option/CharactersPerPost").value = _charactersPerPost.real
+               configuration.getIntValue("Option/PostCutOffLength").value = _postCutOffLength.real
+               configuration.getBooleanValue("Option/RequireFullAccess").value = _requireFullAccess.real
+               configuration.getIntValue("Option/PositiveTrust").value = _positiveTrust.real
+               configuration.getIntValue("Option/NegativeTrust").value = _negativeTrust.real
+               configuration.getStringValue("Option/TrustComment").value = _trustComment.real
+               configuration.getBooleanValue("Option/ActivateFcpInterface").value = _fcpInterfaceActive.real
+               configuration.getIntValue("Option/FcpFullAccessRequired").value = toInt(_fcpFullAccessRequired.real)
+       }
+
+       private fun toInt(fullAccessRequired: FullAccessRequired?): Int? {
+               return fullAccessRequired?.ordinal
+       }
+
+}
+
+private val unsupported: Nothing get() = throw UnsupportedOperationException()
diff --git a/src/main/kotlin/net/pterodactylus/sone/core/SoneComparison.kt b/src/main/kotlin/net/pterodactylus/sone/core/SoneComparison.kt
new file mode 100644 (file)
index 0000000..9a67f80
--- /dev/null
@@ -0,0 +1,12 @@
+package net.pterodactylus.sone.core
+
+import net.pterodactylus.sone.data.*
+
+class SoneComparison(private val oldSone: Sone, private val newSone: Sone) {
+
+       val newPosts: Collection<Post> get() = newSone.posts - oldSone.posts
+       val removedPosts: Collection<Post> get() = oldSone.posts - newSone.posts
+       val newPostReplies: Collection<PostReply> get() = newSone.replies - oldSone.replies
+       val removedPostReplies: Collection<PostReply> get() = oldSone.replies - newSone.replies
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/core/UpdatedSoneProcessor.kt b/src/main/kotlin/net/pterodactylus/sone/core/UpdatedSoneProcessor.kt
new file mode 100644 (file)
index 0000000..28bac6d
--- /dev/null
@@ -0,0 +1,70 @@
+package net.pterodactylus.sone.core
+
+import com.google.common.eventbus.*
+import com.google.inject.*
+import net.pterodactylus.sone.core.event.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.data.Sone.*
+import net.pterodactylus.sone.database.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.util.logging.*
+import javax.inject.Inject
+
+/**
+ * An `UpdatedSoneProcessor` is called to process a [Sone] after it has been
+ * downloaded from Freenet.
+ */
+@ImplementedBy(DefaultUpdateSoneProcessor::class)
+interface UpdatedSoneProcessor {
+
+       fun updateSone(sone: Sone)
+
+}
+
+abstract class BasicUpdateSoneProcessor(private val database: Database, private val eventBus: EventBus) :
+               UpdatedSoneProcessor {
+
+       private val logger = Logging.getLogger(UpdatedSoneProcessor::javaClass.name)!!
+
+       override fun updateSone(sone: Sone) {
+               val storedSone = database.getSone(sone.id) ?: return
+               if (!soneCanBeUpdated(storedSone, sone)) {
+                       logger.fine("Downloaded Sone $sone can not update stored Sone $storedSone.")
+                       return
+               }
+
+               SoneComparison(storedSone, sone).apply {
+                       newPosts
+                                       .onEach { post -> if (post.time <= sone.followingTime) post.isKnown = true }
+                                       .mapNotNull { post -> post.isKnown.ifFalse { NewPostFoundEvent(post) } }
+                                       .forEach(eventBus::post)
+                       removedPosts
+                                       .map { PostRemovedEvent(it) }
+                                       .forEach(eventBus::post)
+                       newPostReplies
+                                       .onEach { postReply -> if (postReply.time <= sone.followingTime) postReply.isKnown = true }
+                                       .mapNotNull { postReply -> postReply.isKnown.ifFalse { NewPostReplyFoundEvent(postReply) } }
+                                       .forEach(eventBus::post)
+                       removedPostReplies
+                                       .map { PostReplyRemovedEvent(it) }
+                                       .forEach(eventBus::post)
+               }
+               database.storeSone(sone)
+               sone.options = storedSone.options
+               sone.isKnown = storedSone.isKnown
+               sone.status = if (sone.time != 0L) SoneStatus.idle else SoneStatus.unknown
+       }
+
+       protected abstract fun soneCanBeUpdated(storedSone: Sone, newSone: Sone): Boolean
+
+       private val Sone.followingTime get() = database.getFollowingTime(id) ?: 0
+
+}
+
+class DefaultUpdateSoneProcessor @Inject constructor(database: Database, eventBus: EventBus) :
+               BasicUpdateSoneProcessor(database, eventBus) {
+
+       override fun soneCanBeUpdated(storedSone: Sone, newSone: Sone) =
+                       newSone.time > storedSone.time
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/core/event/InsertionDelayChangedEvent.kt b/src/main/kotlin/net/pterodactylus/sone/core/event/InsertionDelayChangedEvent.kt
new file mode 100644 (file)
index 0000000..b2909f2
--- /dev/null
@@ -0,0 +1,3 @@
+package net.pterodactylus.sone.core.event
+
+data class InsertionDelayChangedEvent(val insertionDelay: Int)
diff --git a/src/main/kotlin/net/pterodactylus/sone/core/event/NewPostFoundEvent.kt b/src/main/kotlin/net/pterodactylus/sone/core/event/NewPostFoundEvent.kt
new file mode 100644 (file)
index 0000000..ba7f957
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Sone - NewPostFoundEvent.kt - Copyright © 2013–2019 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core.event
+
+import net.pterodactylus.sone.data.Post
+
+/**
+ * Event that signals that a new post was found.
+ */
+data class NewPostFoundEvent(val post: Post) {
+
+       @Deprecated(message = "will go away", replaceWith = ReplaceWith("post"))
+       fun post() = post
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/core/event/NewPostReplyFoundEvent.kt b/src/main/kotlin/net/pterodactylus/sone/core/event/NewPostReplyFoundEvent.kt
new file mode 100644 (file)
index 0000000..a70d1b9
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Sone - NewPostReplyFoundEvent.kt - Copyright © 2013–2019 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core.event
+
+import net.pterodactylus.sone.data.PostReply
+
+/**
+ * Event that signals that a new [PostReply] was found.
+ */
+data class NewPostReplyFoundEvent(val postReply: PostReply) {
+
+       @Deprecated(message = "will go away", replaceWith = ReplaceWith("postReply"))
+       fun postReply() = postReply
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/core/event/PostRemovedEvent.kt b/src/main/kotlin/net/pterodactylus/sone/core/event/PostRemovedEvent.kt
new file mode 100644 (file)
index 0000000..117d800
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Sone - PostRemovedEvent.kt - Copyright © 2013–2019 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core.event
+
+import net.pterodactylus.sone.data.Post
+
+/**
+ * Event that signals that a [Post] was removed.
+ */
+data class PostRemovedEvent(val post: Post) {
+
+       @Deprecated(message = "will go away", replaceWith = ReplaceWith("post"))
+       fun post() = post
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/core/event/PostReplyRemovedEvent.kt b/src/main/kotlin/net/pterodactylus/sone/core/event/PostReplyRemovedEvent.kt
new file mode 100644 (file)
index 0000000..6aa495b
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Sone - PostReplyRemovedEvent.kt - Copyright © 2013–2019 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core.event
+
+import net.pterodactylus.sone.data.PostReply
+
+/**
+ * Event that signals that a [PostReply] was removed.
+ */
+data class PostReplyRemovedEvent(val postReply: PostReply) {
+
+       @Deprecated(message = "will go away", replaceWith = ReplaceWith("postReply"))
+       fun postReply() = postReply
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/data/Fingerprintable.kt b/src/main/kotlin/net/pterodactylus/sone/data/Fingerprintable.kt
new file mode 100644 (file)
index 0000000..97a54f8
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Sone - Fingerprintable.kt - Copyright © 2011–2019 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.data
+
+/**
+ * Interface for objects that can create a fingerprint of themselves, e.g. to
+ * detect modifications. The fingerprint should only contain original
+ * information; derived information should not be included.
+ */
+interface Fingerprintable {
+
+       val fingerprint: String
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/data/Identified.kt b/src/main/kotlin/net/pterodactylus/sone/data/Identified.kt
new file mode 100644 (file)
index 0000000..1cbad81
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Sone - Identified.kt - Copyright © 2013–2019 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.data
+
+/**
+ * Interface for all objects that expose an ID.
+ */
+interface Identified {
+
+       val id: String
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/AlbumBuilder.kt b/src/main/kotlin/net/pterodactylus/sone/database/AlbumBuilder.kt
new file mode 100644 (file)
index 0000000..acace46
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Sone - AlbumBuilder.kt - Copyright © 2013–2019 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.database
+
+import net.pterodactylus.sone.data.Album
+import net.pterodactylus.sone.data.Sone
+
+/**
+ * Builder for [Album] objects.
+ */
+interface AlbumBuilder {
+
+       fun randomId(): AlbumBuilder
+       fun withId(id: String): AlbumBuilder
+
+       fun by(sone: Sone): AlbumBuilder
+
+       fun build(): Album
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/AlbumBuilderFactory.kt b/src/main/kotlin/net/pterodactylus/sone/database/AlbumBuilderFactory.kt
new file mode 100644 (file)
index 0000000..f153f33
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Sone - AlbumBuilderFactory.kt - Copyright © 2013–2019 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.database
+
+/**
+ * Factory for [AlbumBuilder]s.
+ */
+interface AlbumBuilderFactory {
+
+       fun newAlbumBuilder(): AlbumBuilder
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/AlbumDatabase.kt b/src/main/kotlin/net/pterodactylus/sone/database/AlbumDatabase.kt
new file mode 100644 (file)
index 0000000..e61d429
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Sone - AlbumDatabase.kt - Copyright © 2013–2019 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.database
+
+/**
+ * Combines an [AlbumProvider] and an [AlbumStore] into an album
+ * database.
+ */
+interface AlbumDatabase : AlbumProvider, AlbumBuilderFactory, AlbumStore
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/AlbumProvider.kt b/src/main/kotlin/net/pterodactylus/sone/database/AlbumProvider.kt
new file mode 100644 (file)
index 0000000..db0146d
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Sone - AlbumProvider.kt - Copyright © 2013–2019 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.database
+
+import net.pterodactylus.sone.data.*
+
+/**
+ * Interface for objects that can provide [Album]s by their ID.
+ */
+interface AlbumProvider {
+
+       fun getAlbum(albumId: String): Album?
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/AlbumStore.kt b/src/main/kotlin/net/pterodactylus/sone/database/AlbumStore.kt
new file mode 100644 (file)
index 0000000..ddc7bb2
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Sone - AlbumStore.kt - Copyright © 2013–2019 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.database
+
+import net.pterodactylus.sone.data.Album
+
+/**
+ * Interface for a store of albums.
+ */
+interface AlbumStore {
+
+       fun storeAlbum(album: Album)
+       fun removeAlbum(album: Album)
+
+}
index 522ce32..17337e2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Database.java - Copyright © 2013–2016 David Roden
+ * Sone - Database.kt - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index f56f2f7..90b4f5e 100644 (file)
@@ -9,5 +9,6 @@ interface FriendProvider {
 
        fun getFriends(localSone: Sone): Collection<String>
        fun isFriend(localSone: Sone, friendSoneId: String): Boolean
+       fun getFollowingTime(friendSoneId: String): Long?
 
 }
index 5ceb28e..c9ddbaa 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageBuilder.java - Copyright © 2013–2016 David Roden
+ * Sone - ImageBuilder.kt - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index bae797c..f9f8be7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageBuilderFactory.java - Copyright © 2013–2016 David Roden
+ * Sone - ImageBuilderFactory.kt - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 4617cef..b55c073 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageDatabase.java - Copyright © 2013–2016 David Roden
+ * Sone - ImageDatabase.kt - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index c7cc75e..0cde52a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageProvider.java - Copyright © 2013–2016 David Roden
+ * Sone - ImageProvider.kt - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,9 +17,7 @@
 
 package net.pterodactylus.sone.database
 
-import net.pterodactylus.sone.data.Image
-
-import com.google.common.base.Optional
+import net.pterodactylus.sone.data.*
 
 /**
  * Provides [Image]s.
index 91cff2d..4be9dd9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageStore.java - Copyright © 2013–2016 David Roden
+ * Sone - ImageStore.kt - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index c1377ca..542bf78 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostBuilder.java - Copyright © 2013–2016 David Roden
+ * Sone - PostBuilder.kt - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index d7604c3..e20248f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostBuilderFactory.java - Copyright © 2013–2016 David Roden
+ * Sone - PostBuilderFactory.kt - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 7b97c6a..02b9fd1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostDatabase.java - Copyright © 2013–2016 David Roden
+ * Sone - PostDatabase.kt - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 37cb2a1..abf7ab9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostProvider.java - Copyright © 2011–2016 David Roden
+ * Sone - PostProvider.kt - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 
 package net.pterodactylus.sone.database
 
-import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.database.memory.MemoryDatabase
-
-import com.google.common.base.Optional
-import com.google.inject.ImplementedBy
+import com.google.inject.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.database.memory.*
 
 /**
  * Interface for objects that can provide [Post]s by their ID.
index 0c333da..d8d7f8d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostReplyBuilder.java - Copyright © 2013–2016 David Roden
+ * Sone - PostReplyBuilder.kt - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index d039cda..50b58e8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostReplyBuilderFactory.java - Copyright © 2013–2016 David Roden
+ * Sone - PostReplyBuilderFactory.kt - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 8f08cf3..316c772 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostReplyDatabase.java - Copyright © 2013–2016 David Roden
+ * Sone - PostReplyDatabase.kt - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 7e09017..cc797d7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostReplyProvider.java - Copyright © 2013–2016 David Roden
+ * Sone - PostReplyProvider.kt - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,8 +17,7 @@
 
 package net.pterodactylus.sone.database
 
-import com.google.common.base.Optional
-import net.pterodactylus.sone.data.PostReply
+import net.pterodactylus.sone.data.*
 
 /**
  * Interface for objects that can provide [PostReply]s.
index 08ba380..f4839b1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostReplyStore.java - Copyright © 2013–2016 David Roden
+ * Sone - PostReplyStore.kt - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,8 +17,7 @@
 
 package net.pterodactylus.sone.database
 
-import net.pterodactylus.sone.data.PostReply
-import net.pterodactylus.sone.data.Sone
+import net.pterodactylus.sone.data.*
 
 /**
  * Defines a store for [post replies][PostReply].
index 3cb5f17..84ea39e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostStore.java - Copyright © 2013–2016 David Roden
+ * Sone - PostStore.kt - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,8 +17,7 @@
 
 package net.pterodactylus.sone.database
 
-import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.data.Sone
+import net.pterodactylus.sone.data.*
 
 /**
  * Interface for a store for posts.
index 1aa6039..bfc74ac 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ReplyBuilder.java - Copyright © 2013–2016 David Roden
+ * Sone - ReplyBuilder.kt - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,8 +17,7 @@
 
 package net.pterodactylus.sone.database
 
-import net.pterodactylus.sone.data.Reply
-import net.pterodactylus.sone.data.Sone
+import net.pterodactylus.sone.data.*
 
 /**
  * Methods that all reply builders need to implement in order to be able to
index b8ce5e1..4156d66 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneProvider.java - Copyright © 2011–2016 David Roden
+ * Sone - SoneProvider.kt - Copyright © 2011–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 
 package net.pterodactylus.sone.database
 
-import net.pterodactylus.sone.core.Core
-import net.pterodactylus.sone.data.Sone
-
-import com.google.common.base.Function
-import com.google.common.base.Optional
-import com.google.inject.ImplementedBy
+import com.google.inject.*
+import net.pterodactylus.sone.core.*
+import net.pterodactylus.sone.data.*
 
 /**
  * Interface for objects that can provide [Sone]s by their ID.
diff --git a/src/main/kotlin/net/pterodactylus/sone/fcp/AbstractSoneCommand.kt b/src/main/kotlin/net/pterodactylus/sone/fcp/AbstractSoneCommand.kt
new file mode 100644 (file)
index 0000000..915c536
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * Sone - AbstractSoneCommand.kt - Copyright © 2011–2019 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.fcp
+
+import com.google.common.base.Optional
+import freenet.node.FSParseException
+import freenet.support.SimpleFieldSet
+import net.pterodactylus.sone.core.Core
+import net.pterodactylus.sone.data.Post
+import net.pterodactylus.sone.data.PostReply
+import net.pterodactylus.sone.data.Sone
+import net.pterodactylus.sone.freenet.SimpleFieldSetBuilder
+import net.pterodactylus.sone.freenet.fcp.AbstractCommand
+import net.pterodactylus.sone.freenet.fcp.Command
+import net.pterodactylus.sone.freenet.fcp.FcpException
+import net.pterodactylus.sone.template.SoneAccessor
+import net.pterodactylus.sone.utils.asOptional
+import net.pterodactylus.sone.utils.let
+import net.pterodactylus.sone.utils.throwOnNullIf
+
+/**
+ * Abstract base implementation of a [Command] with Sone-related helper
+ * methods.
+ */
+abstract class AbstractSoneCommand
+@JvmOverloads protected constructor(
+               protected val core: Core,
+               @get:JvmName("requiresWriteAccess")
+               val requiresWriteAccess: Boolean = false) : AbstractCommand() {
+
+       @Throws(FcpException::class)
+       protected fun getSone(simpleFieldSet: SimpleFieldSet, parameterName: String, localOnly: Boolean): Sone =
+                       getSone(simpleFieldSet, parameterName, localOnly, true).get()
+
+       @Throws(FcpException::class)
+       protected fun getSone(simpleFieldSet: SimpleFieldSet, parameterName: String, localOnly: Boolean, mandatory: Boolean): Optional<Sone> {
+               val soneId = simpleFieldSet.get(parameterName)
+                               .throwOnNullIf(mandatory) { FcpException("Could not load Sone ID from “$parameterName”.") }
+                               ?: return Optional.absent()
+               val sone = core.getSone(soneId)
+               if (mandatory && sone == null || sone != null && localOnly && !sone.isLocal) {
+                       throw FcpException("Could not load Sone from “$soneId”.")
+               }
+               return sone.asOptional()
+       }
+
+       @Throws(FcpException::class)
+       protected fun getPost(simpleFieldSet: SimpleFieldSet, parameterName: String): Post {
+               try {
+                       val postId = simpleFieldSet.getString(parameterName)
+                       return core.getPost(postId)
+                                       ?: throw FcpException("Could not load post from “$postId”.")
+               } catch (fspe1: FSParseException) {
+                       throw FcpException("Could not post ID from “$parameterName”.", fspe1)
+               }
+       }
+
+       @Throws(FcpException::class)
+       protected fun getReply(simpleFieldSet: SimpleFieldSet, parameterName: String): PostReply {
+               try {
+                       val replyId = simpleFieldSet.getString(parameterName)
+                       return core.getPostReply(replyId)
+                                       ?: throw FcpException("Could not load reply from “$replyId”.")
+               } catch (fspe1: FSParseException) {
+                       throw FcpException("Could not reply ID from “$parameterName”.", fspe1)
+               }
+       }
+
+       protected fun encodePost(post: Post, prefix: String, includeReplies: Boolean): SimpleFieldSet = SimpleFieldSetBuilder().apply {
+               put(prefix + "ID", post.id)
+               put(prefix + "Sone", post.sone.id)
+               post.recipientId.let { put(prefix + "Recipient", it) }
+               put(prefix + "Time", post.time)
+               put(prefix + "Text", encodeString(post.text))
+               put(encodeLikes(core.getLikes(post), "${prefix}Likes."))
+               if (includeReplies) {
+                       val replies = core.getReplies(post.id)
+                       put(encodeReplies(replies, prefix))
+               }
+       }.get()
+
+       protected fun encodePosts(posts: Collection<Post>, prefix: String, includeReplies: Boolean): SimpleFieldSet = SimpleFieldSetBuilder().apply {
+               put(prefix + "Count", posts.size)
+               posts.forEachIndexed { postIndex, post ->
+                       put(encodePost(post, "$prefix$postIndex.", includeReplies))
+               }
+       }.get()
+
+       private fun encodeReplies(replies: Collection<PostReply>, prefix: String): SimpleFieldSet = SimpleFieldSetBuilder().apply {
+               put(prefix + "Replies.Count", replies.size)
+               replies.forEachIndexed { replyIndex, reply ->
+                       val replyPrefix = "${prefix}Replies.$replyIndex."
+                       put(replyPrefix + "ID", reply.id)
+                       put(replyPrefix + "Sone", reply.sone.id)
+                       put(replyPrefix + "Time", reply.time)
+                       put(replyPrefix + "Text", encodeString(reply.text))
+                       put(encodeLikes(core.getLikes(reply), "${replyPrefix}Likes."))
+               }
+       }.get()
+
+       override fun toString() = "${javaClass.name}[requiresWriteAccess=$requiresWriteAccess]"
+}
+
+fun encodeString(text: String) = text
+               .replace("\\\\".toRegex(), "\\\\\\\\")
+               .replace("\n".toRegex(), "\\\\n")
+               .replace("\r".toRegex(), "\\\\r")
+
+fun encodeSone(sone: Sone, prefix: String, localSone: Optional<Sone>): SimpleFieldSet = SimpleFieldSetBuilder().apply {
+       put(prefix + "ID", sone.id)
+       put(prefix + "Name", sone.name)
+       put(prefix + "NiceName", SoneAccessor.getNiceName(sone))
+       put(prefix + "LastUpdated", sone.time)
+       localSone.let { put(prefix + "Followed", it.hasFriend(sone.id).toString()) }
+       val profile = sone.profile
+       put(prefix + "Field.Count", profile.fields.size)
+       profile.fields.forEachIndexed { fieldIndex, field ->
+               put(prefix + "Field." + fieldIndex + ".Name", field.name)
+               put(prefix + "Field." + fieldIndex + ".Value", field.value)
+       }
+}.get()
+
+fun encodeSones(sones: Collection<Sone>, prefix: String): SimpleFieldSet = SimpleFieldSetBuilder().apply {
+       put(prefix + "Count", sones.size)
+       sones.forEachIndexed { soneIndex, sone ->
+               put(encodeSone(sone, "$prefix$soneIndex.", Optional.absent()))
+       }
+}.get()
+
+fun encodeLikes(likes: Collection<Sone>, prefix: String): SimpleFieldSet = SimpleFieldSetBuilder().apply {
+       put(prefix + "Count", likes.size)
+       likes.forEachIndexed { index, sone -> put("$prefix$index.ID", sone.id) }
+}.get()
index a8da595..9b5fa2e 100644 (file)
@@ -1,25 +1,28 @@
 package net.pterodactylus.sone.main
 
-import com.google.inject.Binder
-import com.google.inject.Module
-import com.google.inject.Provides
-import freenet.client.HighLevelSimpleClient
-import freenet.node.Node
-import freenet.pluginmanager.PluginRespirator
+import com.google.inject.*
+import freenet.client.*
+import freenet.clients.http.*
+import freenet.node.*
+import freenet.pluginmanager.*
+import javax.inject.Provider
 import javax.inject.Singleton
 
 /**
  * Guice [Module] that supplies some objects that are in fact supplied by the Freenet node.
  */
-class FreenetModule(private val pluginRespirator: PluginRespirator): Module {
+class FreenetModule(private val pluginRespirator: PluginRespirator) : Module {
 
        override fun configure(binder: Binder): Unit = binder.run {
-               bind(PluginRespirator::class.java).toProvider { pluginRespirator }
-               pluginRespirator.node!!.let { node -> bind(Node::class.java).toProvider { node } }
-               bind(HighLevelSimpleClient::class.java).toProvider { pluginRespirator.hlSimpleClient!! }
+               bind(PluginRespirator::class.java).toProvider(Provider<PluginRespirator> { pluginRespirator })
+               pluginRespirator.node!!.let { node -> bind(Node::class.java).toProvider(Provider<Node> { node }) }
+               bind(HighLevelSimpleClient::class.java).toProvider(Provider<HighLevelSimpleClient> { pluginRespirator.hlSimpleClient!! })
+               bind(ToadletContainer::class.java).toProvider(Provider<ToadletContainer> { pluginRespirator.toadletContainer })
+               bind(PageMaker::class.java).toProvider(Provider<PageMaker> { pluginRespirator.pageMaker })
        }
 
-       @Provides @Singleton
+       @Provides
+       @Singleton
        fun getSessionManager() = pluginRespirator.getSessionManager("Sone")!!
 
 }
diff --git a/src/main/kotlin/net/pterodactylus/sone/main/SoneModuleCreator.kt b/src/main/kotlin/net/pterodactylus/sone/main/SoneModuleCreator.kt
new file mode 100644 (file)
index 0000000..58c8d9e
--- /dev/null
@@ -0,0 +1,65 @@
+package net.pterodactylus.sone.main
+
+import com.google.common.base.*
+import com.google.common.eventbus.*
+import com.google.inject.*
+import com.google.inject.matcher.*
+import com.google.inject.name.Names.*
+import com.google.inject.spi.*
+import net.pterodactylus.sone.database.*
+import net.pterodactylus.sone.database.memory.*
+import net.pterodactylus.sone.freenet.wot.*
+import net.pterodactylus.util.config.*
+import net.pterodactylus.util.config.ConfigurationException
+import net.pterodactylus.util.version.Version
+import java.io.*
+
+class SoneModuleCreator {
+
+       fun createModule(sonePlugin: SonePlugin) = object : AbstractModule() {
+               override fun configure() {
+                       val sonePropertiesFile = File("sone.properties")
+                       val firstStart = !sonePropertiesFile.exists()
+                       var newConfig = false
+                       val configuration = try {
+                               Configuration(MapConfigurationBackend(sonePropertiesFile, false))
+                       } catch (ce: ConfigurationException) {
+                               sonePropertiesFile.delete()
+                               newConfig = true
+                               Configuration(MapConfigurationBackend(sonePropertiesFile, true))
+                       }
+                       val context = Context("Sone")
+                       val loaders = configuration.getStringValue("Developer.LoadFromFilesystem")
+                                       .getValue(null)
+                                       ?.let {
+                                               configuration.getStringValue("Developer.FilesystemPath")
+                                                               .getValue(null)
+                                                               ?.let { DebugLoaders(it) }
+                                       }
+                       val eventBus = EventBus()
+
+                       bind(Configuration::class.java).toInstance(configuration)
+                       bind(EventBus::class.java).toInstance(eventBus)
+                       bind(Boolean::class.java).annotatedWith(named("FirstStart")).toInstance(firstStart)
+                       bind(Boolean::class.java).annotatedWith(named("NewConfig")).toInstance(newConfig)
+                       bind(Context::class.java).toInstance(context)
+                       bind(object : TypeLiteral<Optional<Context>>() {}).toInstance(Optional.of(context))
+                       bind(SonePlugin::class.java).toInstance(sonePlugin)
+                       bind(Version::class.java).toInstance(sonePlugin.version.parseVersion())
+                       bind(PluginVersion::class.java).toInstance(PluginVersion(sonePlugin.version))
+                       bind(PluginYear::class.java).toInstance(PluginYear(sonePlugin.year))
+                       bind(PluginHomepage::class.java).toInstance(PluginHomepage(sonePlugin.homepage))
+                       bind(Database::class.java).to(MemoryDatabase::class.java).`in`(Singleton::class.java)
+                       loaders?.let { bind(Loaders::class.java).toInstance(it) }
+
+                       bindListener(Matchers.any(), object : TypeListener {
+                               override fun <I> hear(typeLiteral: TypeLiteral<I>, typeEncounter: TypeEncounter<I>) {
+                                       typeEncounter.register(InjectionListener { injectee -> eventBus.register(injectee) })
+                               }
+                       })
+               }
+       }
+
+}
+
+private fun String.parseVersion(): Version = Version.parse(this)
index 4536c04..200bd83 100644 (file)
@@ -1,22 +1,18 @@
 package net.pterodactylus.sone.template
 
-import net.pterodactylus.sone.core.LinkedElement
-import net.pterodactylus.sone.utils.asTemplate
-import net.pterodactylus.util.template.Filter
-import net.pterodactylus.util.template.TemplateContext
-import net.pterodactylus.util.template.TemplateContextFactory
-import java.io.StringWriter
-import javax.inject.Inject
+import net.pterodactylus.sone.core.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.util.template.*
+import java.io.*
 
 /**
  * Renders all kinds of [LinkedElement]s.
  */
-class LinkedElementRenderFilter @Inject constructor(private val templateContextFactory: TemplateContextFactory): Filter {
+class LinkedElementRenderFilter : Filter {
 
-       companion object {
-               private val loadedImageTemplate = """<%include linked/image.html>""".asTemplate()
-               private val loadedHtmlPageTemplate = """<%include linked/html-page.html>""".asTemplate()
-               private val notLoadedImageTemplate = """<%include linked/notLoaded.html>""".asTemplate()
+       private val templateContextFactory = TemplateContextFactory().apply {
+               addFilter("html", HtmlFilter())
+               addProvider(ClassPathTemplateProvider(LinkedElementRenderFilter::class.java, "/templates/"))
        }
 
        override fun format(templateContext: TemplateContext?, data: Any?, parameters: Map<String, Any?>?) =
@@ -51,3 +47,7 @@ class LinkedElementRenderFilter @Inject constructor(private val templateContextF
                        }.toString()
 
 }
+
+private val loadedImageTemplate = """<%include linked/image.html>""".asTemplate()
+private val loadedHtmlPageTemplate = """<%include linked/html-page.html>""".asTemplate()
+private val notLoadedImageTemplate = """<%include linked/notLoaded.html>""".asTemplate()
index c9414ea..0ed8df2 100644 (file)
@@ -27,9 +27,9 @@ class LinkedElementsFilter(private val elementLoader: ElementLoader) : Filter {
                                                ?.filterIsInstance<FreenetLinkPart>()
                                                ?.map { elementLoader.loadElement(it.link) }
                                                ?.filter { !it.failed }
-                                               ?: listOf<LinkedElement>()
+                                               ?: listOf()
                        } else {
-                               listOf<LinkedElement>()
+                               listOf()
                        }
 
        private fun showLinkedImages(currentSone: Sone?, sone: Sone?): Boolean {
index 25df239..00015bb 100644 (file)
@@ -1,33 +1,20 @@
 package net.pterodactylus.sone.template
 
-import net.pterodactylus.sone.core.Core
-import net.pterodactylus.sone.text.FreemailPart
-import net.pterodactylus.sone.text.FreenetLinkPart
-import net.pterodactylus.sone.text.LinkPart
+import net.pterodactylus.sone.database.*
+import net.pterodactylus.sone.text.*
 import net.pterodactylus.sone.text.Part
-import net.pterodactylus.sone.text.PlainTextPart
-import net.pterodactylus.sone.text.PostPart
-import net.pterodactylus.sone.text.SonePart
-import net.pterodactylus.sone.text.SoneTextParser
-import net.pterodactylus.sone.text.SoneTextParserContext
-import net.pterodactylus.sone.utils.asTemplate
-import net.pterodactylus.util.template.Filter
-import net.pterodactylus.util.template.TemplateContext
-import net.pterodactylus.util.template.TemplateContextFactory
-import java.io.StringWriter
-import java.io.Writer
-import java.net.URLEncoder
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.util.template.*
+import java.io.*
+import java.net.*
 
 /**
  * Renders a number of pre-parsed [Part] into a [String].
- *
- * @author [David ‘Bombe’ Roden](mailto:bombe@pterodactylus.net)
  */
-class RenderFilter(private val core: Core, private val templateContextFactory: TemplateContextFactory) : Filter {
+class RenderFilter(private val soneProvider: SoneProvider, private val soneTextParser: SoneTextParser, htmlFilter: HtmlFilter) : Filter {
 
-       companion object {
-               private val plainTextTemplate = "<%text|html>".asTemplate()
-               private val linkTemplate = "<a class=\"<%cssClass|html>\" href=\"<%link|html>\" title=\"<%title|html>\"><%text|html></a>".asTemplate()
+       private val templateContextFactory = TemplateContextFactory().apply {
+               addFilter("html", htmlFilter)
        }
 
        override fun format(templateContext: TemplateContext?, data: Any?, parameters: MutableMap<String, Any?>?): Any? {
@@ -79,9 +66,8 @@ class RenderFilter(private val core: Core, private val templateContextFactory: T
        }
 
        private fun render(writer: Writer, postPart: PostPart) {
-               val parser = SoneTextParser(core, core)
                val parserContext = SoneTextParserContext(postPart.post.sone)
-               val parts = parser.parse(postPart.post.text, parserContext)
+               val parts = soneTextParser.parse(postPart.post.text, parserContext)
                val excerpt = StringBuilder()
                for (part in parts) {
                        excerpt.append(part.text)
@@ -100,7 +86,7 @@ class RenderFilter(private val core: Core, private val templateContextFactory: T
        }
 
        private fun render(writer: Writer, freemailPart: FreemailPart) {
-               val sone = core.getSone(freemailPart.identityId)
+               val sone = soneProvider.getSone(freemailPart.identityId)
                val soneName = sone?.let(SoneAccessor::getNiceName) ?: freemailPart.identityId
                renderLink(writer,
                                "/Freemail/NewMessage?to=${freemailPart.identityId}",
@@ -119,3 +105,6 @@ class RenderFilter(private val core: Core, private val templateContextFactory: T
        }
 
 }
+
+private val plainTextTemplate = "<%text|html>".asTemplate()
+private val linkTemplate = "<a class=\"<%cssClass|html>\" href=\"<%link|html>\" title=\"<%title|html>\"><%text|html></a>".asTemplate()
index 4a495b4..4137fe9 100644 (file)
@@ -2,7 +2,5 @@ package net.pterodactylus.sone.text
 
 /**
  * [Part] implementation that holds a single piece of text.
- *
- * @author [David Roden](mailto:d.roden@emetriq.com)
  */
 data class PlainTextPart(override val text: String) : Part
index 64125c1..554c19b 100644 (file)
@@ -1,28 +1,20 @@
 package net.pterodactylus.sone.text
 
-import freenet.keys.FreenetURI
-import freenet.support.Base64
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.data.impl.IdOnlySone
-import net.pterodactylus.sone.database.PostProvider
-import net.pterodactylus.sone.database.SoneProvider
-import net.pterodactylus.sone.text.LinkType.CHK
-import net.pterodactylus.sone.text.LinkType.FREEMAIL
-import net.pterodactylus.sone.text.LinkType.HTTP
-import net.pterodactylus.sone.text.LinkType.HTTPS
-import net.pterodactylus.sone.text.LinkType.KSK
-import net.pterodactylus.sone.text.LinkType.POST
-import net.pterodactylus.sone.text.LinkType.SONE
-import net.pterodactylus.sone.text.LinkType.SSK
+import freenet.keys.*
+import freenet.support.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.data.impl.*
+import net.pterodactylus.sone.database.*
+import net.pterodactylus.sone.text.LinkType.*
 import net.pterodactylus.sone.text.LinkType.USK
-import net.pterodactylus.sone.utils.let
-import org.bitpedia.util.Base32
-import java.net.MalformedURLException
+import org.bitpedia.util.*
+import java.net.*
+import javax.inject.*
 
 /**
  * [Parser] implementation that can recognize Freenet URIs.
  */
-class SoneTextParser(private val soneProvider: SoneProvider?, private val postProvider: PostProvider?) {
+class SoneTextParser @Inject constructor(private val soneProvider: SoneProvider?, private val postProvider: PostProvider?) {
 
        fun parse(source: String, context: SoneTextParserContext?) =
                        source.split("\n")
@@ -50,27 +42,39 @@ class SoneTextParser(private val soneProvider: SoneProvider?, private val postPr
                                                        }
                        }.map { it.first }.toList()
 
+       private val NextLink.linkWithoutBacklink: String
+               get() {
+                       val backlink = link.indexOf("/../")
+                       val query = link.indexOf("?")
+                       return if ((backlink > -1) && ((query == -1) || (query > -1) && (backlink < query)))
+                               link.substring(0, backlink)
+                       else
+                               link
+               }
+
        private fun NextLink.toPart(context: SoneTextParserContext?) = when (linkType) {
                KSK, CHK -> try {
-                       FreenetURI(link).let { freenetUri ->
+                       FreenetURI(linkWithoutBacklink).let { freenetUri ->
                                FreenetLinkPart(
-                                               link,
-                                               if (freenetUri.isKSK) {
-                                                       freenetUri.guessableKey
-                                               } else {
-                                                       freenetUri.metaString ?: freenetUri.docName ?: link.substring(0, 9)
-                                               },
-                                               link.split('?').first()
+                                               linkWithoutBacklink,
+                                               freenetUri.allMetaStrings?.lastOrNull { it != "" } ?: freenetUri.docName ?: linkWithoutBacklink.substring(0, 9),
+                                               linkWithoutBacklink.split('?').first()
                                )
                        }
                } catch (e: MalformedURLException) {
-                       PlainTextPart(link)
+                       PlainTextPart(linkWithoutBacklink)
                }
                SSK, USK ->
                        try {
-                                FreenetLinkPart(link, FreenetURI(link).docName, trusted = context?.routingKey?.contentEquals(FreenetURI(link).routingKey) == true)
+                               FreenetURI(linkWithoutBacklink).let { uri ->
+                                       uri.allMetaStrings
+                                                       ?.takeIf { (it.size > 1) || ((it.size == 1) && (it.single() != "")) }
+                                                       ?.lastOrNull()
+                                                       ?: uri.docName
+                                                       ?: "${uri.keyType}@${uri.routingKey.freenetBase64}"
+                               }.let { FreenetLinkPart(linkWithoutBacklink.removeSuffix("/"), it, trusted = context?.routingKey?.contentEquals(FreenetURI(linkWithoutBacklink).routingKey) == true) }
                        } catch (e: MalformedURLException) {
-                               PlainTextPart(link)
+                               PlainTextPart(linkWithoutBacklink)
                        }
                SONE -> link.substring(7).let { SonePart(soneProvider?.getSone(it) ?: IdOnlySone(it)) }
                POST -> postProvider?.getPost(link.substring(7))?.let { PostPart(it) } ?: PlainTextPart(link)
@@ -195,3 +199,5 @@ private fun isPunctuation(char: Char) = char in punctuationChars
 private val whitespace = Regex("[\\u000a\u0020\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u200c\u200d\u202f\u205f\u2060\u2800\u3000]")
 
 private data class NextLink(val position: Int, val linkType: LinkType, val link: String, val remainder: String)
+
+private val ByteArray.freenetBase64 get() = Base64.encode(this)!!
index 36e530c..6d1d413 100644 (file)
@@ -1,3 +1,7 @@
 package net.pterodactylus.sone.utils
 
-fun <T> T?.asList() = this?.let(::listOf) ?: emptyList<T>()
+fun <T> T?.asList() = this?.let(::listOf) ?: emptyList()
+val Any?.unit get() = Unit
+
+fun <T> T?.throwOnNullIf(throwCondition: Boolean, exception: () -> Throwable) =
+               if (this == null && throwCondition) throw exception() else this
index 6c436e2..5d48d61 100644 (file)
@@ -39,6 +39,9 @@ class Pagination<out T>(private val originalItems: List<T>, pageSize: Int): Iter
 
        override fun iterator() = items.iterator()
 
+       fun turnTo(page: Int) = apply { this.page = page }
+
 }
 
-fun <T> Iterable<T>.paginate(pageSize: Int) = Pagination<T>(toList(), pageSize)
+fun <T> Iterable<T>.paginate(pageSize: Int) = Pagination(toList(), pageSize)
+fun <T> Sequence<T>.paginate(pageSize: Int) = Pagination(toList(), pageSize)
diff --git a/src/main/kotlin/net/pterodactylus/sone/web/PageToadletRegistry.kt b/src/main/kotlin/net/pterodactylus/sone/web/PageToadletRegistry.kt
new file mode 100644 (file)
index 0000000..97e6cfe
--- /dev/null
@@ -0,0 +1,58 @@
+package net.pterodactylus.sone.web
+
+import freenet.clients.http.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.web.*
+import java.util.concurrent.atomic.*
+import javax.inject.*
+
+private const val soneMenu = "Navigation.Menu.Sone"
+private const val soneMenuName = "$soneMenu.Name"
+
+class PageToadletRegistry @Inject constructor(
+               private val pageMaker: PageMaker,
+               private val toadletContainer: ToadletContainer,
+               private val sonePlugin: SonePlugin,
+               private val pageToadletFactory: PageToadletFactory
+) {
+
+       private val pages = mutableListOf<Page<FreenetRequest>>()
+       private val registeredToadlets = mutableListOf<PageToadlet>()
+       private val registered = AtomicBoolean(false)
+
+       fun addPage(page: Page<FreenetRequest>) {
+               if (registered.get()) throw IllegalStateException()
+               pages += page
+       }
+
+       fun registerToadlets() {
+               registered.set(true)
+               pageMaker.addNavigationCategory("/Sone/index.html", soneMenuName, "$soneMenu.Tooltip", sonePlugin)
+               addPages()
+       }
+
+       private fun addPages() =
+                       pages
+                                       .map { pageToadletFactory.createPageToadlet(it) }
+                                       .onEach(registeredToadlets::plusAssign)
+                                       .forEach { pageToadlet ->
+                                               if (pageToadlet.menuName == null) {
+                                                       registerToadletWithoutMenuname(pageToadlet)
+                                               } else {
+                                                       registerToadletWithMenuname(pageToadlet)
+                                               }
+                                       }
+
+       private fun registerToadletWithoutMenuname(pageToadlet: PageToadlet) =
+                       toadletContainer.register(pageToadlet, null, pageToadlet.path(), true, false)
+
+       private fun registerToadletWithMenuname(pageToadlet: PageToadlet) =
+                       toadletContainer.register(pageToadlet, soneMenuName, pageToadlet.path(), true, "$soneMenu.Item.${pageToadlet.menuName}.Name", "$soneMenu.Item.${pageToadlet.menuName}.Tooltip", false, pageToadlet)
+
+       fun unregisterToadlets() {
+               pageMaker.removeNavigationCategory(soneMenuName)
+               registeredToadlets.forEach(toadletContainer::unregister)
+       }
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/web/WebInterfaceModule.kt b/src/main/kotlin/net/pterodactylus/sone/web/WebInterfaceModule.kt
new file mode 100644 (file)
index 0000000..dd5e9f6
--- /dev/null
@@ -0,0 +1,128 @@
+package net.pterodactylus.sone.web
+
+import com.google.inject.*
+import freenet.l10n.*
+import freenet.support.api.*
+import net.pterodactylus.sone.core.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.database.*
+import net.pterodactylus.sone.freenet.*
+import net.pterodactylus.sone.freenet.wot.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.template.*
+import net.pterodactylus.sone.text.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
+import javax.inject.Singleton
+
+class WebInterfaceModule : AbstractModule() {
+
+       @Provides
+       @Singleton
+       fun getTemplateContextFactory(
+                       soneAccessor: SoneAccessor,
+                       postAccessor: PostAccessor,
+                       replyAccessor: ReplyAccessor,
+                       identityAccessor: IdentityAccessor,
+                       profileAccessor: ProfileAccessor,
+                       l10nFilter: L10nFilter,
+                       parserFilter: ParserFilter,
+                       renderFilter: RenderFilter,
+                       linkedElementsFilter: LinkedElementsFilter,
+                       unknownDateFilter: UnknownDateFilter,
+                       imageLinkFilter: ImageLinkFilter,
+                       loaders: Loaders
+       ) =
+                       TemplateContextFactory().apply {
+                               addAccessor(Any::class.java, ReflectionAccessor())
+                               addAccessor(Collection::class.java, CollectionAccessor())
+                               addAccessor(Sone::class.java, soneAccessor)
+                               addAccessor(Post::class.java, postAccessor)
+                               addAccessor(Reply::class.java, replyAccessor)
+                               addAccessor(Album::class.java, AlbumAccessor())
+                               addAccessor(Image::class.java, ImageAccessor())
+                               addAccessor(Identity::class.java, identityAccessor)
+                               addAccessor(Trust::class.java, TrustAccessor())
+                               addAccessor(HTTPRequest::class.java, HttpRequestAccessor())
+                               addAccessor(Profile::class.java, profileAccessor)
+
+                               addFilter("date", DateFilter())
+                               addFilter("html", HtmlFilter())
+                               addFilter("replace", ReplaceFilter())
+                               addFilter("store", StoreFilter())
+                               addFilter("l10n", l10nFilter)
+                               addFilter("substring", SubstringFilter())
+                               addFilter("xml", XmlFilter())
+                               addFilter("change", RequestChangeFilter())
+                               addFilter("match", MatchFilter())
+                               addFilter("css", CssClassNameFilter())
+                               addFilter("js", JavascriptFilter())
+                               addFilter("parse", parserFilter)
+                               addFilter("shorten", ShortenFilter())
+                               addFilter("render", renderFilter)
+                               addFilter("linked-elements", linkedElementsFilter)
+                               addFilter("render-linked-element", LinkedElementRenderFilter())
+                               addFilter("reparse", ReparseFilter())
+                               addFilter("unknown", unknownDateFilter)
+                               addFilter("format", FormatFilter())
+                               addFilter("sort", CollectionSortFilter())
+                               addFilter("image-link", imageLinkFilter)
+                               addFilter("replyGroup", ReplyGroupFilter())
+                               addFilter("in", ContainsFilter())
+                               addFilter("unique", UniqueElementFilter())
+                               addFilter("mod", ModFilter())
+                               addFilter("paginate", PaginationFilter())
+
+                               addProvider(TemplateProvider.TEMPLATE_CONTEXT_PROVIDER)
+                               addProvider(loaders.templateProvider)
+                       }
+
+       @Provides
+       fun getSoneAccessor(core: Core, timeTextConverter: TimeTextConverter) =
+                       SoneAccessor(core, timeTextConverter)
+
+       @Provides
+       fun getPostAccessor(core: Core) =
+                       PostAccessor(core)
+
+       @Provides
+       fun getReplyAccessor(core: Core) =
+                       ReplyAccessor(core)
+
+       @Provides
+       fun getIdentityAccessor(core: Core) =
+                       IdentityAccessor(core)
+
+       @Provides
+       fun getProfileAccessor(core: Core) =
+                       ProfileAccessor(core)
+
+       @Provides
+       fun getL10nFilter(l10n: BaseL10n) =
+                       L10nFilter(l10n)
+
+       @Provides
+       fun getParserFilter(core: Core, soneTextParser: SoneTextParser) =
+                       ParserFilter(core, soneTextParser)
+
+       @Provides
+       fun getRenderFilter(soneProvider: SoneProvider, soneTextParser: SoneTextParser, htmlFilter: HtmlFilter) =
+                       RenderFilter(soneProvider, soneTextParser, htmlFilter)
+
+       @Provides
+       fun getLinkedElementsFilter(elementLoader: ElementLoader) =
+                       LinkedElementsFilter(elementLoader)
+
+       @Provides
+       fun getUnknownDateFilter(l10n: BaseL10n) =
+                       UnknownDateFilter(l10n, "View.Sone.Text.UnknownDate")
+
+       @Provides
+       fun getImageLinkFilter(core: Core) =
+                       ImageLinkFilter(core)
+
+       @Provides
+       @Named("toadletPathPrefix")
+       fun getPathPrefix(): String = "/Sone/"
+
+}
index 35ac0e2..e9a1a50 100644 (file)
@@ -3,12 +3,14 @@ package net.pterodactylus.sone.web.ajax
 import net.pterodactylus.sone.utils.emptyToNull
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.web.page.*
+import javax.inject.Inject
 
 /**
  * AJAX page that lets the user bookmark a post.
  */
-class BookmarkAjaxPage(webInterface: WebInterface) : JsonPage("bookmark.ajax", webInterface) {
+@ToadletPath("bookmark.ajax")
+class BookmarkAjaxPage @Inject constructor(webInterface: WebInterface) : JsonPage(webInterface) {
 
        override val requiresLogin = false
 
index 868e990..79e66ad 100644 (file)
@@ -1,19 +1,17 @@
 package net.pterodactylus.sone.web.ajax
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.text.TextFilter
-import net.pterodactylus.sone.utils.asOptional
-import net.pterodactylus.sone.utils.emptyToNull
-import net.pterodactylus.sone.utils.headers
-import net.pterodactylus.sone.utils.let
-import net.pterodactylus.sone.utils.parameters
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.text.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import javax.inject.*
 
 /**
  * AJAX handler that creates a new post.
  */
-class CreatePostAjaxPage(webInterface: WebInterface) : LoggedInJsonPage("createPost.ajax", webInterface) {
+@ToadletPath("createPost.ajax")
+class CreatePostAjaxPage @Inject constructor(webInterface: WebInterface) : LoggedInJsonPage(webInterface) {
 
        override fun createJsonObject(currentSone: Sone, request: FreenetRequest) =
                        request.parameters["text"].emptyToNull
index f817e28..df86dc0 100644 (file)
@@ -1,18 +1,17 @@
 package net.pterodactylus.sone.web.ajax
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.text.TextFilter
-import net.pterodactylus.sone.utils.emptyToNull
-import net.pterodactylus.sone.utils.headers
-import net.pterodactylus.sone.utils.let
-import net.pterodactylus.sone.utils.parameters
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.text.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import javax.inject.*
 
 /**
  * This AJAX page create a reply.
  */
-class CreateReplyAjaxPage(webInterface: WebInterface) : LoggedInJsonPage("createReply.ajax", webInterface) {
+@ToadletPath("createReply.ajax")
+class CreateReplyAjaxPage @Inject constructor(webInterface: WebInterface) : LoggedInJsonPage(webInterface) {
 
        override fun createJsonObject(currentSone: Sone, request: FreenetRequest): JsonReturnObject =
                        request.parameters["post"].emptyToNull
index 6dcf032..3dca438 100644 (file)
@@ -1,16 +1,16 @@
 package net.pterodactylus.sone.web.ajax
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.utils.ifTrue
-import net.pterodactylus.sone.utils.let
-import net.pterodactylus.sone.utils.parameters
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import javax.inject.*
 
 /**
  * This AJAX page deletes a post.
  */
-class DeletePostAjaxPage(webInterface: WebInterface) : LoggedInJsonPage("deletePost.ajax", webInterface) {
+@ToadletPath("deletePost.ajax")
+class DeletePostAjaxPage @Inject constructor(webInterface: WebInterface) : LoggedInJsonPage(webInterface) {
 
        override fun createJsonObject(currentSone: Sone, request: FreenetRequest) =
                        request.parameters["post"]
index 05f53da..6daa4da 100644 (file)
@@ -3,12 +3,14 @@ package net.pterodactylus.sone.web.ajax
 import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.web.page.*
+import javax.inject.Inject
 
 /**
  * AJAX page that lets the user delete a profile field.
  */
-class DeleteProfileFieldAjaxPage(webInterface: WebInterface) : LoggedInJsonPage("deleteProfileField.ajax", webInterface) {
+@ToadletPath("deleteProfileField.ajax")
+class DeleteProfileFieldAjaxPage @Inject constructor(webInterface: WebInterface) : LoggedInJsonPage(webInterface) {
 
        override fun createJsonObject(currentSone: Sone, request: FreenetRequest) =
                        currentSone.profile.let { profile ->
index d01c077..b558356 100644 (file)
@@ -1,16 +1,16 @@
 package net.pterodactylus.sone.web.ajax
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.utils.ifTrue
-import net.pterodactylus.sone.utils.let
-import net.pterodactylus.sone.utils.parameters
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import javax.inject.*
 
 /**
  * This AJAX page deletes a reply.
  */
-class DeleteReplyAjaxPage(webInterface: WebInterface) : LoggedInJsonPage("deleteReply.ajax", webInterface) {
+@ToadletPath("deleteReply.ajax")
+class DeleteReplyAjaxPage @Inject constructor(webInterface: WebInterface) : LoggedInJsonPage(webInterface) {
 
        override fun createJsonObject(currentSone: Sone, request: FreenetRequest) =
                        request.parameters["reply"]
index 539d57b..06fe51b 100644 (file)
@@ -4,12 +4,14 @@ import net.pterodactylus.sone.utils.ifTrue
 import net.pterodactylus.sone.utils.let
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.web.page.*
+import javax.inject.Inject
 
 /**
  * AJAX page that lets the user dismiss a notification.
  */
-class DismissNotificationAjaxPage(webInterface: WebInterface) : JsonPage("dismissNotification.ajax", webInterface) {
+@ToadletPath("dismissNotification.ajax")
+class DismissNotificationAjaxPage @Inject constructor(webInterface: WebInterface) : JsonPage(webInterface) {
 
        override val requiresLogin = false
 
index 4eadfde..cbeed6e 100644 (file)
@@ -1,18 +1,19 @@
 package net.pterodactylus.sone.web.ajax
 
-import net.pterodactylus.sone.core.Core
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.utils.let
-import net.pterodactylus.sone.utils.parameters
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.core.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import javax.inject.*
 
 /**
  * AJAX page that lets the user distrust a Sone.
  *
  * @see Core.distrustSone(Sone, Sone)
  */
-class DistrustAjaxPage(webInterface: WebInterface) : LoggedInJsonPage("distrustSone.ajax", webInterface) {
+@ToadletPath("distrustSone.ajax")
+class DistrustAjaxPage @Inject constructor(webInterface: WebInterface) : LoggedInJsonPage(webInterface) {
 
        override fun createJsonObject(currentSone: Sone, request: FreenetRequest) =
                        request.parameters["sone"]
index 321ee7d..39442f0 100644 (file)
@@ -5,12 +5,14 @@ import net.pterodactylus.sone.utils.headers
 import net.pterodactylus.sone.utils.ifTrue
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.web.page.*
+import javax.inject.Inject
 
 /**
  * Page that stores a user’s album modifications.
  */
-class EditAlbumAjaxPage(webInterface: WebInterface) : JsonPage("editAlbum.ajax", webInterface) {
+@ToadletPath("editAlbum.ajax")
+class EditAlbumAjaxPage @Inject constructor(webInterface: WebInterface) : JsonPage(webInterface) {
 
        override fun createJsonObject(request: FreenetRequest) =
                        request.parameters["album"]!!
index dbeb2d2..7012cdc 100644 (file)
@@ -8,16 +8,18 @@ import net.pterodactylus.sone.utils.headers
 import net.pterodactylus.sone.utils.ifTrue
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.web.page.*
 import net.pterodactylus.util.template.TemplateContext
+import javax.inject.Inject
 
 /**
  * Page that stores a user’s image modifications.
  */
-class EditImageAjaxPage(webInterface: WebInterface,
+@ToadletPath("editImage.ajax")
+class EditImageAjaxPage @Inject constructor(webInterface: WebInterface,
                private val parserFilter: ParserFilter,
                private val shortenFilter: ShortenFilter,
-               private val renderFilter: RenderFilter) : JsonPage("editImage.ajax", webInterface) {
+               private val renderFilter: RenderFilter) : JsonPage(webInterface) {
 
        override fun createJsonObject(request: FreenetRequest) =
                        request.parameters["image"]
index e48c92d..9d5346c 100644 (file)
@@ -4,12 +4,14 @@ import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.utils.ifFalse
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.web.page.*
+import javax.inject.Inject
 
 /**
  * AJAX page that lets the user rename a profile field.
  */
-class EditProfileFieldAjaxPage(webInterface: WebInterface) : LoggedInJsonPage("editProfileField.ajax", webInterface) {
+@ToadletPath("editProfileField.ajax")
+class EditProfileFieldAjaxPage @Inject constructor(webInterface: WebInterface) : LoggedInJsonPage(webInterface) {
 
        override fun createJsonObject(currentSone: Sone, request: FreenetRequest) =
                        currentSone.profile.let { profile ->
index 0c37ac0..eea5120 100644 (file)
@@ -1,16 +1,16 @@
 package net.pterodactylus.sone.web.ajax
 
 import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.utils.also
-import net.pterodactylus.sone.utils.let
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.web.page.*
+import javax.inject.Inject
 
 /**
  * AJAX page that lets a Sone follow another Sone.
  */
-class FollowSoneAjaxPage(webInterface: WebInterface) : LoggedInJsonPage("followSone.ajax", webInterface) {
+@ToadletPath("followSone.ajax")
+class FollowSoneAjaxPage @Inject constructor(webInterface: WebInterface) : LoggedInJsonPage(webInterface) {
 
        override fun createJsonObject(currentSone: Sone, request: FreenetRequest) =
                        request.parameters["sone"]
index 77e2c87..1175b2b 100644 (file)
@@ -4,16 +4,17 @@ import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.template.SoneAccessor
 import net.pterodactylus.sone.utils.jsonArray
 import net.pterodactylus.sone.utils.jsonObject
-import net.pterodactylus.sone.utils.let
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.web.page.*
+import javax.inject.Inject
 
 /**
  * AJAX page that retrieves the number of “likes” a [net.pterodactylus.sone.data.Post]
  * or [net.pterodactylus.sone.data.PostReply] has.
  */
-class GetLikesAjaxPage(webInterface: WebInterface) : JsonPage("getLikes.ajax", webInterface) {
+@ToadletPath("getLikes.ajax")
+class GetLikesAjaxPage @Inject constructor(webInterface: WebInterface) : JsonPage(webInterface) {
 
        override val needsFormPassword = false
 
index 809abdc..115370a 100644 (file)
@@ -8,13 +8,15 @@ import net.pterodactylus.sone.template.LinkedElementRenderFilter
 import net.pterodactylus.sone.utils.jsonArray
 import net.pterodactylus.sone.utils.jsonObject
 import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.web.page.*
+import javax.inject.Inject
 
 /**
  * Renders linked elements after they have been loaded.
  */
-class GetLinkedElementAjaxPage(webInterface: WebInterface, private val elementLoader: ElementLoader, private val linkedElementRenderFilter: LinkedElementRenderFilter):
-               JsonPage("getLinkedElement.ajax", webInterface) {
+@ToadletPath("getLinkedElement.ajax")
+class GetLinkedElementAjaxPage @Inject constructor(webInterface: WebInterface, private val elementLoader: ElementLoader, private val linkedElementRenderFilter: LinkedElementRenderFilter):
+               JsonPage(webInterface) {
 
        override val needsFormPassword = false
        override val requiresLogin = false
index 19cdd24..db6e5c5 100644 (file)
@@ -6,15 +6,17 @@ import net.pterodactylus.sone.main.SonePlugin
 import net.pterodactylus.sone.utils.jsonArray
 import net.pterodactylus.sone.utils.jsonObject
 import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.web.page.*
 import net.pterodactylus.util.notify.Notification
 import net.pterodactylus.util.notify.TemplateNotification
 import java.io.StringWriter
+import javax.inject.Inject
 
 /**
  * AJAX handler to return all current notifications.
  */
-class GetNotificationsAjaxPage(webInterface: WebInterface) : JsonPage("getNotifications.ajax", webInterface) {
+@ToadletPath("getNotifications.ajax")
+class GetNotificationsAjaxPage @Inject constructor(webInterface: WebInterface) : JsonPage(webInterface) {
 
        override val needsFormPassword = false
        override val requiresLogin = false
index d91fe90..1aedd49 100644 (file)
@@ -3,17 +3,18 @@ package net.pterodactylus.sone.web.ajax
 import net.pterodactylus.sone.data.Post
 import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.utils.jsonObject
-import net.pterodactylus.sone.utils.let
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.utils.render
 import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.web.page.*
 import net.pterodactylus.util.template.Template
+import javax.inject.Inject
 
 /**
  * This AJAX handler retrieves information and rendered representation of a [Post].
  */
-class GetPostAjaxPage(webInterface: WebInterface, private val postTemplate: Template) : LoggedInJsonPage("getPost.ajax", webInterface) {
+@ToadletPath("getPost.ajax")
+class GetPostAjaxPage @Inject constructor(webInterface: WebInterface, private val postTemplate: Template) : LoggedInJsonPage(webInterface) {
 
        override val needsFormPassword = false
 
index b02b85b..ec5a5ac 100644 (file)
@@ -3,17 +3,18 @@ package net.pterodactylus.sone.web.ajax
 import net.pterodactylus.sone.data.PostReply
 import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.utils.jsonObject
-import net.pterodactylus.sone.utils.let
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.utils.render
 import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.web.page.*
 import net.pterodactylus.util.template.Template
+import javax.inject.Inject
 
 /**
  * This AJAX page returns the details of a reply.
  */
-class GetReplyAjaxPage(webInterface: WebInterface, private val template: Template) : LoggedInJsonPage("getReply.ajax", webInterface) {
+@ToadletPath("getReply.ajax")
+class GetReplyAjaxPage @Inject constructor(webInterface: WebInterface, private val template: Template) : LoggedInJsonPage(webInterface) {
 
        override val needsFormPassword = false
 
index 7646704..75f3c3c 100644 (file)
@@ -12,19 +12,23 @@ import net.pterodactylus.sone.freenet.L10nFilter
 import net.pterodactylus.sone.template.SoneAccessor
 import net.pterodactylus.sone.text.TimeTextConverter
 import net.pterodactylus.sone.utils.jsonObject
-import net.pterodactylus.sone.utils.mapPresent
 import net.pterodactylus.sone.utils.toArray
 import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.web.page.*
 import java.text.SimpleDateFormat
 import java.util.TimeZone
+import javax.inject.Inject
 
 /**
  * The “get status” AJAX handler returns all information that is necessary to
  * update the web interface in real-time.
  */
-class GetStatusAjaxPage(webInterface: WebInterface, private val elementLoader: ElementLoader, private val timeTextConverter: TimeTextConverter, private val l10nFilter: L10nFilter, timeZone: TimeZone = TimeZone.getDefault()):
-               JsonPage("getStatus.ajax", webInterface) {
+@ToadletPath("getStatus.ajax")
+class GetStatusAjaxPage(webInterface: WebInterface, private val elementLoader: ElementLoader, private val timeTextConverter: TimeTextConverter, private val l10nFilter: L10nFilter, timeZone: TimeZone):
+               JsonPage(webInterface) {
+
+       @Inject constructor(webInterface: WebInterface, elementLoader: ElementLoader, timeTextConverter: TimeTextConverter, l10nFilter: L10nFilter):
+                       this(webInterface, elementLoader, timeTextConverter, l10nFilter, TimeZone.getDefault())
 
        private val dateFormatter = SimpleDateFormat("MMM d, yyyy, HH:mm:ss").apply {
                this.timeZone = timeZone
index cb3b86d..ef4a0db 100644 (file)
@@ -5,17 +5,19 @@ import net.pterodactylus.sone.text.TimeTextConverter
 import net.pterodactylus.sone.utils.jsonObject
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.web.page.*
 import java.text.SimpleDateFormat
 import java.util.TimeZone
+import javax.inject.Inject
 
 /**
  * Ajax page that returns a formatted, relative timestamp for replies or posts.
  */
-class GetTimesAjaxPage(webInterface: WebInterface,
+@ToadletPath("getTimes.ajax")
+class GetTimesAjaxPage @Inject constructor(webInterface: WebInterface,
                private val timeTextConverter: TimeTextConverter,
                private val l10nFilter: L10nFilter,
-               timeZone: TimeZone) : JsonPage("getTimes.ajax", webInterface) {
+               timeZone: TimeZone) : JsonPage(webInterface) {
 
        private val dateTimeFormatter = SimpleDateFormat("MMM d, yyyy, HH:mm:ss").apply {
                this.timeZone = timeZone
index d260655..41188ea 100644 (file)
@@ -2,12 +2,14 @@ package net.pterodactylus.sone.web.ajax
 
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.web.page.*
+import javax.inject.Inject
 
 /**
  * Returns the translation for a given key as JSON object.
  */
-class GetTranslationAjaxPage(webInterface: WebInterface) : JsonPage("getTranslation.ajax", webInterface) {
+@ToadletPath("getTranslation.ajax")
+class GetTranslationAjaxPage @Inject constructor(webInterface: WebInterface) : JsonPage(webInterface) {
 
        override val needsFormPassword = false
        override val requiresLogin = false
index 2da5edc..99c0828 100644 (file)
@@ -5,7 +5,7 @@ import freenet.clients.http.ToadletContext
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.SessionProvider
 import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.web.page.*
 import net.pterodactylus.util.web.Page
 import net.pterodactylus.util.web.Response
 import java.io.ByteArrayOutputStream
@@ -15,13 +15,13 @@ import java.io.PrintStream
  * A JSON page is a specialized [Page] that will always return a JSON
  * object to the browser, e.g. for use with AJAX or other scripting frameworks.
  */
-abstract class JsonPage(private val path: String, protected val webInterface: WebInterface) : Page<FreenetRequest> {
+abstract class JsonPage(protected val webInterface: WebInterface) : Page<FreenetRequest> {
 
        private val objectMapper = ObjectMapper()
        private val sessionProvider: SessionProvider = webInterface
        protected val core = webInterface.core
 
-       override fun getPath() = path
+       override fun getPath() = toadletPath
        override fun isPrefixPage() = false
 
        open val needsFormPassword = true
@@ -35,7 +35,7 @@ abstract class JsonPage(private val path: String, protected val webInterface: We
                        sessionProvider.getCurrentSone(toadletContext, createSession)
 
        override fun handleRequest(request: FreenetRequest, response: Response): Response {
-               if (core.preferences.isRequireFullAccess && !request.toadletContext.isAllowedFullAccess) {
+               if (core.preferences.requireFullAccess && !request.toadletContext.isAllowedFullAccess) {
                        return response.setStatusCode(403).setStatusText("Forbidden").setContentType("application/json").write(createErrorJsonObject("auth-required").asJsonString())
                }
                if (needsFormPassword && request.parameters["formPassword"] != webInterface.formPassword) {
index 718e6e7..b2993f4 100644 (file)
@@ -1,15 +1,16 @@
 package net.pterodactylus.sone.web.ajax
 
 import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.utils.let
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.web.page.*
+import javax.inject.Inject
 
 /**
  * AJAX page that lets the user like a [net.pterodactylus.sone.data.Post].
  */
-class LikeAjaxPage(webInterface: WebInterface) : LoggedInJsonPage("like.ajax", webInterface) {
+@ToadletPath("like.ajax")
+class LikeAjaxPage @Inject constructor(webInterface: WebInterface) : LoggedInJsonPage(webInterface) {
 
        override fun createJsonObject(currentSone: Sone, request: FreenetRequest) =
                        when (request.parameters["type"]) {
index 8605fe6..c177041 100644 (file)
@@ -2,12 +2,14 @@ package net.pterodactylus.sone.web.ajax
 
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.web.page.*
+import javax.inject.Inject
 
 /**
  * Lets the user [lock][net.pterodactylus.sone.core.Core.lockSone] a [Sone][net.pterodactylus.sone.data.Sone].
  */
-class LockSoneAjaxPage(webInterface: WebInterface) : JsonPage("lockSone.ajax", webInterface) {
+@ToadletPath("lockSone.ajax")
+class LockSoneAjaxPage @Inject constructor(webInterface: WebInterface) : JsonPage(webInterface) {
 
        override val requiresLogin = false
 
index 388c289..d1afaec 100644 (file)
@@ -7,7 +7,7 @@ import net.pterodactylus.sone.web.page.FreenetRequest
 /**
  * Base JSON page for all pages that require the user to be logged in.
  */
-open class LoggedInJsonPage(path: String, webInterface: WebInterface) : JsonPage(path, webInterface) {
+open class LoggedInJsonPage(webInterface: WebInterface) : JsonPage(webInterface) {
 
        final override val requiresLogin = true
 
index 0451eaa..808d2fa 100644 (file)
@@ -2,13 +2,15 @@ package net.pterodactylus.sone.web.ajax
 
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.web.page.*
+import javax.inject.Inject
 
 /**
  * AJAX page that lets the user mark a number of [net.pterodactylus.sone.data.Sone]s,
  * [net.pterodactylus.sone.data.Post]s, or [net.pterodactylus.sone.data.Reply]s as known.
  */
-class MarkAsKnownAjaxPage(webInterface: WebInterface) : JsonPage("markAsKnown.ajax", webInterface) {
+@ToadletPath("markAsKnown.ajax")
+class MarkAsKnownAjaxPage @Inject constructor(webInterface: WebInterface) : JsonPage(webInterface) {
 
        override val requiresLogin = false
 
index 1c050bf..0b2c5ab 100644 (file)
@@ -5,7 +5,8 @@ import net.pterodactylus.sone.data.Profile.Field
 import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.web.page.*
+import javax.inject.Inject
 
 /**
  * AJAX page that lets the user move a profile field up or down.
@@ -13,7 +14,8 @@ import net.pterodactylus.sone.web.page.FreenetRequest
  * @see net.pterodactylus.sone.data.Profile#moveFieldUp(Field)
  * @see net.pterodactylus.sone.data.Profile#moveFieldDown(Field)
  */
-class MoveProfileFieldAjaxPage(webInterface: WebInterface) : LoggedInJsonPage("moveProfileField.ajax", webInterface) {
+@ToadletPath("moveProfileField.ajax")
+class MoveProfileFieldAjaxPage @Inject constructor(webInterface: WebInterface) : LoggedInJsonPage(webInterface) {
 
        override fun createJsonObject(currentSone: Sone, request: FreenetRequest) =
                        currentSone.profile.let { profile ->
index 1dfb058..9f0de87 100644 (file)
@@ -1,17 +1,18 @@
 package net.pterodactylus.sone.web.ajax
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.utils.let
-import net.pterodactylus.sone.utils.parameters
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import javax.inject.*
 
 /**
  * AJAX page that lets the user trust a Sone.
  *
  * @see net.pterodactylus.sone.core.Core.trustSone
  */
-class TrustAjaxPage(webInterface: WebInterface) : LoggedInJsonPage("trustSone.ajax", webInterface) {
+@ToadletPath("trustSone.ajax")
+class TrustAjaxPage @Inject constructor(webInterface: WebInterface) : LoggedInJsonPage(webInterface) {
 
        override fun createJsonObject(currentSone: Sone, request: FreenetRequest) =
                        request.parameters["sone"]
index f8331fe..bc61b21 100644 (file)
@@ -1,15 +1,15 @@
 package net.pterodactylus.sone.web.ajax
 
-import net.pterodactylus.sone.utils.also
-import net.pterodactylus.sone.utils.let
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.web.page.*
+import javax.inject.Inject
 
 /**
  * AJAX page that lets the user unbookmark a post.
  */
-class UnbookmarkAjaxPage(webInterface: WebInterface) : JsonPage("unbookmark.ajax", webInterface) {
+@ToadletPath("unbookmark.ajax")
+class UnbookmarkAjaxPage @Inject constructor(webInterface: WebInterface) : JsonPage(webInterface) {
 
        override val requiresLogin = false
 
index 83b474c..d8b8290 100644 (file)
@@ -3,12 +3,14 @@ package net.pterodactylus.sone.web.ajax
 import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.web.page.*
+import javax.inject.Inject
 
 /**
  * AJAX page that lets a Sone unfollow another Sone.
  */
-class UnfollowSoneAjaxPage(webInterface: WebInterface) : LoggedInJsonPage("unfollowSone.ajax", webInterface) {
+@ToadletPath("unfollowSone.ajax")
+class UnfollowSoneAjaxPage @Inject constructor(webInterface: WebInterface) : LoggedInJsonPage(webInterface) {
 
        override fun createJsonObject(currentSone: Sone, request: FreenetRequest) =
                        request.parameters["sone"]
index b30829a..a8da594 100644 (file)
@@ -4,12 +4,14 @@ import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.utils.emptyToNull
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.web.page.*
+import javax.inject.Inject
 
 /**
  * AJAX page that lets the user unlike a [net.pterodactylus.sone.data.Post].
  */
-class UnlikeAjaxPage(webInterface: WebInterface) : LoggedInJsonPage("unlike.ajax", webInterface) {
+@ToadletPath("unlike.ajax")
+class UnlikeAjaxPage @Inject constructor(webInterface: WebInterface) : LoggedInJsonPage(webInterface) {
 
        override fun createJsonObject(currentSone: Sone, request: FreenetRequest) = when (request.parameters["type"]) {
                "post" -> request.processEntity("post", currentSone::removeLikedPostId)
index 7d77c05..130d102 100644 (file)
@@ -2,12 +2,14 @@ package net.pterodactylus.sone.web.ajax
 
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.web.page.*
+import javax.inject.Inject
 
 /**
  * Lets the user [unlock][net.pterodactylus.sone.core.Core.unlockSone] a [Sone][net.pterodactylus.sone.data.Sone].
  */
-class UnlockSoneAjaxPage(webInterface: WebInterface) : JsonPage("unlockSone.ajax", webInterface) {
+@ToadletPath("unlockSone.ajax")
+class UnlockSoneAjaxPage @Inject constructor(webInterface: WebInterface) : JsonPage(webInterface) {
 
        override val requiresLogin = false
 
index c6274a2..e4b5edb 100644 (file)
@@ -1,16 +1,16 @@
 package net.pterodactylus.sone.web.ajax
 
 import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.utils.also
-import net.pterodactylus.sone.utils.let
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.web.page.*
+import javax.inject.Inject
 
 /**
  * AJAX page that lets the user [untrust][net.pterodactylus.sone.core.Core.untrustSone] a [Sone].
  */
-class UntrustAjaxPage(webInterface: WebInterface) : LoggedInJsonPage("untrustSone.ajax", webInterface) {
+@ToadletPath("untrustSone.ajax")
+class UntrustAjaxPage @Inject constructor(webInterface: WebInterface) : LoggedInJsonPage(webInterface) {
 
        override fun createJsonObject(currentSone: Sone, request: FreenetRequest) =
                        request.parameters["sone"]
diff --git a/src/main/kotlin/net/pterodactylus/sone/web/page/Annotations.kt b/src/main/kotlin/net/pterodactylus/sone/web/page/Annotations.kt
new file mode 100644 (file)
index 0000000..8287130
--- /dev/null
@@ -0,0 +1,15 @@
+package net.pterodactylus.sone.web.page
+
+import net.pterodactylus.util.web.*
+
+annotation class MenuName(val value: String)
+
+val Page<*>.menuName get() = javaClass.getAnnotation(MenuName::class.java)?.value
+
+annotation class TemplatePath(val value: String)
+
+val Page<*>.templatePath get() = javaClass.getAnnotation(TemplatePath::class.java)?.value
+
+annotation class ToadletPath(val value: String)
+
+val Page<*>.toadletPath get() = javaClass.getAnnotation(ToadletPath::class.java)?.value
diff --git a/src/main/kotlin/net/pterodactylus/sone/web/page/FreenetPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/page/FreenetPage.kt
new file mode 100644 (file)
index 0000000..2af0eca
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Sone - FreenetPage.kt - Copyright © 2011–2019 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.web.page
+
+import net.pterodactylus.util.web.*
+import java.net.*
+
+interface FreenetPage : Page<FreenetRequest> {
+
+       fun isLinkExcepted(link: URI): Boolean
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/web/page/FreenetRequest.kt b/src/main/kotlin/net/pterodactylus/sone/web/page/FreenetRequest.kt
new file mode 100644 (file)
index 0000000..54c8f7f
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Sone - FreenetRequest.kt - Copyright © 2011–2019 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.web.page
+
+import freenet.clients.http.*
+import freenet.clients.http.SessionManager.*
+import freenet.l10n.*
+import freenet.support.api.*
+import net.pterodactylus.util.web.*
+import java.net.*
+import java.util.UUID.*
+
+open class FreenetRequest(uri: URI, method: Method,
+               val httpRequest: HTTPRequest,
+               val toadletContext: ToadletContext,
+               val l10n: BaseL10n,
+               val sessionManager: SessionManager
+) : Request(uri, method) {
+
+       val session: Session
+               get() =
+                       sessionManager.useSession(toadletContext)
+                                       ?: sessionManager.createSession(randomUUID().toString(), toadletContext)
+
+       val existingSession: Session? get() = sessionManager.useSession(toadletContext)
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/web/page/FreenetTemplatePage.kt b/src/main/kotlin/net/pterodactylus/sone/web/page/FreenetTemplatePage.kt
new file mode 100644 (file)
index 0000000..2dcef51
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Sone - FreenetTemplatePage.kt - Copyright © 2010–2019 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.web.page
+
+import freenet.clients.http.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.util.template.*
+import net.pterodactylus.util.web.*
+import java.lang.String.*
+import java.net.*
+import java.util.logging.*
+import java.util.logging.Logger.*
+
+/**
+ * Base class for all [Page]s that are rendered with [Template]s and
+ * fit into Freenet’s web interface.
+ */
+open class FreenetTemplatePage(
+               private val templateRenderer: TemplateRenderer,
+               loaders: Loaders,
+               private val invalidFormPasswordRedirectTarget: String
+) : FreenetPage, LinkEnabledCallback {
+
+       private val pageMakerInteractionFactory: PageMakerInteractionFactory = DefaultPageMakerInteractionFactory()
+       open val styleSheets: Collection<String> = emptySet()
+       open val shortcutIcon: String? get() = null
+       open val isFullAccessOnly get() = false
+
+       override fun getPath() = toadletPath
+
+       open fun getPageTitle(request: FreenetRequest) = ""
+
+       override fun isPrefixPage() = false
+
+       open fun getRedirectTarget(request: FreenetRequest): String? = null
+
+       open fun getAdditionalLinkNodes(request: FreenetRequest): List<Map<String, String>> = emptyList()
+
+       override fun isLinkExcepted(link: URI) = false
+
+       override fun isEnabled(toadletContext: ToadletContext) = !isFullAccessOnly
+
+       private val template = templatePath?.let(loaders::loadTemplate) ?: Template()
+
+       override fun handleRequest(request: FreenetRequest, response: Response): Response {
+               getRedirectTarget(request)?.let { redirectTarget -> return RedirectResponse(redirectTarget) }
+
+               if (isFullAccessOnly && !request.toadletContext.isAllowedFullAccess) {
+                       return response.setStatusCode(401).setStatusText("Not authorized").setContentType("text/html")
+               }
+               val toadletContext = request.toadletContext
+               if (request.method == Method.POST) {
+                       /* require form password. */
+                       val formPassword = request.httpRequest.getPartAsStringFailsafe("formPassword", 32)
+                       if (formPassword != toadletContext.container.formPassword) {
+                               return RedirectResponse(invalidFormPasswordRedirectTarget)
+                       }
+               }
+
+               val pageMakerInteraction = pageMakerInteractionFactory.createPageMaker(toadletContext, getPageTitle(request))
+               styleSheets.forEach(pageMakerInteraction::addStyleSheet)
+               getAdditionalLinkNodes(request).forEach(pageMakerInteraction::addLinkNode)
+               shortcutIcon?.let(pageMakerInteraction::addShortcutIcon)
+
+               val output = try {
+                       val start = System.nanoTime()
+                       templateRenderer.render(template) { templateContext ->
+                               processTemplate(request, templateContext)
+                       }.also {
+                               val finish = System.nanoTime()
+                               logger.log(Level.FINEST, format("Template was rendered in %.2fms.", (finish - start) / 1000000.0))
+                       }
+               } catch (re1: RedirectException) {
+                       return RedirectResponse(re1.target ?: "")
+               }
+
+               pageMakerInteraction.setContent(output)
+
+               return response.setStatusCode(200).setStatusText("OK").setContentType("text/html").write(pageMakerInteraction.renderPage())
+       }
+
+       open fun processTemplate(request: FreenetRequest, templateContext: TemplateContext) {
+               /* do nothing. */
+       }
+
+       class RedirectException(val target: String?) : Exception() {
+               override fun toString(): String = format("RedirectException{target='%s'}", target)
+       }
+
+}
+
+private val logger: Logger = getLogger(FreenetTemplatePage::class.java.name)
diff --git a/src/main/kotlin/net/pterodactylus/sone/web/page/PageMakerInteraction.kt b/src/main/kotlin/net/pterodactylus/sone/web/page/PageMakerInteraction.kt
new file mode 100644 (file)
index 0000000..8d6884c
--- /dev/null
@@ -0,0 +1,31 @@
+package net.pterodactylus.sone.web.page
+
+import freenet.clients.http.*
+
+class PageMakerInteraction(toadletContext: ToadletContext, pageTitle: String) {
+
+       private val pageMaker: PageMaker = toadletContext.pageMaker
+       private val pageNode: PageNode = pageMaker.getPageNode(pageTitle, toadletContext)
+
+       fun addStyleSheet(styleSheet: String) {
+               pageNode.addCustomStyleSheet(styleSheet)
+       }
+
+       fun addLinkNode(linkAttributes: Map<String, String>) {
+               pageNode.headNode.addChild("link").let {
+                       linkAttributes.forEach(it::addAttribute)
+               }
+       }
+
+       fun addShortcutIcon(shortcutIcon: String) {
+               pageNode.addForwardLink("icon", shortcutIcon)
+       }
+
+       fun setContent(content: String) {
+               pageNode.content.addChild("%", content)
+       }
+
+       fun renderPage(): String =
+                       pageNode.outer.generate()
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/web/page/PageMakerInteractionFactory.kt b/src/main/kotlin/net/pterodactylus/sone/web/page/PageMakerInteractionFactory.kt
new file mode 100644 (file)
index 0000000..e55e33f
--- /dev/null
@@ -0,0 +1,18 @@
+package net.pterodactylus.sone.web.page
+
+import com.google.inject.*
+import freenet.clients.http.*
+
+@ImplementedBy(DefaultPageMakerInteractionFactory::class)
+interface PageMakerInteractionFactory {
+
+       fun createPageMaker(toadletContext: ToadletContext, pageTitle: String): PageMakerInteraction
+
+}
+
+class DefaultPageMakerInteractionFactory : PageMakerInteractionFactory {
+
+       override fun createPageMaker(toadletContext: ToadletContext, pageTitle: String) =
+                       PageMakerInteraction(toadletContext, pageTitle)
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/web/page/PageToadletFactory.kt b/src/main/kotlin/net/pterodactylus/sone/web/page/PageToadletFactory.kt
new file mode 100644 (file)
index 0000000..3c84c09
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Sone - PageToadletFactory.kt - Copyright © 2010–2019 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.web.page
+
+import freenet.client.*
+import freenet.clients.http.*
+import net.pterodactylus.util.web.*
+import javax.inject.*
+
+class PageToadletFactory @Inject constructor(
+               private val highLevelSimpleClient: HighLevelSimpleClient,
+               private val sessionManager: SessionManager,
+               @Named("toadletPathPrefix") private val pathPrefix: String
+) {
+
+       @JvmOverloads
+       fun createPageToadlet(page: Page<FreenetRequest>, menuName: String? = null) =
+                       PageToadlet(highLevelSimpleClient, sessionManager, menuName ?: page.menuName, page, pathPrefix)
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/web/page/SoneRequest.kt b/src/main/kotlin/net/pterodactylus/sone/web/page/SoneRequest.kt
new file mode 100644 (file)
index 0000000..d3eed37
--- /dev/null
@@ -0,0 +1,16 @@
+package net.pterodactylus.sone.web.page
+
+import freenet.clients.http.*
+import freenet.l10n.*
+import freenet.support.api.*
+import net.pterodactylus.sone.core.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.util.web.*
+import java.net.*
+
+class SoneRequest(uri: URI, method: Method, httpRequest: HTTPRequest, toadletContext: ToadletContext, l10n: BaseL10n, sessionManager: SessionManager,
+               val core: Core,
+               val webInterface: WebInterface
+) : FreenetRequest(uri, method, httpRequest, toadletContext, l10n, sessionManager)
+
+fun FreenetRequest.toSoneRequest(core: Core, webInterface: WebInterface) = SoneRequest(uri, method, httpRequest, toadletContext, l10n, sessionManager, core, webInterface)
diff --git a/src/main/kotlin/net/pterodactylus/sone/web/page/TemplateRenderer.kt b/src/main/kotlin/net/pterodactylus/sone/web/page/TemplateRenderer.kt
new file mode 100644 (file)
index 0000000..21eeb2a
--- /dev/null
@@ -0,0 +1,16 @@
+package net.pterodactylus.sone.web.page
+
+import net.pterodactylus.util.template.*
+import java.io.*
+import javax.inject.*
+
+class TemplateRenderer @Inject constructor(private val templateContextFactory: TemplateContextFactory) {
+
+       fun render(template: Template, processor: (TemplateContext) -> Unit = {}): String =
+                       templateContextFactory.createTemplateContext().let { templateContext ->
+                               templateContext.mergeContext(template.initialContext)
+                               processor(templateContext)
+                               StringWriter().also { template.render(templateContext, it) }.toString()
+                       }
+
+}
index d3382b4..f622319 100644 (file)
@@ -1,20 +1,21 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.main.SonePlugin.PluginHomepage
-import net.pterodactylus.sone.main.SonePlugin.PluginVersion
-import net.pterodactylus.sone.main.SonePlugin.PluginYear
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * A [SoneTemplatePage] that stores information about Sone in the [TemplateContext].
  */
-class AboutPage(template: Template, webInterface: WebInterface,
+@MenuName("About")
+@TemplatePath("/templates/about.html")
+@ToadletPath("about.html")
+class AboutPage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer,
                private val pluginVersion: PluginVersion,
                private val pluginYear: PluginYear,
-               private val pluginHomepage: PluginHomepage): SoneTemplatePage("about.html", template, "Page.About.Title", webInterface, false) {
+               private val pluginHomepage: PluginHomepage) : SoneTemplatePage(webInterface, loaders, templateRenderer, pageTitleKey = "Page.About.Title") {
 
        override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
                templateContext["version"] = pluginVersion.version
index 7fb2320..1dc12e4 100644 (file)
@@ -1,23 +1,25 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.utils.isPOST
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * Page that lets the user bookmark a post.
  */
-class BookmarkPage(template: Template, webInterface: WebInterface)
-       : SoneTemplatePage("bookmark.html", template, "Page.Bookmark.Title", webInterface) {
+@ToadletPath("bookmark.html")
+class BookmarkPage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer)
+       : SoneTemplatePage(webInterface, loaders, templateRenderer, pageTitleKey = "Page.Bookmark.Title") {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
-               if (freenetRequest.isPOST) {
-                       val returnPage = freenetRequest.httpRequest.getPartAsStringFailsafe("returnPage", 256)
-                       val postId = freenetRequest.httpRequest.getPartAsStringFailsafe("post", 36)
-                       webInterface.core.getPost(postId)?.let {
-                               webInterface.core.bookmarkPost(it)
+       override fun handleRequest(soneRequest: SoneRequest, templateContext: TemplateContext) {
+               if (soneRequest.isPOST) {
+                       val returnPage = soneRequest.httpRequest.getPartAsStringFailsafe("returnPage", 256)
+                       val postId = soneRequest.httpRequest.getPartAsStringFailsafe("post", 36)
+                       soneRequest.core.getPost(postId)?.let {
+                               soneRequest.core.bookmarkPost(it)
                        }
                        throw RedirectException(returnPage)
                }
index fbfc7d3..b892263 100644 (file)
@@ -1,20 +1,25 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.utils.Pagination
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * Page that lets the user browse all his bookmarked posts.
  */
-class BookmarksPage(template: Template, webInterface: WebInterface): SoneTemplatePage("bookmarks.html", template, "Page.Bookmarks.Title", webInterface) {
+@MenuName("Bookmarks")
+@TemplatePath("/templates/bookmarks.html")
+@ToadletPath("bookmarks.html")
+class BookmarksPage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               SoneTemplatePage(webInterface, loaders, templateRenderer, pageTitleKey = "Page.Bookmarks.Title") {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
-               webInterface.core.bookmarkedPosts.let { posts ->
-                       val pagination = Pagination<Post>(posts.filter { it.isLoaded }.sortedByDescending { it.time }, webInterface.core.preferences.postsPerPage)
+       override fun handleRequest(soneRequest: SoneRequest, templateContext: TemplateContext) {
+               soneRequest.core.bookmarkedPosts.let { posts ->
+                       val pagination = posts.filter(Post::isLoaded).sortedByDescending { it.time }.paginate(soneRequest.core.preferences.postsPerPage)
                        templateContext["pagination"] = pagination
                        templateContext["posts"] = pagination.items
                        templateContext["postsNotLoaded"] = posts.any { !it.isLoaded }
index b0eea08..a00b4bf 100644 (file)
@@ -1,40 +1,43 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Album.Modifier.AlbumTitleMustNotBeEmpty
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.text.TextFilter
-import net.pterodactylus.sone.utils.isPOST
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.data.Album.Modifier.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.text.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * Page that lets the user create a new album.
  */
-class CreateAlbumPage(template: Template, webInterface: WebInterface):
-               LoggedInPage("createAlbum.html", template, "Page.CreateAlbum.Title", webInterface) {
+@TemplatePath("/templates/createAlbum.html")
+@ToadletPath("createAlbum.html")
+class CreateAlbumPage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               LoggedInPage("Page.CreateAlbum.Title", webInterface, loaders, templateRenderer) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
-               if (freenetRequest.isPOST) {
-                       val name = freenetRequest.httpRequest.getPartAsStringFailsafe("name", 64).trim()
+       override fun handleRequest(soneRequest: SoneRequest, currentSone: Sone, templateContext: TemplateContext) {
+               if (soneRequest.isPOST) {
+                       val name = soneRequest.httpRequest.getPartAsStringFailsafe("name", 64).trim()
                        if (name.isEmpty()) {
                                templateContext["nameMissing"] = true
                                return
                        }
-                       val description = freenetRequest.httpRequest.getPartAsStringFailsafe("description", 256).trim()
-                       val parentId = freenetRequest.httpRequest.getPartAsStringFailsafe("parent", 36)
-                       val parent = if (parentId == "") currentSone.rootAlbum else webInterface.core.getAlbum(parentId)
-                       val album = webInterface.core.createAlbum(currentSone, parent)
+                       val description = soneRequest.httpRequest.getPartAsStringFailsafe("description", 256).trim()
+                       val parentId = soneRequest.httpRequest.getPartAsStringFailsafe("parent", 36)
+                       val parent = if (parentId == "") currentSone.rootAlbum else soneRequest.core.getAlbum(parentId)
+                       val album = soneRequest.core.createAlbum(currentSone, parent)
                        try {
                                album.modify().apply {
                                        setTitle(name)
-                                       setDescription(TextFilter.filter(freenetRequest.httpRequest.getHeader("Host"), description))
+                                       setDescription(TextFilter.filter(soneRequest.httpRequest.getHeader("Host"), description))
                                }.update()
                        } catch (e: AlbumTitleMustNotBeEmpty) {
                                throw RedirectException("emptyAlbumTitle.html")
                        }
-                       webInterface.core.touchConfiguration()
+                       soneRequest.core.touchConfiguration()
                        throw RedirectException("imageBrowser.html?album=${album.id}")
                }
        }
index 31896f7..b47c2b1 100644 (file)
@@ -1,32 +1,34 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.text.TextFilter
-import net.pterodactylus.sone.utils.asOptional
-import net.pterodactylus.sone.utils.isPOST
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.text.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * This page lets the user create a new [Post].
  */
-class CreatePostPage(template: Template, webInterface: WebInterface):
-               LoggedInPage("createPost.html", template, "Page.CreatePost.Title", webInterface) {
+@TemplatePath("/templates/createPost.html")
+@ToadletPath("createPost.html")
+class CreatePostPage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               LoggedInPage("Page.CreatePost.Title", webInterface, loaders, templateRenderer) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
-               val returnPage = freenetRequest.httpRequest.getPartAsStringFailsafe("returnPage", 256)
+       override fun handleRequest(soneRequest: SoneRequest, currentSone: Sone, templateContext: TemplateContext) {
+               val returnPage = soneRequest.httpRequest.getPartAsStringFailsafe("returnPage", 256)
                templateContext["returnPage"] = returnPage
-               if (freenetRequest.isPOST) {
-                       val text = freenetRequest.httpRequest.getPartAsStringFailsafe("text", 65536).trim()
+               if (soneRequest.isPOST) {
+                       val text = soneRequest.httpRequest.getPartAsStringFailsafe("text", 65536).trim()
                        if (text == "") {
                                templateContext["errorTextEmpty"] = true
                                return
                        }
-                       val sender = webInterface.core.getLocalSone(freenetRequest.httpRequest.getPartAsStringFailsafe("sender", 43)) ?: currentSone
-                       val recipient = webInterface.core.getSone(freenetRequest.httpRequest.getPartAsStringFailsafe("recipient", 43))
-                       webInterface.core.createPost(sender, recipient.asOptional(), TextFilter.filter(freenetRequest.httpRequest.getHeader("Host"), text))
+                       val sender = soneRequest.core.getLocalSone(soneRequest.httpRequest.getPartAsStringFailsafe("sender", 43)) ?: currentSone
+                       val recipient = soneRequest.core.getSone(soneRequest.httpRequest.getPartAsStringFailsafe("recipient", 43))
+                       soneRequest.core.createPost(sender, recipient.asOptional(), TextFilter.filter(soneRequest.httpRequest.getHeader("Host"), text))
                        throw RedirectException(returnPage)
                }
        }
index bc6faa4..562e647 100644 (file)
@@ -1,31 +1,34 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.text.TextFilter
-import net.pterodactylus.sone.utils.isPOST
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.text.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * This page lets the user post a reply to a post.
  */
-class CreateReplyPage(template: Template, webInterface: WebInterface):
-               LoggedInPage("createReply.html", template, "Page.CreateReply.Title", webInterface) {
+@TemplatePath("/templates/createReply.html")
+@ToadletPath("createReply.html")
+class CreateReplyPage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               LoggedInPage("Page.CreateReply.Title", webInterface, loaders, templateRenderer) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
-               val postId = freenetRequest.httpRequest.getPartAsStringFailsafe("post", 36).apply { templateContext["postId"] = this }
-               val text = freenetRequest.httpRequest.getPartAsStringFailsafe("text", 65536).trim().apply { templateContext["text"] = this }
-               val returnPage = freenetRequest.httpRequest.getPartAsStringFailsafe("returnPage", 256).apply { templateContext["returnPage"] = this }
-               if (freenetRequest.isPOST) {
+       override fun handleRequest(soneRequest: SoneRequest, currentSone: Sone, templateContext: TemplateContext) {
+               val postId = soneRequest.httpRequest.getPartAsStringFailsafe("post", 36).apply { templateContext["postId"] = this }
+               val text = soneRequest.httpRequest.getPartAsStringFailsafe("text", 65536).trim().apply { templateContext["text"] = this }
+               val returnPage = soneRequest.httpRequest.getPartAsStringFailsafe("returnPage", 256).apply { templateContext["returnPage"] = this }
+               if (soneRequest.isPOST) {
                        if (text == "") {
                                templateContext["errorTextEmpty"] = true
                                return
                        }
-                       val post = webInterface.core.getPost(postId) ?: throw RedirectException("noPermission.html")
-                       val sender = webInterface.core.getLocalSone(freenetRequest.httpRequest.getPartAsStringFailsafe("sender", 43)) ?: currentSone
-                       webInterface.core.createReply(sender, post, TextFilter.filter(freenetRequest.httpRequest.getHeader("Host"), text))
+                       val post = soneRequest.core.getPost(postId) ?: throw RedirectException("noPermission.html")
+                       val sender = soneRequest.core.getLocalSone(soneRequest.httpRequest.getPartAsStringFailsafe("sender", 43)) ?: currentSone
+                       soneRequest.core.createReply(sender, post, TextFilter.filter(soneRequest.httpRequest.getHeader("Host"), text))
                        throw RedirectException(returnPage)
                }
        }
index efa6f1d..b2056c2 100644 (file)
@@ -1,45 +1,47 @@
 package net.pterodactylus.sone.web.pages
 
-import freenet.clients.http.ToadletContext
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.utils.isPOST
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
-import java.util.logging.Level
-import java.util.logging.Logger
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import java.util.logging.*
+import javax.inject.*
 
 /**
  * The “create Sone” page lets the user create a new Sone.
  */
-class CreateSonePage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("createSone.html", template, "Page.CreateSone.Title", webInterface, false) {
+@MenuName("CreateSone")
+@TemplatePath("/templates/createSone.html")
+@ToadletPath("createSone.html")
+class CreateSonePage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               SoneTemplatePage(webInterface, loaders, templateRenderer, pageTitleKey = "Page.CreateSone.Title") {
 
        private val logger = Logger.getLogger(CreateSonePage::class.java.name)
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
-               templateContext["sones"] = webInterface.core.localSones.sortedWith(Sone.NICE_NAME_COMPARATOR)
-               templateContext["identitiesWithoutSone"] = webInterface.core.identityManager.allOwnIdentities.filterNot { "Sone" in it.contexts }.sortedBy { "${it.nickname}@${it.id}".toLowerCase() }
-               if (freenetRequest.isPOST) {
-                       val identity = freenetRequest.httpRequest.getPartAsStringFailsafe("identity", 43)
-                       webInterface.core.identityManager.allOwnIdentities.firstOrNull { it.id == identity }?.let { ownIdentity ->
-                               val sone = webInterface.core.createSone(ownIdentity)
+       override fun handleRequest(soneRequest: SoneRequest, templateContext: TemplateContext) {
+               templateContext["sones"] = soneRequest.core.localSones.sortedWith(Sone.NICE_NAME_COMPARATOR)
+               templateContext["identitiesWithoutSone"] = soneRequest.core.identityManager.allOwnIdentities.filterNot { "Sone" in it.contexts }.sortedBy { "${it.nickname}@${it.id}".toLowerCase() }
+               if (soneRequest.isPOST) {
+                       val identity = soneRequest.httpRequest.getPartAsStringFailsafe("identity", 43)
+                       soneRequest.core.identityManager.allOwnIdentities.firstOrNull { it.id == identity }?.let { ownIdentity ->
+                               val sone = soneRequest.core.createSone(ownIdentity)
                                if (sone == null) {
                                        logger.log(Level.SEVERE, "Could not create Sone for OwnIdentity: $ownIdentity")
                                }
-                               setCurrentSone(freenetRequest.toadletContext, sone)
+                               setCurrentSone(soneRequest.toadletContext, sone)
                                throw RedirectException("index.html")
                        }
                        templateContext["errorNoIdentity"] = true
                }
        }
 
-       override fun isEnabled(toadletContext: ToadletContext) =
-                       if (webInterface.core.preferences.isRequireFullAccess && !toadletContext.isAllowedFullAccess) {
+       override fun isEnabled(soneRequest: SoneRequest) =
+                       if (soneRequest.core.preferences.requireFullAccess && !soneRequest.toadletContext.isAllowedFullAccess) {
                                false
                        } else {
-                               (getCurrentSone(toadletContext) == null) || (webInterface.core.localSones.size == 1)
+                               (getCurrentSone(soneRequest.toadletContext) == null) || (soneRequest.core.localSones.size == 1)
                        }
 
 }
index eff79b9..d55c7cb 100644 (file)
@@ -1,31 +1,34 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.utils.isPOST
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * Page that lets the user delete an {@link Album}.
  */
-class DeleteAlbumPage(template: Template, webInterface: WebInterface):
-               LoggedInPage("deleteAlbum.html", template, "Page.DeleteAlbum.Title", webInterface) {
+@TemplatePath("/templates/deleteAlbum.html")
+@ToadletPath("deleteAlbum.html")
+class DeleteAlbumPage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               LoggedInPage("Page.DeleteAlbum.Title", webInterface, loaders, templateRenderer) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
-               if (freenetRequest.isPOST) {
-                       val album = webInterface.core.getAlbum(freenetRequest.httpRequest.getPartAsStringFailsafe("album", 36)) ?: throw RedirectException("invalid.html")
+       override fun handleRequest(soneRequest: SoneRequest, currentSone: Sone, templateContext: TemplateContext) {
+               if (soneRequest.isPOST) {
+                       val album = soneRequest.core.getAlbum(soneRequest.httpRequest.getPartAsStringFailsafe("album", 36)) ?: throw RedirectException("invalid.html")
                        if (!album.sone.isLocal) {
                                throw RedirectException("noPermission.html")
                        }
-                       if (freenetRequest.httpRequest.getPartAsStringFailsafe("abortDelete", 4) == "true") {
+                       if (soneRequest.httpRequest.getPartAsStringFailsafe("abortDelete", 4) == "true") {
                                throw RedirectException("imageBrowser.html?album=${album.id}")
                        }
-                       webInterface.core.deleteAlbum(album)
+                       soneRequest.core.deleteAlbum(album)
                        throw RedirectException(if (album.parent.isRoot) "imageBrowser.html?sone=${album.sone.id}" else "imageBrowser.html?album=${album.parent.id}")
                }
-               val album = webInterface.core.getAlbum(freenetRequest.httpRequest.getParam("album"))
+               val album = soneRequest.core.getAlbum(soneRequest.httpRequest.getParam("album"))
                templateContext["album"] = album ?: throw RedirectException("invalid.html")
        }
 
index fb77707..a9d601c 100644 (file)
@@ -1,31 +1,34 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.utils.isPOST
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * Page that lets the user delete an {@link Image}.
  */
-class DeleteImagePage(template: Template, webInterface: WebInterface):
-               LoggedInPage("deleteImage.html", template, "Page.DeleteImage.Title", webInterface) {
+@TemplatePath("/templates/deleteImage.html")
+@ToadletPath("deleteImage.html")
+class DeleteImagePage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               LoggedInPage("Page.DeleteImage.Title", webInterface, loaders, templateRenderer) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
-               if (freenetRequest.isPOST) {
-                       val image = webInterface.core.getImage(freenetRequest.httpRequest.getPartAsStringFailsafe("image", 36)) ?: throw RedirectException("invalid.html")
+       override fun handleRequest(soneRequest: SoneRequest, currentSone: Sone, templateContext: TemplateContext) {
+               if (soneRequest.isPOST) {
+                       val image = soneRequest.core.getImage(soneRequest.httpRequest.getPartAsStringFailsafe("image", 36)) ?: throw RedirectException("invalid.html")
                        if (!image.sone.isLocal) {
                                throw RedirectException("noPermission.html")
                        }
-                       if (freenetRequest.httpRequest.isPartSet("abortDelete")) {
+                       if (soneRequest.httpRequest.isPartSet("abortDelete")) {
                                throw RedirectException("imageBrowser.html?image=${image.id}")
                        }
-                       webInterface.core.deleteImage(image)
+                       soneRequest.core.deleteImage(image)
                        throw RedirectException("imageBrowser.html?album=${image.album.id}")
                }
-               val image = webInterface.core.getImage(freenetRequest.httpRequest.getParam("image")) ?: throw RedirectException("invalid.html")
+               val image = soneRequest.core.getImage(soneRequest.httpRequest.getParam("image")) ?: throw RedirectException("invalid.html")
                if (!image.sone.isLocal) {
                        throw RedirectException("noPermission.html")
                }
index 8eccfb4..b3749b3 100644 (file)
@@ -1,37 +1,40 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.utils.isPOST
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * Lets the user delete a post they made.
  */
-class DeletePostPage(template: Template, webInterface: WebInterface):
-               LoggedInPage("deletePost.html", template, "Page.DeletePost.Title", webInterface) {
+@TemplatePath("/templates/deletePost.html")
+@ToadletPath("deletePost.html")
+class DeletePostPage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               LoggedInPage("Page.DeletePost.Title", webInterface, loaders, templateRenderer) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
-               if (freenetRequest.isPOST) {
-                       val post = webInterface.core.getPost(freenetRequest.httpRequest.getPartAsStringFailsafe("post", 36)) ?: throw RedirectException("noPermission.html")
-                       val returnPage = freenetRequest.httpRequest.getPartAsStringFailsafe("returnPage", 256)
+       override fun handleRequest(soneRequest: SoneRequest, currentSone: Sone, templateContext: TemplateContext) {
+               if (soneRequest.isPOST) {
+                       val post = soneRequest.core.getPost(soneRequest.httpRequest.getPartAsStringFailsafe("post", 36)) ?: throw RedirectException("noPermission.html")
+                       val returnPage = soneRequest.httpRequest.getPartAsStringFailsafe("returnPage", 256)
                        if (!post.sone.isLocal) {
                                throw RedirectException("noPermission.html")
                        }
-                       if (freenetRequest.httpRequest.isPartSet("confirmDelete")) {
-                               webInterface.core.deletePost(post)
+                       if (soneRequest.httpRequest.isPartSet("confirmDelete")) {
+                               soneRequest.core.deletePost(post)
                                throw RedirectException(returnPage)
-                       } else if (freenetRequest.httpRequest.isPartSet("abortDelete")) {
+                       } else if (soneRequest.httpRequest.isPartSet("abortDelete")) {
                                throw RedirectException(returnPage)
                        }
                        templateContext["post"] = post
                        templateContext["returnPage"] = returnPage
                        return
                }
-               templateContext["post"] = webInterface.core.getPost(freenetRequest.httpRequest.getParam("post")) ?: throw RedirectException("noPermission.html")
-               templateContext["returnPage"] = freenetRequest.httpRequest.getParam("returnPage")
+               templateContext["post"] = soneRequest.core.getPost(soneRequest.httpRequest.getParam("post")) ?: throw RedirectException("noPermission.html")
+               templateContext["returnPage"] = soneRequest.httpRequest.getParam("returnPage")
        }
 
 }
index f25299f..0fabad9 100644 (file)
@@ -1,27 +1,30 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.utils.isPOST
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * Page that lets the user confirm the deletion of a profile field.
  */
-class DeleteProfileFieldPage(template: Template, webInterface: WebInterface):
-               LoggedInPage("deleteProfileField.html", template, "Page.DeleteProfileField.Title", webInterface) {
+@TemplatePath("/templates/deleteProfileField.html")
+@ToadletPath("deleteProfileField.html")
+class DeleteProfileFieldPage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               LoggedInPage("Page.DeleteProfileField.Title", webInterface, loaders, templateRenderer) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
-               if (freenetRequest.isPOST) {
-                       val field = currentSone.profile.getFieldById(freenetRequest.httpRequest.getPartAsStringFailsafe("field", 36)) ?: throw RedirectException("invalid.html")
-                       if (freenetRequest.httpRequest.getPartAsStringFailsafe("confirm", 4) == "true") {
+       override fun handleRequest(soneRequest: SoneRequest, currentSone: Sone, templateContext: TemplateContext) {
+               if (soneRequest.isPOST) {
+                       val field = currentSone.profile.getFieldById(soneRequest.httpRequest.getPartAsStringFailsafe("field", 36)) ?: throw RedirectException("invalid.html")
+                       if (soneRequest.httpRequest.getPartAsStringFailsafe("confirm", 4) == "true") {
                                currentSone.profile = currentSone.profile.apply { removeField(field) }
                        }
                        throw RedirectException("editProfile.html#profile-fields")
                }
-               val field = currentSone.profile.getFieldById(freenetRequest.httpRequest.getParam("field")) ?: throw RedirectException("invalid.html")
+               val field = currentSone.profile.getFieldById(soneRequest.httpRequest.getParam("field")) ?: throw RedirectException("invalid.html")
                templateContext["field"] = field
        }
 
index 29d8c81..e3f30ce 100644 (file)
@@ -1,39 +1,42 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.utils.isPOST
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * This page lets the user delete a reply.
  */
-class DeleteReplyPage(template: Template, webInterface: WebInterface):
-               LoggedInPage("deleteReply.html", template, "Page.DeleteReply.Title", webInterface) {
+@TemplatePath("/templates/deleteReply.html")
+@ToadletPath("deleteReply.html")
+class DeleteReplyPage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               LoggedInPage("Page.DeleteReply.Title", webInterface, loaders, templateRenderer) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
-               if (freenetRequest.isPOST) {
-                       val replyId = freenetRequest.httpRequest.getPartAsStringFailsafe("reply", 36)
-                       val reply = webInterface.core.getPostReply(replyId) ?: throw RedirectException("noPermission.html")
+       override fun handleRequest(soneRequest: SoneRequest, currentSone: Sone, templateContext: TemplateContext) {
+               if (soneRequest.isPOST) {
+                       val replyId = soneRequest.httpRequest.getPartAsStringFailsafe("reply", 36)
+                       val reply = soneRequest.core.getPostReply(replyId) ?: throw RedirectException("noPermission.html")
                        if (!reply.sone.isLocal) {
                                throw RedirectException("noPermission.html")
                        }
-                       val returnPage = freenetRequest.httpRequest.getPartAsStringFailsafe("returnPage", 256)
-                       if (freenetRequest.httpRequest.isPartSet("confirmDelete")) {
-                               webInterface.core.deleteReply(reply)
+                       val returnPage = soneRequest.httpRequest.getPartAsStringFailsafe("returnPage", 256)
+                       if (soneRequest.httpRequest.isPartSet("confirmDelete")) {
+                               soneRequest.core.deleteReply(reply)
                                throw RedirectException(returnPage)
                        }
-                       if (freenetRequest.httpRequest.isPartSet("abortDelete")) {
+                       if (soneRequest.httpRequest.isPartSet("abortDelete")) {
                                throw RedirectException(returnPage)
                        }
                        templateContext["reply"] = replyId
                        templateContext["returnPage"] = returnPage
                        return
                }
-               templateContext["reply"] = freenetRequest.httpRequest.getParam("reply")
-               templateContext["returnPage"] = freenetRequest.httpRequest.getParam("returnPage")
+               templateContext["reply"] = soneRequest.httpRequest.getParam("reply")
+               templateContext["returnPage"] = soneRequest.httpRequest.getParam("returnPage")
        }
 
 }
index b2a6d7b..f65e378 100644 (file)
@@ -1,24 +1,28 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.utils.isPOST
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * Lets the user delete a Sone. Of course the Sone is not really deleted from
  * Freenet; merely all references to it are removed from the local plugin
  * installation.
  */
-class DeleteSonePage(template: Template, webInterface: WebInterface):
-               LoggedInPage("deleteSone.html", template, "Page.DeleteSone.Title", webInterface) {
+@MenuName("DeleteSone")
+@TemplatePath("/templates/deleteSone.html")
+@ToadletPath("deleteSone.html")
+class DeleteSonePage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               LoggedInPage("Page.DeleteSone.Title", webInterface, loaders, templateRenderer) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
-               if (freenetRequest.isPOST) {
-                       if (freenetRequest.httpRequest.isPartSet("deleteSone")) {
-                               webInterface.core.deleteSone(currentSone)
+       override fun handleRequest(soneRequest: SoneRequest, currentSone: Sone, templateContext: TemplateContext) {
+               if (soneRequest.isPOST) {
+                       if (soneRequest.httpRequest.isPartSet("deleteSone")) {
+                               soneRequest.core.deleteSone(currentSone)
                        }
                        throw RedirectException("index.html")
                }
index 78cdb83..862db67 100644 (file)
@@ -1,20 +1,22 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * Page that lets the user dismiss a notification.
  */
-class DismissNotificationPage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("dismissNotification.html", template, "Page.DismissNotification.Title", webInterface) {
+@ToadletPath("dismissNotification.html")
+class DismissNotificationPage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               SoneTemplatePage(webInterface, loaders, templateRenderer, pageTitleKey = "Page.DismissNotification.Title") {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
-               val returnPage = freenetRequest.httpRequest.getPartAsStringFailsafe("returnPage", 256)
-               val notificationId = freenetRequest.httpRequest.getPartAsStringFailsafe("notification", 36)
-               webInterface.getNotification(notificationId).orNull()?.takeIf { it.isDismissable }?.dismiss()
+       override fun handleRequest(soneRequest: SoneRequest, templateContext: TemplateContext) {
+               val returnPage = soneRequest.httpRequest.getPartAsStringFailsafe("returnPage", 256)
+               val notificationId = soneRequest.httpRequest.getPartAsStringFailsafe("notification", 36)
+               soneRequest.webInterface.getNotification(notificationId).orNull()?.takeIf { it.isDismissable }?.dismiss()
                throw RedirectException(returnPage)
        }
 
index 70755f5..f2115e5 100644 (file)
@@ -1,11 +1,12 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.utils.isPOST
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * Page that lets the user distrust another Sone. This will assign a
@@ -13,14 +14,15 @@ import net.pterodactylus.util.template.TemplateContext
  *
  * @see net.pterodactylus.sone.core.Core#distrustSone(Sone, Sone)
  */
-class DistrustPage(template: Template, webInterface: WebInterface):
-               LoggedInPage("distrust.html", template, "Page.Distrust.Title", webInterface) {
+@ToadletPath("distrust.html")
+class DistrustPage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               LoggedInPage("Page.Distrust.Title", webInterface, loaders, templateRenderer) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
-               if (freenetRequest.isPOST) {
-                       webInterface.core.getSone(freenetRequest.httpRequest.getPartAsStringFailsafe("sone", 44))
-                                       ?.run { webInterface.core.distrustSone(currentSone, this) }
-                       throw RedirectException(freenetRequest.httpRequest.getPartAsStringFailsafe("returnPage", 256))
+       override fun handleRequest(soneRequest: SoneRequest, currentSone: Sone, templateContext: TemplateContext) {
+               if (soneRequest.isPOST) {
+                       soneRequest.core.getSone(soneRequest.httpRequest.getPartAsStringFailsafe("sone", 44))
+                                       ?.run { soneRequest.core.distrustSone(currentSone, this) }
+                       throw RedirectException(soneRequest.httpRequest.getPartAsStringFailsafe("returnPage", 256))
                }
        }
 
index 26d7c3a..aeaf14e 100644 (file)
@@ -1,41 +1,43 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Album.Modifier.AlbumTitleMustNotBeEmpty
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.utils.isPOST
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.data.Album.Modifier.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * Page that lets the user edit the name and description of an album.
  */
-class EditAlbumPage(template: Template, webInterface: WebInterface):
-               LoggedInPage("editAlbum.html", template, "Page.EditAlbum.Title", webInterface) {
+@ToadletPath("editAlbum.html")
+class EditAlbumPage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               LoggedInPage("Page.EditAlbum.Title", webInterface, loaders, templateRenderer) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
-               if (freenetRequest.isPOST) {
-                       val album = webInterface.core.getAlbum(freenetRequest.httpRequest.getPartAsStringFailsafe("album", 36)) ?: throw RedirectException("invalid.html")
+       override fun handleRequest(soneRequest: SoneRequest, currentSone: Sone, templateContext: TemplateContext) {
+               if (soneRequest.isPOST) {
+                       val album = soneRequest.core.getAlbum(soneRequest.httpRequest.getPartAsStringFailsafe("album", 36)) ?: throw RedirectException("invalid.html")
                        album.takeUnless { it.sone.isLocal }?.run { throw RedirectException("noPermission.html") }
-                       if (freenetRequest.httpRequest.getPartAsStringFailsafe("moveLeft", 4) == "true") {
+                       if (soneRequest.httpRequest.getPartAsStringFailsafe("moveLeft", 4) == "true") {
                                album.parent?.moveAlbumUp(album)
-                               webInterface.core.touchConfiguration()
+                               soneRequest.core.touchConfiguration()
                                throw RedirectException("imageBrowser.html?album=${album.parent?.id}")
-                       } else if (freenetRequest.httpRequest.getPartAsStringFailsafe("moveRight", 4) == "true") {
+                       } else if (soneRequest.httpRequest.getPartAsStringFailsafe("moveRight", 4) == "true") {
                                album.parent?.moveAlbumDown(album)
-                               webInterface.core.touchConfiguration()
+                               soneRequest.core.touchConfiguration()
                                throw RedirectException("imageBrowser.html?album=${album.parent?.id}")
                        } else {
                                try {
                                        album.modify()
-                                                       .setTitle(freenetRequest.httpRequest.getPartAsStringFailsafe("title", 100))
-                                                       .setDescription(freenetRequest.httpRequest.getPartAsStringFailsafe("description", 1000))
+                                                       .setTitle(soneRequest.httpRequest.getPartAsStringFailsafe("title", 100))
+                                                       .setDescription(soneRequest.httpRequest.getPartAsStringFailsafe("description", 1000))
                                                        .update()
                                } catch (e: AlbumTitleMustNotBeEmpty) {
                                        throw RedirectException("emptyAlbumTitle.html")
                                }
-                               webInterface.core.touchConfiguration()
+                               soneRequest.core.touchConfiguration()
                                throw RedirectException("imageBrowser.html?album=${album.id}")
                        }
                }
index 350dec7..76f6e23 100644 (file)
@@ -1,40 +1,42 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Image.Modifier.ImageTitleMustNotBeEmpty
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.text.TextFilter
-import net.pterodactylus.sone.utils.isPOST
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.data.Image.Modifier.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.text.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * Page that lets the user edit title and description of an {@link Image}.
  */
-class EditImagePage(template: Template, webInterface: WebInterface):
-               LoggedInPage("editImage.html", template, "Page.EditImage.Title", webInterface) {
+@ToadletPath("editImage.html")
+class EditImagePage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               LoggedInPage("Page.EditImage.Title", webInterface, loaders, templateRenderer) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
-               if (freenetRequest.isPOST) {
-                       val image = webInterface.core.getImage(freenetRequest.httpRequest.getPartAsStringFailsafe("image", 36)) ?: throw RedirectException("invalid.html")
+       override fun handleRequest(soneRequest: SoneRequest, currentSone: Sone, templateContext: TemplateContext) {
+               if (soneRequest.isPOST) {
+                       val image = soneRequest.core.getImage(soneRequest.httpRequest.getPartAsStringFailsafe("image", 36)) ?: throw RedirectException("invalid.html")
                        if (!image.sone.isLocal) {
                                throw RedirectException("noPermission.html")
                        }
-                       freenetRequest.httpRequest.getPartAsStringFailsafe("returnPage", 256).let { returnPage ->
-                               if (freenetRequest.httpRequest.getPartAsStringFailsafe("moveLeft", 4) == "true") {
+                       soneRequest.httpRequest.getPartAsStringFailsafe("returnPage", 256).let { returnPage ->
+                               if (soneRequest.httpRequest.getPartAsStringFailsafe("moveLeft", 4) == "true") {
                                        image.album.moveImageUp(image)
-                                       webInterface.core.touchConfiguration()
-                               } else if (freenetRequest.httpRequest.getPartAsStringFailsafe("moveRight", 4) == "true") {
+                                       soneRequest.core.touchConfiguration()
+                               } else if (soneRequest.httpRequest.getPartAsStringFailsafe("moveRight", 4) == "true") {
                                        image.album.moveImageDown(image)
-                                       webInterface.core.touchConfiguration()
+                                       soneRequest.core.touchConfiguration()
                                } else {
                                        try {
                                                image.modify()
-                                                               .setTitle(freenetRequest.httpRequest.getPartAsStringFailsafe("title", 100))
-                                                               .setDescription(TextFilter.filter(freenetRequest.httpRequest.getHeader("Host"), freenetRequest.httpRequest.getPartAsStringFailsafe("description", 1024)))
+                                                               .setTitle(soneRequest.httpRequest.getPartAsStringFailsafe("title", 100))
+                                                               .setDescription(TextFilter.filter(soneRequest.httpRequest.getHeader("Host"), soneRequest.httpRequest.getPartAsStringFailsafe("description", 1024)))
                                                                .update()
-                                               webInterface.core.touchConfiguration()
+                                               soneRequest.core.touchConfiguration()
                                        } catch (e: ImageTitleMustNotBeEmpty) {
                                                throw RedirectException("emptyImageTitle.html")
                                        }
index f3486d2..386f66b 100644 (file)
@@ -1,26 +1,29 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.utils.isPOST
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * Page that lets the user edit the name of a profile field.
  */
-class EditProfileFieldPage(template: Template, webInterface: WebInterface) :
-               LoggedInPage("editProfileField.html", template, "Page.EditProfileField.Title", webInterface) {
+@TemplatePath("/templates/editProfileField.html")
+@ToadletPath("editProfileField.html")
+class EditProfileFieldPage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               LoggedInPage("Page.EditProfileField.Title", webInterface, loaders, templateRenderer) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
+       override fun handleRequest(soneRequest: SoneRequest, currentSone: Sone, templateContext: TemplateContext) {
                currentSone.profile.let { profile ->
-                       if (freenetRequest.isPOST) {
-                               if (freenetRequest.httpRequest.getPartAsStringFailsafe("cancel", 4) == "true") {
+                       if (soneRequest.isPOST) {
+                               if (soneRequest.httpRequest.getPartAsStringFailsafe("cancel", 4) == "true") {
                                        throw RedirectException("editProfile.html#profile-fields")
                                }
-                               val field = profile.getFieldById(freenetRequest.httpRequest.getPartAsStringFailsafe("field", 36)) ?: throw RedirectException("invalid.html")
-                               freenetRequest.httpRequest.getPartAsStringFailsafe("name", 256).let { name ->
+                               val field = profile.getFieldById(soneRequest.httpRequest.getPartAsStringFailsafe("field", 36)) ?: throw RedirectException("invalid.html")
+                               soneRequest.httpRequest.getPartAsStringFailsafe("name", 256).let { name ->
                                        try {
                                                if (name != field.name) {
                                                        field.name = name
@@ -33,7 +36,7 @@ class EditProfileFieldPage(template: Template, webInterface: WebInterface) :
                                        }
                                }
                        }
-                       templateContext["field"] = profile.getFieldById(freenetRequest.httpRequest.getParam("field")) ?: throw RedirectException("invalid.html")
+                       templateContext["field"] = profile.getFieldById(soneRequest.httpRequest.getParam("field")) ?: throw RedirectException("invalid.html")
                }
        }
 
index 95c40c8..54b7efd 100644 (file)
@@ -1,21 +1,25 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Profile.DuplicateField
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.text.TextFilter
-import net.pterodactylus.sone.utils.isPOST
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.data.Profile.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.text.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * This page lets the user edit her profile.
  */
-class EditProfilePage(template: Template, webInterface: WebInterface) :
-               LoggedInPage("editProfile.html", template, "Page.EditProfile.Title", webInterface) {
+@MenuName("EditProfile")
+@TemplatePath("/templates/editProfile.html")
+@ToadletPath("editProfile.html")
+class EditProfilePage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               LoggedInPage("Page.EditProfile.Title", webInterface, loaders, templateRenderer) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
+       override fun handleRequest(soneRequest: SoneRequest, currentSone: Sone, templateContext: TemplateContext) {
                currentSone.profile.let { profile ->
                        templateContext["firstName"] = profile.firstName
                        templateContext["middleName"] = profile.middleName
@@ -25,42 +29,42 @@ class EditProfilePage(template: Template, webInterface: WebInterface) :
                        templateContext["birthYear"] = profile.birthYear
                        templateContext["avatarId"] = profile.avatar
                        templateContext["fields"] = profile.fields
-                       if (freenetRequest.isPOST) {
-                               if (freenetRequest.httpRequest.getPartAsStringFailsafe("save-profile", 4) == "true") {
-                                       profile.firstName = freenetRequest.httpRequest.getPartAsStringFailsafe("first-name", 256).trim()
-                                       profile.middleName = freenetRequest.httpRequest.getPartAsStringFailsafe("middle-name", 256).trim()
-                                       profile.lastName = freenetRequest.httpRequest.getPartAsStringFailsafe("last-name", 256).trim()
-                                       profile.birthDay = freenetRequest.httpRequest.getPartAsStringFailsafe("birth-day", 256).trim().toIntOrNull()
-                                       profile.birthMonth = freenetRequest.httpRequest.getPartAsStringFailsafe("birth-month", 256).trim().toIntOrNull()
-                                       profile.birthYear = freenetRequest.httpRequest.getPartAsStringFailsafe("birth-year", 256).trim().toIntOrNull()
-                                       profile.setAvatar(webInterface.core.getImage(freenetRequest.httpRequest.getPartAsStringFailsafe("avatarId", 256).trim(), false))
+                       if (soneRequest.isPOST) {
+                               if (soneRequest.httpRequest.getPartAsStringFailsafe("save-profile", 4) == "true") {
+                                       profile.firstName = soneRequest.httpRequest.getPartAsStringFailsafe("first-name", 256).trim()
+                                       profile.middleName = soneRequest.httpRequest.getPartAsStringFailsafe("middle-name", 256).trim()
+                                       profile.lastName = soneRequest.httpRequest.getPartAsStringFailsafe("last-name", 256).trim()
+                                       profile.birthDay = soneRequest.httpRequest.getPartAsStringFailsafe("birth-day", 256).trim().toIntOrNull()
+                                       profile.birthMonth = soneRequest.httpRequest.getPartAsStringFailsafe("birth-month", 256).trim().toIntOrNull()
+                                       profile.birthYear = soneRequest.httpRequest.getPartAsStringFailsafe("birth-year", 256).trim().toIntOrNull()
+                                       profile.setAvatar(soneRequest.core.getImage(soneRequest.httpRequest.getPartAsStringFailsafe("avatarId", 256).trim(), false))
                                        profile.fields.forEach { field ->
-                                               field.value = TextFilter.filter(freenetRequest.httpRequest.getHeader("Host"), freenetRequest.httpRequest.getPartAsStringFailsafe("field-${field.id}", 400).trim())
+                                               field.value = TextFilter.filter(soneRequest.httpRequest.getHeader("Host"), soneRequest.httpRequest.getPartAsStringFailsafe("field-${field.id}", 400).trim())
                                        }
                                        currentSone.profile = profile
-                                       webInterface.core.touchConfiguration()
+                                       soneRequest.core.touchConfiguration()
                                        throw RedirectException("editProfile.html")
-                               } else if (freenetRequest.httpRequest.getPartAsStringFailsafe("add-field", 4) == "true") {
-                                       val fieldName = freenetRequest.httpRequest.getPartAsStringFailsafe("field-name", 100)
+                               } else if (soneRequest.httpRequest.getPartAsStringFailsafe("add-field", 4) == "true") {
+                                       val fieldName = soneRequest.httpRequest.getPartAsStringFailsafe("field-name", 100)
                                        try {
                                                profile.addField(fieldName)
                                                currentSone.profile = profile
-                                               webInterface.core.touchConfiguration()
+                                               soneRequest.core.touchConfiguration()
                                                throw RedirectException("editProfile.html#profile-fields")
                                        } catch (e: DuplicateField) {
                                                templateContext["fieldName"] = fieldName
                                                templateContext["duplicateFieldName"] = true
                                        }
                                } else profile.fields.forEach { field ->
-                                       if (freenetRequest.httpRequest.getPartAsStringFailsafe("delete-field-${field.id}", 4) == "true") {
+                                       if (soneRequest.httpRequest.getPartAsStringFailsafe("delete-field-${field.id}", 4) == "true") {
                                                throw RedirectException("deleteProfileField.html?field=${field.id}")
-                                       } else if (freenetRequest.httpRequest.getPartAsStringFailsafe("edit-field-${field.id}", 4) == "true") {
+                                       } else if (soneRequest.httpRequest.getPartAsStringFailsafe("edit-field-${field.id}", 4) == "true") {
                                                throw RedirectException("editProfileField.html?field=${field.id}")
-                                       } else if (freenetRequest.httpRequest.getPartAsStringFailsafe("move-down-field-${field.id}", 4) == "true") {
+                                       } else if (soneRequest.httpRequest.getPartAsStringFailsafe("move-down-field-${field.id}", 4) == "true") {
                                                profile.moveFieldDown(field)
                                                currentSone.profile = profile
                                                throw RedirectException("editProfile.html#profile-fields")
-                                       } else if (freenetRequest.httpRequest.getPartAsStringFailsafe("move-up-field-${field.id}", 4) == "true") {
+                                       } else if (soneRequest.httpRequest.getPartAsStringFailsafe("move-up-field-${field.id}", 4) == "true") {
                                                profile.moveFieldUp(field)
                                                currentSone.profile = profile
                                                throw RedirectException("editProfile.html#profile-fields")
diff --git a/src/main/kotlin/net/pterodactylus/sone/web/pages/ErrorPages.kt b/src/main/kotlin/net/pterodactylus/sone/web/pages/ErrorPages.kt
new file mode 100644 (file)
index 0000000..8d52c87
--- /dev/null
@@ -0,0 +1,21 @@
+package net.pterodactylus.sone.web.pages
+
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+
+@TemplatePath("/templates/invalid.html")
+@ToadletPath("invalid.html")
+class InvalidPage(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) : SoneTemplatePage(webInterface, loaders, templateRenderer, "Page.Invalid.Title")
+
+@TemplatePath("/templates/noPermission.html")
+@ToadletPath("noPermission.html")
+class NoPermissionPage(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) : SoneTemplatePage(webInterface, loaders, templateRenderer, "Page.NoPermission.Title")
+
+@TemplatePath("/templates/emptyImageTitle.html")
+@ToadletPath("emptyImageTitle.html")
+class EmptyImageTitlePage(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) : SoneTemplatePage(webInterface, loaders, templateRenderer, "Page.EmptyImageTitle.Title")
+
+@TemplatePath("/templates/emptyAlbumTitle.html")
+@ToadletPath("emptyAlbumTitle.html")
+class EmptyAlbumTitlePage(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) : SoneTemplatePage(webInterface, loaders, templateRenderer, "Page.EmptyAlbumTitle.Title")
index cf5bcee..244cb52 100644 (file)
@@ -1,28 +1,30 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.utils.isPOST
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * This page lets the user follow another Sone.
  */
-class FollowSonePage(template: Template, webInterface: WebInterface):
-               LoggedInPage("followSone.html", template, "Page.FollowSone.Title", webInterface) {
+@ToadletPath("followSone.html")
+class FollowSonePage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               LoggedInPage("Page.FollowSone.Title", webInterface, loaders, templateRenderer) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
-               if (freenetRequest.isPOST) {
-                       freenetRequest.httpRequest.getPartAsStringFailsafe("sone", 1200).split(Regex("[ ,]+"))
-                                       .map { it to webInterface.core.getSone(it) }
+       override fun handleRequest(soneRequest: SoneRequest, currentSone: Sone, templateContext: TemplateContext) {
+               if (soneRequest.isPOST) {
+                       soneRequest.httpRequest.getPartAsStringFailsafe("sone", 1200).split(Regex("[ ,]+"))
+                                       .map { it to soneRequest.core.getSone(it) }
                                        .filterNot { it.second == null }
                                        .forEach { sone ->
-                                               webInterface.core.followSone(currentSone, sone.first)
-                                               webInterface.core.markSoneKnown(sone.second)
+                                               soneRequest.core.followSone(currentSone, sone.first)
+                                               soneRequest.core.markSoneKnown(sone.second)
                                        }
-                       throw RedirectException(freenetRequest.httpRequest.getPartAsStringFailsafe("returnPage", 256))
+                       throw RedirectException(soneRequest.httpRequest.getPartAsStringFailsafe("returnPage", 256))
                }
        }
 
index 932827e..f7857c3 100644 (file)
@@ -1,15 +1,15 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetPage
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.web.Response
-import java.net.URI
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.web.*
+import java.net.*
+import javax.inject.*
 
 /**
  * Page that delivers a {@link TemporaryImage} to the browser.
  */
-class GetImagePage(webInterface: WebInterface): FreenetPage {
+class GetImagePage @Inject constructor(webInterface: WebInterface) : FreenetPage {
 
        private val core = webInterface.core
 
@@ -35,7 +35,7 @@ class GetImagePage(webInterface: WebInterface): FreenetPage {
                }
        }
 
-       override fun isLinkExcepted(link: URI?): Boolean {
+       override fun isLinkExcepted(link: URI): Boolean {
                return false
        }
 
index e9911e6..20219d7 100644 (file)
@@ -1,49 +1,53 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Album
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.utils.Pagination
-import net.pterodactylus.sone.utils.parameters
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
-import java.net.URI
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import java.net.*
+import javax.inject.*
 
 /**
  * The image browser page is the entry page for the image management.
  */
-class ImageBrowserPage(template: Template, webInterface: WebInterface):
-               LoggedInPage("imageBrowser.html", template, "Page.ImageBrowser.Title", webInterface) {
+@MenuName("ImageBrowser")
+@TemplatePath("/templates/imageBrowser.html")
+@ToadletPath("imageBrowser.html")
+class ImageBrowserPage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               LoggedInPage("Page.ImageBrowser.Title", webInterface, loaders, templateRenderer) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
-               if ("album" in freenetRequest.parameters) {
+       override fun handleRequest(soneRequest: SoneRequest, currentSone: Sone, templateContext: TemplateContext) {
+               if ("album" in soneRequest.parameters) {
                        templateContext["albumRequested"] = true
-                       templateContext["album"] = webInterface.core.getAlbum(freenetRequest.parameters["album"]!!)
-                       templateContext["page"] = freenetRequest.parameters["page"]
-               } else if ("image" in freenetRequest.parameters) {
+                       templateContext["album"] = soneRequest.core.getAlbum(soneRequest.parameters["album"]!!)
+                       templateContext["page"] = soneRequest.parameters["page"]
+               } else if ("image" in soneRequest.parameters) {
                        templateContext["imageRequested"] = true
-                       templateContext["image"] = webInterface.core.getImage(freenetRequest.parameters["image"])
-               } else if (freenetRequest.parameters["mode"] == "gallery") {
+                       templateContext["image"] = soneRequest.core.getImage(soneRequest.parameters["image"])
+               } else if (soneRequest.parameters["mode"] == "gallery") {
                        templateContext["galleryRequested"] = true
-                       webInterface.core.sones
+                       soneRequest.core.sones
                                        .map(Sone::getRootAlbum)
                                        .flatMap(Album::getAlbums)
                                        .flatMap { Album.FLATTENER.apply(it)!! }
                                        .filterNot(Album::isEmpty)
                                        .sortedBy(Album::getTitle)
                                        .also { albums ->
-                                               Pagination(albums, webInterface.core.preferences.imagesPerPage).apply { page = freenetRequest.parameters["page"]?.toIntOrNull() ?: 0 }.also { pagination ->
-                                                       templateContext["albumPagination"] = pagination
-                                                       templateContext["albums"] = pagination.items
-                                               }
+                                               albums.paginate(soneRequest.core.preferences.imagesPerPage)
+                                                               .turnTo(soneRequest.parameters["page"]?.toIntOrNull() ?: 0)
+                                                               .also { pagination ->
+                                                                       templateContext["albumPagination"] = pagination
+                                                                       templateContext["albums"] = pagination.items
+                                                               }
                                        }
                } else {
                        templateContext["soneRequested"] = true
-                       templateContext["sone"] = webInterface.core.getSone(freenetRequest.httpRequest.getParam("sone")) ?: currentSone
+                       templateContext["sone"] = soneRequest.core.getSone(soneRequest.httpRequest.getParam("sone")) ?: currentSone
                }
        }
 
-       override fun isLinkExcepted(link: URI?) = true
+       override fun isLinkExcepted(link: URI) = true
 
 }
index 0257cb1..069ef9f 100644 (file)
@@ -1,38 +1,41 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.notify.PostVisibilityFilter
-import net.pterodactylus.sone.utils.Pagination
-import net.pterodactylus.sone.utils.parameters
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.notify.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * The index page shows the main page of Sone. This page will contain the posts
  * of all friends of the current user.
  */
-class IndexPage(template: Template, webInterface: WebInterface, private val postVisibilityFilter: PostVisibilityFilter):
-               LoggedInPage("index.html", template, "Page.Index.Title", webInterface) {
+@MenuName("Index")
+@TemplatePath("/templates/index.html")
+@ToadletPath("index.html")
+class IndexPage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer, private val postVisibilityFilter: PostVisibilityFilter) :
+               LoggedInPage("Page.Index.Title", webInterface, loaders, templateRenderer) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
-                       (currentSone.posts +
-                                       currentSone.friends
-                                                       .mapNotNull(webInterface.core::getSone)
-                                                       .flatMap { it.posts } +
-                                       webInterface.core.getDirectedPosts(currentSone.id)
-                                       ).distinct()
-                                       .filter { postVisibilityFilter.isVisible(currentSone).apply(it) }
-                                       .sortedByDescending { it.time }
-                                       .let { posts ->
-                                               Pagination(posts, webInterface.core.preferences.postsPerPage).apply {
-                                                       page = freenetRequest.parameters["page"]?.toIntOrNull() ?: 0
-                                               }.let { pagination ->
-                                                       templateContext["pagination"] = pagination
-                                                       templateContext["posts"] = pagination.items
-                                               }
-                                       }
+       override fun handleRequest(soneRequest: SoneRequest, currentSone: Sone, templateContext: TemplateContext) {
+               (currentSone.posts +
+                               currentSone.friends
+                                               .mapNotNull(soneRequest.core::getSone)
+                                               .flatMap { it.posts } +
+                               soneRequest.core.getDirectedPosts(currentSone.id)
+                               ).distinct()
+                               .filter { postVisibilityFilter.isVisible(currentSone).apply(it) }
+                               .sortedByDescending { it.time }
+                               .let { posts ->
+                                       posts.paginate(soneRequest.core.preferences.postsPerPage)
+                                                       .turnTo(soneRequest.parameters["page"]?.toIntOrNull() ?: 0)
+                                                       .let { pagination ->
+                                                               templateContext["pagination"] = pagination
+                                                               templateContext["posts"] = pagination.items
+                                                       }
+                               }
        }
 
 }
index 9d6930d..ae0d7d1 100644 (file)
@@ -1,48 +1,52 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.utils.Pagination
-import net.pterodactylus.sone.utils.parameters
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * This page shows all known Sones.
  */
-class KnownSonesPage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("knownSones.html", template, "Page.KnownSones.Title", webInterface, false) {
+@MenuName("KnownSones")
+@TemplatePath("/templates/knownSones.html")
+@ToadletPath("knownSones.html")
+class KnownSonesPage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               SoneTemplatePage(webInterface, loaders, templateRenderer, pageTitleKey = "Page.KnownSones.Title") {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
-               getCurrentSone(freenetRequest.toadletContext).let { currentSone ->
-                       Pagination(webInterface.core.sones
-                                       .filterNot { freenetRequest.parameters["filter"] == "followed" && currentSone != null && !currentSone.hasFriend(it.id) }
-                                       .filterNot { freenetRequest.parameters["filter"] == "not-followed" && currentSone != null && currentSone.hasFriend(it.id) }
-                                       .filterNot { freenetRequest.parameters["filter"] == "new" && it.isKnown }
-                                       .filterNot { freenetRequest.parameters["filter"] == "not-new" && !it.isKnown }
-                                       .filterNot { freenetRequest.parameters["filter"] == "own" && !it.isLocal }
-                                       .filterNot { freenetRequest.parameters["filter"] == "not-own" && it.isLocal }
+       override fun handleRequest(soneRequest: SoneRequest, templateContext: TemplateContext) {
+               getCurrentSone(soneRequest.toadletContext).let { currentSone ->
+                       soneRequest.core.sones.asSequence()
+                                       .filterNot { soneRequest.parameters["filter"] == "followed" && currentSone != null && !currentSone.hasFriend(it.id) }
+                                       .filterNot { soneRequest.parameters["filter"] == "not-followed" && currentSone != null && currentSone.hasFriend(it.id) }
+                                       .filterNot { soneRequest.parameters["filter"] == "new" && it.isKnown }
+                                       .filterNot { soneRequest.parameters["filter"] == "not-new" && !it.isKnown }
+                                       .filterNot { soneRequest.parameters["filter"] == "own" && !it.isLocal }
+                                       .filterNot { soneRequest.parameters["filter"] == "not-own" && it.isLocal }
                                        .sortedWith(
-                                                       when (freenetRequest.parameters["sort"]) {
+                                                       when (soneRequest.parameters["sort"]) {
                                                                "images" -> Sone.IMAGE_COUNT_COMPARATOR
                                                                "name" -> Sone.NICE_NAME_COMPARATOR.reversed()
                                                                "posts" -> Sone.POST_COUNT_COMPARATOR
                                                                else -> Sone.LAST_ACTIVITY_COMPARATOR
                                                        }.let { comparator ->
-                                                               when (freenetRequest.parameters["order"]) {
+                                                               when (soneRequest.parameters["order"]) {
                                                                        "asc" -> comparator.reversed()
                                                                        else -> comparator
                                                                }
                                                        }
-                                       ), 25).apply { page = freenetRequest.parameters["page"]?.toIntOrNull() ?: 0 }
+                                       ).paginate(25)
+                                       .turnTo(soneRequest.parameters["page"]?.toIntOrNull() ?: 0)
                                        .let { pagination ->
                                                templateContext["pagination"] = pagination
                                                templateContext["knownSones"] = pagination.items
                                        }
-                       templateContext["sort"] = freenetRequest.parameters["sort"].let { sort -> if (sort in listOf("images", "name", "posts")) sort else "activity" }
-                       templateContext["order"] = freenetRequest.parameters["order"].let { order -> if (order == "asc") "asc" else "desc" }
-                       templateContext["filter"] = freenetRequest.parameters["filter"]
+                       templateContext["sort"] = soneRequest.parameters["sort"].let { sort -> if (sort in listOf("images", "name", "posts")) sort else "activity" }
+                       templateContext["order"] = soneRequest.parameters["order"].let { order -> if (order == "asc") "asc" else "desc" }
+                       templateContext["filter"] = soneRequest.parameters["filter"]
                }
        }
 
index ef622df..cc5043f 100644 (file)
@@ -1,28 +1,29 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.utils.isPOST
-import net.pterodactylus.sone.utils.parameters
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * Page that lets the user like [net.pterodactylus.sone.data.Post]s and [net.pterodactylus.sone.data.Reply]s.
  */
-class LikePage(template: Template, webInterface: WebInterface) :
-               LoggedInPage("like.html", template, "Page.Like.Title", webInterface) {
+@ToadletPath("like.html")
+class LikePage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               LoggedInPage("Page.Like.Title", webInterface, loaders, templateRenderer) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
-               if (freenetRequest.isPOST) {
-                       freenetRequest.parameters["type", 16]?.also { type ->
+       override fun handleRequest(soneRequest: SoneRequest, currentSone: Sone, templateContext: TemplateContext) {
+               if (soneRequest.isPOST) {
+                       soneRequest.parameters["type", 16]?.also { type ->
                                when (type) {
-                                       "post" -> currentSone.addLikedPostId(freenetRequest.parameters["post", 36]!!)
-                                       "reply" -> currentSone.addLikedReplyId(freenetRequest.parameters["reply", 36]!!)
+                                       "post" -> currentSone.addLikedPostId(soneRequest.parameters["post", 36]!!)
+                                       "reply" -> currentSone.addLikedReplyId(soneRequest.parameters["reply", 36]!!)
                                }
                        }
-                       throw RedirectException(freenetRequest.parameters["returnPage", 256]!!)
+                       throw RedirectException(soneRequest.parameters["returnPage", 256]!!)
                }
        }
 
index 835fe23..2a62a7d 100644 (file)
@@ -1,22 +1,24 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.utils.parameters
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * This page lets the user lock a [net.pterodactylus.sone.data.Sone] to prevent it from being inserted.
  */
-class LockSonePage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("lockSone.html", template, "Page.LockSone.Title", webInterface, false) {
+@ToadletPath("lockSone.html")
+class LockSonePage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               SoneTemplatePage(webInterface, loaders, templateRenderer, pageTitleKey = "Page.LockSone.Title") {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
-               freenetRequest.parameters["returnPage", 256]!!.let { returnPage ->
-                       freenetRequest.parameters["sone", 44]!!
-                                       .let { webInterface.core.getLocalSone(it) }
-                                       ?.let { webInterface.core.lockSone(it) }
+       override fun handleRequest(soneRequest: SoneRequest, templateContext: TemplateContext) {
+               soneRequest.parameters["returnPage", 256]!!.let { returnPage ->
+                       soneRequest.parameters["sone", 44]!!
+                                       .let { soneRequest.core.getLocalSone(it) }
+                                       ?.let { soneRequest.core.lockSone(it) }
                        throw RedirectException(returnPage)
                }
        }
index c88499e..5d389b9 100644 (file)
@@ -1,21 +1,21 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
 
 /**
  * Base class for [SoneTemplatePage] implementations that require a logged in user.
  */
-abstract class LoggedInPage(path: String, template: Template, pageTitleKey: String, webInterface: WebInterface) :
-               SoneTemplatePage(path, template, pageTitleKey, webInterface, true) {
+abstract class LoggedInPage(pageTitleKey: String, webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               SoneTemplatePage(webInterface, loaders, templateRenderer, pageTitleKey = pageTitleKey, requiresLogin = true) {
 
-       final override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
-               handleRequest(freenetRequest, getCurrentSone(freenetRequest.toadletContext, false)!!, templateContext)
+       final override fun handleRequest(soneRequest: SoneRequest, templateContext: TemplateContext) {
+               handleRequest(soneRequest, getCurrentSone(soneRequest.toadletContext, false)!!, templateContext)
        }
 
-       protected abstract fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext)
+       protected abstract fun handleRequest(soneRequest: SoneRequest, currentSone: Sone, templateContext: TemplateContext)
 
 }
index 0e0fee5..9e47049 100644 (file)
@@ -1,39 +1,41 @@
 package net.pterodactylus.sone.web.pages
 
-import freenet.clients.http.ToadletContext
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.utils.emptyToNull
-import net.pterodactylus.sone.utils.isPOST
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * The login page lets the user log in.
  */
-class LoginPage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("login.html", template, "Page.Login.Title", webInterface) {
+@MenuName("Login")
+@TemplatePath("/templates/login.html")
+@ToadletPath("login.html")
+class LoginPage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               SoneTemplatePage(webInterface, loaders, templateRenderer, pageTitleKey = "Page.Login.Title") {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
-               if (freenetRequest.isPOST) {
-                       val soneId = freenetRequest.httpRequest.getPartAsStringFailsafe("sone-id", 43)
-                       webInterface.core.getLocalSone(soneId)?.let { sone ->
-                               setCurrentSone(freenetRequest.toadletContext, sone)
-                               val target = freenetRequest.httpRequest.getParam("target").emptyToNull ?: "index.html"
+       override fun handleRequest(soneRequest: SoneRequest, templateContext: TemplateContext) {
+               if (soneRequest.isPOST) {
+                       val soneId = soneRequest.httpRequest.getPartAsStringFailsafe("sone-id", 43)
+                       soneRequest.core.getLocalSone(soneId)?.let { sone ->
+                               setCurrentSone(soneRequest.toadletContext, sone)
+                               val target = soneRequest.httpRequest.getParam("target").emptyToNull ?: "index.html"
                                throw RedirectException(target)
                        }
                }
-               templateContext["sones"] = webInterface.core.localSones.sortedWith(Sone.NICE_NAME_COMPARATOR)
-               templateContext["identitiesWithoutSone"] = webInterface.core.identityManager.allOwnIdentities.filterNot { "Sone" in it.contexts }.sortedBy { "${it.nickname}@${it.id}" }
+               templateContext["sones"] = soneRequest.core.localSones.sortedWith(Sone.NICE_NAME_COMPARATOR)
+               templateContext["identitiesWithoutSone"] = soneRequest.core.identityManager.allOwnIdentities.filterNot { "Sone" in it.contexts }.sortedBy { "${it.nickname}@${it.id}" }
        }
 
-       override public fun getRedirectTarget(freenetRequest: FreenetRequest) =
-                       getCurrentSone(freenetRequest.toadletContext)?.let { "index.html" }
+       override fun getRedirectTarget(request: FreenetRequest) =
+                       getCurrentSone(request.toadletContext)?.let { "index.html" }
 
-       override fun isEnabled(toadletContext: ToadletContext) = when {
-               webInterface.core.preferences.isRequireFullAccess && !toadletContext.isAllowedFullAccess -> false
-               else -> getCurrentSone(toadletContext, false) == null
+       override fun isEnabled(soneRequest: SoneRequest) = when {
+               soneRequest.core.preferences.requireFullAccess && !soneRequest.toadletContext.isAllowedFullAccess -> false
+               else -> getCurrentSone(soneRequest.toadletContext, false) == null
        }
 
 }
index 7e608ff..325d876 100644 (file)
@@ -1,27 +1,29 @@
 package net.pterodactylus.sone.web.pages
 
-import freenet.clients.http.ToadletContext
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * Logs a user out.
  */
-class LogoutPage(template: Template, webInterface: WebInterface):
-               LoggedInPage("logout.html", template, "Page.Logout.Title", webInterface) {
+@MenuName("Logout")
+@ToadletPath("logout.html")
+class LogoutPage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               LoggedInPage("Page.Logout.Title", webInterface, loaders, templateRenderer) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
-               setCurrentSone(freenetRequest.toadletContext, null)
+       override fun handleRequest(soneRequest: SoneRequest, currentSone: Sone, templateContext: TemplateContext) {
+               setCurrentSone(soneRequest.toadletContext, null)
                throw RedirectException("index.html")
        }
 
-       override fun isEnabled(toadletContext: ToadletContext): Boolean =
-                       if (webInterface.core.preferences.isRequireFullAccess && !toadletContext.isAllowedFullAccess) {
+       override fun isEnabled(soneRequest: SoneRequest): Boolean =
+                       if (soneRequest.core.preferences.requireFullAccess && !soneRequest.toadletContext.isAllowedFullAccess) {
                                false
                        } else
-                               getCurrentSone(toadletContext) != null && webInterface.core.localSones.size != 1
+                               getCurrentSone(soneRequest.toadletContext) != null && soneRequest.core.localSones.size != 1
 
 }
index 49c1075..652881d 100644 (file)
@@ -1,29 +1,30 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.utils.mapPresent
-import net.pterodactylus.sone.utils.parameters
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * Page that lets the user mark a number of [net.pterodactylus.sone.data.Sone]s, [Post]s, or
  * [Replie][net.pterodactylus.sone.data.Reply]s as known.
  */
-class MarkAsKnownPage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("markAsKnown.html", template, "Page.MarkAsKnown.Title", webInterface, false) {
+@ToadletPath("markAsKnown.html")
+class MarkAsKnownPage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               SoneTemplatePage(webInterface, loaders, templateRenderer, pageTitleKey = "Page.MarkAsKnown.Title") {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
-               val ids = freenetRequest.parameters["id", 65536]!!.split(" ")
-               when (freenetRequest.parameters["type", 5]) {
-                       "sone" -> ids.mapNotNull(webInterface.core::getSone).forEach(webInterface.core::markSoneKnown)
-                       "post" -> ids.mapNotNull(webInterface.core::getPost).forEach(webInterface.core::markPostKnown)
-                       "reply" -> ids.mapNotNull(webInterface.core::getPostReply).forEach(webInterface.core::markReplyKnown)
+       override fun handleRequest(soneRequest: SoneRequest, templateContext: TemplateContext) {
+               val ids = soneRequest.parameters["id", 65536]!!.split(" ")
+               when (soneRequest.parameters["type", 5]) {
+                       "sone" -> ids.mapNotNull(soneRequest.core::getSone).forEach(soneRequest.core::markSoneKnown)
+                       "post" -> ids.mapNotNull(soneRequest.core::getPost).forEach(soneRequest.core::markPostKnown)
+                       "reply" -> ids.mapNotNull(soneRequest.core::getPostReply).forEach(soneRequest.core::markReplyKnown)
                        else -> throw RedirectException("invalid.html")
                }
-               throw RedirectException(freenetRequest.parameters["returnPage", 256]!!)
+               throw RedirectException(soneRequest.parameters["returnPage", 256]!!)
        }
 
 }
index 55df67a..940e6e8 100644 (file)
@@ -1,32 +1,34 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.utils.Pagination
-import net.pterodactylus.sone.utils.mapPresent
-import net.pterodactylus.sone.utils.parameters
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * Page that displays all new posts and replies. The posts are filtered using
  * [PostVisibilityFilter.isPostVisible(Sone, Post)] and sorted by time.
  */
-class NewPage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("new.html", template, "Page.New.Title", webInterface, false) {
+@MenuName("New")
+@TemplatePath("/templates/new.html")
+@ToadletPath("new.html")
+class NewPage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               SoneTemplatePage(webInterface, loaders, templateRenderer, pageTitleKey = "Page.New.Title") {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) =
-                       getCurrentSone(freenetRequest.toadletContext).let { currentSone ->
-                               (webInterface.getNewPosts(currentSone) + webInterface.getNewReplies(currentSone).mapPresent { it.post })
+       override fun handleRequest(soneRequest: SoneRequest, templateContext: TemplateContext) =
+                       getCurrentSone(soneRequest.toadletContext).let { currentSone ->
+                               (soneRequest.webInterface.getNewPosts(currentSone) + soneRequest.webInterface.getNewReplies(currentSone).mapPresent { it.post })
                                                .distinct()
                                                .sortedByDescending { it.time }
                                                .let { posts ->
-                                                       Pagination(posts, webInterface.core.preferences.postsPerPage).apply {
-                                                               page = freenetRequest.parameters["page"]?.toIntOrNull() ?: 0
-                                                       }.let { pagination ->
-                                                               templateContext["pagination"] = pagination
-                                                               templateContext["posts"] = pagination.items
-                                                       }
+                                                       posts.paginate(soneRequest.core.preferences.postsPerPage)
+                                                                       .turnTo(soneRequest.parameters["page"]?.toIntOrNull() ?: 0)
+                                                                       .let { pagination ->
+                                                                               templateContext["pagination"] = pagination
+                                                                               templateContext["posts"] = pagination.items
+                                                                       }
                                                }
                        }
 
index 7cf1bb3..8829030 100644 (file)
@@ -1,33 +1,34 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.core.Preferences
-import net.pterodactylus.sone.data.SoneOptions.LoadExternalContent
-import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired
-import net.pterodactylus.sone.utils.emptyToNull
-import net.pterodactylus.sone.utils.isPOST
-import net.pterodactylus.sone.utils.parameters
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.SoneOptions.*
+import net.pterodactylus.sone.fcp.FcpInterface.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * This page lets the user edit the options of the Sone plugin.
  */
-class OptionsPage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("options.html", template, "Page.Options.Title", webInterface, false) {
+@MenuName("Options")
+@TemplatePath("/templates/options.html")
+@ToadletPath("options.html")
+class OptionsPage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               SoneTemplatePage(webInterface, loaders, templateRenderer, pageTitleKey = "Page.Options.Title") {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
-               if (freenetRequest.isPOST) {
+       override fun handleRequest(soneRequest: SoneRequest, templateContext: TemplateContext) {
+               if (soneRequest.isPOST) {
                        val fieldsWithErrors = mutableListOf<String>()
-                       getCurrentSone(freenetRequest.toadletContext)?.options?.let { options ->
-                               val autoFollow = "auto-follow" in freenetRequest.parameters
-                               val loadLinkedImages = freenetRequest.parameters["load-linked-images"].emptyToNull
-                               val showCustomAvatars = freenetRequest.parameters["show-custom-avatars"].emptyToNull
-                               val enableSoneInsertNotification = "enable-sone-insert-notifications" in freenetRequest.parameters
-                               val showNewSoneNotification = "show-notification-new-sones" in freenetRequest.parameters
-                               val showNewPostNotification = "show-notification-new-posts" in freenetRequest.parameters
-                               val showNewReplyNotification = "show-notification-new-replies" in freenetRequest.parameters
+                       getCurrentSone(soneRequest.toadletContext)?.options?.let { options ->
+                               val autoFollow = "auto-follow" in soneRequest.parameters
+                               val loadLinkedImages = soneRequest.parameters["load-linked-images"].emptyToNull
+                               val showCustomAvatars = soneRequest.parameters["show-custom-avatars"].emptyToNull
+                               val enableSoneInsertNotification = "enable-sone-insert-notifications" in soneRequest.parameters
+                               val showNewSoneNotification = "show-notification-new-sones" in soneRequest.parameters
+                               val showNewPostNotification = "show-notification-new-posts" in soneRequest.parameters
+                               val showNewReplyNotification = "show-notification-new-replies" in soneRequest.parameters
 
                                options.isAutoFollow = autoFollow
                                options.isSoneInsertNotificationEnabled = enableSoneInsertNotification
@@ -37,39 +38,39 @@ class OptionsPage(template: Template, webInterface: WebInterface):
                                loadLinkedImages?.also { if (cantSetOption { options.loadLinkedImages = LoadExternalContent.valueOf(loadLinkedImages) }) fieldsWithErrors += "load-linked-images" }
                                showCustomAvatars?.also { if (cantSetOption { options.showCustomAvatars = LoadExternalContent.valueOf(showCustomAvatars) }) fieldsWithErrors += "show-custom-avatars" }
                        }
-                       val fullAccessRequired = "require-full-access" in freenetRequest.parameters
-                       val fcpInterfaceActive = "fcp-interface-active" in freenetRequest.parameters
+                       val fullAccessRequired = "require-full-access" in soneRequest.parameters
+                       val fcpInterfaceActive = "fcp-interface-active" in soneRequest.parameters
 
-                       webInterface.core.preferences.isRequireFullAccess = fullAccessRequired
-                       webInterface.core.preferences.isFcpInterfaceActive = fcpInterfaceActive
+                       soneRequest.core.preferences.newRequireFullAccess = fullAccessRequired
+                       soneRequest.core.preferences.newFcpInterfaceActive = fcpInterfaceActive
 
-                       val postsPerPage = freenetRequest.parameters["posts-per-page"]?.toIntOrNull()
-                       val charactersPerPost = freenetRequest.parameters["characters-per-post"]?.toIntOrNull()
-                       val postCutOffLength = freenetRequest.parameters["post-cut-off-length"]?.toIntOrNull()
-                       val imagesPerPage = freenetRequest.parameters["images-per-page"]?.toIntOrNull()
-                       val insertionDelay = freenetRequest.parameters["insertion-delay"]?.toIntOrNull()
-                       val fcpFullAccessRequired = freenetRequest.parameters["fcp-full-access-required"]?.toIntOrNull()
-                       val negativeTrust = freenetRequest.parameters["negative-trust"]?.toIntOrNull()
-                       val positiveTrust = freenetRequest.parameters["positive-trust"]?.toIntOrNull()
-                       val trustComment = freenetRequest.parameters["trust-comment"]?.emptyToNull
+                       val postsPerPage = soneRequest.parameters["posts-per-page"]?.toIntOrNull()
+                       val charactersPerPost = soneRequest.parameters["characters-per-post"]?.toIntOrNull()
+                       val postCutOffLength = soneRequest.parameters["post-cut-off-length"]?.toIntOrNull()
+                       val imagesPerPage = soneRequest.parameters["images-per-page"]?.toIntOrNull()
+                       val insertionDelay = soneRequest.parameters["insertion-delay"]?.toIntOrNull()
+                       val fcpFullAccessRequired = soneRequest.parameters["fcp-full-access-required"]?.toIntOrNull()
+                       val negativeTrust = soneRequest.parameters["negative-trust"]?.toIntOrNull()
+                       val positiveTrust = soneRequest.parameters["positive-trust"]?.toIntOrNull()
+                       val trustComment = soneRequest.parameters["trust-comment"]?.emptyToNull
 
-                       if (cantSetOption { it.setPostsPerPage(postsPerPage) }) fieldsWithErrors += "posts-per-page"
-                       if (cantSetOption { it.setCharactersPerPost(charactersPerPost) }) fieldsWithErrors += "characters-per-post"
-                       if (cantSetOption { it.setPostCutOffLength(postCutOffLength) }) fieldsWithErrors += "post-cut-off-length"
-                       if (cantSetOption { it.setImagesPerPage(imagesPerPage) }) fieldsWithErrors += "images-per-page"
-                       if (cantSetOption { it.setInsertionDelay(insertionDelay) }) fieldsWithErrors += "insertion-delay"
-                       fcpFullAccessRequired?.also { if (cantSetOption { it.fcpFullAccessRequired = FullAccessRequired.values()[fcpFullAccessRequired] }) fieldsWithErrors += "fcp-full-access-required" }
-                       if (cantSetOption { it.setNegativeTrust(negativeTrust) }) fieldsWithErrors += "negative-trust"
-                       if (cantSetOption { it.setPositiveTrust(positiveTrust) }) fieldsWithErrors += "positive-trust"
-                       if (cantSetOption { it.trustComment = trustComment }) fieldsWithErrors += "trust-comment"
+                       if (cantSetOption { soneRequest.core.preferences.newPostsPerPage = postsPerPage }) fieldsWithErrors += "posts-per-page"
+                       if (cantSetOption { soneRequest.core.preferences.newCharactersPerPost = charactersPerPost }) fieldsWithErrors += "characters-per-post"
+                       if (cantSetOption { soneRequest.core.preferences.newPostCutOffLength = postCutOffLength }) fieldsWithErrors += "post-cut-off-length"
+                       if (cantSetOption { soneRequest.core.preferences.newImagesPerPage = imagesPerPage }) fieldsWithErrors += "images-per-page"
+                       if (cantSetOption { soneRequest.core.preferences.newInsertionDelay = insertionDelay }) fieldsWithErrors += "insertion-delay"
+                       fcpFullAccessRequired?.also { if (cantSetOption { soneRequest.core.preferences.newFcpFullAccessRequired = FullAccessRequired.values()[fcpFullAccessRequired] }) fieldsWithErrors += "fcp-full-access-required" }
+                       if (cantSetOption { soneRequest.core.preferences.newNegativeTrust = negativeTrust }) fieldsWithErrors += "negative-trust"
+                       if (cantSetOption { soneRequest.core.preferences.newPositiveTrust = positiveTrust }) fieldsWithErrors += "positive-trust"
+                       if (cantSetOption { soneRequest.core.preferences.newTrustComment = trustComment }) fieldsWithErrors += "trust-comment"
 
                        if (fieldsWithErrors.isEmpty()) {
-                               webInterface.core.touchConfiguration()
+                               soneRequest.core.touchConfiguration()
                                throw RedirectException("options.html")
                        }
                        templateContext["fieldErrors"] = fieldsWithErrors
                }
-               getCurrentSone(freenetRequest.toadletContext)?.options?.let { options ->
+               getCurrentSone(soneRequest.toadletContext)?.options?.let { options ->
                        templateContext["auto-follow"] = options.isAutoFollow
                        templateContext["show-notification-new-sones"] = options.isShowNewSoneNotifications
                        templateContext["show-notification-new-posts"] = options.isShowNewPostNotifications
@@ -78,13 +79,13 @@ class OptionsPage(template: Template, webInterface: WebInterface):
                        templateContext["load-linked-images"] = options.loadLinkedImages.toString()
                        templateContext["show-custom-avatars"] = options.showCustomAvatars.toString()
                }
-               webInterface.core.preferences.let { preferences ->
+               soneRequest.core.preferences.let { preferences ->
                        templateContext["insertion-delay"] = preferences.insertionDelay
                        templateContext["characters-per-post"] = preferences.charactersPerPost
                        templateContext["fcp-full-access-required"] = preferences.fcpFullAccessRequired.ordinal
                        templateContext["images-per-page"] = preferences.imagesPerPage
-                       templateContext["fcp-interface-active"] = preferences.isFcpInterfaceActive
-                       templateContext["require-full-access"] = preferences.isRequireFullAccess
+                       templateContext["fcp-interface-active"] = preferences.fcpInterfaceActive
+                       templateContext["require-full-access"] = preferences.requireFullAccess
                        templateContext["negative-trust"] = preferences.negativeTrust
                        templateContext["positive-trust"] = preferences.positiveTrust
                        templateContext["post-cut-off-length"] = preferences.postCutOffLength
@@ -93,9 +94,9 @@ class OptionsPage(template: Template, webInterface: WebInterface):
                }
        }
 
-       private fun cantSetOption(setter: (Preferences) -> Unit) =
+       private fun cantSetOption(setter: () -> Unit) =
                        try {
-                               setter(webInterface.core.preferences)
+                               setter()
                                false
                        } catch (iae: IllegalArgumentException) {
                                true
index 697d173..71a7a9b 100644 (file)
@@ -1,14 +1,13 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.util.web.Page
-import net.pterodactylus.util.web.Request
-import net.pterodactylus.util.web.Response
-import java.io.File
+import net.pterodactylus.util.web.*
+import java.io.*
+import javax.inject.*
 
 /**
  * [Page] implementation that delivers static files from the filesystem.
  */
-class ReloadingPage<R: Request>(private val prefix: String, private val path: String, private val mimeType: String): Page<R> {
+class ReloadingPage<R : Request> @Inject constructor(private val prefix: String, private val path: String, private val mimeType: String) : Page<R> {
 
        override fun isPrefixPage() = true
 
index c3fabdb..2ed4940 100644 (file)
@@ -1,29 +1,32 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.utils.isPOST
-import net.pterodactylus.sone.utils.parameters
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * Page that lets the user control the rescue mode for a Sone.
  */
-class RescuePage(template: Template, webInterface: WebInterface):
-               LoggedInPage("rescue.html", template, "Page.Rescue.Title", webInterface) {
+@MenuName("Rescue")
+@TemplatePath("/templates/rescue.html")
+@ToadletPath("rescue.html")
+class RescuePage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               LoggedInPage("Page.Rescue.Title", webInterface, loaders, templateRenderer) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
-               val soneRescuer = webInterface.core.getSoneRescuer(currentSone)
+       override fun handleRequest(soneRequest: SoneRequest, currentSone: Sone, templateContext: TemplateContext) {
+               val soneRescuer = soneRequest.core.getSoneRescuer(currentSone)
                templateContext["soneRescuer"] = soneRescuer
-               if (freenetRequest.isPOST) {
-                       freenetRequest.parameters["edition", 9]?.toIntOrNull()?.also {
+               if (soneRequest.isPOST) {
+                       soneRequest.parameters["edition", 9]?.toIntOrNull()?.also {
                                if (it > -1) {
                                        soneRescuer.setEdition(it.toLong())
                                }
                        }
-                       if (freenetRequest.parameters["fetch", 8] == "true") {
+                       if (soneRequest.parameters["fetch", 8] == "true") {
                                soneRescuer.startNextFetch()
                        }
                        throw RedirectException("rescue.html")
index f296e03..3c14604 100644 (file)
@@ -1,41 +1,38 @@
 package net.pterodactylus.sone.web.pages
 
 import com.google.common.base.Ticker
-import com.google.common.cache.Cache
-import com.google.common.cache.CacheBuilder
-import freenet.support.Logger
-import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.data.PostReply
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.utils.Pagination
-import net.pterodactylus.sone.utils.emptyToNull
-import net.pterodactylus.sone.utils.memoize
-import net.pterodactylus.sone.utils.paginate
-import net.pterodactylus.sone.utils.parameters
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.sone.web.pages.SearchPage.Optionality.FORBIDDEN
-import net.pterodactylus.sone.web.pages.SearchPage.Optionality.OPTIONAL
-import net.pterodactylus.sone.web.pages.SearchPage.Optionality.REQUIRED
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
-import net.pterodactylus.util.text.StringEscaper
-import net.pterodactylus.util.text.TextException
-import java.util.concurrent.TimeUnit.MINUTES
+import com.google.common.cache.*
+import freenet.support.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.sone.web.pages.SearchPage.Optionality.*
+import net.pterodactylus.util.template.*
+import net.pterodactylus.util.text.*
+import java.util.concurrent.TimeUnit.*
+import javax.inject.*
 
 /**
  * This page lets the user search for posts and replies that contain certain
  * words.
  */
-class SearchPage @JvmOverloads constructor(template: Template, webInterface: WebInterface, ticker: Ticker = Ticker.systemTicker()):
-               SoneTemplatePage("search.html", template, "Page.Search.Title", webInterface, false) {
+@TemplatePath("/templates/search.html")
+@ToadletPath("search.html")
+class SearchPage(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer, ticker: Ticker = Ticker.systemTicker()) :
+               SoneTemplatePage(webInterface, loaders, templateRenderer, pageTitleKey = "Page.Search.Title") {
+
+       @Inject
+       constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+                       this(webInterface, loaders, templateRenderer, Ticker.systemTicker())
 
        private val cache: Cache<Iterable<Phrase>, Pagination<Post>> = CacheBuilder.newBuilder().ticker(ticker).expireAfterAccess(5, MINUTES).build()
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
+       override fun handleRequest(soneRequest: SoneRequest, templateContext: TemplateContext) {
                val startTime = System.currentTimeMillis()
                val phrases = try {
-                       freenetRequest.parameters["query"].emptyToNull?.parse()
+                       soneRequest.parameters["query"].emptyToNull?.parse()
                } catch (te: TextException) {
                        redirect("index.html")
                }
@@ -45,39 +42,39 @@ class SearchPage @JvmOverloads constructor(template: Template, webInterface: Web
                        0 -> redirect("index.html")
                        1 -> phrases.first().phrase.also { word ->
                                when {
-                                       word.removePrefix("sone://").let(webInterface.core::getSone) != null -> redirect("viewSone.html?sone=${word.removePrefix("sone://")}")
-                                       word.removePrefix("post://").let(webInterface.core::getPost) != null -> redirect("viewPost.html?post=${word.removePrefix("post://")}")
-                                       word.removePrefix("reply://").let(webInterface.core::getPostReply) != null -> redirect("viewPost.html?post=${word.removePrefix("reply://").let(webInterface.core::getPostReply)?.postId}")
-                                       word.removePrefix("album://").let(webInterface.core::getAlbum) != null -> redirect("imageBrowser.html?album=${word.removePrefix("album://")}")
-                                       word.removePrefix("image://").let { webInterface.core.getImage(it, false) } != null -> redirect("imageBrowser.html?image=${word.removePrefix("image://")}")
+                                       word.removePrefix("sone://").let(soneRequest.core::getSone) != null -> redirect("viewSone.html?sone=${word.removePrefix("sone://")}")
+                                       word.removePrefix("post://").let(soneRequest.core::getPost) != null -> redirect("viewPost.html?post=${word.removePrefix("post://")}")
+                                       word.removePrefix("reply://").let(soneRequest.core::getPostReply) != null -> redirect("viewPost.html?post=${word.removePrefix("reply://").let(soneRequest.core::getPostReply)?.postId}")
+                                       word.removePrefix("album://").let(soneRequest.core::getAlbum) != null -> redirect("imageBrowser.html?album=${word.removePrefix("album://")}")
+                                       word.removePrefix("image://").let { soneRequest.core.getImage(it, false) } != null -> redirect("imageBrowser.html?image=${word.removePrefix("image://")}")
                                }
                        }
                }
 
                val soneNameCache = { sone: Sone -> sone.names() }.memoize()
-               val sonePagination = webInterface.core.sones
-                               .scoreAndPaginate(phrases) { it.allText(soneNameCache) }
-                               .apply { page = freenetRequest.parameters["sonePage"].emptyToNull?.toIntOrNull() ?: 0 }
+               val sonePagination = soneRequest.core.sones
+                               .scoreAndPaginate(phrases, soneRequest.core.preferences.postsPerPage) { it.allText(soneNameCache) }
+                               .apply { page = soneRequest.parameters["sonePage"].emptyToNull?.toIntOrNull() ?: 0 }
                val postPagination = cache.get(phrases) {
-                       webInterface.core.sones
+                       soneRequest.core.sones
                                        .flatMap(Sone::getPosts)
                                        .filter { Post.FUTURE_POSTS_FILTER.apply(it) }
-                                       .scoreAndPaginate(phrases) { it.allText(soneNameCache) }
-               }.apply { page = freenetRequest.parameters["postPage"].emptyToNull?.toIntOrNull() ?: 0 }
+                                       .scoreAndPaginate(phrases, soneRequest.core.preferences.postsPerPage) { it.allText(soneNameCache, soneRequest.core::getReplies) }
+               }.apply { page = soneRequest.parameters["postPage"].emptyToNull?.toIntOrNull() ?: 0 }
 
-               Logger.normal(SearchPage::class.java, "Finished search for “${freenetRequest.parameters["query"]}” in ${System.currentTimeMillis() - startTime}ms.")
+               Logger.normal(SearchPage::class.java, "Finished search for “${soneRequest.parameters["query"]}” in ${System.currentTimeMillis() - startTime}ms.")
                templateContext["sonePagination"] = sonePagination
                templateContext["soneHits"] = sonePagination.items
                templateContext["postPagination"] = postPagination
                templateContext["postHits"] = postPagination.items
        }
 
-       private fun <T> Iterable<T>.scoreAndPaginate(phrases: Iterable<Phrase>, texter: (T) -> String) =
+       private fun <T> Iterable<T>.scoreAndPaginate(phrases: Iterable<Phrase>, postsPerPage: Int, texter: (T) -> String) =
                        map { it to score(texter(it), phrases) }
                                        .filter { it.second > 0 }
                                        .sortedByDescending { it.second }
                                        .map { it.first }
-                                       .paginate(webInterface.core.preferences.postsPerPage)
+                                       .paginate(postsPerPage)
 
        private fun Sone.names() =
                        with(profile) {
@@ -89,27 +86,24 @@ class SearchPage @JvmOverloads constructor(template: Template, webInterface: Web
        private fun Sone.allText(soneNameCache: (Sone) -> String) =
                        (soneNameCache(this) + profile.fields.map { "${it.name} ${it.value}" }.joinToString(" ", " ")).toLowerCase()
 
-       private fun Post.allText(soneNameCache: (Sone) -> String) =
-                       (text + recipient.orNull()?.let { " ${soneNameCache(it)}" } + webInterface.core.getReplies(id)
+       private fun Post.allText(soneNameCache: (Sone) -> String, getReplies: (String) -> Collection<PostReply>) =
+                       (text + recipient.orNull()?.let { " ${soneNameCache(it)}" } + getReplies(id)
                                        .filter { PostReply.FUTURE_REPLY_FILTER.apply(it) }
                                        .map { "${soneNameCache(it.sone)} ${it.text}" }.joinToString(" ", " ")).toLowerCase()
 
+       private fun Iterable<Phrase>.indicesFor(text: String, predicate: (Phrase) -> Boolean) =
+                       filter(predicate).map(Phrase::phrase).map(String::toLowerCase).flatMap { text.findAll(it) }
+
        private fun score(text: String, phrases: Iterable<Phrase>): Double {
                val requiredPhrases = phrases.count { it.required }
-               val requiredHits = phrases.filter(Phrase::required)
-                               .map(Phrase::phrase)
-                               .flatMap { text.findAll(it) }
+               val requiredHits = phrases.indicesFor(text, Phrase::required)
                                .map { Math.pow(1 - it / text.length.toDouble(), 2.0) }
                                .sum()
-               val optionalHits = phrases.filter(Phrase::optional)
-                               .map(Phrase::phrase)
-                               .flatMap { text.findAll(it) }
+               val optionalHits = phrases.indicesFor(text, Phrase::optional)
                                .map { Math.pow(1 - it / text.length.toDouble(), 2.0) }
                                .sum()
-               val forbiddenHits = phrases.filter(Phrase::forbidden)
-                               .map(Phrase::phrase)
-                               .map { text.findAll(it).size }
-                               .sum()
+               val forbiddenHits = phrases.indicesFor(text, Phrase::forbidden)
+                               .count()
                return requiredHits * 3 + optionalHits + (requiredHits - requiredPhrases) * 5 - (forbiddenHits * 2)
        }
 
@@ -122,7 +116,6 @@ class SearchPage @JvmOverloads constructor(template: Template, webInterface: Web
 
        private fun String.parse() =
                        StringEscaper.parseLine(this)
-                                       .map(String::toLowerCase)
                                        .map {
                                                when {
                                                        it == "+" || it == "-" -> Phrase(it, OPTIONAL)
index 3f04236..c905ed2 100644 (file)
@@ -1,38 +1,30 @@
 package net.pterodactylus.sone.web.pages
 
-import freenet.clients.http.ToadletContext
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.main.SonePlugin
-import net.pterodactylus.sone.utils.emptyToNull
-import net.pterodactylus.sone.web.SessionProvider
-import net.pterodactylus.sone.web.WebInterface
-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 java.net.URLEncoder
+import freenet.clients.http.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.notify.*
+import net.pterodactylus.util.template.*
+import net.pterodactylus.util.web.*
+import java.net.*
 
 /**
  * Base page for the Sone web interface.
  */
 open class SoneTemplatePage(
-               path: String,
-               protected val webInterface: WebInterface,
-               template: Template,
+               private val webInterface: WebInterface,
+               loaders: Loaders,
+               templateRenderer: TemplateRenderer,
                private val pageTitleKey: String? = null,
-               private val requiresLogin: Boolean = true
-) : FreenetTemplatePage(path, webInterface.templateContextFactory, template, "noPermission.html") {
-
-       @JvmOverloads
-       constructor(path: String, template: Template, pageTitleKey: String?, webInterface: WebInterface, requireLogin: Boolean = false) :
-                       this(path, webInterface, template, pageTitleKey, requireLogin)
-
-       constructor(path: String, template: Template, webInterface: WebInterface, requireLogin: Boolean = true) :
-                       this(path, webInterface, template, null, requireLogin)
+               private val requiresLogin: Boolean = false,
+               private val pageTitle: (FreenetRequest) -> String = { pageTitleKey?.let(webInterface.l10n::getString) ?: "" }
+) : FreenetTemplatePage(templateRenderer, loaders, "noPermission.html") {
 
        private val core = webInterface.core
-       protected val sessionProvider: SessionProvider = webInterface
+       private val sessionProvider: SessionProvider = webInterface
 
        protected fun getCurrentSone(toadletContext: ToadletContext, createSession: Boolean = true) =
                        sessionProvider.getCurrentSone(toadletContext, createSession)
@@ -42,15 +34,15 @@ open class SoneTemplatePage(
 
        fun requiresLogin() = requiresLogin
 
-       override public fun getPageTitle(freenetRequest: FreenetRequest) =
-                       pageTitleKey?.let(webInterface.l10n::getString) ?: ""
+       override fun getPageTitle(request: FreenetRequest) = getPageTitle(request.toSoneRequest(core, webInterface))
+
+       open fun getPageTitle(soneRequest: SoneRequest) = pageTitle(soneRequest)
 
-       override public fun getStyleSheets() =
-                       listOf("css/sone.css")
+       override val styleSheets = listOf("css/sone.css")
 
-       override public fun getShortcutIcon() = "images/icon.png"
+       override val shortcutIcon = "images/icon.png"
 
-       override public fun getAdditionalLinkNodes(request: FreenetRequest) =
+       override fun getAdditionalLinkNodes(request: FreenetRequest) =
                        listOf(mapOf(
                                        "rel" to "search",
                                        "type" to "application/opensearchdescription+xml",
@@ -58,43 +50,50 @@ open class SoneTemplatePage(
                                        "href" to "http://${request.httpRequest.getHeader("host")}/Sone/OpenSearch.xml"
                        ))
 
-       final override public fun processTemplate(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
-               super.processTemplate(freenetRequest, templateContext)
+       final override fun processTemplate(request: FreenetRequest, templateContext: TemplateContext) {
+               super.processTemplate(request, templateContext)
                templateContext["preferences"] = core.preferences
-               templateContext["currentSone"] = getCurrentSone(freenetRequest.toadletContext)
+               templateContext["currentSone"] = getCurrentSone(request.toadletContext)
                templateContext["localSones"] = core.localSones
-               templateContext["request"] = freenetRequest
+               templateContext["request"] = request
                templateContext["currentVersion"] = SonePlugin.getPluginVersion()
                templateContext["hasLatestVersion"] = core.updateChecker.hasLatestVersion()
                templateContext["latestEdition"] = core.updateChecker.latestEdition
                templateContext["latestVersion"] = core.updateChecker.latestVersion
                templateContext["latestVersionTime"] = core.updateChecker.latestVersionDate
-               webInterface.getNotifications(getCurrentSone(freenetRequest.toadletContext)).sortedBy(Notification::getCreatedTime).run {
+               webInterface.getNotifications(getCurrentSone(request.toadletContext)).sortedBy(Notification::getCreatedTime).run {
                        templateContext["notifications"] = this
                        templateContext["notificationHash"] = this.hashCode()
                }
-               handleRequest(freenetRequest, templateContext)
+               handleRequest(request, templateContext)
        }
 
-       internal open fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
+       open fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
+               handleRequest(freenetRequest.toSoneRequest(core, webInterface), templateContext)
        }
 
-       override public fun getRedirectTarget(freenetRequest: FreenetRequest): String? {
-               if (requiresLogin && getCurrentSone(freenetRequest.toadletContext) == null) {
-                       val parameters = freenetRequest.httpRequest.parameterNames
-                                       .flatMap { name -> freenetRequest.httpRequest.getMultipleParam(name).map { name to it } }
+       open fun handleRequest(soneRequest: SoneRequest, templateContext: TemplateContext) {
+       }
+
+       override fun getRedirectTarget(request: FreenetRequest): String? {
+               if (requiresLogin && getCurrentSone(request.toadletContext) == null) {
+                       val parameters = request.httpRequest.parameterNames
+                                       .flatMap { name -> request.httpRequest.getMultipleParam(name).map { name to it } }
                                        .joinToString("&") { "${it.first.urlEncode}=${it.second.urlEncode}" }
                                        .emptyToNull
-                       return "login.html?target=${freenetRequest.httpRequest.path}${parameters?.let { ("?" + it).urlEncode } ?: ""}"
+                       return "login.html?target=${request.httpRequest.path}${parameters?.let { ("?" + it).urlEncode } ?: ""}"
                }
                return null
        }
 
        private val String.urlEncode: String get() = URLEncoder.encode(this, "UTF-8")
 
-       override fun isEnabled(toadletContext: ToadletContext) = when {
-               requiresLogin && getCurrentSone(toadletContext) == null -> false
-               core.preferences.isRequireFullAccess && !toadletContext.isAllowedFullAccess -> false
+       override fun isEnabled(toadletContext: ToadletContext) =
+                       isEnabled(SoneRequest(toadletContext.uri, Method.GET, HTTPRequestImpl(toadletContext.uri, "GET"), toadletContext, webInterface.l10n, webInterface.sessionManager, core, webInterface))
+
+       open fun isEnabled(soneRequest: SoneRequest) = when {
+               requiresLogin && getCurrentSone(soneRequest.toadletContext) == null -> false
+               core.preferences.requireFullAccess && !soneRequest.toadletContext.isAllowedFullAccess -> false
                else -> true
        }
 
index 758ae07..9a4c02f 100644 (file)
@@ -1,26 +1,27 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.utils.isPOST
-import net.pterodactylus.sone.utils.parameters
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * Page that lets the user trust another Sone. This will assign a configurable
  * amount of trust to an identity.
  */
-class TrustPage(template: Template, webInterface: WebInterface) :
-               LoggedInPage("trust.html", template, "Page.Trust.Title", webInterface) {
+@ToadletPath("trust.html")
+class TrustPage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               LoggedInPage("Page.Trust.Title", webInterface, loaders, templateRenderer) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
-               if (freenetRequest.isPOST) {
-                       webInterface.core.getSone(freenetRequest.parameters["sone"]!!)?.let { sone ->
-                               webInterface.core.trustSone(currentSone, sone)
+       override fun handleRequest(soneRequest: SoneRequest, currentSone: Sone, templateContext: TemplateContext) {
+               if (soneRequest.isPOST) {
+                       soneRequest.core.getSone(soneRequest.parameters["sone"]!!)?.let { sone ->
+                               soneRequest.core.trustSone(currentSone, sone)
                        }
-                       throw RedirectException(freenetRequest.parameters["returnPage", 256])
+                       throw RedirectException(soneRequest.parameters["returnPage", 256])
                }
        }
 
index c742542..9c226d4 100644 (file)
@@ -1,34 +1,33 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.utils.also
-import net.pterodactylus.sone.utils.isGET
-import net.pterodactylus.sone.utils.isPOST
-import net.pterodactylus.sone.utils.parameters
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * Page that lets the user unbookmark a post.
  */
-class UnbookmarkPage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("unbookmark.html", template, "Page.Unbookmark.Title", webInterface, false) {
+@ToadletPath("unbookmark.html")
+class UnbookmarkPage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               SoneTemplatePage(webInterface, loaders, templateRenderer, pageTitleKey = "Page.Unbookmark.Title") {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
+       override fun handleRequest(soneRequest: SoneRequest, templateContext: TemplateContext) {
                when {
-                       freenetRequest.isGET && (freenetRequest.parameters["post"] == "allNotLoaded") -> {
-                               webInterface.core.bookmarkedPosts
+                       soneRequest.isGET && (soneRequest.parameters["post"] == "allNotLoaded") -> {
+                               soneRequest.core.bookmarkedPosts
                                                .filterNot(Post::isLoaded)
-                                               .forEach(webInterface.core::unbookmarkPost)
+                                               .forEach(soneRequest.core::unbookmarkPost)
                                throw RedirectException("bookmarks.html")
                        }
-                       freenetRequest.isPOST -> {
-                               freenetRequest.parameters["post", 36]
-                                               ?.let(webInterface.core::getPost)
-                                               ?.also(webInterface.core::unbookmarkPost)
-                               throw RedirectException(freenetRequest.parameters["returnPage", 256])
+                       soneRequest.isPOST -> {
+                               soneRequest.parameters["post", 36]
+                                               ?.let(soneRequest.core::getPost)
+                                               ?.also(soneRequest.core::unbookmarkPost)
+                               throw RedirectException(soneRequest.parameters["returnPage", 256])
                        }
                }
        }
index 2521ef0..79fdef5 100644 (file)
@@ -1,24 +1,25 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.utils.isPOST
-import net.pterodactylus.sone.utils.parameters
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * This page lets the user unfollow another Sone.
  */
-class UnfollowSonePage(template: Template, webInterface: WebInterface) :
-               LoggedInPage("unfollowSone.html", template, "Page.UnfollowSone.Title", webInterface) {
+@ToadletPath("unfollowSone.html")
+class UnfollowSonePage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               LoggedInPage("Page.UnfollowSone.Title", webInterface, loaders, templateRenderer) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
-               if (freenetRequest.isPOST) {
-                       freenetRequest.parameters["sone"]!!.split(Regex("[ ,]+"))
-                                       .forEach { webInterface.core.unfollowSone(currentSone, it) }
-                       throw RedirectException(freenetRequest.parameters["returnPage", 256])
+       override fun handleRequest(soneRequest: SoneRequest, currentSone: Sone, templateContext: TemplateContext) {
+               if (soneRequest.isPOST) {
+                       soneRequest.parameters["sone"]!!.split(Regex("[ ,]+"))
+                                       .forEach { soneRequest.core.unfollowSone(currentSone, it) }
+                       throw RedirectException(soneRequest.parameters["returnPage", 256])
                }
        }
 
index 6bfabe2..82147a9 100644 (file)
@@ -1,26 +1,27 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.utils.isPOST
-import net.pterodactylus.sone.utils.parameters
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * Page that lets the user unlike a [net.pterodactylus.sone.data.Post] or [net.pterodactylus.sone.data.Reply].
  */
-class UnlikePage(template: Template, webInterface: WebInterface):
-               LoggedInPage("unlike.html", template, "Page.Unlike.Title", webInterface) {
+@ToadletPath("unlike.html")
+class UnlikePage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               LoggedInPage("Page.Unlike.Title", webInterface, loaders, templateRenderer) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
-               if (freenetRequest.isPOST) {
-                       when (freenetRequest.parameters["type"]) {
-                               "post" -> currentSone.removeLikedPostId(freenetRequest.parameters["post"]!!)
-                               "reply" -> currentSone.removeLikedReplyId(freenetRequest.parameters["reply"]!!)
+       override fun handleRequest(soneRequest: SoneRequest, currentSone: Sone, templateContext: TemplateContext) {
+               if (soneRequest.isPOST) {
+                       when (soneRequest.parameters["type"]) {
+                               "post" -> currentSone.removeLikedPostId(soneRequest.parameters["post"]!!)
+                               "reply" -> currentSone.removeLikedReplyId(soneRequest.parameters["reply"]!!)
                        }
-                       throw RedirectException(freenetRequest.parameters["returnPage", 256])
+                       throw RedirectException(soneRequest.parameters["returnPage", 256])
                }
        }
 
index e792318..2c5735b 100644 (file)
@@ -1,24 +1,25 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.utils.isPOST
-import net.pterodactylus.sone.utils.parameters
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * This page lets the user unlock a [net.pterodactylus.sone.data.Sone] to allow its insertion.
  */
-class UnlockSonePage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("unlockSone.html", template, "Page.UnlockSone.Title", webInterface, false) {
+@ToadletPath("unlockSone.html")
+class UnlockSonePage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               SoneTemplatePage(webInterface, loaders, templateRenderer, pageTitleKey = "Page.UnlockSone.Title") {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
-               if (freenetRequest.isPOST) {
-                       freenetRequest.parameters["sone", 44]
-                                       .let(webInterface.core::getLocalSone)
-                                       ?.also(webInterface.core::unlockSone)
-                       throw RedirectException(freenetRequest.parameters["returnPage", 256])
+       override fun handleRequest(soneRequest: SoneRequest, templateContext: TemplateContext) {
+               if (soneRequest.isPOST) {
+                       soneRequest.parameters["sone", 44]
+                                       .let(soneRequest.core::getLocalSone)
+                                       ?.also(soneRequest.core::unlockSone)
+                       throw RedirectException(soneRequest.parameters["returnPage", 256])
                }
        }
 
index f522c34..7dd342e 100644 (file)
@@ -1,26 +1,27 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.utils.isPOST
-import net.pterodactylus.sone.utils.parameters
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import javax.inject.*
 
 /**
  * Page that lets the user untrust another Sone. This will remove all trust
  * assignments for an identity.
  */
-class UntrustPage(template: Template, webInterface: WebInterface) :
-               LoggedInPage("untrust.html", template, "Page.Untrust.Title", webInterface) {
+@ToadletPath("untrust.html")
+class UntrustPage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               LoggedInPage("Page.Untrust.Title", webInterface, loaders, templateRenderer) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
-               if (freenetRequest.isPOST) {
-                       freenetRequest.parameters["sone", 44]!!
-                                       .let(webInterface.core::getSone)
-                                       ?.also { webInterface.core.untrustSone(currentSone, it) }
-                       throw RedirectException(freenetRequest.parameters["returnPage", 256])
+       override fun handleRequest(soneRequest: SoneRequest, currentSone: Sone, templateContext: TemplateContext) {
+               if (soneRequest.isPOST) {
+                       soneRequest.parameters["sone", 44]!!
+                                       .let(soneRequest.core::getSone)
+                                       ?.also { soneRequest.core.untrustSone(currentSone, it) }
+                       throw RedirectException(soneRequest.parameters["returnPage", 256])
                }
        }
 
index 4aa35ac..de61835 100644 (file)
@@ -1,50 +1,48 @@
 package net.pterodactylus.sone.web.pages
 
-import freenet.support.api.Bucket
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.text.TextFilter
-import net.pterodactylus.sone.utils.emptyToNull
-import net.pterodactylus.sone.utils.headers
-import net.pterodactylus.sone.utils.isPOST
-import net.pterodactylus.sone.utils.parameters
-import net.pterodactylus.sone.utils.use
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
-import java.awt.image.BufferedImage
-import java.io.ByteArrayInputStream
-import java.io.ByteArrayOutputStream
-import javax.imageio.ImageIO
+import freenet.support.api.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.text.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import java.awt.image.*
+import java.io.*
+import javax.imageio.*
+import javax.inject.*
 
 /**
  * Page implementation that lets the user upload an image.
  */
-class UploadImagePage(template: Template, webInterface: WebInterface):
-               LoggedInPage("uploadImage.html", template, "Page.UploadImage.Title", webInterface) {
+@TemplatePath("/templates/invalid.html")
+@ToadletPath("uploadImage.html")
+class UploadImagePage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               LoggedInPage("Page.UploadImage.Title", webInterface, loaders, templateRenderer) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
-               if (freenetRequest.isPOST) {
-                       val parentAlbum = freenetRequest.parameters["parent"]!!.let(webInterface.core::getAlbum) ?: throw RedirectException("noPermission.html")
+       override fun handleRequest(soneRequest: SoneRequest, currentSone: Sone, templateContext: TemplateContext) {
+               if (soneRequest.isPOST) {
+                       val parentAlbum = soneRequest.parameters["parent"]!!.let(soneRequest.core::getAlbum) ?: throw RedirectException("noPermission.html")
                        if (parentAlbum.sone != currentSone) {
                                throw RedirectException("noPermission.html")
                        }
-                       val title = freenetRequest.parameters["title", 200].emptyToNull ?: throw RedirectException("emptyImageTitle.html")
+                       val title = soneRequest.parameters["title", 200].emptyToNull ?: throw RedirectException("emptyImageTitle.html")
 
-                       val uploadedFile = freenetRequest.httpRequest.getUploadedFile("image")
+                       val uploadedFile = soneRequest.httpRequest.getUploadedFile("image")
                        val bytes = uploadedFile.data.use { it.toByteArray() }
                        val bufferedImage = bytes.toImage()
                        if (bufferedImage == null) {
-                               templateContext["messages"] = webInterface.l10n.getString("Page.UploadImage.Error.InvalidImage")
+                               templateContext["messages"] = soneRequest.l10n.getString("Page.UploadImage.Error.InvalidImage")
                                return
                        }
 
-                       val temporaryImage = webInterface.core.createTemporaryImage(bytes.mimeType, bytes)
-                       webInterface.core.createImage(currentSone, parentAlbum, temporaryImage).modify().apply {
+                       val temporaryImage = soneRequest.core.createTemporaryImage(bytes.mimeType, bytes)
+                       soneRequest.core.createImage(currentSone, parentAlbum, temporaryImage).modify().apply {
                                setWidth(bufferedImage.width)
                                setHeight(bufferedImage.height)
                                setTitle(title)
-                               setDescription(TextFilter.filter(freenetRequest.headers["Host"], freenetRequest.parameters["description", 4000]))
+                               setDescription(TextFilter.filter(soneRequest.headers["Host"], soneRequest.parameters["description", 4000]))
                        }.update()
                        throw RedirectException("imageBrowser.html?album=${parentAlbum.id}")
                }
@@ -59,13 +57,14 @@ class UploadImagePage(template: Template, webInterface: WebInterface):
                ImageIO.read(it)
        }
 
-       private val ByteArray.mimeType get() = ByteArrayInputStream(this).use {
-               ImageIO.createImageInputStream(it).use {
-                       ImageIO.getImageReaders(it).asSequence()
-                                       .firstOrNull()?.originatingProvider?.mimeTypes?.firstOrNull()
-                                       ?: UNKNOWN_MIME_TYPE
+       private val ByteArray.mimeType
+               get() = ByteArrayInputStream(this).use {
+                       ImageIO.createImageInputStream(it).use {
+                               ImageIO.getImageReaders(it).asSequence()
+                                               .firstOrNull()?.originatingProvider?.mimeTypes?.firstOrNull()
+                                               ?: UNKNOWN_MIME_TYPE
+                       }
                }
-       }
 
 }
 
index 910c939..7e4d6a5 100644 (file)
@@ -1,34 +1,36 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.template.SoneAccessor
-import net.pterodactylus.sone.utils.let
-import net.pterodactylus.sone.utils.parameters
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
-import java.net.URI
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.template.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import java.net.*
+import javax.inject.*
 
 /**
  * This page lets the user view a post and all its replies.
  */
-class ViewPostPage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("viewPost.html", template, "Page.ViewPost.Title", webInterface, false) {
+@TemplatePath("/templates/viewPost.html")
+@ToadletPath("viewPost.html")
+class ViewPostPage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               SoneTemplatePage(webInterface, loaders, templateRenderer, pageTitleKey = "Page.ViewPost.Title") {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
-               templateContext["post"] = freenetRequest.parameters["post"]?.let(webInterface.core::getPost)
-               templateContext["raw"] = freenetRequest.parameters["raw"] == "true"
+       override fun handleRequest(soneRequest: SoneRequest, templateContext: TemplateContext) {
+               templateContext["post"] = soneRequest.parameters["post"]?.let(soneRequest.core::getPost)
+               templateContext["raw"] = soneRequest.parameters["raw"] == "true"
        }
 
-       override fun isLinkExcepted(link: URI?) = true
+       override fun isLinkExcepted(link: URI) = true
 
-       public override fun getPageTitle(freenetRequest: FreenetRequest) =
-                       (freenetRequest.parameters["post"]?.let(webInterface.core::getPost)?.let {
+       override fun getPageTitle(soneRequest: SoneRequest) =
+                       (soneRequest.parameters["post"]?.let(soneRequest.core::getPost)?.let {
                                if (it.text.length > 20) {
                                        it.text.substring(0..19) + "…"
                                } else {
                                        it.text
                                } + " - ${SoneAccessor.getNiceName(it.sone)} - "
-                       } ?: "") + super.getPageTitle(freenetRequest)
+                       } ?: "") + super.getPageTitle(soneRequest)
 
 }
index 2e175af..a498d24 100644 (file)
@@ -1,33 +1,33 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.data.PostReply
-import net.pterodactylus.sone.template.SoneAccessor
-import net.pterodactylus.sone.utils.mapPresent
-import net.pterodactylus.sone.utils.paginate
-import net.pterodactylus.sone.utils.parameters
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.template.Template
-import net.pterodactylus.util.template.TemplateContext
-import java.net.URI
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.template.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import java.net.*
+import javax.inject.*
 
 /**
  * Lets the user browser another Sone.
  */
-class ViewSonePage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("viewSone.html", template, webInterface, false) {
+@TemplatePath("/templates/viewSone.html")
+@ToadletPath("viewSone.html")
+class ViewSonePage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer) :
+               SoneTemplatePage(webInterface, loaders, templateRenderer) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
-               templateContext["soneId"] = freenetRequest.parameters["sone"]
-               freenetRequest.parameters["sone"]!!.let(webInterface.core::getSone)?.let { sone ->
+       override fun handleRequest(soneRequest: SoneRequest, templateContext: TemplateContext) {
+               templateContext["soneId"] = soneRequest.parameters["sone"]
+               soneRequest.parameters["sone"]!!.let(soneRequest.core::getSone)?.let { sone ->
                        templateContext["sone"] = sone
                        val sonePosts = sone.posts
-                       val directedPosts = webInterface.core.getDirectedPosts(sone.id)
+                       val directedPosts = soneRequest.core.getDirectedPosts(sone.id)
                        (sonePosts + directedPosts)
                                        .sortedByDescending(Post::getTime)
-                                       .paginate(webInterface.core.preferences.postsPerPage)
-                                       .apply { page = freenetRequest.parameters["postPage"]?.toIntOrNull() ?: 0 }
+                                       .paginate(soneRequest.core.preferences.postsPerPage)
+                                       .apply { page = soneRequest.parameters["postPage"]?.toIntOrNull() ?: 0 }
                                        .also {
                                                templateContext["postPagination"] = it
                                                templateContext["posts"] = it.items
@@ -37,9 +37,9 @@ class ViewSonePage(template: Template, webInterface: WebInterface):
                                        .distinct()
                                        .minus(sonePosts)
                                        .minus(directedPosts)
-                                       .sortedByDescending { webInterface.core.getReplies(it.id).first().time }
-                                       .paginate(webInterface.core.preferences.postsPerPage)
-                                       .apply { page = freenetRequest.parameters["repliedPostPage"]?.toIntOrNull() ?: 0 }
+                                       .sortedByDescending { soneRequest.core.getReplies(it.id).first().time }
+                                       .paginate(soneRequest.core.preferences.postsPerPage)
+                                       .apply { page = soneRequest.parameters["repliedPostPage"]?.toIntOrNull() ?: 0 }
                                        .also {
                                                templateContext["repliedPostPagination"] = it
                                                templateContext["repliedPosts"] = it.items
@@ -47,11 +47,11 @@ class ViewSonePage(template: Template, webInterface: WebInterface):
                }
        }
 
-       override fun isLinkExcepted(link: URI?) = true
+       override fun isLinkExcepted(link: URI) = true
 
-       public override fun getPageTitle(freenetRequest: FreenetRequest): String =
-                       freenetRequest.parameters["sone"]!!.let(webInterface.core::getSone)?.let { sone ->
-                               "${SoneAccessor.getNiceName(sone)} - ${webInterface.l10n.getString("Page.ViewSone.Title")}"
-                       } ?: webInterface.l10n.getString("Page.ViewSone.Page.TitleWithoutSone")
+       override fun getPageTitle(soneRequest: SoneRequest): String =
+                       soneRequest.parameters["sone"]!!.let(soneRequest.core::getSone)?.let { sone ->
+                               "${SoneAccessor.getNiceName(sone)} - ${soneRequest.l10n.getString("Page.ViewSone.Title")}"
+                       } ?: soneRequest.l10n.getString("Page.ViewSone.Page.TitleWithoutSone")
 
 }
index e655691..7e42167 100644 (file)
@@ -227,7 +227,7 @@ Page.FollowSone.Title=Sone folgen - Sone
 
 Page.UnfollowSone.Title=Sone entfolgen - Sone
 
-Page.ImageBrowser.Title=Bildergallerie - Sone
+Page.ImageBrowser.Title=Bildergalerie - Sone
 Page.ImageBrowser.Album.Title=Album “{album}”
 Page.ImageBrowser.Album.Error.NotFound.Text=Das gewünschte Album konnte nicht gefunden werden. Es ist möglich, dass es noch nicht herunter geladen oder bereits gelöscht wurde.
 Page.ImageBrowser.Sone.Title=Alben von {sone}
index 4a26203..4edc1b7 100644 (file)
@@ -57,8 +57,6 @@ import org.mockito.stubbing.Answer;
 
 /**
  * Unit test for {@link ConfigurationSoneParser}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class ConfigurationSoneParserTest {
 
index dbe70d5..66ef743 100644 (file)
@@ -33,8 +33,6 @@ import org.mockito.InOrder;
 
 /**
  * Unit test for {@link Core} and its subclasses.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class CoreTest {
 
index 2ebc428..c91de68 100644 (file)
@@ -31,7 +31,6 @@ import java.util.HashMap;
 
 import net.pterodactylus.sone.core.FreenetInterface.BackgroundFetchCallback;
 import net.pterodactylus.sone.core.FreenetInterface.Callback;
-import net.pterodactylus.sone.core.FreenetInterface.Fetched;
 import net.pterodactylus.sone.core.FreenetInterface.InsertToken;
 import net.pterodactylus.sone.core.FreenetInterface.InsertTokenSupplier;
 import net.pterodactylus.sone.core.event.ImageInsertAbortedEvent;
@@ -83,8 +82,6 @@ import org.mockito.ArgumentMatchers;
 
 /**
  * Unit test for {@link FreenetInterface}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class FreenetInterfaceTest {
 
@@ -215,7 +212,7 @@ public class FreenetInterfaceTest {
        @Test
        public void insertingADirectory() throws InsertException, SoneException {
                FreenetURI freenetUri = mock(FreenetURI.class);
-               HashMap<String, Object> manifestEntries = new HashMap<String, Object>();
+               HashMap<String, Object> manifestEntries = new HashMap<>();
                String defaultFile = "index.html";
                FreenetURI resultingUri = mock(FreenetURI.class);
                when(highLevelSimpleClient.insertManifest(eq(freenetUri), eq(manifestEntries), eq(defaultFile))).thenReturn(resultingUri);
@@ -240,7 +237,7 @@ public class FreenetInterfaceTest {
                FreenetURI freenetUri = createRandom(randomSource, "test-0").getURI().uskForSSK();
                Callback callback = mock(Callback.class);
                freenetInterface.registerUsk(freenetUri, callback);
-               verify(uskManager).subscribe(any(USK.class), any(USKCallback.class), anyBoolean(), eq((RequestClient) highLevelSimpleClient));
+               verify(uskManager).subscribe(any(USK.class), any(USKCallback.class), anyBoolean(), any(RequestClient.class));
        }
 
        @Test
@@ -248,7 +245,7 @@ public class FreenetInterfaceTest {
                FreenetURI freenetUri = new FreenetURI("KSK@GPLv3.txt");
                Callback callback = mock(Callback.class);
                freenetInterface.registerUsk(freenetUri, callback);
-               verify(uskManager, never()).subscribe(any(USK.class), any(USKCallback.class), anyBoolean(), eq((RequestClient) highLevelSimpleClient));
+               verify(uskManager, never()).subscribe(any(USK.class), any(USKCallback.class), anyBoolean(), any(RequestClient.class));
        }
 
        @Test
@@ -272,9 +269,7 @@ public class FreenetInterfaceTest {
        throws MalformedURLException {
                FreenetURI freenetUri = createRandom(randomSource, "test-0").getURI();
                freenetInterface.registerActiveUsk(freenetUri, null);
-               verify(uskManager, never()).subscribe(any(USK.class),
-                               any(USKCallback.class), anyBoolean(),
-                               eq((RequestClient) highLevelSimpleClient));
+               verify(uskManager, never()).subscribe(any(USK.class), any(USKCallback.class), anyBoolean(), any(RequestClient.class));
        }
 
        @Test
@@ -282,9 +277,7 @@ public class FreenetInterfaceTest {
        throws MalformedURLException {
                FreenetURI freenetUri = createRandom(randomSource, "test-0").getURI();
                freenetInterface.registerPassiveUsk(freenetUri, null);
-               verify(uskManager, never()).subscribe(any(USK.class),
-                               any(USKCallback.class), anyBoolean(),
-                               eq((RequestClient) highLevelSimpleClient));
+               verify(uskManager, never()).subscribe(any(USK.class), any(USKCallback.class), anyBoolean(), any(RequestClient.class));
        }
 
        @Test
@@ -420,7 +413,7 @@ public class FreenetInterfaceTest {
 
        @Test
        public void insertTokenSupplierSuppliesInsertTokens() {
-               InsertTokenSupplier insertTokenSupplier = freenetInterface.new InsertTokenSupplier();
+               InsertTokenSupplier insertTokenSupplier = new InsertTokenSupplier(freenetInterface);
                assertThat(insertTokenSupplier.apply(image), notNullValue());
        }
 
diff --git a/src/test/java/net/pterodactylus/sone/core/ImageInserterTest.java b/src/test/java/net/pterodactylus/sone/core/ImageInserterTest.java
deleted file mode 100644 (file)
index 75788d1..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-package net.pterodactylus.sone.core;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import net.pterodactylus.sone.core.FreenetInterface.InsertToken;
-import net.pterodactylus.sone.data.Image;
-import net.pterodactylus.sone.data.TemporaryImage;
-
-import com.google.common.base.Function;
-import org.junit.Test;
-
-/**
- * Unit test for {@link ImageInserter}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class ImageInserterTest {
-
-       private final TemporaryImage temporaryImage = when(mock(TemporaryImage.class).getId()).thenReturn("image-id").getMock();
-       private final Image image = when(mock(Image.class).getId()).thenReturn("image-id").getMock();
-       private final FreenetInterface freenetInterface = mock(FreenetInterface.class);
-       private final InsertToken insertToken = mock(InsertToken.class);
-       private final Function<Image, InsertToken> insertTokenSupplier = when(mock(Function.class).apply(any(Image.class))).thenReturn(insertToken).getMock();
-       private final ImageInserter imageInserter = new ImageInserter(freenetInterface, insertTokenSupplier);
-
-       @Test
-       public void inserterInsertsImage() throws SoneException {
-               imageInserter.insertImage(temporaryImage, image);
-               verify(freenetInterface).insertImage(eq(temporaryImage), eq(image), any(InsertToken.class));
-       }
-
-       @Test
-       public void exceptionWhenInsertingImageIsIgnored() throws SoneException {
-               doThrow(SoneException.class).when(freenetInterface).insertImage(eq(temporaryImage), eq(image), any(InsertToken.class));
-               imageInserter.insertImage(temporaryImage, image);
-               verify(freenetInterface).insertImage(eq(temporaryImage), eq(image), any(InsertToken.class));
-       }
-
-       @Test
-       public void cancellingImageInsertThatIsNotRunningDoesNothing() {
-               imageInserter.cancelImageInsert(image);
-               verify(insertToken, never()).cancel();
-       }
-
-       @Test
-       public void cancellingImage() {
-               imageInserter.insertImage(temporaryImage, image);
-               imageInserter.cancelImageInsert(image);
-               verify(insertToken).cancel();
-       }
-
-}
index cce4df0..113430c 100644 (file)
@@ -11,8 +11,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link Options}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class OptionsTest {
 
index 49777f0..d550f25 100644 (file)
@@ -16,8 +16,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link PreferencesLoader}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class PreferencesLoaderTest {
 
@@ -63,11 +61,11 @@ public class PreferencesLoaderTest {
                assertThat(preferences.getImagesPerPage(), is(12));
                assertThat(preferences.getCharactersPerPost(), is(150));
                assertThat(preferences.getPostCutOffLength(), is(300));
-               assertThat(preferences.isRequireFullAccess(), is(true));
+               assertThat(preferences.getRequireFullAccess(), is(true));
                assertThat(preferences.getPositiveTrust(), is(50));
                assertThat(preferences.getNegativeTrust(), is(-50));
                assertThat(preferences.getTrustComment(), is("Trusted"));
-               assertThat(preferences.isFcpInterfaceActive(), is(true));
+               assertThat(preferences.getFcpInterfaceActive(), is(true));
                assertThat(preferences.getFcpFullAccessRequired(), is(WRITING));
        }
 
diff --git a/src/test/java/net/pterodactylus/sone/core/PreferencesTest.java b/src/test/java/net/pterodactylus/sone/core/PreferencesTest.java
deleted file mode 100644 (file)
index 6756185..0000000
+++ /dev/null
@@ -1,311 +0,0 @@
-package net.pterodactylus.sone.core;
-
-import static net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.ALWAYS;
-import static net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.NO;
-import static net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.WRITING;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-
-import net.pterodactylus.sone.core.event.InsertionDelayChangedEvent;
-import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired;
-import net.pterodactylus.sone.fcp.event.FcpInterfaceActivatedEvent;
-import net.pterodactylus.sone.fcp.event.FcpInterfaceDeactivatedEvent;
-import net.pterodactylus.sone.fcp.event.FullAccessRequiredChanged;
-
-import com.google.common.eventbus.EventBus;
-import org.junit.After;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-
-/**
- * Unit test for {@link Preferences}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class PreferencesTest {
-
-       private final EventBus eventBus = mock(EventBus.class);
-       private final Preferences preferences = new Preferences(eventBus);
-
-       @After
-       public void tearDown() {
-               verifyNoMoreInteractions(eventBus);
-       }
-
-       @Test
-       public void preferencesRetainInsertionDelay() {
-               preferences.setInsertionDelay(15);
-               assertThat(preferences.getInsertionDelay(), is(15));
-               verify(eventBus).post(any(InsertionDelayChangedEvent.class));
-       }
-
-       @Test(expected = IllegalArgumentException.class)
-       public void invalidInsertionDelayIsRejected() {
-               preferences.setInsertionDelay(-15);
-       }
-
-       @Test
-       public void preferencesReturnDefaultValueWhenInsertionDelayIsSetToNull() {
-               preferences.setInsertionDelay(null);
-               assertThat(preferences.getInsertionDelay(), is(60));
-               verify(eventBus).post(any(InsertionDelayChangedEvent.class));
-       }
-
-       @Test
-       public void preferencesStartWithInsertionDelayDefaultValue() {
-               assertThat(preferences.getInsertionDelay(), is(60));
-       }
-
-       @Test
-       public void preferencesRetainPostsPerPage() {
-               preferences.setPostsPerPage(15);
-               assertThat(preferences.getPostsPerPage(), is(15));
-       }
-
-       @Test(expected = IllegalArgumentException.class)
-       public void invalidPostsPerPageIsRejected() {
-               preferences.setPostsPerPage(-15);
-       }
-
-       @Test
-       public void preferencesReturnDefaultValueWhenPostsPerPageIsSetToNull() {
-               preferences.setPostsPerPage(null);
-               assertThat(preferences.getPostsPerPage(), is(10));
-       }
-
-       @Test
-       public void preferencesStartWithPostsPerPageDefaultValue() {
-               assertThat(preferences.getPostsPerPage(), is(10));
-       }
-
-       @Test
-       public void preferencesRetainImagesPerPage() {
-               preferences.setImagesPerPage(15);
-               assertThat(preferences.getImagesPerPage(), is(15));
-       }
-
-       @Test(expected = IllegalArgumentException.class)
-       public void invalidImagesPerPageIsRejected() {
-               preferences.setImagesPerPage(-15);
-       }
-
-       @Test
-       public void preferencesReturnDefaultValueWhenImagesPerPageIsSetToNull() {
-               preferences.setImagesPerPage(null);
-               assertThat(preferences.getImagesPerPage(), is(9));
-       }
-
-       @Test
-       public void preferencesStartWithImagesPerPageDefaultValue() {
-               assertThat(preferences.getImagesPerPage(), is(9));
-       }
-
-       @Test
-       public void preferencesRetainCharactersPerPost() {
-               preferences.setCharactersPerPost(150);
-               assertThat(preferences.getCharactersPerPost(), is(150));
-       }
-
-       @Test(expected = IllegalArgumentException.class)
-       public void invalidCharactersPerPostIsRejected() {
-               preferences.setCharactersPerPost(-15);
-       }
-
-       @Test
-       public void preferencesReturnDefaultValueWhenCharactersPerPostIsSetToNull() {
-               preferences.setCharactersPerPost(null);
-               assertThat(preferences.getCharactersPerPost(), is(400));
-       }
-
-       @Test
-       public void preferencesStartWithCharactersPerPostDefaultValue() {
-               assertThat(preferences.getCharactersPerPost(), is(400));
-       }
-
-       @Test
-       public void preferencesRetainPostCutOffLength() {
-               preferences.setPostCutOffLength(150);
-               assertThat(preferences.getPostCutOffLength(), is(150));
-       }
-
-       @Test(expected = IllegalArgumentException.class)
-       public void invalidPostCutOffLengthIsRejected() {
-               preferences.setPostCutOffLength(-15);
-       }
-
-       @Test(expected = IllegalArgumentException.class)
-       public void cutOffLengthOfMinusOneIsNotAllowed() {
-               preferences.setPostCutOffLength(-1);
-       }
-
-       @Test
-       public void preferencesReturnDefaultValueWhenPostCutOffLengthIsSetToNull() {
-               preferences.setPostCutOffLength(null);
-               assertThat(preferences.getPostCutOffLength(), is(200));
-       }
-
-       @Test
-       public void preferencesStartWithPostCutOffLengthDefaultValue() {
-               assertThat(preferences.getPostCutOffLength(), is(200));
-       }
-
-       @Test
-       public void preferencesRetainRequireFullAccessOfTrue() {
-               preferences.setRequireFullAccess(true);
-               assertThat(preferences.isRequireFullAccess(), is(true));
-       }
-
-       @Test
-       public void preferencesRetainRequireFullAccessOfFalse() {
-               preferences.setRequireFullAccess(false);
-               assertThat(preferences.isRequireFullAccess(), is(false));
-       }
-
-       @Test
-       public void preferencesReturnDefaultValueWhenRequireFullAccessIsSetToNull() {
-               preferences.setRequireFullAccess(null);
-               assertThat(preferences.isRequireFullAccess(), is(false));
-       }
-
-       @Test
-       public void preferencesStartWithRequireFullAccessDefaultValue() {
-               assertThat(preferences.isRequireFullAccess(), is(false));
-       }
-
-       @Test
-       public void preferencesRetainPositiveTrust() {
-               preferences.setPositiveTrust(15);
-               assertThat(preferences.getPositiveTrust(), is(15));
-       }
-
-       @Test(expected = IllegalArgumentException.class)
-       public void invalidPositiveTrustIsRejected() {
-               preferences.setPositiveTrust(-15);
-       }
-
-       @Test
-       public void preferencesReturnDefaultValueWhenPositiveTrustIsSetToNull() {
-               preferences.setPositiveTrust(null);
-               assertThat(preferences.getPositiveTrust(), is(75));
-       }
-
-       @Test
-       public void preferencesStartWithPositiveTrustDefaultValue() {
-               assertThat(preferences.getPositiveTrust(), is(75));
-       }
-
-       @Test
-       public void preferencesRetainNegativeTrust() {
-               preferences.setNegativeTrust(-15);
-               assertThat(preferences.getNegativeTrust(), is(-15));
-       }
-
-       @Test(expected = IllegalArgumentException.class)
-       public void invalidNegativeTrustIsRejected() {
-               preferences.setNegativeTrust(150);
-       }
-
-       @Test
-       public void preferencesReturnDefaultValueWhenNegativeTrustIsSetToNull() {
-               preferences.setNegativeTrust(null);
-               assertThat(preferences.getNegativeTrust(), is(-25));
-       }
-
-       @Test
-       public void preferencesStartWithNegativeTrustDefaultValue() {
-               assertThat(preferences.getNegativeTrust(), is(-25));
-       }
-
-       @Test
-       public void preferencesRetainTrustComment() {
-               preferences.setTrustComment("Trust");
-               assertThat(preferences.getTrustComment(), is("Trust"));
-       }
-
-       @Test
-       public void preferencesReturnDefaultValueWhenTrustCommentIsSetToNull() {
-               preferences.setTrustComment(null);
-               assertThat(preferences.getTrustComment(),
-                               is("Set from Sone Web Interface"));
-       }
-
-       @Test
-       public void preferencesStartWithTrustCommentDefaultValue() {
-               assertThat(preferences.getTrustComment(),
-                               is("Set from Sone Web Interface"));
-       }
-
-       @Test
-       public void preferencesRetainFcpInterfaceActiveOfTrue() {
-               preferences.setFcpInterfaceActive(true);
-               assertThat(preferences.isFcpInterfaceActive(), is(true));
-               verify(eventBus).post(any(FcpInterfaceActivatedEvent.class));
-       }
-
-       @Test
-       public void preferencesRetainFcpInterfaceActiveOfFalse() {
-               preferences.setFcpInterfaceActive(false);
-               assertThat(preferences.isFcpInterfaceActive(), is(false));
-               verify(eventBus).post(any(FcpInterfaceDeactivatedEvent.class));
-       }
-
-       @Test
-       public void preferencesReturnDefaultValueWhenFcpInterfaceActiveIsSetToNull() {
-               preferences.setFcpInterfaceActive(null);
-               assertThat(preferences.isFcpInterfaceActive(), is(false));
-               verify(eventBus).post(any(FcpInterfaceDeactivatedEvent.class));
-       }
-
-       @Test
-       public void preferencesStartWithFcpInterfaceActiveDefaultValue() {
-               assertThat(preferences.isFcpInterfaceActive(), is(false));
-       }
-
-       @Test
-       public void preferencesRetainFcpFullAccessRequiredOfNo() {
-               preferences.setFcpFullAccessRequired(NO);
-               assertThat(preferences.getFcpFullAccessRequired(), is(NO));
-               verifyFullAccessRequiredChangedEvent(NO);
-       }
-
-       private void verifyFullAccessRequiredChangedEvent(
-                       FullAccessRequired fullAccessRequired) {
-               ArgumentCaptor<FullAccessRequiredChanged> fullAccessRequiredCaptor =
-                               ArgumentCaptor.forClass(FullAccessRequiredChanged.class);
-               verify(eventBus).post(fullAccessRequiredCaptor.capture());
-               assertThat(
-                               fullAccessRequiredCaptor.getValue().getFullAccessRequired(),
-                               is(fullAccessRequired));
-       }
-
-       @Test
-       public void preferencesRetainFcpFullAccessRequiredOfWriting() {
-               preferences.setFcpFullAccessRequired(WRITING);
-               assertThat(preferences.getFcpFullAccessRequired(), is(WRITING));
-               verifyFullAccessRequiredChangedEvent(WRITING);
-       }
-
-       @Test
-       public void preferencesRetainFcpFullAccessRequiredOfAlways() {
-               preferences.setFcpFullAccessRequired(ALWAYS);
-               assertThat(preferences.getFcpFullAccessRequired(), is(ALWAYS));
-               verifyFullAccessRequiredChangedEvent(ALWAYS);
-       }
-
-       @Test
-       public void preferencesReturnDefaultValueWhenFcpFullAccessRequiredIsSetToNull() {
-               preferences.setFcpFullAccessRequired(null);
-               assertThat(preferences.getFcpFullAccessRequired(), is(ALWAYS));
-               verifyFullAccessRequiredChangedEvent(ALWAYS);
-       }
-
-       @Test
-       public void preferencesStartWithFcpFullAccessRequiredDefaultValue() {
-               assertThat(preferences.getFcpFullAccessRequired(), is(ALWAYS));
-       }
-
-}
diff --git a/src/test/java/net/pterodactylus/sone/core/SoneChangeDetectorTest.java b/src/test/java/net/pterodactylus/sone/core/SoneChangeDetectorTest.java
deleted file mode 100644 (file)
index 34e3573..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-package net.pterodactylus.sone.core;
-
-import static java.util.Arrays.asList;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.util.HashSet;
-
-import net.pterodactylus.sone.core.SoneChangeDetector.PostProcessor;
-import net.pterodactylus.sone.core.SoneChangeDetector.PostReplyProcessor;
-import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.PostReply;
-import net.pterodactylus.sone.data.Sone;
-
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Unit test for {@link SoneChangeDetector}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class SoneChangeDetectorTest {
-
-       private final Sone oldSone = mock(Sone.class);
-       private final Sone newSone = mock(Sone.class);
-       private final SoneChangeDetector soneChangeDetector =
-                       new SoneChangeDetector(oldSone);
-       private final Post oldPost = mock(Post.class);
-       private final Post removedPost = mock(Post.class);
-       private final Post newPost = mock(Post.class);
-       private final PostProcessor newPostProcessor = mock(PostProcessor.class);
-       private final PostProcessor removedPostProcessor =
-                       mock(PostProcessor.class);
-       private final PostReply oldPostReply = mock(PostReply.class);
-       private final PostReply removedPostReply = mock(PostReply.class);
-       private final PostReply newPostReply = mock(PostReply.class);
-       private final PostReplyProcessor newPostReplyProcessor =
-                       mock(PostReplyProcessor.class);
-       private final PostReplyProcessor removedPostReplyProcessor =
-                       mock(PostReplyProcessor.class);
-
-       @Before
-       public void setupPosts() {
-               when(oldSone.getPosts()).thenReturn(asList(oldPost, removedPost));
-               when(newSone.getPosts()).thenReturn(asList(oldPost, newPost));
-       }
-
-       @Before
-       public void setupPostProcessors() {
-               soneChangeDetector.onNewPosts(newPostProcessor);
-               soneChangeDetector.onRemovedPosts(removedPostProcessor);
-       }
-
-       @Before
-       public void setupPostReplies() {
-               when(oldSone.getReplies()).thenReturn(
-                               new HashSet<PostReply>(
-                                               asList(oldPostReply, removedPostReply)));
-               when(newSone.getReplies()).thenReturn(
-                               new HashSet<PostReply>(asList(oldPostReply, newPostReply)));
-       }
-
-       @Before
-       public void setupPostReplyProcessors() {
-               soneChangeDetector.onNewPostReplies(newPostReplyProcessor);
-               soneChangeDetector.onRemovedPostReplies(removedPostReplyProcessor);
-       }
-
-       @Test
-       public void changeDetectorDetectsChanges() {
-               soneChangeDetector.detectChanges(newSone);
-
-               verify(newPostProcessor).processPost(newPost);
-               verify(newPostProcessor, never()).processPost(oldPost);
-               verify(newPostProcessor, never()).processPost(removedPost);
-               verify(removedPostProcessor).processPost(removedPost);
-               verify(removedPostProcessor, never()).processPost(oldPost);
-               verify(removedPostProcessor, never()).processPost(newPost);
-
-               verify(newPostReplyProcessor).processPostReply(newPostReply);
-               verify(newPostReplyProcessor, never()).processPostReply(oldPostReply);
-               verify(newPostReplyProcessor, never()).processPostReply(
-                               removedPostReply);
-               verify(removedPostReplyProcessor).processPostReply(removedPostReply);
-               verify(removedPostReplyProcessor, never()).processPostReply(
-                               oldPostReply);
-               verify(removedPostReplyProcessor, never()).processPostReply(
-                               newPostReply);
-       }
-
-       @Test
-       public void changeDetectorDoesNotNotifyAnyProcessorIfProcessorsUnset() {
-           soneChangeDetector.onNewPosts(null);
-           soneChangeDetector.onRemovedPosts(null);
-           soneChangeDetector.onNewPostReplies(null);
-           soneChangeDetector.onRemovedPostReplies(null);
-               soneChangeDetector.detectChanges(newSone);
-               verify(newPostProcessor, never()).processPost(any(Post.class));
-               verify(removedPostProcessor, never()).processPost(any(Post.class));
-               verify(newPostReplyProcessor, never()).processPostReply(any(PostReply.class));
-               verify(removedPostReplyProcessor, never()).processPostReply(any(PostReply.class));
-       }
-
-}
index 9043974..284d9b2 100644 (file)
@@ -6,8 +6,10 @@ import static java.util.concurrent.TimeUnit.DAYS;
 import static net.pterodactylus.sone.data.Sone.SoneStatus.downloading;
 import static net.pterodactylus.sone.data.Sone.SoneStatus.idle;
 import static net.pterodactylus.sone.data.Sone.SoneStatus.unknown;
+import static net.pterodactylus.sone.web.AllPagesTestKt.getBaseInjector;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
 import static org.mockito.ArgumentCaptor.forClass;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -20,10 +22,10 @@ import static org.mockito.Mockito.when;
 import java.io.IOException;
 import java.io.InputStream;
 
-import net.pterodactylus.sone.core.FreenetInterface.Fetched;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.data.Sone.SoneStatus;
 import net.pterodactylus.sone.freenet.wot.Identity;
+import net.pterodactylus.sone.test.GuiceKt;
 
 import freenet.client.ClientMetadata;
 import freenet.client.FetchResult;
@@ -36,40 +38,30 @@ import freenet.support.api.Bucket;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
 
 /**
  * Unit test for {@link SoneDownloaderImpl} and its subclasses.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class SoneDownloaderTest {
 
-       private final Core core = mock(Core.class);
        private final FreenetInterface freenetInterface = mock(FreenetInterface.class);
        private final SoneParser soneParser = mock(SoneParser.class);
-       private final SoneDownloaderImpl soneDownloader = new SoneDownloaderImpl(core, freenetInterface, soneParser);
-       private FreenetURI requestUri = mock(FreenetURI.class);
-       private Sone sone = mock(Sone.class);
+       private final UpdatedSoneProcessor updatedSoneProcessor = mock(UpdatedSoneProcessor.class);
+       private final SoneDownloaderImpl soneDownloader = new SoneDownloaderImpl(updatedSoneProcessor, freenetInterface, soneParser);
+       private final InsertableClientSSK clientSSK = createRandom(new DummyRandomSource(), "WoT");
+       private final FreenetURI requestUri = clientSSK.getURI().setKeyType("USK").setDocName("Sone");
+       private final FreenetURI finalRequestUri = requestUri.setMetaString(new String[] { "sone.xml" });
+       private final Sone sone = mock(Sone.class);
+       private final Sone parsedSone = mock(Sone.class);
 
        @Before
        public void setupSone() {
-               Sone sone = SoneDownloaderTest.this.sone;
                Identity identity = mock(Identity.class);
-               InsertableClientSSK clientSSK = createRandom(new DummyRandomSource(), "WoT");
                when(identity.getRequestUri()).thenReturn(clientSSK.getURI().toString());
                when(identity.getId()).thenReturn("identity");
                when(sone.getId()).thenReturn("identity");
                when(sone.getIdentity()).thenReturn(identity);
-               requestUri = clientSSK.getURI().setKeyType("USK").setDocName("Sone");
-               when(sone.getRequestUri()).thenAnswer(new Answer<FreenetURI>() {
-                       @Override
-                       public FreenetURI answer(InvocationOnMock invocation)
-                       throws Throwable {
-                               return requestUri;
-                       }
-               });
+               when(sone.getRequestUri()).thenReturn(requestUri);
                when(sone.getTime()).thenReturn(currentTimeMillis() - DAYS.toMillis(1));
        }
 
@@ -104,13 +96,11 @@ public class SoneDownloaderTest {
 
        @Test
        public void notBeingAbleToFetchAnUnknownSoneDoesNotUpdateCore() {
-               FreenetURI finalRequestUri = requestUri.sskForUSK()
-                               .setMetaString(new String[] { "sone.xml" });
                setupSoneAsUnknown();
-               soneDownloader.fetchSoneAction(sone).run();
-               verify(freenetInterface).fetchUri(finalRequestUri);
+               soneDownloader.fetchSoneAsSskAction(sone).run();
+               verify(freenetInterface).fetchUri(finalRequestUri.sskForUSK());
                verifyThatSoneStatusWasChangedToDownloadingAndBackTo(unknown);
-               verify(core, never()).updateSone(any(Sone.class));
+               verify(updatedSoneProcessor, never()).updateSone(any(Sone.class));
        }
 
        private void verifyThatSoneStatusWasChangedToDownloadingAndBackTo(SoneStatus soneStatus) {
@@ -122,75 +112,55 @@ public class SoneDownloaderTest {
 
        @Test
        public void notBeingAbleToFetchAKnownSoneDoesNotUpdateCore() {
-               FreenetURI finalRequestUri = requestUri.sskForUSK()
-                               .setMetaString(new String[] { "sone.xml" });
-               soneDownloader.fetchSoneAction(sone).run();
-               verify(freenetInterface).fetchUri(finalRequestUri);
+               soneDownloader.fetchSoneAsSskAction(sone).run();
+               verify(freenetInterface).fetchUri(finalRequestUri.sskForUSK());
                verifyThatSoneStatusWasChangedToDownloadingAndBackTo(idle);
-               verify(core, never()).updateSone(any(Sone.class));
+               verify(updatedSoneProcessor, never()).updateSone(any(Sone.class));
        }
 
        @Test(expected = NullPointerException.class)
-       public void exceptionWhileFetchingAnUnknownSoneDoesNotUpdateCore() {
-               FreenetURI finalRequestUri = requestUri.sskForUSK()
-                               .setMetaString(new String[] { "sone.xml" });
-               setupSoneAsUnknown();
-               when(freenetInterface.fetchUri(finalRequestUri)).thenThrow(NullPointerException.class);
-               try {
-                       soneDownloader.fetchSoneAction(sone).run();
-               } finally {
-                       verify(freenetInterface).fetchUri(finalRequestUri);
-                       verifyThatSoneStatusWasChangedToDownloadingAndBackTo(unknown);
-                       verify(core, never()).updateSone(any(Sone.class));
-               }
-       }
-
-       @Test(expected = NullPointerException.class)
-       public void exceptionWhileFetchingAKnownSoneDoesNotUpdateCore() {
-               FreenetURI finalRequestUri = requestUri.sskForUSK()
-                               .setMetaString(new String[] { "sone.xml" });
-               when(freenetInterface.fetchUri(finalRequestUri)).thenThrow( NullPointerException.class);
+       public void exceptionWhileFetchingSoneDoesNotProcessUpdatedSone() {
+               when(freenetInterface.fetchUri(any(FreenetURI.class))).thenThrow(NullPointerException.class);
                try {
-                       soneDownloader.fetchSoneAction(sone).run();
+                       soneDownloader.fetchSoneAsSskAction(sone).run();
                } finally {
-                       verify(freenetInterface).fetchUri(finalRequestUri);
-                       verifyThatSoneStatusWasChangedToDownloadingAndBackTo(idle);
-                       verify(core, never()).updateSone(any(Sone.class));
+                       verify(updatedSoneProcessor, never()).updateSone(any(Sone.class));
                }
        }
 
        @Test
-       public void fetchingSoneWithInvalidXmlWillNotUpdateTheCore() throws IOException {
-               final Fetched fetchResult = createFetchResult(requestUri, getClass().getResourceAsStream("sone-parser-not-xml.xml"));
-               when(freenetInterface.fetchUri(requestUri)).thenReturn(fetchResult);
-               soneDownloader.fetchSoneAction(sone).run();
-               verify(core, never()).updateSone(any(Sone.class));
-       }
-
-       @Test
-       public void exceptionWhileFetchingSoneWillNotUpdateTheCore() throws IOException {
-               final Fetched fetchResult = createFetchResult(requestUri, getClass().getResourceAsStream("sone-parser-no-payload.xml"));
-               when(core.soneBuilder()).thenReturn(null);
-               when(freenetInterface.fetchUri(requestUri)).thenReturn(fetchResult);
-               soneDownloader.fetchSoneAction(sone).run();
-               verify(core, never()).updateSone(any(Sone.class));
+       public void onlyFetchingASoneWillNotUpdateTheCore() throws IOException, SoneException {
+               setupParsedSone();
+               soneDownloader.fetchSone(sone, sone.getRequestUri(), true);
+               verify(updatedSoneProcessor, never()).updateSone(any(Sone.class));
+               verifyThatSoneStatusWasChangedToDownloadingAndBackTo(idle);
        }
 
        @Test
-       public void onlyFetchingASoneWillNotUpdateTheCore() throws IOException {
-               final Fetched fetchResult = createFetchResult(requestUri, getClass().getResourceAsStream("sone-parser-no-payload.xml"));
-               when(freenetInterface.fetchUri(requestUri)).thenReturn(fetchResult);
-               soneDownloader.fetchSone(sone, sone.getRequestUri(), true);
-               verify(core, never()).updateSone(any(Sone.class));
+       public void fetchingACompleteSoneNotifiesTheUpdatedSoneProcessor() throws IOException, SoneException {
+               setupParsedSone();
+               soneDownloader.fetchSone(sone, sone.getRequestUri(), false);
+               verify(updatedSoneProcessor).updateSone(parsedSone);
                verifyThatSoneStatusWasChangedToDownloadingAndBackTo(idle);
        }
 
-       private Fetched createFetchResult(FreenetURI uri, InputStream inputStream) throws IOException {
+       private void setupParsedSone() throws IOException, SoneException {
+               InputStream inputStream = mock(InputStream.class);
                ClientMetadata clientMetadata = new ClientMetadata("application/xml");
                Bucket bucket = mock(Bucket.class);
                when(bucket.getInputStream()).thenReturn(inputStream);
                FetchResult fetchResult = new FetchResult(clientMetadata, bucket);
-               return new Fetched(uri, fetchResult);
+               Fetched fetched = new Fetched(finalRequestUri, fetchResult);
+               when(freenetInterface.fetchUri(eq(finalRequestUri))).thenReturn(fetched);
+               when(soneParser.parseSone(sone, inputStream)).thenReturn(parsedSone);
+       }
+
+       @Test
+       public void soneDownloaderCanBeCreatedByDependencyInjection() {
+               assertThat(getBaseInjector().createChildInjector(
+                               GuiceKt.supply(UpdatedSoneProcessor.class).byInstance(mock(UpdatedSoneProcessor.class)),
+                               GuiceKt.supply(SoneParser.class).byInstance(mock(SoneParser.class))
+               ).getInstance(SoneDownloader.class), notNullValue());
        }
 
 }
index 5b94f08..31d7b6b 100644 (file)
@@ -1,7 +1,7 @@
 package net.pterodactylus.sone.core;
 
 import static com.google.common.io.ByteStreams.toByteArray;
-import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor;
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
 import static java.lang.System.currentTimeMillis;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.containsString;
@@ -48,8 +48,6 @@ import org.mockito.stubbing.Answer;
 
 /**
  * Unit test for {@link SoneInserter} and its subclasses.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class SoneInserterTest {
 
@@ -66,7 +64,7 @@ public class SoneInserterTest {
 
        @Test
        public void insertionDelayIsForwardedToSoneInserter() {
-               EventBus eventBus = new AsyncEventBus(sameThreadExecutor());
+               EventBus eventBus = new AsyncEventBus(directExecutor());
                eventBus.register(new SoneInserter(core, eventBus, freenetInterface, "SoneId"));
                eventBus.post(new InsertionDelayChangedEvent(15));
                assertThat(SoneInserter.getInsertionDelay().get(), is(15));
@@ -250,7 +248,7 @@ public class SoneInserterTest {
        @Test
        public void templateIsRenderedCorrectlyForManifestElement()
        throws IOException {
-               Map<String, Object> soneProperties = new HashMap<String, Object>();
+               Map<String, Object> soneProperties = new HashMap<>();
                soneProperties.put("id", "SoneId");
                ManifestCreator manifestCreator = new ManifestCreator(core, soneProperties);
                long now = currentTimeMillis();
@@ -266,7 +264,7 @@ public class SoneInserterTest {
 
        @Test
        public void invalidTemplateReturnsANullManifestElement() {
-               Map<String, Object> soneProperties = new HashMap<String, Object>();
+               Map<String, Object> soneProperties = new HashMap<>();
                ManifestCreator manifestCreator = new ManifestCreator(core, soneProperties);
                assertThat(manifestCreator.createManifestElement("test.txt",
                                "plain/text; charset=utf-8",
@@ -276,7 +274,7 @@ public class SoneInserterTest {
 
        @Test
        public void errorWhileRenderingTemplateReturnsANullManifestElement() {
-               Map<String, Object> soneProperties = new HashMap<String, Object>();
+               Map<String, Object> soneProperties = new HashMap<>();
                ManifestCreator manifestCreator = new ManifestCreator(core, soneProperties);
                when(core.toString()).thenThrow(NullPointerException.class);
                assertThat(manifestCreator.createManifestElement("test.txt",
index a45556a..1fd52c8 100644 (file)
@@ -16,8 +16,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link SoneModificationDetector}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class SoneModificationDetectorTest {
 
index da5ca50..400b9ae 100644 (file)
@@ -36,6 +36,7 @@ import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Profile;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.database.AlbumBuilder;
+import net.pterodactylus.sone.database.Database;
 import net.pterodactylus.sone.database.ImageBuilder;
 import net.pterodactylus.sone.database.PostBuilder;
 import net.pterodactylus.sone.database.PostReplyBuilder;
@@ -59,30 +60,28 @@ import org.mockito.stubbing.Answer;
 
 /**
  * Unit test for {@link SoneParser}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class SoneParserTest {
 
-       private final Core core = mock(Core.class);
-       private final SoneParser soneParser = new SoneParser(core);
+       private final Database database = mock(Database.class);
+       private final SoneParser soneParser = new SoneParser(database);
        private final Sone sone = mock(Sone.class);
        private FreenetURI requestUri = mock(FreenetURI.class);
        private final PostBuilder postBuilder = mock(PostBuilder.class);
-       private final List<Post> createdPosts = new ArrayList<Post>();
+       private final List<Post> createdPosts = new ArrayList<>();
        private Post post = mock(Post.class);
        private final PostReplyBuilder postReplyBuilder = mock(PostReplyBuilder.class);
-       private final Set<PostReply> createdPostReplies = new HashSet<PostReply>();
+       private final Set<PostReply> createdPostReplies = new HashSet<>();
        private PostReply postReply = mock(PostReply.class);
        private final AlbumBuilder albumBuilder = mock(AlbumBuilder.class);
        private final ListMultimap<Album, Album>
                        nestedAlbums = ArrayListMultimap.create();
        private final ListMultimap<Album, Image> albumImages = ArrayListMultimap.create();
        private Album album = mock(Album.class);
-       private final Map<String, Album> albums = new HashMap<String, Album>();
+       private final Map<String, Album> albums = new HashMap<>();
        private final ImageBuilder imageBuilder = mock(ImageBuilder.class);
        private Image image = mock(Image.class);
-       private final Map<String, Image> images = new HashMap<String, Image>();
+       private final Map<String, Image> images = new HashMap<>();
 
        @Before
        public void setupSone() {
@@ -111,7 +110,7 @@ public class SoneParserTest {
 
        @Before
        public void setupSoneBuilder() {
-               when(core.soneBuilder()).thenAnswer(new Answer<SoneBuilder>() {
+               when(database.newSoneBuilder()).thenAnswer(new Answer<SoneBuilder>() {
                        @Override
                        public SoneBuilder answer(InvocationOnMock invocation) {
                                return new MemorySoneBuilder(null);
@@ -173,7 +172,7 @@ public class SoneParserTest {
                                return post;
                        }
                });
-               when(core.postBuilder()).thenReturn(postBuilder);
+               when(database.newPostBuilder()).thenReturn(postBuilder);
        }
 
        @Before
@@ -237,7 +236,7 @@ public class SoneParserTest {
                                return postReply;
                        }
                });
-               when(core.postReplyBuilder()).thenReturn(postReplyBuilder);
+               when(database.newPostReplyBuilder()).thenReturn(postReplyBuilder);
        }
 
        @Before
@@ -328,12 +327,12 @@ public class SoneParserTest {
                                return album;
                        }
                });
-               when(core.albumBuilder()).thenReturn(albumBuilder);
+               when(database.newAlbumBuilder()).thenReturn(albumBuilder);
        }
 
        @Before
        public void setupAlbums() {
-               when(core.getAlbum(anyString())).thenAnswer(new Answer<Album>() {
+               when(database.getAlbum(anyString())).thenAnswer(new Answer<Album>() {
                        @Override
                        public Album answer(InvocationOnMock invocation)
                        throws Throwable {
@@ -439,12 +438,12 @@ public class SoneParserTest {
                                return image;
                        }
                });
-               when(core.imageBuilder()).thenReturn(imageBuilder);
+               when(database.newImageBuilder()).thenReturn(imageBuilder);
        }
 
        @Before
        public void setupImages() {
-               when(core.getImage(anyString())).thenAnswer(new Answer<Image>() {
+               when(database.getImage(anyString())).thenAnswer(new Answer<Image>() {
                        @Override
                        public Image answer(InvocationOnMock invocation)
                        throws Throwable {
index 0b339fb..ff49a4c 100644 (file)
@@ -21,8 +21,6 @@ import org.mockito.stubbing.Answer;
 
 /**
  * Unit test for {@link SoneRescuer}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class SoneRescuerTest {
 
index 879da4a..3525c9b 100644 (file)
@@ -13,8 +13,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link SoneUri}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class SoneUriTest {
 
index ad6b331..e7f1afe 100644 (file)
@@ -17,8 +17,8 @@ import java.io.IOException;
 import java.io.InputStream;
 
 import net.pterodactylus.sone.core.FreenetInterface.Callback;
-import net.pterodactylus.sone.core.FreenetInterface.Fetched;
 import net.pterodactylus.sone.core.event.UpdateFoundEvent;
+import net.pterodactylus.sone.main.PluginHomepage;
 import net.pterodactylus.util.version.Version;
 
 import freenet.client.ClientMetadata;
@@ -36,15 +36,14 @@ import org.mockito.stubbing.Answer;
 
 /**
  * Unit test for {@link UpdateChecker}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class UpdateCheckerTest {
 
        private final EventBus eventBus = mock(EventBus.class);
        private final FreenetInterface freenetInterface = mock(FreenetInterface.class);
        private final Version currentVersion = new Version(1, 0, 0);
-       private final UpdateChecker updateChecker = new UpdateChecker(eventBus, freenetInterface, currentVersion);
+       private final PluginHomepage pluginHomepage = new PluginHomepage("KSK@homepage");
+       private final UpdateChecker updateChecker = new UpdateChecker(eventBus, freenetInterface, currentVersion, pluginHomepage);
 
        @Before
        public void startUpdateChecker() {
index 7529fc6..664e4a1 100644 (file)
@@ -35,8 +35,6 @@ import org.mockito.stubbing.Answer;
 
 /**
  * Unit test for {@link WebOfTrustUpdaterImpl} and its subclasses.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class WebOfTrustUpdaterTest {
 
diff --git a/src/test/java/net/pterodactylus/sone/data/ProfileTest.java b/src/test/java/net/pterodactylus/sone/data/ProfileTest.java
deleted file mode 100644 (file)
index b06e2f4..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-package net.pterodactylus.sone.data;
-
-import net.pterodactylus.sone.data.Profile.Field;
-
-import org.hamcrest.MatcherAssert;
-import org.hamcrest.Matchers;
-import org.junit.Test;
-import org.mockito.Mockito;
-
-/**
- * Unit test for {@link Profile}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class ProfileTest {
-
-       private final Sone sone = Mockito.mock(Sone.class);
-       private final Profile profile = new Profile(sone);
-
-       @Test
-       public void newFieldsAreInitializedWithAnEmptyString() {
-               Field newField = profile.addField("testField");
-               MatcherAssert.assertThat(newField.getValue(), Matchers.is(""));
-       }
-
-}
index b2d86dd..8489d3e 100644 (file)
@@ -10,8 +10,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link AbstractSoneBuilder}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class AbstractSoneBuilderTest {
 
index b78f0f6..b3801b2 100644 (file)
@@ -7,8 +7,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link ImageImpl}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class ImageImplTest {
 
diff --git a/src/test/java/net/pterodactylus/sone/database/memory/ConfigurationLoaderTest.java b/src/test/java/net/pterodactylus/sone/database/memory/ConfigurationLoaderTest.java
deleted file mode 100644 (file)
index 8dfce3d..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-package net.pterodactylus.sone.database.memory;
-
-import static java.util.Arrays.asList;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.containsInAnyOrder;
-import static org.hamcrest.Matchers.nullValue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.util.HashSet;
-import java.util.Set;
-
-import net.pterodactylus.sone.test.TestValue;
-import net.pterodactylus.util.config.Configuration;
-import net.pterodactylus.util.config.ConfigurationException;
-import net.pterodactylus.util.config.Value;
-
-import org.junit.Test;
-
-/**
- * Unit test for {@link ConfigurationLoader}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class ConfigurationLoaderTest {
-
-       private final Configuration configuration = mock(Configuration.class);
-       private final ConfigurationLoader configurationLoader =
-                       new ConfigurationLoader(configuration);
-
-       @Test
-       public void loaderCanLoadKnownPosts() {
-               when(configuration.getStringValue("KnownPosts/0/ID"))
-                               .thenReturn(TestValue.from("Post2"));
-               when(configuration.getStringValue("KnownPosts/1/ID"))
-                               .thenReturn(TestValue.from("Post1"));
-               when(configuration.getStringValue("KnownPosts/2/ID"))
-                               .thenReturn(TestValue.<String>from(null));
-               Set<String> knownPosts = configurationLoader.loadKnownPosts();
-               assertThat(knownPosts, containsInAnyOrder("Post1", "Post2"));
-       }
-
-       @Test
-       public void loaderCanLoadKnownPostReplies() {
-               when(configuration.getStringValue("KnownReplies/0/ID"))
-                               .thenReturn(TestValue.from("PostReply2"));
-               when(configuration.getStringValue("KnownReplies/1/ID"))
-                               .thenReturn(TestValue.from("PostReply1"));
-               when(configuration.getStringValue("KnownReplies/2/ID"))
-                               .thenReturn(TestValue.<String>from(null));
-               Set<String> knownPosts = configurationLoader.loadKnownPostReplies();
-               assertThat(knownPosts,
-                               containsInAnyOrder("PostReply1", "PostReply2"));
-       }
-
-       @Test
-       public void loaderCanLoadBookmarkedPosts() {
-               when(configuration.getStringValue("Bookmarks/Post/0/ID"))
-                               .thenReturn(TestValue.from("Post2"));
-               when(configuration.getStringValue("Bookmarks/Post/1/ID"))
-                               .thenReturn(TestValue.from("Post1"));
-               when(configuration.getStringValue("Bookmarks/Post/2/ID"))
-                               .thenReturn(TestValue.<String>from(null));
-               Set<String> knownPosts = configurationLoader.loadBookmarkedPosts();
-               assertThat(knownPosts, containsInAnyOrder("Post1", "Post2"));
-       }
-
-       @Test
-       public void loaderCanSaveBookmarkedPosts() throws ConfigurationException {
-               final Value<String> post1 = TestValue.<String>from(null);
-               final Value<String> post2 = TestValue.<String>from(null);
-               final Value<String> post3 = TestValue.<String>from(null);
-               when(configuration.getStringValue("Bookmarks/Post/0/ID")).thenReturn(post1);
-               when(configuration.getStringValue("Bookmarks/Post/1/ID")).thenReturn(post2);
-               when(configuration.getStringValue("Bookmarks/Post/2/ID")).thenReturn(post3);
-               HashSet<String> originalPosts = new HashSet<String>(asList("Post1", "Post2"));
-               configurationLoader.saveBookmarkedPosts(originalPosts);
-               HashSet<String> extractedPosts =
-                               new HashSet<String>(asList(post1.getValue(), post2.getValue()));
-               assertThat(extractedPosts, containsInAnyOrder("Post1", "Post2"));
-               assertThat(post3.getValue(), nullValue());
-       }
-
-}
index 7edea06..188b740 100644 (file)
@@ -25,8 +25,6 @@ import org.mockito.stubbing.Answer;
 
 /**
  * Unit test for {@link MemoryBookmarkDatabase}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class MemoryBookmarkDatabaseTest {
 
@@ -35,7 +33,7 @@ public class MemoryBookmarkDatabaseTest {
                        mock(ConfigurationLoader.class);
        private final MemoryBookmarkDatabase bookmarkDatabase =
                        new MemoryBookmarkDatabase(memoryDatabase, configurationLoader);
-       private final Map<String, Post> posts = new HashMap<String, Post>();
+       private final Map<String, Post> posts = new HashMap<>();
 
        @Before
        public void setupMemoryDatabase() {
@@ -69,7 +67,7 @@ public class MemoryBookmarkDatabaseTest {
 
        @Test
        public void bookmarkDatabaseRetainsBookmarkedPosts() {
-               Set<Post> allPosts = new HashSet<Post>(posts.values());
+               Set<Post> allPosts = new HashSet<>(posts.values());
                for (Post post : allPosts) {
                        bookmarkDatabase.bookmarkPost(post);
                }
@@ -100,7 +98,7 @@ public class MemoryBookmarkDatabaseTest {
 
        @Test
        public void removingABookmarkRemovesTheCorrectBookmark() {
-               Set<Post> allPosts = new HashSet<Post>(posts.values());
+               Set<Post> allPosts = new HashSet<>(posts.values());
                for (Post post : allPosts) {
                        bookmarkDatabase.bookmarkPost(post);
                }
diff --git a/src/test/java/net/pterodactylus/sone/database/memory/MemoryDatabaseTest.java b/src/test/java/net/pterodactylus/sone/database/memory/MemoryDatabaseTest.java
deleted file mode 100644 (file)
index bb62eaf..0000000
+++ /dev/null
@@ -1,386 +0,0 @@
-/*
- * 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
- * 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.database.memory;
-
-import static com.google.common.base.Optional.of;
-import static java.util.Arrays.asList;
-import static java.util.UUID.randomUUID;
-import static net.pterodactylus.sone.test.Matchers.isAlbum;
-import static net.pterodactylus.sone.test.Matchers.isImage;
-import static net.pterodactylus.sone.test.Matchers.isPost;
-import static net.pterodactylus.sone.test.Matchers.isPostReply;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.contains;
-import static org.hamcrest.Matchers.containsInAnyOrder;
-import static org.hamcrest.Matchers.empty;
-import static org.hamcrest.Matchers.emptyIterable;
-import static org.hamcrest.Matchers.nullValue;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import net.pterodactylus.sone.data.Album;
-import net.pterodactylus.sone.data.Image;
-import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.PostReply;
-import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.sone.data.impl.AlbumImpl;
-import net.pterodactylus.sone.test.TestAlbumBuilder;
-import net.pterodactylus.sone.test.TestImageBuilder;
-import net.pterodactylus.sone.test.TestPostBuilder;
-import net.pterodactylus.sone.test.TestPostReplyBuilder;
-import net.pterodactylus.sone.test.TestValue;
-import net.pterodactylus.util.config.Configuration;
-import net.pterodactylus.util.config.Value;
-
-import com.google.common.base.Optional;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-/**
- * Tests for {@link MemoryDatabase}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class MemoryDatabaseTest {
-
-       private static final String SONE_ID = "sone";
-       private static final String RECIPIENT_ID = "recipient";
-       private final Configuration configuration = mock(Configuration.class);
-       private final MemoryDatabase memoryDatabase = new MemoryDatabase(null, configuration);
-       private final Sone sone = mock(Sone.class);
-
-       @Before
-       public void setupSone() {
-               when(sone.getId()).thenReturn(SONE_ID);
-       }
-
-       @Test
-       public void storedSoneIsMadeAvailable() {
-               Post firstPost = new TestPostBuilder().withId("post1")
-                               .from(SONE_ID)
-                               .withTime(1000L)
-                               .withText("post1")
-                               .build();
-               Post secondPost = new TestPostBuilder().withId("post2")
-                               .from(SONE_ID)
-                               .withTime(2000L)
-                               .withText("post2")
-                               .to(RECIPIENT_ID)
-                               .build();
-               List<Post> posts = asList(firstPost, secondPost);
-               when(sone.getPosts()).thenReturn(posts);
-               PostReply firstPostFirstReply =
-                               new TestPostReplyBuilder().withId("reply1")
-                                               .from(SONE_ID)
-                                               .to(firstPost.getId())
-                                               .withTime(3000L)
-                                               .withText("reply1")
-                                               .build();
-               PostReply firstPostSecondReply =
-                               new TestPostReplyBuilder().withId("reply3")
-                                               .from(RECIPIENT_ID)
-                                               .to(firstPost.getId())
-                                               .withTime(5000L)
-                                               .withText("reply3")
-                                               .build();
-               PostReply secondPostReply =
-                               new TestPostReplyBuilder().withId("reply2")
-                                               .from(SONE_ID)
-                                               .to(secondPost.getId())
-                                               .withTime(4000L)
-                                               .withText("reply2")
-                                               .build();
-               Set<PostReply> postReplies = new HashSet<PostReply>(
-                               asList(firstPostFirstReply, firstPostSecondReply,
-                                               secondPostReply));
-               when(sone.getReplies()).thenReturn(postReplies);
-               Album firstAlbum = new TestAlbumBuilder().withId("album1")
-                               .by(sone)
-                               .build()
-                               .modify()
-                               .setTitle("album1")
-                               .setDescription("album-description1")
-                               .update();
-               Album secondAlbum = new TestAlbumBuilder().withId("album2").by(
-                               sone).build().modify().setTitle("album2").setDescription(
-                               "album-description2").update();
-               Album thirdAlbum = new TestAlbumBuilder().withId("album3").by(
-                               sone).build().modify().setTitle("album3").setDescription(
-                               "album-description3").update();
-               firstAlbum.addAlbum(thirdAlbum);
-               Album rootAlbum = mock(Album.class);
-               when(rootAlbum.getAlbums()).thenReturn(
-                               asList(firstAlbum, secondAlbum));
-               when(sone.getRootAlbum()).thenReturn(rootAlbum);
-               Image firstImage = new TestImageBuilder().withId("image1")
-                               .build()
-                               .modify()
-                               .setSone(sone)
-                               .setCreationTime(1000L)
-                               .setKey("KSK@image1")
-                               .setTitle("image1")
-                               .setDescription("image-description1")
-                               .setWidth(16)
-                               .setHeight(9)
-                               .update();
-               Image secondImage = new TestImageBuilder().withId("image2")
-                               .build()
-                               .modify()
-                               .setSone(sone)
-                               .setCreationTime(2000L)
-                               .setKey("KSK@image2")
-                               .setTitle("image2")
-                               .setDescription("image-description2")
-                               .setWidth(32)
-                               .setHeight(18)
-                               .update();
-               Image thirdImage = new TestImageBuilder().withId("image3")
-                               .build()
-                               .modify()
-                               .setSone(sone)
-                               .setCreationTime(3000L)
-                               .setKey("KSK@image3")
-                               .setTitle("image3")
-                               .setDescription("image-description3")
-                               .setWidth(48)
-                               .setHeight(27)
-                               .update();
-               firstAlbum.addImage(firstImage);
-               firstAlbum.addImage(thirdImage);
-               secondAlbum.addImage(secondImage);
-               memoryDatabase.storeSone(sone);
-               assertThat(memoryDatabase.getPost("post1"),
-                               isPost(firstPost.getId(), 1000L, "post1",
-                                               Optional.<String>absent()));
-               assertThat(memoryDatabase.getPost("post2"),
-                               isPost(secondPost.getId(), 2000L, "post2", of(RECIPIENT_ID)));
-               assertThat(memoryDatabase.getPost("post3"), nullValue());
-               assertThat(memoryDatabase.getPostReply("reply1"),
-                               isPostReply("reply1", "post1", 3000L, "reply1"));
-               assertThat(memoryDatabase.getPostReply("reply2"),
-                               isPostReply("reply2", "post2", 4000L, "reply2"));
-               assertThat(memoryDatabase.getPostReply("reply3"),
-                               isPostReply("reply3", "post1", 5000L, "reply3"));
-               assertThat(memoryDatabase.getPostReply("reply4"), nullValue());
-               assertThat(memoryDatabase.getAlbum("album1"),
-                               isAlbum("album1", null, "album1", "album-description1"));
-               assertThat(memoryDatabase.getAlbum("album2"),
-                               isAlbum("album2", null, "album2", "album-description2"));
-               assertThat(memoryDatabase.getAlbum("album3"),
-                               isAlbum("album3", "album1", "album3", "album-description3"));
-               assertThat(memoryDatabase.getAlbum("album4"), nullValue());
-               assertThat(memoryDatabase.getImage("image1"),
-                               isImage("image1", 1000L, "KSK@image1", "image1",
-                                               "image-description1", 16, 9));
-               assertThat(memoryDatabase.getImage("image2"),
-                               isImage("image2", 2000L, "KSK@image2", "image2",
-                                               "image-description2", 32, 18));
-               assertThat(memoryDatabase.getImage("image3"),
-                               isImage("image3", 3000L, "KSK@image3", "image3",
-                                               "image-description3", 48, 27));
-               assertThat(memoryDatabase.getImage("image4"), nullValue());
-       }
-
-       @Test
-       public void storedAndRemovedSoneIsNotAvailable() {
-           storedSoneIsMadeAvailable();
-               memoryDatabase.removeSone(sone);
-               assertThat(memoryDatabase.getSones(), empty());
-       }
-
-       @Test
-       public void postRecipientsAreDetectedCorrectly() {
-               Post postWithRecipient = createPost(of(RECIPIENT_ID));
-               memoryDatabase.storePost(postWithRecipient);
-               Post postWithoutRecipient = createPost(Optional.<String>absent());
-               memoryDatabase.storePost(postWithoutRecipient);
-               assertThat(memoryDatabase.getDirectedPosts(RECIPIENT_ID),
-                               contains(postWithRecipient));
-       }
-
-       private Post createPost(Optional<String> recipient) {
-               Post postWithRecipient = mock(Post.class);
-               when(postWithRecipient.getId()).thenReturn(randomUUID().toString());
-               when(postWithRecipient.getSone()).thenReturn(sone);
-               when(postWithRecipient.getRecipientId()).thenReturn(recipient);
-               return postWithRecipient;
-       }
-
-       @Test
-       public void postRepliesAreManagedCorrectly() {
-               Post firstPost = createPost(Optional.<String>absent());
-               PostReply firstPostFirstReply = createPostReply(firstPost, 1000L);
-               Post secondPost = createPost(Optional.<String>absent());
-               PostReply secondPostFirstReply = createPostReply(secondPost, 1000L);
-               PostReply secondPostSecondReply = createPostReply(secondPost, 2000L);
-               memoryDatabase.storePost(firstPost);
-               memoryDatabase.storePost(secondPost);
-               memoryDatabase.storePostReply(firstPostFirstReply);
-               memoryDatabase.storePostReply(secondPostFirstReply);
-               memoryDatabase.storePostReply(secondPostSecondReply);
-               assertThat(memoryDatabase.getReplies(firstPost.getId()),
-                               contains(firstPostFirstReply));
-               assertThat(memoryDatabase.getReplies(secondPost.getId()),
-                               contains(secondPostFirstReply, secondPostSecondReply));
-       }
-
-       private PostReply createPostReply(Post post, long time) {
-               PostReply postReply = mock(PostReply.class);
-               when(postReply.getId()).thenReturn(randomUUID().toString());
-               when(postReply.getTime()).thenReturn(time);
-               when(postReply.getPost()).thenReturn(of(post));
-               final String postId = post.getId();
-               when(postReply.getPostId()).thenReturn(postId);
-               return postReply;
-       }
-
-       @Test
-       public void testBasicAlbumFunctionality() {
-               Album newAlbum = new AlbumImpl(mock(Sone.class));
-               assertThat(memoryDatabase.getAlbum(newAlbum.getId()), nullValue());
-               memoryDatabase.storeAlbum(newAlbum);
-               assertThat(memoryDatabase.getAlbum(newAlbum.getId()), is(newAlbum));
-               memoryDatabase.removeAlbum(newAlbum);
-               assertThat(memoryDatabase.getAlbum(newAlbum.getId()), nullValue());
-       }
-
-       private void initializeFriends() {
-               when(configuration.getStringValue("Sone/" + SONE_ID + "/Friends/0/ID")).thenReturn(
-                               TestValue.from("Friend1"));
-               when(configuration.getStringValue("Sone/" + SONE_ID + "/Friends/1/ID")).thenReturn(
-                               TestValue.from("Friend2"));
-               when(configuration.getStringValue("Sone/" + SONE_ID + "/Friends/2/ID")).thenReturn(
-                               TestValue.<String>from(null));
-       }
-
-       @Test
-       public void friendsAreReturnedCorrectly() {
-               initializeFriends();
-               when(sone.isLocal()).thenReturn(true);
-               Collection<String> friends = memoryDatabase.getFriends(sone);
-               assertThat(friends, containsInAnyOrder("Friend1", "Friend2"));
-       }
-
-       @Test
-       public void friendsAreOnlyLoadedOnceFromConfiguration() {
-               friendsAreReturnedCorrectly();
-               memoryDatabase.getFriends(sone);
-               verify(configuration).getStringValue("Sone/" + SONE_ID + "/Friends/0/ID");
-       }
-
-       @Test
-       public void friendsAreOnlyReturnedForLocalSones() {
-               Collection<String> friends = memoryDatabase.getFriends(sone);
-               assertThat(friends, emptyIterable());
-               verify(configuration, never()).getStringValue("Sone/" + SONE_ID + "/Friends/0/ID");
-       }
-
-       @Test
-       public void checkingForAFriendReturnsTrue() {
-               initializeFriends();
-               when(sone.isLocal()).thenReturn(true);
-               assertThat(memoryDatabase.isFriend(sone, "Friend1"), is(true));
-       }
-
-       @Test
-       public void checkingForAFriendThatIsNotAFriendReturnsFalse() {
-               initializeFriends();
-               when(sone.isLocal()).thenReturn(true);
-               assertThat(memoryDatabase.isFriend(sone, "FriendX"), is(false));
-       }
-
-       @Test
-       public void checkingForAFriendOfRemoteSoneReturnsFalse() {
-               initializeFriends();
-               assertThat(memoryDatabase.isFriend(sone, "Friend1"), is(false));
-       }
-
-       private Map<String, Value<String>> prepareConfigurationValues() {
-               final Map<String, Value<String>> configurationValues = new HashMap<String, Value<String>>();
-               when(configuration.getStringValue(anyString())).thenAnswer(new Answer<Value<String>>() {
-                       @Override
-                       public Value<String> answer(InvocationOnMock invocation) throws Throwable {
-                               Value<String> stringValue = TestValue.from(null);
-                               configurationValues.put((String) invocation.getArguments()[0], stringValue);
-                               return stringValue;
-                       }
-               });
-               return configurationValues;
-       }
-
-       @Test
-       public void friendIsAddedCorrectlyToLocalSone() {
-               Map<String, Value<String>> configurationValues = prepareConfigurationValues();
-               when(sone.isLocal()).thenReturn(true);
-               memoryDatabase.addFriend(sone, "Friend1");
-               assertThat(configurationValues.get("Sone/" + SONE_ID + "/Friends/0/ID"),
-                               is(TestValue.from("Friend1")));
-               assertThat(configurationValues.get("Sone/" + SONE_ID + "/Friends/1/ID"),
-                               is(TestValue.<String>from(null)));
-       }
-
-       @Test
-       public void friendIsNotAddedToRemoteSone() {
-               memoryDatabase.addFriend(sone, "Friend1");
-               verify(configuration, never()).getStringValue(anyString());
-       }
-
-       @Test
-       public void configurationIsWrittenOnceIfFriendIsAddedTwice() {
-               prepareConfigurationValues();
-               when(sone.isLocal()).thenReturn(true);
-               memoryDatabase.addFriend(sone, "Friend1");
-               memoryDatabase.addFriend(sone, "Friend1");
-               verify(configuration, times(3)).getStringValue(anyString());
-       }
-
-       @Test
-       public void friendIsRemovedCorrectlyFromLocalSone() {
-               Map<String, Value<String>> configurationValues = prepareConfigurationValues();
-               when(sone.isLocal()).thenReturn(true);
-               memoryDatabase.addFriend(sone, "Friend1");
-               memoryDatabase.removeFriend(sone, "Friend1");
-               assertThat(configurationValues.get("Sone/" + SONE_ID + "/Friends/0/ID"),
-                               is(TestValue.<String>from(null)));
-               assertThat(configurationValues.get("Sone/" + SONE_ID + "/Friends/1/ID"),
-                               is(TestValue.<String>from(null)));
-       }
-
-       @Test
-       public void configurationIsNotWrittenWhenANonFriendIsRemoved() {
-               prepareConfigurationValues();
-               when(sone.isLocal()).thenReturn(true);
-               memoryDatabase.removeFriend(sone, "Friend1");
-               verify(configuration).getStringValue(anyString());
-       }
-
-}
index 8fff7bd..80e1e45 100644 (file)
@@ -14,8 +14,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link Key}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class KeyTest {
 
index 8def0e0..9d59570 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DefaultIdentityTest.java - Copyright © 2013–2016 David Roden
+ * Sone - DefaultIdentityTest.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -36,8 +36,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link DefaultIdentity}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class DefaultIdentityTest {
 
index c5dc840..138ced3 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DefaultOwnIdentityTest.java - Copyright © 2013–2016 David Roden
+ * Sone - DefaultOwnIdentityTest.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -24,8 +24,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link DefaultOwnIdentity}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class DefaultOwnIdentityTest extends DefaultIdentityTest {
 
index 9f92308..7ed2b59 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Identities.java - Copyright © 2013–2016 David Roden
+ * Sone - Identities.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -22,8 +22,6 @@ import java.util.Map;
 
 /**
  * Creates {@link Identity}s and {@link OwnIdentity}s.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class Identities {
 
index 3773cbe..e1eb1c8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IdentityChangeDetectorTest.java - Copyright © 2013–2016 David Roden
+ * Sone - IdentityChangeDetectorTest.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -34,8 +34,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link IdentityChangeDetector}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class IdentityChangeDetectorTest {
 
index c5dc82b..ff442a4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IdentityChangeEventSenderTest.java - Copyright © 2013–2016 David Roden
+ * Sone - IdentityChangeEventSenderTest.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -42,8 +42,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link IdentityChangeEventSender}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class IdentityChangeEventSenderTest {
 
@@ -77,14 +75,14 @@ public class IdentityChangeEventSenderTest {
        }
 
        private Map<OwnIdentity, Collection<Identity>> createNewIdentities() {
-               Map<OwnIdentity, Collection<Identity>> oldIdentities = new HashMap<OwnIdentity, Collection<Identity>>();
+               Map<OwnIdentity, Collection<Identity>> oldIdentities = new HashMap<>();
                oldIdentities.put(ownIdentities.get(1), asList(identities.get(3), identities.get(2)));
                oldIdentities.put(ownIdentities.get(2), asList(identities.get(1), identities.get(2)));
                return oldIdentities;
        }
 
        private Map<OwnIdentity, Collection<Identity>> createOldIdentities() {
-               Map<OwnIdentity, Collection<Identity>> oldIdentities = new HashMap<OwnIdentity, Collection<Identity>>();
+               Map<OwnIdentity, Collection<Identity>> oldIdentities = new HashMap<>();
                oldIdentities.put(ownIdentities.get(0), asList(identities.get(0), identities.get(1)));
                oldIdentities.put(ownIdentities.get(1), asList(identities.get(0), identities.get(1)));
                return oldIdentities;
index 72175bd..8744c15 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IdentityLoaderTest.java - Copyright © 2013–2016 David Roden
+ * Sone - IdentityLoaderTest.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -47,8 +47,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link IdentityLoader}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class IdentityLoaderTest {
 
index 83c696d..45e95db 100644 (file)
@@ -14,8 +14,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link IdentityManagerImpl}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class IdentityManagerTest {
 
index 461e303..4fa5bc9 100644 (file)
@@ -12,8 +12,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link IdentityEvent}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class IdentityEventTest {
 
index c4ec43b..3d27c34 100644 (file)
@@ -11,8 +11,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link OwnIdentityEvent}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class OwnIdentityEventTest {
 
index c3fa7e9..7a84978 100644 (file)
@@ -21,7 +21,9 @@ import net.pterodactylus.util.web.Method;
 import net.pterodactylus.util.web.Page;
 import net.pterodactylus.util.web.Response;
 
+import freenet.clients.http.SessionManager;
 import freenet.clients.http.ToadletContext;
+import freenet.l10n.BaseL10n;
 import freenet.support.api.HTTPRequest;
 
 import com.google.common.base.Charsets;
@@ -33,14 +35,13 @@ import org.junit.rules.TemporaryFolder;
 
 /**
  * Unit test for {@link DebugLoaders}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class DebugLoadersTest {
 
        @Rule
        public final TemporaryFolder temporaryFolder = new TemporaryFolder();
 
+       private final BaseL10n l10n = mock(BaseL10n.class);
        private final StringWriter stringWriter = new StringWriter();
        private final TemplateContext templateContext = new TemplateContext();
        private Loaders loaders;
@@ -70,7 +71,8 @@ public class DebugLoadersTest {
                Method method = Method.GET;
                HTTPRequest httpRequest = mock(HTTPRequest.class);
                ToadletContext toadletContext = mock(ToadletContext.class);
-               FreenetRequest request = new FreenetRequest(uri, method, httpRequest, toadletContext);
+               SessionManager sessionManager = mock(SessionManager.class);
+               FreenetRequest request = new FreenetRequest(uri, method, httpRequest, toadletContext, l10n, sessionManager);
                OutputStream outputStream = new ByteArrayOutputStream();
                Response response = new Response(outputStream);
                page.handleRequest(request, response);
index 0f0a9f1..dc51479 100644 (file)
@@ -21,18 +21,19 @@ import net.pterodactylus.util.web.Method;
 import net.pterodactylus.util.web.Page;
 import net.pterodactylus.util.web.Response;
 
+import freenet.clients.http.SessionManager;
 import freenet.clients.http.ToadletContext;
+import freenet.l10n.BaseL10n;
 import freenet.support.api.HTTPRequest;
 
 import org.junit.Test;
 
 /**
  * Unit test for {@link DefaultLoaders}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class DefaultLoadersTest {
 
+       private final BaseL10n l10n = mock(BaseL10n.class);
        private final Loaders loaders = new DefaultLoaders();
        private final StringWriter stringWriter = new StringWriter();
        private final TemplateContext templateContext = new TemplateContext();
@@ -51,7 +52,8 @@ public class DefaultLoadersTest {
                Method method = Method.GET;
                HTTPRequest httpRequest = mock(HTTPRequest.class);
                ToadletContext toadletContext = mock(ToadletContext.class);
-               FreenetRequest request = new FreenetRequest(uri, method, httpRequest, toadletContext);
+               SessionManager sessionManager = mock(SessionManager.class);
+               FreenetRequest request = new FreenetRequest(uri, method, httpRequest, toadletContext, l10n, sessionManager);
                OutputStream outputStream = new ByteArrayOutputStream();
                Response response = new Response(outputStream);
                staticPage.handleRequest(request, response);
index 879fe36..87dae7e 100644 (file)
@@ -11,8 +11,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link ReparseFilter}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class ReparseFilterTest {
 
index dabfd5a..af6339b 100644 (file)
@@ -33,8 +33,6 @@ import org.mockito.ArgumentMatchers;
 
 /**
  * Unit test for {@link ListNotificationFilterTest}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class ListNotificationFilterTest {
 
index ea5f067..5e40f81 100644 (file)
@@ -21,8 +21,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link ListNotification}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class ListNotificationTest {
 
@@ -36,7 +34,7 @@ public class ListNotificationTest {
 
        public ListNotificationTest() {
                when(template.getInitialContext()).thenReturn(templateInitialContext);
-               listNotification = new ListNotification<Object>(ID, KEY, template);
+               listNotification = new ListNotification<>(ID, KEY, template);
        }
 
        @Test
index d49e59b..21baa4e 100644 (file)
@@ -19,8 +19,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link PostVisibilityFilterTest}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class PostVisibilityFilterTest {
 
index 7187847..3f430be 100644 (file)
@@ -18,8 +18,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link ReplyVisibilityFilterTest}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class ReplyVisibilityFilterTest {
 
index 56d2f29..28f6fd1 100644 (file)
@@ -28,8 +28,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link AlbumAccessor}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class AlbumAccessorTest {
 
index d0e5057..e83693a 100644 (file)
@@ -16,13 +16,11 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link CollectionAccessor}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class CollectionAccessorTest {
 
        private final CollectionAccessor accessor = new CollectionAccessor();
-       private final Collection<Object> collection = new ArrayList<Object>();
+       private final Collection<Object> collection = new ArrayList<>();
 
        @Before
        public void setupCollection() {
index 7b47567..0d67548 100644 (file)
@@ -10,8 +10,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link CssClassNameFilter}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class CssClassNameFilterTest {
 
index 7b93477..9259ef5 100644 (file)
@@ -22,14 +22,12 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link FilesystemTemplate}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class FilesystemTemplateTest {
 
        private final File tempFile;
        private final FilesystemTemplate filesystemTemplate;
-       private final AtomicReference<StringWriter> stringWriter = new AtomicReference<StringWriter>(new StringWriter());
+       private final AtomicReference<StringWriter> stringWriter = new AtomicReference<>(new StringWriter());
        private final TemplateContext templateContext = new TemplateContext();
 
        public FilesystemTemplateTest() throws IOException {
index febd06a..cefc8b0 100644 (file)
@@ -17,8 +17,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link GetPagePlugin}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class GetPagePluginTest {
 
@@ -26,7 +24,7 @@ public class GetPagePluginTest {
        private final TemplateContext context = mock(TemplateContext.class);
        private final FreenetRequest request = mock(FreenetRequest.class);
        private final Map<String, String> parameters =
-                       new HashMap<String, String>();
+                       new HashMap<>();
        private HTTPRequest httpRequest = mock(HTTPRequest.class);
 
        @Before
index 37c6260..73e3f4c 100644 (file)
@@ -16,8 +16,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link HttpRequestAccessor}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class HttpRequestAccessorTest {
 
index 6744bd8..4a8c624 100644 (file)
@@ -24,8 +24,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link IdentityAccessor}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class IdentityAccessorTest {
 
index e96b6cb..5d95423 100644 (file)
@@ -9,9 +9,6 @@ import static org.mockito.Mockito.when;
 
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Image;
-import net.pterodactylus.util.template.HtmlFilter;
-import net.pterodactylus.util.template.TemplateContext;
-import net.pterodactylus.util.template.TemplateContextFactory;
 
 import com.google.common.collect.ImmutableMap;
 import org.hamcrest.Matchers;
@@ -23,23 +20,14 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link ImageLinkFilterTest}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class ImageLinkFilterTest {
 
        private final Core core = mock(Core.class);
-       private final TemplateContextFactory templateContextFactory = new TemplateContextFactory();
-       private final ImageLinkFilter imageLinkFilter = new ImageLinkFilter(core, templateContextFactory);
-       private final TemplateContext templateContext = null;
+       private final ImageLinkFilter imageLinkFilter = new ImageLinkFilter(core);
        private final Image image = mock(Image.class);
 
        @Before
-       public void setupTemplateContextFactory() {
-               templateContextFactory.addFilter("html", new HtmlFilter());
-       }
-
-       @Before
        public void setupCore() {
                when(core.getImage("image-id", false)).thenReturn(image);
        }
@@ -58,7 +46,7 @@ public class ImageLinkFilterTest {
        @Test
        public void imageLinkIsGeneratedCorrectlyForNotInsertedImages() {
                when(image.isInserted()).thenReturn(false);
-               String result = String.valueOf(imageLinkFilter.format(templateContext, image, ImmutableMap.<String, Object>of()));
+               String result = String.valueOf(imageLinkFilter.format(null, image, ImmutableMap.<String, Object>of()));
                Element imageElement = getSingleElement(result);
                assertThat(imageElement.attr("class"), is(""));
                assertThat(imageElement.attr("src"), is("getImage.html?image=image-id"));
@@ -70,7 +58,7 @@ public class ImageLinkFilterTest {
 
        @Test
        public void imageLinkIsGeneratedCorrectlyForInsertedImages() {
-               String result = String.valueOf(imageLinkFilter.format(templateContext, image, ImmutableMap.<String, Object>of()));
+               String result = String.valueOf(imageLinkFilter.format(null, image, ImmutableMap.<String, Object>of()));
                Element imageElement = getSingleElement(result);
                assertThat(imageElement.attr("class"), is(""));
                assertThat(imageElement.attr("src"), is("/image-key"));
@@ -82,7 +70,7 @@ public class ImageLinkFilterTest {
 
        @Test
        public void imageTitleAndDescriptionAreOverriddenCorrectly() {
-               String result = String.valueOf(imageLinkFilter.format(templateContext, image, ImmutableMap.<String, Object>of("title", "Test Title")));
+               String result = String.valueOf(imageLinkFilter.format(null, image, ImmutableMap.<String, Object>of("title", "Test Title")));
                Element imageElement = getSingleElement(result);
                assertThat(imageElement.attr("title"), is("Test Title"));
                assertThat(imageElement.attr("alt"), is("Test Title"));
@@ -90,7 +78,7 @@ public class ImageLinkFilterTest {
 
        @Test
        public void imageIsScaledByWidthCorrectly() {
-               String result = String.valueOf(imageLinkFilter.format(templateContext, image, ImmutableMap.<String, Object>of("max-width", "320")));
+               String result = String.valueOf(imageLinkFilter.format(null, image, ImmutableMap.<String, Object>of("max-width", "320")));
                Element imageElement = getSingleElement(result);
                assertThat(imageElement.attr("width"), is("320"));
                assertThat(imageElement.attr("height"), is("135"));
@@ -98,7 +86,7 @@ public class ImageLinkFilterTest {
 
        @Test
        public void imageIsScaledByHeightCorrectly() {
-               String result = String.valueOf(imageLinkFilter.format(templateContext, image, ImmutableMap.<String, Object>of("max-height", "135")));
+               String result = String.valueOf(imageLinkFilter.format(null, image, ImmutableMap.<String, Object>of("max-height", "135")));
                Element imageElement = getSingleElement(result);
                assertThat(imageElement.attr("width"), is("320"));
                assertThat(imageElement.attr("height"), is("135"));
@@ -106,7 +94,7 @@ public class ImageLinkFilterTest {
 
        @Test
        public void wideImageIsEnlargedCorrectly() {
-               String result = String.valueOf(imageLinkFilter.format(templateContext, image,
+               String result = String.valueOf(imageLinkFilter.format(null, image,
                                ImmutableMap.<String, Object>of("mode", "enlarge", "max-width", "100", "max-height", "100")));
                Element imageElement = getSingleElement(result);
                assertThat(imageElement.attr("width"), is("237"));
@@ -119,7 +107,7 @@ public class ImageLinkFilterTest {
        public void highImageIsEnlargedCorrectly() {
                when(image.getWidth()).thenReturn(270);
                when(image.getHeight()).thenReturn(640);
-               String result = String.valueOf(imageLinkFilter.format(templateContext, image,
+               String result = String.valueOf(imageLinkFilter.format(null, image,
                                ImmutableMap.<String, Object>of("mode", "enlarge", "max-width", "100", "max-height", "100")));
                Element imageElement = getSingleElement(result);
                assertThat(imageElement.attr("width"), is("100"));
@@ -130,12 +118,12 @@ public class ImageLinkFilterTest {
 
        @Test
        public void nullImageIsReturnedAsNull() {
-               assertThat(imageLinkFilter.format(templateContext, null, null), nullValue());
+               assertThat(imageLinkFilter.format(null, null, null), nullValue());
        }
 
        @Test
        public void stringIsUsedToLoadImageFromCore() {
-               String result = String.valueOf(imageLinkFilter.format(templateContext, "image-id", ImmutableMap.<String, Object>of()));
+               String result = String.valueOf(imageLinkFilter.format(null, "image-id", ImmutableMap.<String, Object>of()));
                Element imageElement = getSingleElement(result);
                assertThat(imageElement.attr("class"), is(""));
                assertThat(imageElement.attr("src"), is("/image-key"));
index 6718c5a..c924d41 100644 (file)
@@ -9,8 +9,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link JavascriptFilter}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class JavascriptFilterTest {
 
index a6bc381..e60dce9 100644 (file)
@@ -22,8 +22,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link PostAccessor}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class PostAccessorTest {
 
index def311a..5d2873f 100644 (file)
@@ -1,18 +1,18 @@
 package net.pterodactylus.sone.test;
 
 import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 
 /**
- * This annotation marks test methods that are somehow not good test methods.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ * This annotation marks tests or test methods that are dirty,
+ * i.e. written in a way no test should ever be written in.
  */
 @Retention(SOURCE)
-@Target(METHOD)
+@Target(value = { TYPE, METHOD })
 public @interface Dirty {
 
        String value() default "";
index 08ad611..d04da0f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Matchers.java - Copyright © 2013–2016 David Roden
+ * Sone - Matchers.java - Copyright © 2013–2019 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -35,8 +35,6 @@ import org.hamcrest.TypeSafeMatcher;
 
 /**
  * Matchers used throughout the tests.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class Matchers {
 
index 8871ee3..e091566 100644 (file)
@@ -20,14 +20,12 @@ import org.mockito.stubbing.Answer;
 
 /**
  * {@link AlbumBuilder} that returns a mocked {@link Album}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class TestAlbumBuilder implements AlbumBuilder {
 
        private final Album album = mock(Album.class);
-       private final List<Album> albums = new ArrayList<Album>();
-       private final List<Image> images = new ArrayList<Image>();
+       private final List<Album> albums = new ArrayList<>();
+       private final List<Image> images = new ArrayList<>();
        private Album parentAlbum;
        private String title;
        private String description;
index edeeb1a..d571a1a 100644 (file)
@@ -10,8 +10,6 @@ import net.pterodactylus.sone.database.ImageBuilder;
 
 /**
  * {@link ImageBuilder} implementation that returns a mocked {@link Image}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class TestImageBuilder implements ImageBuilder {
 
index 1933857..6cb7828 100644 (file)
@@ -12,8 +12,6 @@ import net.pterodactylus.sone.database.PostBuilder;
 
 /**
  * {@link PostBuilder} implementation that returns a mocked {@link Post}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class TestPostBuilder implements PostBuilder {
 
index e09e120..a4f7748 100644 (file)
@@ -11,8 +11,6 @@ import net.pterodactylus.sone.database.PostReplyBuilder;
 
 /**
  * {@link PostReplyBuilder} that returns a mocked {@link PostReply}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class TestPostReplyBuilder implements PostReplyBuilder {
 
index 8b3160f..745733f 100644 (file)
@@ -1,17 +1,20 @@
 package net.pterodactylus.sone.test;
 
+import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 
+import kotlin.Deprecated;
+import kotlin.ReplaceWith;
+
 /**
  * Utilities for testing.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class TestUtil {
 
+       @Deprecated(message = "It only checks the given class, not its superclasses.", replaceWith = @ReplaceWith(imports = { "net.pterodactylus.sone.test" }, expression = "setField(`object`, fieldName, value)"))
        public static void setFinalField(Object object, String fieldName, Object value) {
                try {
                        Field clientCoreField = object.getClass().getField(fieldName);
@@ -20,9 +23,7 @@ public class TestUtil {
                        modifiersField.setAccessible(true);
                        modifiersField.setInt(clientCoreField, clientCoreField.getModifiers() & ~Modifier.FINAL);
                        clientCoreField.set(object, value);
-               } catch (NoSuchFieldException e) {
-                       throw new RuntimeException(e);
-               } catch (IllegalAccessException e) {
+               } catch (NoSuchFieldException | IllegalAccessException e) {
                        throw new RuntimeException(e);
                }
        }
@@ -32,9 +33,7 @@ public class TestUtil {
                        Field field = object.getClass().getDeclaredField(fieldName);
                        field.setAccessible(true);
                        return (T) field.get(object);
-               } catch (NoSuchFieldException e) {
-                       throw new RuntimeException(e);
-               } catch (IllegalAccessException e) {
+               } catch (NoSuchFieldException | IllegalAccessException e) {
                        throw new RuntimeException(e);
                }
        }
@@ -44,11 +43,17 @@ public class TestUtil {
                        Method method = object.getClass().getDeclaredMethod(methodName, new Class[0]);
                        method.setAccessible(true);
                        return (T) method.invoke(object);
-               } catch (NoSuchMethodException e) {
-                       throw new RuntimeException(e);
-               } catch (InvocationTargetException e) {
+               } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                        throw new RuntimeException(e);
-               } catch (IllegalAccessException e) {
+               }
+       }
+
+       public static <T> T createObject(Class<T> clazz, Class[] parameterTypes, Object... arguments) {
+               try {
+                       Constructor<T> constructor = clazz.getDeclaredConstructor(parameterTypes);
+                       constructor.setAccessible(true);
+                       return constructor.newInstance(arguments);
+               } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
                        throw new RuntimeException(e);
                }
        }
index 94ff3c6..6435d16 100644 (file)
@@ -2,6 +2,9 @@ package net.pterodactylus.sone.test;
 
 import java.util.concurrent.atomic.AtomicReference;
 
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
 import net.pterodactylus.util.config.ConfigurationException;
 import net.pterodactylus.util.config.Value;
 
@@ -9,30 +12,30 @@ import com.google.common.base.Objects;
 
 /**
  * Simple {@link Value} implementation.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class TestValue<T> implements Value<T> {
 
-       private final AtomicReference<T> value = new AtomicReference<T>();
+       private final AtomicReference<T> value = new AtomicReference<>();
 
-       public TestValue(T originalValue) {
+       public TestValue(@Nullable T originalValue) {
                value.set(originalValue);
        }
 
        @Override
+       @Nullable
        public T getValue() throws ConfigurationException {
                return value.get();
        }
 
        @Override
-       public T getValue(T defaultValue) {
+       @Nullable
+       public T getValue(@Nullable T defaultValue) {
                final T realValue = value.get();
                return (realValue != null) ? realValue : defaultValue;
        }
 
        @Override
-       public void setValue(T newValue) throws ConfigurationException {
+       public void setValue(@Nullable T newValue) throws ConfigurationException {
                value.set(newValue);
        }
 
@@ -48,12 +51,14 @@ public class TestValue<T> implements Value<T> {
        }
 
        @Override
+       @Nonnull
        public String toString() {
                return String.valueOf(value.get());
        }
 
-       public static <T> Value<T> from(T value) {
-               return new TestValue<T>(value);
+       @Nonnull
+       public static <T> Value<T> from(@Nullable T value) {
+               return new TestValue<>(value);
        }
 
 }
index 49972df..5ad1e71 100644 (file)
@@ -7,8 +7,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link FreemailPart}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class FreemailPartTest {
 
index 7b4ea19..5292f5f 100644 (file)
@@ -11,8 +11,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link PostPart}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class PostPartTest {
 
diff --git a/src/test/java/net/pterodactylus/sone/text/SoneTextParserTest.java b/src/test/java/net/pterodactylus/sone/text/SoneTextParserTest.java
deleted file mode 100644 (file)
index 79f1974..0000000
+++ /dev/null
@@ -1,543 +0,0 @@
-/*
- * 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
- * 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.text;
-
-import static java.lang.String.format;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.isIn;
-import static org.hamcrest.Matchers.notNullValue;
-
-import java.io.IOException;
-import java.util.Collection;
-
-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;
-import net.pterodactylus.sone.database.PostProvider;
-import net.pterodactylus.sone.database.SoneProvider;
-
-import com.google.common.base.Optional;
-import kotlin.jvm.functions.Function1;
-import org.junit.Test;
-
-/**
- * JUnit test case for {@link SoneTextParser}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class SoneTextParserTest {
-
-       private final SoneTextParser soneTextParser = new SoneTextParser(null, null);
-
-       @SuppressWarnings("static-method")
-       @Test
-       public void testPlainText() throws IOException {
-               /* check basic operation. */
-               Iterable<Part> parts = soneTextParser.parse("Test.", null);
-               assertThat("Part Text", convertText(parts, PlainTextPart.class), is("Test."));
-
-               /* check empty lines at start and end. */
-               parts = soneTextParser.parse("\nTest.\n\n", null);
-               assertThat("Part Text", convertText(parts, PlainTextPart.class), is("Test."));
-
-               /* check duplicate empty lines in the text. */
-               parts = soneTextParser.parse("\nTest.\n\n\nTest.", null);
-               assertThat("Part Text", convertText(parts, PlainTextPart.class), is("Test.\n\nTest."));
-       }
-
-       @Test
-       public void consecutiveLinesAreSeparatedByLinefeed() {
-               Iterable<Part> parts = soneTextParser.parse("Text.\nText", null);
-               assertThat("Part Text", convertText(parts), is("Text.\nText"));
-       }
-
-       @Test
-       public void freenetLinksHaveTheFreenetPrefixRemoved() {
-               Iterable<Part> parts = soneTextParser.parse("freenet:KSK@gpl.txt", null);
-               assertThat("Part Text", convertText(parts), is("[KSK@gpl.txt|KSK@gpl.txt|gpl.txt]"));
-       }
-
-       @Test
-       public void onlyTheFirstItemInALineIsPrefixedWithALineBreak() {
-               Iterable<Part> parts = soneTextParser.parse("Text.\nKSK@gpl.txt and KSK@gpl.txt", null);
-               assertThat("Part Text", convertText(parts), is("Text.\n[KSK@gpl.txt|KSK@gpl.txt|gpl.txt] and [KSK@gpl.txt|KSK@gpl.txt|gpl.txt]"));
-       }
-
-       @Test
-       public void soneLinkWithTooShortSoneIdIsRenderedAsPlainText() {
-               Iterable<Part> parts = soneTextParser.parse("sone://too-short", null);
-               assertThat("Part Text", convertText(parts), is("sone://too-short"));
-       }
-
-       @Test
-       public void soneLinkIsRenderedCorrectlyIfSoneIsNotPresent() {
-               SoneTextParser parser = new SoneTextParser(new AbsentSoneProvider(), null);
-               Iterable<Part> parts = parser.parse("sone://DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU", null);
-               assertThat("Part Text", convertText(parts), is("[Sone|DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU]"));
-       }
-
-       @Test
-       public void soneAndPostCanBeParsedFromTheSameText() {
-               SoneTextParser parser = new SoneTextParser(new TestSoneProvider(), new TestPostProvider());
-               Iterable<Part> parts = parser.parse("Text sone://DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU more text post://f3757817-b45a-497a-803f-9c5aafc10dc6 even more text", null);
-               assertThat("Part Text", convertText(parts), is("Text [Sone|DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU] more text [Post|f3757817-b45a-497a-803f-9c5aafc10dc6|text] even more text"));
-       }
-
-       @Test
-       public void postLinkIsRenderedAsPlainTextIfPostIdIsTooShort() {
-               Iterable<Part> parts = soneTextParser.parse("post://too-short", null);
-               assertThat("Part Text", convertText(parts), is("post://too-short"));
-       }
-
-       @Test
-       public void postLinkIsRenderedCorrectlyIfPostIsPresent() {
-               SoneTextParser parser = new SoneTextParser(null, new TestPostProvider());
-               Iterable<Part> parts = parser.parse("post://f3757817-b45a-497a-803f-9c5aafc10dc6", null);
-               assertThat("Part Text", convertText(parts), is("[Post|f3757817-b45a-497a-803f-9c5aafc10dc6|text]"));
-       }
-
-       @Test
-       public void postLinkIsRenderedAsPlainTextIfPostIsAbsent() {
-               SoneTextParser parser = new SoneTextParser(null, new AbsentPostProvider());
-               Iterable<Part> parts = parser.parse("post://f3757817-b45a-497a-803f-9c5aafc10dc6", null);
-               assertThat("Part Text", convertText(parts), is("post://f3757817-b45a-497a-803f-9c5aafc10dc6"));
-       }
-
-       @Test
-       public void nameOfFreenetLinkDoesNotContainUrlParameters() {
-               Iterable<Part> parts = soneTextParser.parse("KSK@gpl.txt?max-size=12345", null);
-               assertThat("Part Text", convertText(parts), is("[KSK@gpl.txt?max-size=12345|KSK@gpl.txt|gpl.txt]"));
-       }
-
-       @Test
-       public void trailingSlashInFreenetLinkIsRemovedForName() {
-               Iterable<Part> parts = soneTextParser.parse("KSK@gpl.txt/", null);
-               assertThat("Part Text", convertText(parts), is("[KSK@gpl.txt/|KSK@gpl.txt/|gpl.txt]"));
-       }
-
-       @Test
-       public void lastMetaStringOfFreenetLinkIsUsedAsName() {
-               Iterable<Part> parts = soneTextParser.parse("CHK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/COPYING", null);
-               assertThat("Part Text", convertText(parts), is("[CHK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/COPYING|CHK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/COPYING|COPYING]"));
-       }
-
-       @Test
-       public void freenetLinkWithoutMetaStringsAndDocNameGetsFirstNineCharactersOfKeyAsName() {
-               Iterable<Part> parts = soneTextParser.parse("CHK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8", null);
-               assertThat("Part Text", convertText(parts), is("[CHK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8|CHK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8|CHK@qM1nm]"));
-       }
-
-       @Test
-       public void malformedKeyIsRenderedAsPlainText() {
-               Iterable<Part> parts = soneTextParser.parse("CHK@qM1nmgU", null);
-               assertThat("Part Text", convertText(parts), is("CHK@qM1nmgU"));
-       }
-
-       @Test
-       public void httpsLinkHasItsPathsShortened() {
-               Iterable<Part> parts = soneTextParser.parse("https://test.test/some-long-path/file.txt", null);
-               assertThat("Part Text", convertText(parts), is("[https://test.test/some-long-path/file.txt|https://test.test/some-long-path/file.txt|test.test/…/file.txt]"));
-       }
-
-       @Test
-       public void httpLinksHaveTheirLastSlashRemoved() {
-               Iterable<Part> parts = soneTextParser.parse("http://test.test/test/", null);
-               assertThat("Part Text", convertText(parts), is("[http://test.test/test/|http://test.test/test/|test.test/…]"));
-       }
-
-       @Test
-       public void wwwPrefixIsRemovedForHostnameWithTwoDotsAndNoPath() {
-               Iterable<Part> parts = soneTextParser.parse("http://www.test.test", null);
-               assertThat("Part Text", convertText(parts), is("[http://www.test.test|http://www.test.test|test.test]"));
-       }
-
-       @Test
-       public void wwwPrefixIsRemovedForHostnameWithTwoDotsAndAPath() {
-               Iterable<Part> parts = soneTextParser.parse("http://www.test.test/test.html", null);
-               assertThat("Part Text", convertText(parts), is("[http://www.test.test/test.html|http://www.test.test/test.html|test.test/test.html]"));
-       }
-
-       @Test
-       public void hostnameIsKeptIntactIfNotBeginningWithWww() {
-               Iterable<Part> parts = soneTextParser.parse("http://test.test.test/test.html", null);
-               assertThat("Part Text", convertText(parts), is("[http://test.test.test/test.html|http://test.test.test/test.html|test.test.test/test.html]"));
-       }
-
-       @Test
-       public void hostnameWithOneDotButNoSlashIsKeptIntact() {
-               Iterable<Part> parts = soneTextParser.parse("http://test.test", null);
-               assertThat("Part Text", convertText(parts), is("[http://test.test|http://test.test|test.test]"));
-       }
-
-       @Test
-       public void urlParametersAreRemovedForHttpLinks() {
-               Iterable<Part> parts = soneTextParser.parse("http://test.test?foo=bar", null);
-               assertThat("Part Text", convertText(parts), is("[http://test.test?foo=bar|http://test.test?foo=bar|test.test]"));
-       }
-
-       @Test
-       public void emptyStringIsParsedCorrectly() {
-               Iterable<Part> parts = soneTextParser.parse("", null);
-               assertThat("Part Text", convertText(parts), is(""));
-       }
-
-       @Test
-       public void linksAreParsedInCorrectOrder() {
-               Iterable<Part> parts = soneTextParser.parse("KSK@ CHK@", null);
-               assertThat("Part Text", convertText(parts), is("KSK@ CHK@"));
-       }
-
-       @Test
-       public void invalidSskAndUskLinkIsParsedAsText() {
-               Iterable<Part> parts = soneTextParser.parse("SSK@a USK@a", null);
-               assertThat("Part Text", convertText(parts), is("SSK@a USK@a"));
-       }
-
-       @Test
-       public void sskLinkWithoutContextIsNotTrusted() {
-               Iterable<Part> parts = soneTextParser.parse("SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test", null);
-               assertThat("Part Text", convertText(parts), is("[SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test|SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test|test]"));
-       }
-
-       @Test
-       public void sskLinkWithContextWithoutSoneIsNotTrusted() {
-               SoneTextParserContext context = new SoneTextParserContext(null);
-               Iterable<Part> parts = soneTextParser.parse("SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test", context);
-               assertThat("Part Text", convertText(parts), is("[SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test|SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test|test]"));
-       }
-
-       @Test
-       public void sskLinkWithContextWithDifferentSoneIsNotTrusted() {
-               SoneTextParserContext context = new SoneTextParserContext(new IdOnlySone("DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU"));
-               Iterable<Part> parts = soneTextParser.parse("SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test", context);
-               assertThat("Part Text", convertText(parts), is("[SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test|SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test|test]"));
-       }
-
-       @Test
-       public void sskLinkWithContextWithCorrectSoneIsTrusted() {
-               SoneTextParserContext context = new SoneTextParserContext(new IdOnlySone("qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU"));
-               Iterable<Part> parts = soneTextParser.parse("SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test", context);
-               assertThat("Part Text", convertText(parts), is("[SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test|trusted|SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test|test]"));
-       }
-
-       @Test
-       public void uskLinkWithContextWithCorrectSoneIsTrusted() {
-               SoneTextParserContext context = new SoneTextParserContext(new IdOnlySone("qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU"));
-               Iterable<Part> parts = soneTextParser.parse("USK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test/0", context);
-               assertThat("Part Text", convertText(parts), is("[USK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test/0|trusted|USK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test/0|test]"));
-       }
-
-       @SuppressWarnings("static-method")
-       @Test
-       public void testKSKLinks() throws IOException {
-               /* check basic links. */
-               Iterable<Part> parts = soneTextParser.parse("KSK@gpl.txt", null);
-               assertThat("Part Text", convertText(parts, FreenetLinkPart.class), is("[KSK@gpl.txt|KSK@gpl.txt|gpl.txt]"));
-
-               /* check embedded links. */
-               parts = soneTextParser.parse("Link is KSK@gpl.txt\u200b.", null);
-               assertThat("Part Text", convertText(parts, PlainTextPart.class, FreenetLinkPart.class), is("Link is [KSK@gpl.txt|KSK@gpl.txt|gpl.txt]\u200b."));
-
-               /* check embedded links and line breaks. */
-               parts = soneTextParser.parse("Link is KSK@gpl.txt\nKSK@test.dat\n", null);
-               assertThat("Part Text", convertText(parts, PlainTextPart.class, FreenetLinkPart.class), is("Link is [KSK@gpl.txt|KSK@gpl.txt|gpl.txt]\n[KSK@test.dat|KSK@test.dat|test.dat]"));
-       }
-
-       @SuppressWarnings({ "synthetic-access", "static-method" })
-       @Test
-       public void testEmptyLinesAndSoneLinks() throws IOException {
-               SoneTextParser soneTextParser = new SoneTextParser(new TestSoneProvider(), null);
-
-               /* check basic links. */
-               Iterable<Part> parts = soneTextParser.parse("Some text.\n\nLink to sone://DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU and stuff.", null);
-               assertThat("Part Text", convertText(parts, PlainTextPart.class, SonePart.class), is("Some text.\n\nLink to [Sone|DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU] and stuff."));
-       }
-
-       @SuppressWarnings({ "synthetic-access", "static-method" })
-       @Test
-       public void testEmpyHttpLinks() throws IOException {
-               SoneTextParser soneTextParser = new SoneTextParser(new TestSoneProvider(), null);
-
-               /* check empty http links. */
-               Iterable<Part> parts = soneTextParser.parse("Some text. Empty link: http:// – nice!", null);
-               assertThat("Part Text", convertText(parts, PlainTextPart.class), is("Some text. Empty link: http:// – nice!"));
-       }
-
-       @Test
-       public void httpLinkWithoutParensEndsAtNextClosingParen() {
-               Iterable<Part> parts = soneTextParser.parse("Some text (and a link: http://example.sone/abc) – nice!", null);
-               assertThat("Part Text", convertText(parts, PlainTextPart.class, LinkPart.class), is("Some text (and a link: [http://example.sone/abc|http://example.sone/abc|example.sone/abc]) – nice!"));
-       }
-
-       @Test
-       public void uskLinkEndsAtFirstNonNumericNonSlashCharacterAfterVersionNumber() {
-               Iterable<Part> parts = soneTextParser.parse("Some link (USK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test/0). Nice", null);
-               assertThat("Part Text", convertText(parts), is("Some link ([USK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test/0|USK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test/0|test]). Nice"));
-       }
-
-       @Test
-       public void httpLinkWithOpenedAndClosedParensEndsAtNextClosingParen() {
-               Iterable<Part> parts = soneTextParser.parse("Some text (and a link: http://example.sone/abc_(def)) – nice!", null);
-               assertThat("Part Text", convertText(parts, PlainTextPart.class, LinkPart.class), is("Some text (and a link: [http://example.sone/abc_(def)|http://example.sone/abc_(def)|example.sone/abc_(def)]) – nice!"));
-       }
-
-       @Test
-       public void punctuationIsIgnoredAtEndOfLinkBeforeWhitespace() {
-               Iterable<Part> parts = soneTextParser.parse("Some text and a link: http://example.sone/abc. Nice!", null);
-               assertThat("Part Text", convertText(parts, PlainTextPart.class, LinkPart.class), is("Some text and a link: [http://example.sone/abc|http://example.sone/abc|example.sone/abc]. Nice!"));
-       }
-
-       @Test
-       public void multiplePunctuationCharactersAreIgnoredAtEndOfLinkBeforeWhitespace() {
-               Iterable<Part> parts = soneTextParser.parse("Some text and a link: http://example.sone/abc... Nice!", null);
-               assertThat("Part Text", convertText(parts, PlainTextPart.class, LinkPart.class), is("Some text and a link: [http://example.sone/abc|http://example.sone/abc|example.sone/abc]... Nice!"));
-       }
-
-       @Test
-       public void commasAreIgnoredAtEndOfLinkBeforeWhitespace() {
-               Iterable<Part> parts = soneTextParser.parse("Some text and a link: http://example.sone/abc, nice!", null);
-               assertThat("Part Text", convertText(parts, PlainTextPart.class, LinkPart.class), is("Some text and a link: [http://example.sone/abc|http://example.sone/abc|example.sone/abc], nice!"));
-       }
-
-       @Test
-       public void exclamationMarksAreIgnoredAtEndOfLinkBeforeWhitespace() {
-               Iterable<Part> parts = soneTextParser.parse("A link: http://example.sone/abc!", null);
-               assertThat("Part Text", convertText(parts, PlainTextPart.class, LinkPart.class), is("A link: [http://example.sone/abc|http://example.sone/abc|example.sone/abc]!"));
-       }
-
-       @Test
-       public void questionMarksAreIgnoredAtEndOfLinkBeforeWhitespace() {
-               Iterable<Part> parts = soneTextParser.parse("A link: http://example.sone/abc?", null);
-               assertThat("Part Text", convertText(parts, PlainTextPart.class, LinkPart.class), is("A link: [http://example.sone/abc|http://example.sone/abc|example.sone/abc]?"));
-       }
-
-       @Test
-       public void correctFreemailAddressIsLinkedToCorrectly() {
-               Iterable<Part> parts = soneTextParser.parse("Mail me at sone@t4dlzfdww3xvsnsc6j6gtliox6zaoak7ymkobbmcmdw527ubuqra.freemail!", null);
-               assertThat("Part Text", convertText(parts), is("Mail me at [Freemail|sone|t4dlzfdww3xvsnsc6j6gtliox6zaoak7ymkobbmcmdw527ubuqra|nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI]!"));
-       }
-
-       @Test
-       public void freemailAddressWithInvalidFreemailIdIsParsedAsText() {
-               Iterable<Part> parts = soneTextParser.parse("Mail me at sone@t4dlzfdww3xvsnsc6j6gtliox6zaoak7ymkobbmcmdw527ubuqr8.freemail!", null);
-               assertThat("Part Text", convertText(parts), is("Mail me at sone@t4dlzfdww3xvsnsc6j6gtliox6zaoak7ymkobbmcmdw527ubuqr8.freemail!"));
-       }
-
-       @Test
-       public void freemailAddressWithInvalidSizedFreemailIdIsParsedAsText() {
-               Iterable<Part> parts = soneTextParser.parse("Mail me at sone@4dlzfdww3xvsnsc6j6gtliox6zaoak7ymkobbmcmdw527ubuqra.freemail!", null);
-               assertThat("Part Text", convertText(parts), is("Mail me at sone@4dlzfdww3xvsnsc6j6gtliox6zaoak7ymkobbmcmdw527ubuqra.freemail!"));
-       }
-
-       @Test
-       public void freemailAddressWithoutLocalPartIsParsedAsText() {
-               Iterable<Part> parts = soneTextParser.parse("     @t4dlzfdww3xvsnsc6j6gtliox6zaoak7ymkobbmcmdw527ubuqra.freemail!", null);
-               assertThat("Part Text", convertText(parts), is("     @t4dlzfdww3xvsnsc6j6gtliox6zaoak7ymkobbmcmdw527ubuqra.freemail!"));
-       }
-
-       @Test
-       public void correctFreemailAddressIsParsedCorrectly() {
-               Iterable<Part> parts = soneTextParser.parse("sone@t4dlzfdww3xvsnsc6j6gtliox6zaoak7ymkobbmcmdw527ubuqra.freemail", null);
-               assertThat("Part Text", convertText(parts), is("[Freemail|sone|t4dlzfdww3xvsnsc6j6gtliox6zaoak7ymkobbmcmdw527ubuqra|nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI]"));
-       }
-
-       @Test
-       public void localPartOfFreemailAddressCanContainLettersDigitsMinusDotUnderscore() {
-               Iterable<Part> parts = soneTextParser.parse("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._@t4dlzfdww3xvsnsc6j6gtliox6zaoak7ymkobbmcmdw527ubuqra.freemail", null);
-               assertThat("Part Text", convertText(parts), is("[Freemail|ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._|t4dlzfdww3xvsnsc6j6gtliox6zaoak7ymkobbmcmdw527ubuqra|nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI]"));
-       }
-
-       /**
-        * Converts all given {@link Part}s into a string, validating that the
-        * part’s classes match only the expected classes.
-        *
-        * @param parts
-        *            The parts to convert to text
-        * @param validClasses
-        *            The valid classes; if no classes are given, all classes are
-        *            valid
-        * @return The converted text
-        */
-       private static String convertText(Iterable<Part> parts, Class<?>... validClasses) {
-               StringBuilder text = new StringBuilder();
-               for (Part part : parts) {
-                       assertThat("Part", part, notNullValue());
-                       if (validClasses.length != 0) {
-                               assertThat("Part’s class", part.getClass(), isIn(validClasses));
-                       }
-                       if (part instanceof PlainTextPart) {
-                               text.append(((PlainTextPart) part).getText());
-                       } else if (part instanceof FreenetLinkPart) {
-                               FreenetLinkPart freenetLinkPart = (FreenetLinkPart) part;
-                               text.append('[').append(freenetLinkPart.getLink()).append('|').append(freenetLinkPart.getTrusted() ? "trusted|" : "").append(freenetLinkPart.getTitle()).append('|').append(freenetLinkPart.getText()).append(']');
-                       } else if (part instanceof FreemailPart) {
-                               FreemailPart freemailPart = (FreemailPart) part;
-                               text.append(format("[Freemail|%s|%s|%s]", freemailPart.getEmailLocalPart(), freemailPart.getFreemailId(), freemailPart.getIdentityId()));
-                       } else if (part instanceof LinkPart) {
-                               LinkPart linkPart = (LinkPart) part;
-                               text.append('[').append(linkPart.getLink()).append('|').append(linkPart.getTitle()).append('|').append(linkPart.getText()).append(']');
-                       } else if (part instanceof SonePart) {
-                               SonePart sonePart = (SonePart) part;
-                               text.append("[Sone|").append(sonePart.getSone().getId()).append(']');
-                       } else if (part instanceof PostPart) {
-                               PostPart postPart = (PostPart) part;
-                               text.append("[Post|").append(postPart.getPost().getId()).append("|").append(postPart.getPost().getText()).append("]");
-                       }
-               }
-               return text.toString();
-       }
-
-       /**
-        * Mock Sone provider.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
-        */
-       private static class TestSoneProvider implements SoneProvider {
-
-               @Nonnull
-               @Override
-               public Function1<String, Sone> getSoneLoader() {
-                       return new Function1<String, Sone>() {
-                               @Override
-                               public Sone invoke(String soneId) {
-                                       return getSone(soneId);
-                               }
-                       };
-               }
-
-               @Nullable
-               @Override
-               public Sone getSone(final String soneId) {
-                       return new IdOnlySone(soneId);
-               }
-
-               /**
-                * {@inheritDocs}
-                */
-               @Override
-               public Collection<Sone> getSones() {
-                       return null;
-               }
-
-               /**
-                * {@inheritDocs}
-                */
-               @Override
-               public Collection<Sone> getLocalSones() {
-                       return null;
-               }
-
-               /**
-                * {@inheritDocs}
-                */
-               @Override
-               public Collection<Sone> getRemoteSones() {
-                       return null;
-               }
-
-       }
-
-       private static class AbsentSoneProvider extends TestSoneProvider {
-
-               @Override
-               public Sone getSone(String soneId) {
-                       return null;
-               }
-
-       }
-
-       private static class TestPostProvider implements PostProvider {
-
-               @Nullable
-               @Override
-               public Post getPost(@Nonnull final String postId) {
-                       return new Post() {
-                               @Override
-                               public String getId() {
-                                       return postId;
-                               }
-
-                               @Override
-                               public boolean isLoaded() {
-                                       return false;
-                               }
-
-                               @Override
-                               public Sone getSone() {
-                                       return null;
-                               }
-
-                               @Override
-                               public Optional<String> getRecipientId() {
-                                       return null;
-                               }
-
-                               @Override
-                               public Optional<Sone> getRecipient() {
-                                       return null;
-                               }
-
-                               @Override
-                               public long getTime() {
-                                       return 0;
-                               }
-
-                               @Override
-                               public String getText() {
-                                       return "text";
-                               }
-
-                               @Override
-                               public boolean isKnown() {
-                                       return false;
-                               }
-
-                               @Override
-                               public Post setKnown(boolean known) {
-                                       return null;
-                               }
-                       };
-               }
-
-               @Override
-               public Collection<Post> getPosts(String soneId) {
-                       return null;
-               }
-
-               @Override
-               public Collection<Post> getDirectedPosts(String recipientId) {
-                       return null;
-               }
-
-       }
-
-       private static class AbsentPostProvider extends TestPostProvider {
-
-               @Nullable
-               @Override
-               public Post getPost(@Nonnull String postId) {
-                       return null;
-               }
-
-       }
-
-}
index 1076a8a..35623a7 100644 (file)
@@ -9,8 +9,6 @@ import org.junit.Test;
 
 /**
  * JUnit test for {@link TextFilter}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class TextFilterTest {
 
index 065e1d4..3c82c40 100644 (file)
@@ -11,8 +11,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link DefaultOption}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class DefaultOptionTest {
 
@@ -27,19 +25,19 @@ public class DefaultOptionTest {
 
        @Test
        public void defaultOptionReturnsDefaultValueWhenUnset() {
-               DefaultOption<Object> defaultOption = new DefaultOption<Object>(defaultValue);
+               DefaultOption<Object> defaultOption = new DefaultOption<>(defaultValue);
                assertThat(defaultOption.get(), is(defaultValue));
        }
 
        @Test
        public void defaultOptionReturnsNullForRealWhenUnset() {
-               DefaultOption<Object> defaultOption = new DefaultOption<Object>(defaultValue);
+               DefaultOption<Object> defaultOption = new DefaultOption<>(defaultValue);
                assertThat(defaultOption.getReal(), nullValue());
        }
 
        @Test
        public void defaultOptionWillReturnSetValue() {
-               DefaultOption<Object> defaultOption = new DefaultOption<Object>(defaultValue);
+               DefaultOption<Object> defaultOption = new DefaultOption<>(defaultValue);
                Object newValue = new Object();
                defaultOption.set(newValue);
                assertThat(defaultOption.get(), is(newValue));
@@ -47,40 +45,40 @@ public class DefaultOptionTest {
 
        @Test
        public void defaultOptionWithValidatorAcceptsValidValues() {
-               DefaultOption<Object> defaultOption = new DefaultOption<Object>(defaultValue, matchesAcceptedValue);
+               DefaultOption<Object> defaultOption = new DefaultOption<>(defaultValue, matchesAcceptedValue);
                defaultOption.set(acceptedValue);
                assertThat(defaultOption.get(), is(acceptedValue));
        }
 
        @Test(expected = IllegalArgumentException.class)
        public void defaultOptionWithValidatorRejectsInvalidValues() {
-               DefaultOption<Object> defaultOption = new DefaultOption<Object>(defaultValue, matchesAcceptedValue);
+               DefaultOption<Object> defaultOption = new DefaultOption<>(defaultValue, matchesAcceptedValue);
                defaultOption.set(new Object());
        }
 
        @Test
        public void defaultOptionValidatesObjectsCorrectly() {
-               DefaultOption<Object> defaultOption = new DefaultOption<Object>(defaultValue, matchesAcceptedValue);
+               DefaultOption<Object> defaultOption = new DefaultOption<>(defaultValue, matchesAcceptedValue);
                assertThat(defaultOption.validate(acceptedValue), is(true));
                assertThat(defaultOption.validate(new Object()), is(false));
        }
 
        @Test
        public void settingToNullWillRestoreDefaultValue() {
-               DefaultOption<Object> defaultOption = new DefaultOption<Object>(defaultValue);
+               DefaultOption<Object> defaultOption = new DefaultOption<>(defaultValue);
                defaultOption.set(null);
                assertThat(defaultOption.get(), is(defaultValue));
        }
 
        @Test
        public void validateWithoutValidatorWillValidateNull() {
-               DefaultOption<Object> defaultOption = new DefaultOption<Object>(defaultValue);
+               DefaultOption<Object> defaultOption = new DefaultOption<>(defaultValue);
                assertThat(defaultOption.validate(null), is(true));
        }
 
        @Test
        public void validateWithValidatorWillValidateNull() {
-               DefaultOption<Object> defaultOption = new DefaultOption<Object>(defaultValue, matchesAcceptedValue);
+               DefaultOption<Object> defaultOption = new DefaultOption<>(defaultValue, matchesAcceptedValue);
                assertThat(defaultOption.validate(null), is(true));
        }
 
index 687afa3..2bea3f7 100644 (file)
@@ -10,8 +10,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link IntegerRangePredicate}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class IntegerRangePredicateTest {
 
index 00c2263..0cb4367 100644 (file)
@@ -10,8 +10,6 @@ import org.junit.Test;
 
 /**
  * Unit test for {@link NumberParsers}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class NumberParsersTest {
 
index d4e7596..11a44a4 100644 (file)
@@ -10,8 +10,6 @@ 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 {
 
diff --git a/src/test/java/net/pterodactylus/sone/web/page/FreenetRequestTest.java b/src/test/java/net/pterodactylus/sone/web/page/FreenetRequestTest.java
deleted file mode 100644 (file)
index ac01e51..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-package net.pterodactylus.sone.web.page;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-import static org.mockito.Mockito.mock;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-
-import net.pterodactylus.util.web.Method;
-
-import freenet.clients.http.ToadletContext;
-import freenet.support.api.HTTPRequest;
-
-import org.junit.Test;
-
-/**
- * Unit test for {@link FreenetRequest}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class FreenetRequestTest {
-
-       private final URI uri = new URI(".");
-       private final Method method = Method.GET;
-       private final HTTPRequest httpRequest = mock(HTTPRequest.class);
-       private final ToadletContext toadletContext = mock(ToadletContext.class);
-       private final FreenetRequest request = new FreenetRequest(uri, method, httpRequest, toadletContext);
-
-       @SuppressWarnings("unused")
-       public FreenetRequestTest() throws URISyntaxException {
-       }
-
-       @Test
-       public void uriIsRetainedCorrectly() {
-               assertThat(request.getUri(), is(uri));
-       }
-
-       @Test
-       public void methodIsRetainedCorrectly() {
-               assertThat(request.getMethod(), is(method));
-       }
-
-       @Test
-       public void httpRequestIsRetainedCorrectly() {
-               assertThat(request.getHttpRequest(), is(httpRequest));
-       }
-
-       @Test
-       public void toadletContextIsRetainedCorrectly() {
-               assertThat(request.getToadletContext(), is(toadletContext));
-       }
-
-}
index dbb5bcc..77c4c62 100644 (file)
@@ -14,7 +14,7 @@ class ElementLoaderTest {
        @Test
        fun `default image loader can be loaded by guice`() {
                val injector = createInjector(bindMock<FreenetInterface>())
-               assertThat(injector.getInstance(ElementLoader::class.java), notNullValue());
+               assertThat(injector.getInstance(ElementLoader::class.java), notNullValue())
        }
 
 }
diff --git a/src/test/kotlin/net/pterodactylus/sone/core/ImageInserterTest.kt b/src/test/kotlin/net/pterodactylus/sone/core/ImageInserterTest.kt
new file mode 100644 (file)
index 0000000..39a11dc
--- /dev/null
@@ -0,0 +1,63 @@
+package net.pterodactylus.sone.core
+
+import net.pterodactylus.sone.core.FreenetInterface.InsertToken
+import net.pterodactylus.sone.core.FreenetInterface.InsertTokenSupplier
+import net.pterodactylus.sone.data.Image
+import net.pterodactylus.sone.data.TemporaryImage
+import net.pterodactylus.sone.test.getInstance
+import net.pterodactylus.sone.test.mock
+import net.pterodactylus.sone.test.whenever
+import net.pterodactylus.sone.web.baseInjector
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.notNullValue
+import org.junit.Test
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.doThrow
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+/**
+ * Unit test for [ImageInserter].
+ */
+class ImageInserterTest {
+
+       private val temporaryImage = mock<TemporaryImage>().apply { whenever(id).thenReturn("image-id") }
+       private val image = mock<Image>().apply { whenever(id).thenReturn("image-id") }
+       private val freenetInterface = mock<FreenetInterface>()
+       private val insertToken = mock<InsertToken>()
+       private val insertTokenSupplier: InsertTokenSupplier = mock<InsertTokenSupplier>().apply { whenever(apply(any())).thenReturn(insertToken) }
+       private val imageInserter = ImageInserter(freenetInterface, insertTokenSupplier)
+
+       @Test
+       fun `inserter inserts image`() {
+               imageInserter.insertImage(temporaryImage, image)
+               verify(freenetInterface).insertImage(eq(temporaryImage), eq(image), any(InsertToken::class.java))
+       }
+
+       @Test
+       fun `exception when inserting image is ignored`() {
+               doThrow(SoneException::class.java).`when`(freenetInterface).insertImage(eq(temporaryImage), eq(image), any(InsertToken::class.java))
+               imageInserter.insertImage(temporaryImage, image)
+               verify(freenetInterface).insertImage(eq(temporaryImage), eq(image), any(InsertToken::class.java))
+       }
+
+       @Test
+       fun `cancelling image insert that is not running does nothing`() {
+               imageInserter.cancelImageInsert(image)
+               verify(insertToken, never()).cancel()
+       }
+
+       @Test
+       fun `cancelling image cancels the insert token`() {
+               imageInserter.insertImage(temporaryImage, image)
+               imageInserter.cancelImageInsert(image)
+               verify(insertToken).cancel()
+       }
+
+       @Test
+       fun `image inserter can be created by dependency injection`() {
+           assertThat(baseInjector.getInstance<ImageInserter>(), notNullValue())
+       }
+
+}
diff --git a/src/test/kotlin/net/pterodactylus/sone/core/PreferencesTest.kt b/src/test/kotlin/net/pterodactylus/sone/core/PreferencesTest.kt
new file mode 100644 (file)
index 0000000..199604b
--- /dev/null
@@ -0,0 +1,328 @@
+package net.pterodactylus.sone.core
+
+import com.google.common.eventbus.*
+import net.pterodactylus.sone.core.event.*
+import net.pterodactylus.sone.fcp.FcpInterface.*
+import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.*
+import net.pterodactylus.sone.fcp.event.*
+import net.pterodactylus.sone.test.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+/**
+ * Unit test for [Preferences].
+ */
+class PreferencesTest {
+
+       private val eventBus = mock<EventBus>()
+       private val preferences = Preferences(eventBus)
+       private val eventsCaptor = capture<Any>()
+
+       @Test
+       fun `preferences retain insertion delay`() {
+               preferences.newInsertionDelay = 15
+               assertThat(preferences.insertionDelay, equalTo(15))
+       }
+
+       @Test
+       fun `preferences sends event on setting insertion delay`() {
+               preferences.newInsertionDelay = 15
+               verify(eventBus, atLeastOnce()).post(eventsCaptor.capture())
+               assertThat(eventsCaptor.allValues, hasItem(InsertionDelayChangedEvent(15)))
+       }
+
+       @Test(expected = IllegalArgumentException::class)
+       fun `invalid insertion delay is rejected`() {
+               preferences.newInsertionDelay = -15
+       }
+
+       @Test
+       fun `no event is sent when invalid insertion delay is set`() {
+               try {
+                       preferences.newInsertionDelay = -15
+               } catch (iae: IllegalArgumentException) {
+                       /* ignore. */
+               }
+
+               verify(eventBus, never()).post(any())
+       }
+
+       @Test
+       fun `preferences return default value when insertion delay is set to null`() {
+               preferences.newInsertionDelay = null
+               assertThat(preferences.insertionDelay, equalTo(60))
+       }
+
+       @Test
+       fun `preferences start with insertion delay default value`() {
+               assertThat(preferences.insertionDelay, equalTo(60))
+       }
+
+       @Test
+       fun `preferences retain posts per page`() {
+               preferences.newPostsPerPage = 15
+               assertThat(preferences.postsPerPage, equalTo(15))
+       }
+
+       @Test(expected = IllegalArgumentException::class)
+       fun `invalid posts per page is rejected`() {
+               preferences.newPostsPerPage = -15
+       }
+
+       @Test
+       fun `preferences return default value when posts per page is set to null`() {
+               preferences.newPostsPerPage = null
+               assertThat(preferences.postsPerPage, equalTo(10))
+       }
+
+       @Test
+       fun `preferences start with posts per page default value`() {
+               assertThat(preferences.postsPerPage, equalTo(10))
+       }
+
+       @Test
+       fun `preferences retain images per page`() {
+               preferences.newImagesPerPage = 15
+               assertThat(preferences.imagesPerPage, equalTo(15))
+       }
+
+       @Test(expected = IllegalArgumentException::class)
+       fun `invalid images per page is rejected`() {
+               preferences.newImagesPerPage = -15
+       }
+
+       @Test
+       fun `preferences return default value when images per page is set to null`() {
+               preferences.newImagesPerPage = null
+               assertThat(preferences.imagesPerPage, equalTo(9))
+       }
+
+       @Test
+       fun `preferences start with images per page default value`() {
+               assertThat(preferences.imagesPerPage, equalTo(9))
+       }
+
+       @Test
+       fun `preferences retain characters per post`() {
+               preferences.newCharactersPerPost = 150
+               assertThat(preferences.charactersPerPost, equalTo(150))
+       }
+
+       @Test(expected = IllegalArgumentException::class)
+       fun `invalid characters per post is rejected`() {
+               preferences.newCharactersPerPost = -15
+       }
+
+       @Test
+       fun `preferences return default value when characters per post is set to null`() {
+               preferences.newCharactersPerPost = null
+               assertThat(preferences.charactersPerPost, equalTo(400))
+       }
+
+       @Test
+       fun `preferences start with characters per post default value`() {
+               assertThat(preferences.charactersPerPost, equalTo(400))
+       }
+
+       @Test
+       fun `preferences retain post cut off length`() {
+               preferences.newPostCutOffLength = 150
+               assertThat(preferences.postCutOffLength, equalTo(150))
+       }
+
+       @Test(expected = IllegalArgumentException::class)
+       fun `invalid post cut off length is rejected`() {
+               preferences.newPostCutOffLength = -15
+       }
+
+       @Test(expected = IllegalArgumentException::class)
+       fun `cut off length of minus one is not allowed`() {
+               preferences.newPostCutOffLength = -1
+       }
+
+       @Test
+       fun `preferences return default value when post cut off length is set to null`() {
+               preferences.newPostCutOffLength = null
+               assertThat(preferences.postCutOffLength, equalTo(200))
+       }
+
+       @Test
+       fun `preferences start with post cut off length default value`() {
+               assertThat(preferences.postCutOffLength, equalTo(200))
+       }
+
+       @Test
+       fun `preferences retain require full access of true`() {
+               preferences.newRequireFullAccess = true
+               assertThat(preferences.requireFullAccess, equalTo(true))
+       }
+
+       @Test
+       fun `preferences retain require full access of false`() {
+               preferences.newRequireFullAccess = false
+               assertThat(preferences.requireFullAccess, equalTo(false))
+       }
+
+       @Test
+       fun `preferences return default value when require full access is set to null`() {
+               preferences.newRequireFullAccess = null
+               assertThat(preferences.requireFullAccess, equalTo(false))
+       }
+
+       @Test
+       fun `preferences start with require full access default value`() {
+               assertThat(preferences.requireFullAccess, equalTo(false))
+       }
+
+       @Test
+       fun `preferences retain positive trust`() {
+               preferences.newPositiveTrust = 15
+               assertThat(preferences.positiveTrust, equalTo(15))
+       }
+
+       @Test(expected = IllegalArgumentException::class)
+       fun `invalid positive trust is rejected`() {
+               preferences.newPositiveTrust = -15
+       }
+
+       @Test
+       fun `preferences return default value when positive trust is set to null`() {
+               preferences.newPositiveTrust = null
+               assertThat(preferences.positiveTrust, equalTo(75))
+       }
+
+       @Test
+       fun `preferences start with positive trust default value`() {
+               assertThat(preferences.positiveTrust, equalTo(75))
+       }
+
+       @Test
+       fun `preferences retain negative trust`() {
+               preferences.newNegativeTrust = -15
+               assertThat(preferences.negativeTrust, equalTo(-15))
+       }
+
+       @Test(expected = IllegalArgumentException::class)
+       fun `invalid negative trust is rejected`() {
+               preferences.newNegativeTrust = 150
+       }
+
+       @Test
+       fun `preferences return default value when negative trust is set to null`() {
+               preferences.newNegativeTrust = null
+               assertThat(preferences.negativeTrust, equalTo(-25))
+       }
+
+       @Test
+       fun `preferences start with negative trust default value`() {
+               assertThat(preferences.negativeTrust, equalTo(-25))
+       }
+
+       @Test
+       fun `preferences retain trust comment`() {
+               preferences.newTrustComment = "Trust"
+               assertThat(preferences.trustComment, equalTo("Trust"))
+       }
+
+       @Test
+       fun `preferences return default value when trust comment is set to null`() {
+               preferences.newTrustComment = null
+               assertThat(preferences.trustComment,
+                               equalTo("Set from Sone Web Interface"))
+       }
+
+       @Test
+       fun `preferences start with trust comment default value`() {
+               assertThat(preferences.trustComment,
+                               equalTo("Set from Sone Web Interface"))
+       }
+
+       @Test
+       fun `preferences retain fcp interface active of true`() {
+               preferences.newFcpInterfaceActive = true
+               assertThat(preferences.fcpInterfaceActive, equalTo(true))
+               verify(eventBus).post(any(FcpInterfaceActivatedEvent::class.java))
+       }
+
+       @Test
+       fun `preferences retain fcp interface active of false`() {
+               preferences.newFcpInterfaceActive = false
+               assertThat(preferences.fcpInterfaceActive, equalTo(false))
+               verify(eventBus).post(any(FcpInterfaceDeactivatedEvent::class.java))
+       }
+
+       @Test
+       fun `preferences return default value when fcp interface active is set to null`() {
+               preferences.newFcpInterfaceActive = null
+               assertThat(preferences.fcpInterfaceActive, equalTo(false))
+               verify(eventBus).post(any(FcpInterfaceDeactivatedEvent::class.java))
+       }
+
+       @Test
+       fun `preferences start with fcp interface active default value`() {
+               assertThat(preferences.fcpInterfaceActive, equalTo(false))
+       }
+
+       @Test
+       fun `preferences retain fcp full access required of no`() {
+               preferences.newFcpFullAccessRequired = NO
+               assertThat(preferences.fcpFullAccessRequired, equalTo(NO))
+               verifyFullAccessRequiredChangedEvent(NO)
+       }
+
+       private fun verifyFullAccessRequiredChangedEvent(
+                       fullAccessRequired: FullAccessRequired) {
+               verify(eventBus).post(eventsCaptor.capture())
+               assertThat(eventsCaptor.value, instanceOf(FullAccessRequiredChanged::class.java))
+               assertThat((eventsCaptor.value as FullAccessRequiredChanged).fullAccessRequired,
+                               equalTo(fullAccessRequired))
+       }
+
+       @Test
+       fun `preferences retain fcp full access required of writing`() {
+               preferences.newFcpFullAccessRequired = WRITING
+               assertThat(preferences.fcpFullAccessRequired, equalTo(WRITING))
+               verifyFullAccessRequiredChangedEvent(WRITING)
+       }
+
+       @Test
+       fun `preferences retain fcp full access required of always`() {
+               preferences.newFcpFullAccessRequired = ALWAYS
+               assertThat(preferences.fcpFullAccessRequired, equalTo(ALWAYS))
+               verifyFullAccessRequiredChangedEvent(ALWAYS)
+       }
+
+       @Test
+       fun `preferences return default value when fcp full access required is set to null`() {
+               preferences.newFcpFullAccessRequired = null
+               assertThat(preferences.fcpFullAccessRequired, equalTo(ALWAYS))
+               verifyFullAccessRequiredChangedEvent(ALWAYS)
+       }
+
+       @Test
+       fun `preferences start with fcp full access required default value`() {
+               assertThat(preferences.fcpFullAccessRequired, equalTo(ALWAYS))
+       }
+
+       @Test
+       fun `setting insertion delay to valid value sends change event`() {
+               testPreferencesChangedEvent("InsertionDelay", { preferences.newInsertionDelay = it }, 30)
+       }
+
+       @Test
+       fun `setting posts per page to valid value sends change event`() {
+               testPreferencesChangedEvent("PostsPerPage", { preferences.newPostsPerPage = it }, 31)
+       }
+
+       private fun <T : Any> testPreferencesChangedEvent(name: String, setter: (T) -> Unit, value: T) {
+               setter(value)
+               verify(eventBus, atLeastOnce()).post(eventsCaptor.capture())
+               assertThat(eventsCaptor.allValues, hasItem(PreferenceChangedEvent(name, value)))
+       }
+
+}
diff --git a/src/test/kotlin/net/pterodactylus/sone/core/SoneComparisonTest.kt b/src/test/kotlin/net/pterodactylus/sone/core/SoneComparisonTest.kt
new file mode 100644 (file)
index 0000000..f79d736
--- /dev/null
@@ -0,0 +1,50 @@
+package net.pterodactylus.sone.core
+
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+
+class SoneComparsisonTest {
+
+       private val oldSone = mock<Sone>()
+       private val newSone = mock<Sone>()
+
+       private val oldPost = mock<Post>()
+       private val removedPost = mock<Post>()
+       private val newPost = mock<Post>()
+       private val oldPostReply = mock<PostReply>()
+       private val removedPostReply = mock<PostReply>()
+       private val newPostReply = mock<PostReply>()
+
+       init {
+               whenever(oldSone.posts).thenReturn(listOf(oldPost, removedPost))
+               whenever(newSone.posts).thenReturn(listOf(oldPost, newPost))
+               whenever(oldSone.replies).thenReturn(setOf(oldPostReply, removedPostReply))
+               whenever(newSone.replies).thenReturn(setOf(oldPostReply, newPostReply))
+       }
+
+       private val soneComparison = SoneComparison(oldSone, newSone)
+
+       @Test
+       fun `new posts are identified correctly`() {
+               assertThat(soneComparison.newPosts, contains(newPost))
+       }
+
+       @Test
+       fun `removed posts are identified correctly`() {
+               assertThat(soneComparison.removedPosts, contains(removedPost))
+       }
+
+       @Test
+       fun `new post replies are identified correctly`() {
+               assertThat(soneComparison.newPostReplies, contains(newPostReply))
+       }
+
+       @Test
+       fun `removed post replies are identified correctly`() {
+               assertThat(soneComparison.removedPostReplies, contains(removedPostReply))
+       }
+
+}
diff --git a/src/test/kotlin/net/pterodactylus/sone/core/UpdatedSoneProcessorTest.kt b/src/test/kotlin/net/pterodactylus/sone/core/UpdatedSoneProcessorTest.kt
new file mode 100644 (file)
index 0000000..9e7c71e
--- /dev/null
@@ -0,0 +1,157 @@
+package net.pterodactylus.sone.core
+
+import com.google.common.eventbus.EventBus
+import net.pterodactylus.sone.core.event.NewPostFoundEvent
+import net.pterodactylus.sone.core.event.NewPostReplyFoundEvent
+import net.pterodactylus.sone.core.event.PostRemovedEvent
+import net.pterodactylus.sone.core.event.PostReplyRemovedEvent
+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.test.argumentCaptor
+import net.pterodactylus.sone.test.getInstance
+import net.pterodactylus.sone.test.isProvidedByMock
+import net.pterodactylus.sone.test.mock
+import net.pterodactylus.sone.test.whenever
+import net.pterodactylus.sone.web.baseInjector
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.contains
+import org.hamcrest.Matchers.containsInAnyOrder
+import org.hamcrest.Matchers.not
+import org.hamcrest.Matchers.notNullValue
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.any
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+/**
+ * Unit test for all [UpdatedSoneProcessor] implementations.
+ */
+class UpdatedSoneProcessorTest {
+
+       private val database = mock<Database>()
+       private val eventBus = mock<EventBus>()
+       private val updatedSoneProcessor = DefaultUpdateSoneProcessor(database, eventBus)
+       private val storedSone = mock<Sone>()
+       private val newSone = mock<Sone>()
+       private val posts = listOf(mock<Post>(), mock(), mock())
+       private val postReplies = listOf(mock<PostReply>(), mock(), mock())
+
+       private val events = argumentCaptor<Any>()
+
+       @Before
+       fun setupPostsAndReplies() {
+               posts.forEachIndexed { index, post -> whenever(post.time).thenReturn((index + 1) * 1000L + 100) }
+               postReplies.forEachIndexed { index, postReply -> whenever(postReply.time).thenReturn((index + 1) * 1000L + 200) }
+       }
+
+       @Before
+       fun setupSones() {
+               whenever(storedSone.time).thenReturn(1000L)
+               whenever(storedSone.posts).thenReturn(posts.slice(0..1))
+               whenever(storedSone.replies).thenReturn(postReplies.slice(0..1).toSet())
+               whenever(newSone.id).thenReturn("sone")
+               whenever(newSone.time).thenReturn(2000L)
+               whenever(newSone.posts).thenReturn(posts.slice(1..2))
+               whenever(newSone.replies).thenReturn(postReplies.slice(1..2).toSet())
+       }
+
+       @Before
+       fun setupDatabase() {
+               whenever(database.getSone("sone")).thenReturn(storedSone)
+               whenever(database.getFollowingTime("sone")).thenReturn(500L)
+       }
+
+       @Test
+       fun `updated Sone processor emits no event if no stored sone exists`() {
+               whenever(database.getSone("sone")).thenReturn(null)
+               updatedSoneProcessor.updateSone(newSone)
+               verify(eventBus, never()).post(any())
+       }
+
+       @Test
+       fun `updated Sone processor emits no event if new Sone is older than stored Sone`() {
+               whenever(newSone.time).thenReturn(500L)
+               updatedSoneProcessor.updateSone(newSone)
+               verify(eventBus, never()).post(any())
+       }
+
+       @Test
+       fun `updated Sone processor emits correct events when new Sone is newer`() {
+               updatedSoneProcessor.updateSone(newSone)
+               verify(eventBus, times(4)).post(events.capture())
+               assertThat(events.allValues, containsInAnyOrder(
+                               NewPostFoundEvent(posts[2]),
+                               PostRemovedEvent(posts[0]),
+                               NewPostReplyFoundEvent(postReplies[2]),
+                               PostReplyRemovedEvent(postReplies[0])
+               ))
+       }
+
+       @Test
+       fun `updated Sone processor does not mark new post as known if sone was not followed after post`() {
+               updatedSoneProcessor.updateSone(newSone)
+               verify(posts[2], never()).isKnown = true
+       }
+
+       @Test
+       fun `updated Sone processor does not mark new posts as known if Sone is not followed`() {
+               whenever(database.getFollowingTime("sone")).thenReturn(null)
+               updatedSoneProcessor.updateSone(newSone)
+               posts.forEach { verify(it, never()).isKnown = true }
+       }
+
+       @Test
+       fun `updated Sone processor marks new post as known if sone was followed after post`() {
+               whenever(database.getFollowingTime("sone")).thenReturn(3500L)
+               updatedSoneProcessor.updateSone(newSone)
+               verify(posts[2]).isKnown = true
+       }
+
+       @Test
+       fun `updated Sone processor does not emit event for post if it is already known`() {
+               whenever(posts[2].isKnown).thenReturn(true)
+               updatedSoneProcessor.updateSone(newSone)
+               verify(eventBus, atLeastOnce()).post(events.capture())
+               assertThat(events.allValues, not(contains<Any>(NewPostFoundEvent(posts[2]))))
+       }
+
+       @Test
+       fun `updated Sone processor does not mark new reply as known if sone was not followed after reply`() {
+               updatedSoneProcessor.updateSone(newSone)
+               verify(postReplies[2], never()).isKnown = true
+       }
+
+       @Test
+       fun `updated Sone processor marks new reply as known if sone was followed after reply`() {
+               whenever(database.getFollowingTime("sone")).thenReturn(3500L)
+               updatedSoneProcessor.updateSone(newSone)
+               verify(postReplies[2]).isKnown = true
+       }
+
+       @Test
+       fun `updated Sone processor does not emit event for reply if it is already known`() {
+               whenever(postReplies[2].isKnown).thenReturn(true)
+               updatedSoneProcessor.updateSone(newSone)
+               verify(eventBus, atLeastOnce()).post(events.capture())
+               assertThat(events.allValues, not(contains<Any>(NewPostReplyFoundEvent(postReplies[2]))))
+       }
+
+       @Test
+       fun `updated sone processor stores sone in database`() {
+               updatedSoneProcessor.updateSone(newSone)
+               verify(database).storeSone(newSone)
+       }
+
+       @Test
+       fun `default updated Sone processor can be created by dependency injection`() {
+               assertThat(baseInjector.createChildInjector(
+                               Database::class.isProvidedByMock()
+               ).getInstance<UpdatedSoneProcessor>(), notNullValue())
+       }
+
+}
diff --git a/src/test/kotlin/net/pterodactylus/sone/data/ProfileTest.kt b/src/test/kotlin/net/pterodactylus/sone/data/ProfileTest.kt
new file mode 100644 (file)
index 0000000..54ba0e0
--- /dev/null
@@ -0,0 +1,91 @@
+package net.pterodactylus.sone.data
+
+import net.pterodactylus.sone.test.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+
+/**
+ * Unit test for [Profile].
+ */
+class ProfileTest {
+
+       private val sone = mock<Sone>()
+       private val profile = Profile(sone)
+
+       @Test
+       fun `first name is initialized with null`() {
+               assertThat(profile.firstName, nullValue())
+       }
+
+       @Test
+       fun `setting first name to value will set it to value`() {
+           profile.firstName = "first name"
+               assertThat(profile.firstName, equalTo("first name"))
+       }
+
+       @Test
+       fun `setting first name to null will set it to null`() {
+           profile.firstName = null
+               assertThat(profile.firstName, nullValue())
+       }
+
+       @Test
+       fun `setting first name to empty string will set it to null`() {
+           profile.firstName = ""
+               assertThat(profile.firstName, nullValue())
+       }
+
+       @Test
+       fun `middle name is initialized with null`() {
+               assertThat(profile.middleName, nullValue())
+       }
+
+       @Test
+       fun `setting middle name to value will set it to value`() {
+               profile.middleName = "middle name"
+               assertThat(profile.middleName, equalTo("middle name"))
+       }
+
+       @Test
+       fun `setting middle name to null will set it to null`() {
+               profile.middleName = null
+               assertThat(profile.middleName, nullValue())
+       }
+
+       @Test
+       fun `setting middle name to empty string will set it to null`() {
+               profile.middleName = ""
+               assertThat(profile.middleName, nullValue())
+       }
+
+       @Test
+       fun `last name is initialized with null`() {
+               assertThat(profile.lastName, nullValue())
+       }
+
+       @Test
+       fun `setting last name to value will set it to value`() {
+               profile.lastName = "last name"
+               assertThat(profile.lastName, equalTo("last name"))
+       }
+
+       @Test
+       fun `setting last name to null will set it to null`() {
+               profile.lastName = null
+               assertThat(profile.lastName, nullValue())
+       }
+
+       @Test
+       fun `setting last name to empty string will set it to null`() {
+               profile.lastName = ""
+               assertThat(profile.lastName, nullValue())
+       }
+
+       @Test
+       fun `new fields are initialized with an empty string`() {
+               val newField = profile.addField("testField")
+               assertThat(newField.value, equalTo(""))
+       }
+
+}
diff --git a/src/test/kotlin/net/pterodactylus/sone/database/memory/ConfigurationLoaderTest.kt b/src/test/kotlin/net/pterodactylus/sone/database/memory/ConfigurationLoaderTest.kt
new file mode 100644 (file)
index 0000000..6f8d5d1
--- /dev/null
@@ -0,0 +1,122 @@
+package net.pterodactylus.sone.database.memory
+
+import net.pterodactylus.sone.test.TestValue.from
+import net.pterodactylus.sone.test.mock
+import net.pterodactylus.sone.test.whenever
+import net.pterodactylus.util.config.Configuration
+import net.pterodactylus.util.config.Value
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.containsInAnyOrder
+import org.hamcrest.Matchers.equalTo
+import org.hamcrest.Matchers.nullValue
+import org.junit.Test
+
+/**
+ * Unit test for [ConfigurationLoader].
+ */
+class ConfigurationLoaderTest {
+
+       private val configuration = mock<Configuration>()
+       private val configurationLoader = ConfigurationLoader(configuration)
+
+       private fun setupStringValue(attribute: String, value: String? = null): Value<String?> =
+                       from(value).apply {
+                               whenever(configuration.getStringValue(attribute)).thenReturn(this)
+                       }
+
+       private fun setupLongValue(attribute: String, value: Long? = null): Value<Long?> =
+                       from(value).apply {
+                               whenever(configuration.getLongValue(attribute)).thenReturn(this)
+                       }
+
+       @Test
+       fun `loader can load known posts`() {
+               setupStringValue("KnownPosts/0/ID", "Post2")
+               setupStringValue("KnownPosts/1/ID", "Post1")
+               setupStringValue("KnownPosts/2/ID")
+               val knownPosts = configurationLoader.loadKnownPosts()
+               assertThat(knownPosts, containsInAnyOrder("Post1", "Post2"))
+       }
+
+       @Test
+       fun `loader can load known post replies`() {
+               setupStringValue("KnownReplies/0/ID", "PostReply2")
+               setupStringValue("KnownReplies/1/ID", "PostReply1")
+               setupStringValue("KnownReplies/2/ID")
+               val knownPosts = configurationLoader.loadKnownPostReplies()
+               assertThat(knownPosts, containsInAnyOrder("PostReply1", "PostReply2"))
+       }
+
+       @Test
+       fun `loader can load bookmarked posts`() {
+               setupStringValue("Bookmarks/Post/0/ID", "Post2")
+               setupStringValue("Bookmarks/Post/1/ID", "Post1")
+               setupStringValue("Bookmarks/Post/2/ID")
+               val knownPosts = configurationLoader.loadBookmarkedPosts()
+               assertThat(knownPosts, containsInAnyOrder("Post1", "Post2"))
+       }
+
+       @Test
+       fun `loader can save bookmarked posts`() {
+               val post1 = setupStringValue("Bookmarks/Post/0/ID")
+               val post2 = setupStringValue("Bookmarks/Post/1/ID")
+               val post3 = setupStringValue("Bookmarks/Post/2/ID")
+               val originalPosts = setOf("Post1", "Post2")
+               configurationLoader.saveBookmarkedPosts(originalPosts)
+               val extractedPosts = setOf(post1.value, post2.value)
+               assertThat(extractedPosts, containsInAnyOrder("Post1", "Post2"))
+               assertThat(post3.value, nullValue())
+       }
+
+       @Test
+       fun `loader can load Sone following times`() {
+               setupStringValue("SoneFollowingTimes/0/Sone", "Sone1")
+               setupLongValue("SoneFollowingTimes/0/Time", 1000L)
+               setupStringValue("SoneFollowingTimes/1/Sone", "Sone2")
+               setupLongValue("SoneFollowingTimes/1/Time", 2000L)
+               setupStringValue("SoneFollowingTimes/2/Sone")
+               assertThat(configurationLoader.getSoneFollowingTime("Sone1"), equalTo(1000L))
+               assertThat(configurationLoader.getSoneFollowingTime("Sone2"), equalTo(2000L))
+               assertThat(configurationLoader.getSoneFollowingTime("Sone3"), nullValue())
+       }
+
+       @Test
+       fun `loader can overwrite existing Sone following time`() {
+               val sone1Id = setupStringValue("SoneFollowingTimes/0/Sone", "Sone1")
+               val sone1Time = setupLongValue("SoneFollowingTimes/0/Time", 1000L)
+               val sone2Id = setupStringValue("SoneFollowingTimes/1/Sone", "Sone2")
+               val sone2Time = setupLongValue("SoneFollowingTimes/1/Time", 2000L)
+               setupStringValue("SoneFollowingTimes/2/Sone")
+               configurationLoader.setSoneFollowingTime("Sone1", 3000L)
+               assertThat(listOf(sone1Id.value to sone1Time.value, sone2Id.value to sone2Time.value), containsInAnyOrder<Pair<String?, Long?>>(
+                               "Sone1" to 3000L,
+                               "Sone2" to 2000L
+               ))
+       }
+
+       @Test
+       fun `loader can remove Sone following time`() {
+               val sone1Id = setupStringValue("SoneFollowingTimes/0/Sone", "Sone1")
+               val sone1Time = setupLongValue("SoneFollowingTimes/0/Time", 1000L)
+               val sone2Id = setupStringValue("SoneFollowingTimes/1/Sone", "Sone2")
+               val sone2Time = setupLongValue("SoneFollowingTimes/1/Time", 2000L)
+               setupStringValue("SoneFollowingTimes/2/Sone")
+               configurationLoader.removeSoneFollowingTime("Sone1")
+               assertThat(sone1Id.value, equalTo("Sone2"))
+               assertThat(sone1Time.value, equalTo(2000L))
+               assertThat(sone2Id.value, nullValue())
+       }
+
+       @Test
+       fun `sone with missing following time is not loaded`() {
+               setupStringValue("SoneFollowingTimes/0/Sone", "Sone1")
+               setupLongValue("SoneFollowingTimes/0/Time", 1000L)
+               setupStringValue("SoneFollowingTimes/1/Sone", "Sone2")
+               setupLongValue("SoneFollowingTimes/1/Time")
+               setupStringValue("SoneFollowingTimes/2/Sone")
+               assertThat(configurationLoader.getSoneFollowingTime("Sone1"), equalTo(1000L))
+               assertThat(configurationLoader.getSoneFollowingTime("Sone2"), nullValue())
+               assertThat(configurationLoader.getSoneFollowingTime("Sone3"), nullValue())
+       }
+
+}
diff --git a/src/test/kotlin/net/pterodactylus/sone/database/memory/MemoryDatabaseTest.kt b/src/test/kotlin/net/pterodactylus/sone/database/memory/MemoryDatabaseTest.kt
new file mode 100644 (file)
index 0000000..1536da4
--- /dev/null
@@ -0,0 +1,415 @@
+/*
+ * Sone - MemoryDatabaseTest.kt - Copyright © 2013–2019 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.database.memory
+
+import com.google.common.base.*
+import com.google.common.base.Optional.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.data.impl.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.test.Matchers.*
+import net.pterodactylus.util.config.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mockito.*
+import org.mockito.invocation.*
+import java.util.Arrays.*
+import java.util.UUID.*
+import kotlin.test.*
+
+/**
+ * Tests for [MemoryDatabase].
+ */
+class MemoryDatabaseTest {
+
+       private val configuration = mock<Configuration>()
+       private val memoryDatabase = MemoryDatabase(configuration)
+       private val sone = mock<Sone>()
+
+       @BeforeTest
+       fun setupSone() {
+               whenever(sone.id).thenReturn(SONE_ID)
+       }
+
+       @Test
+       fun `stored sone is made available`() {
+               storeSone()
+               assertThat(memoryDatabase.getPost("post1"), isPost("post1", 1000L, "post1", absent()))
+               assertThat(memoryDatabase.getPost("post2"), isPost("post2", 2000L, "post2", of(RECIPIENT_ID)))
+               assertThat(memoryDatabase.getPost("post3"), nullValue())
+               assertThat(memoryDatabase.getPostReply("reply1"), isPostReply("reply1", "post1", 3000L, "reply1"))
+               assertThat(memoryDatabase.getPostReply("reply2"), isPostReply("reply2", "post2", 4000L, "reply2"))
+               assertThat(memoryDatabase.getPostReply("reply3"), isPostReply("reply3", "post1", 5000L, "reply3"))
+               assertThat(memoryDatabase.getPostReply("reply4"), nullValue())
+               assertThat(memoryDatabase.getAlbum("album1"), isAlbum("album1", null, "album1", "album-description1"))
+               assertThat(memoryDatabase.getAlbum("album2"), isAlbum("album2", null, "album2", "album-description2"))
+               assertThat(memoryDatabase.getAlbum("album3"), isAlbum("album3", "album1", "album3", "album-description3"))
+               assertThat(memoryDatabase.getAlbum("album4"), nullValue())
+               assertThat(memoryDatabase.getImage("image1"), isImage("image1", 1000L, "KSK@image1", "image1", "image-description1", 16, 9))
+               assertThat(memoryDatabase.getImage("image2"), isImage("image2", 2000L, "KSK@image2", "image2", "image-description2", 32, 18))
+               assertThat(memoryDatabase.getImage("image3"), isImage("image3", 3000L, "KSK@image3", "image3", "image-description3", 48, 27))
+               assertThat(memoryDatabase.getImage("image4"), nullValue())
+       }
+
+       private fun storeSone() {
+               val firstPost = TestPostBuilder().withId("post1")
+                               .from(SONE_ID)
+                               .withTime(1000L)
+                               .withText("post1")
+                               .build()
+               val secondPost = TestPostBuilder().withId("post2")
+                               .from(SONE_ID)
+                               .withTime(2000L)
+                               .withText("post2")
+                               .to(RECIPIENT_ID)
+                               .build()
+               val posts = asList(firstPost, secondPost)
+               whenever(sone.posts).thenReturn(posts)
+               val firstPostFirstReply = TestPostReplyBuilder().withId("reply1")
+                               .from(SONE_ID)
+                               .to(firstPost.id)
+                               .withTime(3000L)
+                               .withText("reply1")
+                               .build()
+               val firstPostSecondReply = TestPostReplyBuilder().withId("reply3")
+                               .from(RECIPIENT_ID)
+                               .to(firstPost.id)
+                               .withTime(5000L)
+                               .withText("reply3")
+                               .build()
+               val secondPostReply = TestPostReplyBuilder().withId("reply2")
+                               .from(SONE_ID)
+                               .to(secondPost.id)
+                               .withTime(4000L)
+                               .withText("reply2")
+                               .build()
+               val postReplies = setOf(firstPostFirstReply, firstPostSecondReply, secondPostReply)
+               whenever(sone.replies).thenReturn(postReplies)
+               val firstAlbum = TestAlbumBuilder().withId("album1")
+                               .by(sone)
+                               .build()
+                               .modify()
+                               .setTitle("album1")
+                               .setDescription("album-description1")
+                               .update()
+               val secondAlbum = TestAlbumBuilder().withId("album2")
+                               .by(sone)
+                               .build()
+                               .modify()
+                               .setTitle("album2")
+                               .setDescription("album-description2")
+                               .update()
+               val thirdAlbum = TestAlbumBuilder().withId("album3")
+                               .by(sone)
+                               .build()
+                               .modify()
+                               .setTitle("album3")
+                               .setDescription("album-description3")
+                               .update()
+               firstAlbum.addAlbum(thirdAlbum)
+               val rootAlbum = mock<Album>()
+               whenever(rootAlbum.id).thenReturn("root")
+               whenever(rootAlbum.albums).thenReturn(listOf(firstAlbum, secondAlbum))
+               whenever(sone.rootAlbum).thenReturn(rootAlbum)
+               val firstImage = TestImageBuilder().withId("image1")
+                               .build()
+                               .modify()
+                               .setSone(sone)
+                               .setCreationTime(1000L)
+                               .setKey("KSK@image1")
+                               .setTitle("image1")
+                               .setDescription("image-description1")
+                               .setWidth(16)
+                               .setHeight(9)
+                               .update()
+               val secondImage = TestImageBuilder().withId("image2")
+                               .build()
+                               .modify()
+                               .setSone(sone)
+                               .setCreationTime(2000L)
+                               .setKey("KSK@image2")
+                               .setTitle("image2")
+                               .setDescription("image-description2")
+                               .setWidth(32)
+                               .setHeight(18)
+                               .update()
+               val thirdImage = TestImageBuilder().withId("image3")
+                               .build()
+                               .modify()
+                               .setSone(sone)
+                               .setCreationTime(3000L)
+                               .setKey("KSK@image3")
+                               .setTitle("image3")
+                               .setDescription("image-description3")
+                               .setWidth(48)
+                               .setHeight(27)
+                               .update()
+               firstAlbum.addImage(firstImage)
+               firstAlbum.addImage(thirdImage)
+               secondAlbum.addImage(secondImage)
+               memoryDatabase.storeSone(sone)
+       }
+
+       @Test
+       fun `stored and removed sone is not available`() {
+               storeSone()
+               memoryDatabase.removeSone(sone)
+               assertThat(memoryDatabase.sones, empty())
+       }
+
+       @Test
+       fun `post recipients are detected correctly`() {
+               val postWithRecipient = createPost(of(RECIPIENT_ID))
+               memoryDatabase.storePost(postWithRecipient)
+               val postWithoutRecipient = createPost(absent())
+               memoryDatabase.storePost(postWithoutRecipient)
+               assertThat(memoryDatabase.getDirectedPosts(RECIPIENT_ID), contains(postWithRecipient))
+       }
+
+       private fun createPost(recipient: Optional<String>): Post {
+               val postWithRecipient = mock<Post>()
+               whenever(postWithRecipient.id).thenReturn(randomUUID().toString())
+               whenever(postWithRecipient.sone).thenReturn(sone)
+               whenever(postWithRecipient.recipientId).thenReturn(recipient)
+               return postWithRecipient
+       }
+
+       @Test
+       fun `post replies are managed correctly`() {
+               val firstPost = createPost(absent())
+               val firstPostFirstReply = createPostReply(firstPost, 1000L)
+               val secondPost = createPost(absent())
+               val secondPostFirstReply = createPostReply(secondPost, 1000L)
+               val secondPostSecondReply = createPostReply(secondPost, 2000L)
+               memoryDatabase.storePost(firstPost)
+               memoryDatabase.storePost(secondPost)
+               memoryDatabase.storePostReply(firstPostFirstReply)
+               memoryDatabase.storePostReply(secondPostFirstReply)
+               memoryDatabase.storePostReply(secondPostSecondReply)
+               assertThat(memoryDatabase.getReplies(firstPost.id), contains(firstPostFirstReply))
+               assertThat(memoryDatabase.getReplies(secondPost.id), contains(secondPostFirstReply, secondPostSecondReply))
+       }
+
+       private fun createPostReply(post: Post, time: Long): PostReply {
+               val postReply = mock<PostReply>()
+               whenever(postReply.id).thenReturn(randomUUID().toString())
+               whenever(postReply.time).thenReturn(time)
+               whenever(postReply.post).thenReturn(of(post))
+               val postId = post.id
+               whenever(postReply.postId).thenReturn(postId)
+               return postReply
+       }
+
+       @Test
+       fun `test basic album functionality`() {
+               val newAlbum = AlbumImpl(mock())
+               assertThat(memoryDatabase.getAlbum(newAlbum.id), nullValue())
+               memoryDatabase.storeAlbum(newAlbum)
+               assertThat(memoryDatabase.getAlbum(newAlbum.id), equalTo<Album>(newAlbum))
+               memoryDatabase.removeAlbum(newAlbum)
+               assertThat(memoryDatabase.getAlbum(newAlbum.id), nullValue())
+       }
+
+       private fun initializeFriends() {
+               whenever(configuration.getStringValue("Sone/$SONE_ID/Friends/0/ID")).thenReturn(TestValue.from("Friend1"))
+               whenever(configuration.getStringValue("Sone/$SONE_ID/Friends/1/ID")).thenReturn(TestValue.from("Friend2"))
+               whenever(configuration.getStringValue("Sone/$SONE_ID/Friends/2/ID")).thenReturn(TestValue.from(null))
+       }
+
+       @Test
+       fun `friends are returned correctly`() {
+               initializeFriends()
+               whenever(sone.isLocal).thenReturn(true)
+               val friends = memoryDatabase.getFriends(sone)
+               assertThat(friends, containsInAnyOrder("Friend1", "Friend2"))
+       }
+
+       @Test
+       fun `friends are only loaded once from configuration`() {
+               initializeFriends()
+               whenever(sone.isLocal).thenReturn(true)
+               memoryDatabase.getFriends(sone)
+               memoryDatabase.getFriends(sone)
+               verify(configuration, times(1)).getStringValue("Sone/$SONE_ID/Friends/0/ID")
+       }
+
+       @Test
+       fun `friends are only returned for local sones`() {
+               val friends = memoryDatabase.getFriends(sone)
+               assertThat(friends, emptyIterable<Any>())
+               verify(configuration, never()).getStringValue("Sone/$SONE_ID/Friends/0/ID")
+       }
+
+       @Test
+       fun `checking for a friend returns true`() {
+               initializeFriends()
+               whenever(sone.isLocal).thenReturn(true)
+               assertThat(memoryDatabase.isFriend(sone, "Friend1"), equalTo(true))
+       }
+
+       @Test
+       fun `checking for a friend that is not a friend returns false`() {
+               initializeFriends()
+               whenever(sone.isLocal).thenReturn(true)
+               assertThat(memoryDatabase.isFriend(sone, "FriendX"), equalTo(false))
+       }
+
+       @Test
+       fun `checking for a friend of remote sone returns false`() {
+               initializeFriends()
+               assertThat(memoryDatabase.isFriend(sone, "Friend1"), equalTo(false))
+       }
+
+       private fun prepareConfigurationValues(): Map<String, Value<*>> =
+                       mutableMapOf<String, Value<*>>().also { configurationValues ->
+                               whenever(configuration.getStringValue(anyString())).thenAnswer(createAndCacheValue(configurationValues))
+                               whenever(configuration.getLongValue(anyString())).thenAnswer(createAndCacheValue(configurationValues))
+                       }
+
+       private fun createAndCacheValue(configurationValues: MutableMap<String, Value<*>>) =
+                       { invocation: InvocationOnMock ->
+                               configurationValues[invocation[0]]
+                                               ?: TestValue.from(null).also {
+                                                       configurationValues[invocation[0]] = it
+                                               }
+                       }
+
+       @Test
+       fun `friend is added correctly to local sone`() {
+               val configurationValues = prepareConfigurationValues()
+               whenever(sone.isLocal).thenReturn(true)
+               memoryDatabase.addFriend(sone, "Friend1")
+               assertThat(configurationValues["Sone/$SONE_ID/Friends/0/ID"], equalTo<Value<*>>(TestValue.from("Friend1")))
+               assertThat(configurationValues["Sone/$SONE_ID/Friends/1/ID"], equalTo<Value<*>>(TestValue.from(null)))
+       }
+
+       @Test
+       fun `friend is not added to remote sone`() {
+               memoryDatabase.addFriend(sone, "Friend1")
+               verify(configuration, never()).getStringValue(anyString())
+       }
+
+       @Test
+       fun `friend is removed correctly from local sone`() {
+               val configurationValues = prepareConfigurationValues()
+               whenever(sone.isLocal).thenReturn(true)
+               memoryDatabase.addFriend(sone, "Friend1")
+               memoryDatabase.removeFriend(sone, "Friend1")
+               assertThat(configurationValues["Sone/$SONE_ID/Friends/0/ID"], equalTo<Value<*>>(TestValue.from(null)))
+               assertThat(configurationValues["Sone/$SONE_ID/Friends/1/ID"], equalTo<Value<*>>(TestValue.from(null)))
+       }
+
+       @Test
+       fun `configuration is not written when a non-friend is removed`() {
+               prepareConfigurationValues()
+               whenever(sone.isLocal).thenReturn(true)
+               memoryDatabase.removeFriend(sone, "Friend1")
+               verify(configuration).getStringValue(anyString())
+       }
+
+       @Test
+       fun `sone following time is returned correctly`() {
+               prepareConfigurationValues()
+               configuration.getStringValue("SoneFollowingTimes/0/Sone").value = "sone"
+               configuration.getLongValue("SoneFollowingTimes/0/Time").value = 1000L
+               assertThat(memoryDatabase.getFollowingTime("sone"), equalTo(1000L))
+       }
+
+       @Test
+       fun `null is returned when sone is not followed`() {
+               prepareConfigurationValues()
+               configuration.getStringValue("SoneFollowingTimes/0/Sone").value = "otherSone"
+               configuration.getLongValue("SoneFollowingTimes/0/Time").value = 1000L
+               assertThat(memoryDatabase.getFollowingTime("sone"), nullValue())
+       }
+
+       @Test
+       fun `time is stored in configuration when a sone is followed`() {
+               prepareConfigurationValues()
+               whenever(sone.isLocal).thenReturn(true)
+               memoryDatabase.addFriend(sone, "Friend")
+               assertThat(configuration.getStringValue("SoneFollowingTimes/0/Sone").value, equalTo("Friend"))
+               assertThat(System.currentTimeMillis() - configuration.getLongValue("SoneFollowingTimes/0/Time").value, lessThan(1000L))
+               assertThat(configuration.getStringValue("SoneFollowingTimes/1/Sone").value, nullValue())
+       }
+
+       @Test
+       fun `existing time is not overwritten when a sone is followed`() {
+               prepareConfigurationValues()
+               configuration.getStringValue("SoneFollowingTimes/0/Sone").value = "Friend"
+               configuration.getLongValue("SoneFollowingTimes/0/Time").value = 1000L
+               whenever(sone.isLocal).thenReturn(true)
+               memoryDatabase.addFriend(sone, "Friend")
+               assertThat(configuration.getStringValue("SoneFollowingTimes/0/Sone").value, equalTo("Friend"))
+               assertThat(configuration.getLongValue("SoneFollowingTimes/0/Time").value, equalTo(1000L))
+               assertThat(configuration.getStringValue("SoneFollowingTimes/1/Sone").value, nullValue())
+       }
+
+       @Test
+       fun `unfollowing a sone removes the following time`() {
+               prepareConfigurationValues()
+               configuration.getStringValue("Sone/sone/Friends/0/ID").value = "Friend"
+               configuration.getStringValue("SoneFollowingTimes/0/Sone").value = "Friend"
+               configuration.getLongValue("SoneFollowingTimes/0/Time").value = 1000L
+               whenever(sone.isLocal).thenReturn(true)
+               memoryDatabase.removeFriend(sone, "Friend")
+               assertThat(configuration.getStringValue("SoneFollowingTimes/0/Sone").value, nullValue())
+       }
+
+       @Test
+       fun `unfollowing a sone does not remove the following time if another local sone follows it`() {
+               prepareConfigurationValues()
+               configuration.getStringValue("Sone/sone/Friends/0/ID").value = "Friend"
+               configuration.getStringValue("Sone/other-sone/Friends/0/ID").value = "Friend"
+               configuration.getStringValue("SoneFollowingTimes/0/Sone").value = "Friend"
+               configuration.getLongValue("SoneFollowingTimes/0/Time").value = 1000L
+               val otherSone = mock<Sone>()
+               whenever(otherSone.isLocal).thenReturn(true)
+               whenever(otherSone.id).thenReturn("other-sone")
+               memoryDatabase.getFriends(otherSone)
+               whenever(sone.isLocal).thenReturn(true)
+               memoryDatabase.removeFriend(sone, "Friend")
+               assertThat(configuration.getStringValue("SoneFollowingTimes/0/Sone").value, equalTo("Friend"))
+               assertThat(configuration.getLongValue("SoneFollowingTimes/0/Time").value, equalTo(1000L))
+       }
+
+       @Test
+       fun `marking a post as known saves configuration`() {
+               prepareConfigurationValues()
+               val post = mock<Post>()
+               whenever(post.id).thenReturn("post-id")
+               memoryDatabase.setPostKnown(post, true)
+               assertThat(configuration.getStringValue("KnownPosts/0/ID").value, equalTo("post-id"))
+               assertThat(configuration.getStringValue("KnownPosts/1/ID").value, equalTo<Any>(null))
+       }
+
+       @Test
+       fun `marking a post reply as known saves configuration`() {
+               prepareConfigurationValues()
+               val postReply = mock<PostReply>()
+               whenever(postReply.id).thenReturn("post-reply-id")
+               memoryDatabase.setPostReplyKnown(postReply, true)
+               assertThat(configuration.getStringValue("KnownReplies/0/ID").value, equalTo("post-reply-id"))
+               assertThat(configuration.getStringValue("KnownReplies/1/ID").value, equalTo<Any>(null))
+       }
+
+}
+
+private const val SONE_ID = "sone"
+private const val RECIPIENT_ID = "recipient"
diff --git a/src/test/kotlin/net/pterodactylus/sone/database/memory/MemoryPostTest.kt b/src/test/kotlin/net/pterodactylus/sone/database/memory/MemoryPostTest.kt
new file mode 100644 (file)
index 0000000..90344e1
--- /dev/null
@@ -0,0 +1,26 @@
+package net.pterodactylus.sone.database.memory
+
+import com.google.common.base.Optional.*
+import net.pterodactylus.sone.test.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import java.util.*
+
+class MemoryPostTest {
+
+       private val memoryDatabase = mock<MemoryDatabase>()
+
+       @Test
+       fun `memory post returns empty optional for post without recipient`() {
+               val memoryPost = MemoryPost(memoryDatabase, memoryDatabase, UUID.randomUUID().toString(), "soneId", null, 123, "text")
+               assertThat(memoryPost.recipient, equalTo(absent()))
+       }
+
+       @Test
+       fun `empty optional is returned if recipient is set but non-existent`() {
+               val memoryPost = MemoryPost(memoryDatabase, memoryDatabase, UUID.randomUUID().toString(), "soneId", "recipientId", 123, "text")
+               assertThat(memoryPost.recipient, equalTo(absent()))
+       }
+
+}
index 2d77c6e..29db41c 100644 (file)
@@ -20,7 +20,7 @@ class CreatePostCommandTest : SoneCommandTest() {
 
        @Test
        fun `command requires write access`() {
-               assertThat(command.requiresWriteAccess(), equalTo(true))
+               assertThat(command.requiresWriteAccess, equalTo(true))
        }
 
        @Test
index a6e69cd..373e3db 100644 (file)
@@ -1,14 +1,11 @@
 package net.pterodactylus.sone.fcp
 
-import com.google.common.base.Optional.of
-import net.pterodactylus.sone.core.Core
-import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.data.PostReply
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
+import net.pterodactylus.sone.core.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
 
 /**
  * Unit test for [CreateReplyCommand].
@@ -21,7 +18,7 @@ class CreateReplyCommandTest : SoneCommandTest() {
 
        @Test
        fun `command requires write access`() {
-               assertThat(command.requiresWriteAccess(), equalTo(true))
+               assertThat(command.requiresWriteAccess, equalTo(true))
        }
 
        @Test
index 30fc4cb..15173d2 100644 (file)
@@ -1,14 +1,12 @@
 package net.pterodactylus.sone.fcp
 
-import com.google.common.base.Optional.of
-import net.pterodactylus.sone.core.Core
-import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.core.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [DeletePostCommand].
@@ -21,7 +19,7 @@ class DeletePostCommandTest : SoneCommandTest() {
 
        @Test
        fun `command requires write access`() {
-               assertThat(command.requiresWriteAccess(), equalTo(true))
+               assertThat(command.requiresWriteAccess, equalTo(true))
        }
 
        @Test
index 6920a33..ca6ec9d 100644 (file)
@@ -1,14 +1,12 @@
 package net.pterodactylus.sone.fcp
 
-import com.google.common.base.Optional.of
-import net.pterodactylus.sone.core.Core
-import net.pterodactylus.sone.data.PostReply
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.core.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [DeleteReplyCommand].
@@ -22,7 +20,7 @@ class DeleteReplyCommandTest : SoneCommandTest() {
 
        @Test
        fun `command requires write access`() {
-               assertThat(command.requiresWriteAccess(), equalTo(true))
+               assertThat(command.requiresWriteAccess, equalTo(true))
        }
 
        @Test
index 2a78efc..7fd8db3 100644 (file)
@@ -18,7 +18,7 @@ class GetLocalSonesCommandTest : SoneCommandTest() {
 
        @Test
        fun `command does not require write access`() {
-               assertThat(command.requiresWriteAccess(), equalTo(false))
+               assertThat(command.requiresWriteAccess, equalTo(false))
        }
 
        @Test
index 5c76c5b..f8113af 100644 (file)
@@ -39,7 +39,7 @@ class GetPostCommandTest : SoneCommandTest() {
 
        @Test
        fun `command does not require write access`() {
-               assertThat(command.requiresWriteAccess(), equalTo(false))
+               assertThat(command.requiresWriteAccess, equalTo(false))
        }
 
        @Test
index d8ab243..767aa28 100644 (file)
@@ -31,7 +31,7 @@ class GetPostFeedCommandTest : SoneCommandTest() {
 
        @Test
        fun `command does not require write access`() {
-               assertThat(command.requiresWriteAccess(), equalTo(false))
+               assertThat(command.requiresWriteAccess, equalTo(false))
        }
 
        @Test
index acf1824..9f7600a 100644 (file)
@@ -24,7 +24,7 @@ class GetPostsCommandTest : SoneCommandTest() {
 
        @Test
        fun `command does not require write access`() {
-               assertThat(command.requiresWriteAccess(), equalTo(false))
+               assertThat(command.requiresWriteAccess, equalTo(false))
        }
 
        @Test
index 79aad1a..9b9ed8b 100644 (file)
@@ -22,7 +22,7 @@ class GetSoneCommandTest : SoneCommandTest() {
 
        @Test
        fun `command does not require write access`() {
-               assertThat(command.requiresWriteAccess(), equalTo(false))
+               assertThat(command.requiresWriteAccess, equalTo(false))
        }
 
        @Test
index 252eae3..10f6501 100644 (file)
@@ -25,7 +25,7 @@ class GetSonesCommandTest : SoneCommandTest() {
 
        @Test
        fun `command does not require write access`() {
-               assertThat(command.requiresWriteAccess(), equalTo(false))
+               assertThat(command.requiresWriteAccess, equalTo(false))
        }
 
        @Test
index 6c7d40c..7041f25 100644 (file)
@@ -1,23 +1,19 @@
 package net.pterodactylus.sone.fcp
 
-import net.pterodactylus.sone.core.Core
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.freenet.fcp.FcpException
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.sone.utils.asOptional
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.core.*
+import net.pterodactylus.sone.freenet.fcp.*
+import net.pterodactylus.sone.test.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [LikePostCommand].
  */
 class LikePostCommandTest : SoneCommandTest() {
 
-       private val post = createPost("PostId", mock<Sone>(), null, 1000, "Text")
+       private val post = createPost("PostId", mock(), null, 1000, "Text")
 
        override fun createCommand(core: Core) = LikePostCommand(core)
 
@@ -30,7 +26,7 @@ class LikePostCommandTest : SoneCommandTest() {
 
        @Test
        fun `command requires write access`() {
-               assertThat(command.requiresWriteAccess(), equalTo(true))
+               assertThat(command.requiresWriteAccess, equalTo(true))
        }
 
        @Test
@@ -70,7 +66,7 @@ class LikePostCommandTest : SoneCommandTest() {
 
        @Test
        fun `request with valid parameters adds post to liked posts for sone`() {
-               whenever(core.getLikes(post)).thenReturn(setOf(mock<Sone>(), mock<Sone>(), mock<Sone>()))
+               whenever(core.getLikes(post)).thenReturn(setOf(mock(), mock(), mock()))
                parameters += "Post" to "PostId"
                parameters += "Sone" to "LocalSoneId"
                val replyParameters = command.execute(parameters).replyParameters
index 7b9256a..0d0579a 100644 (file)
@@ -1,24 +1,19 @@
 package net.pterodactylus.sone.fcp
 
-import net.pterodactylus.sone.core.Core
-import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.freenet.fcp.FcpException
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.sone.utils.asOptional
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.core.*
+import net.pterodactylus.sone.freenet.fcp.*
+import net.pterodactylus.sone.test.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [LikeReplyCommand].
  */
 class LikeReplyCommandTest : SoneCommandTest() {
 
-       private val reply = createReply("ReplyId", mock<Sone>(), mock<Post>(), 1000, "Text")
+       private val reply = createReply("ReplyId", mock(), mock(), 1000, "Text")
 
        override fun createCommand(core: Core) = LikeReplyCommand(core)
 
@@ -31,7 +26,7 @@ class LikeReplyCommandTest : SoneCommandTest() {
 
        @Test
        fun `command requires write access`() {
-               assertThat(command.requiresWriteAccess(), equalTo(true))
+               assertThat(command.requiresWriteAccess, equalTo(true))
        }
 
        @Test
@@ -71,7 +66,7 @@ class LikeReplyCommandTest : SoneCommandTest() {
 
        @Test
        fun `request with local sone adds reply id to sone`() {
-               whenever(core.getLikes(reply)).thenReturn(setOf(mock<Sone>(), mock<Sone>(), mock<Sone>()))
+               whenever(core.getLikes(reply)).thenReturn(setOf(mock(), mock(), mock()))
                parameters += "Reply" to "ReplyId"
                parameters += "Sone" to "LocalSoneId"
                val replyParameters = command.execute(parameters).replyParameters
index 284df69..dea019f 100644 (file)
@@ -24,7 +24,7 @@ class LockSoneCommandTest : SoneCommandTest() {
 
        @Test
        fun `command requires write access`() {
-               assertThat(command.requiresWriteAccess(), equalTo(true))
+               assertThat(command.requiresWriteAccess, equalTo(true))
        }
 
        @Test
index de92612..91eabf5 100644 (file)
@@ -1,22 +1,15 @@
 package net.pterodactylus.sone.fcp
 
-import com.google.common.base.Optional.absent
-import freenet.support.SimpleFieldSet
-import net.pterodactylus.sone.core.Core
-import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.data.PostReply
-import net.pterodactylus.sone.data.Profile
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.freenet.fcp.FcpException
-import net.pterodactylus.sone.template.SoneAccessor
-import net.pterodactylus.sone.test.OneByOneMatcher
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.sone.utils.asOptional
-import org.junit.Before
-import org.junit.Rule
-import org.junit.rules.ExpectedException
-import org.mockito.ArgumentMatchers.anyString
+import freenet.support.*
+import net.pterodactylus.sone.core.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.freenet.fcp.*
+import net.pterodactylus.sone.template.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.utils.*
+import org.junit.*
+import org.junit.rules.*
+import org.mockito.ArgumentMatchers.*
 
 /**
  * Base class for Sone FCP command tests.
index 77e78b3..f520349 100644 (file)
@@ -24,7 +24,7 @@ class UnlockSoneCommandTest : SoneCommandTest() {
 
        @Test
        fun `command requires write access`() {
-               assertThat(command.requiresWriteAccess(), equalTo(true))
+               assertThat(command.requiresWriteAccess, equalTo(true))
        }
 
        @Test
index e54e7aa..9cc9c21 100644 (file)
@@ -15,7 +15,7 @@ class VersionCommandTest : SoneCommandTest() {
 
        @Test
        fun `command does not require write access`() {
-               assertThat(command.requiresWriteAccess(), equalTo(false))
+               assertThat(command.requiresWriteAccess, equalTo(false))
        }
 
        @Test
index 39196c1..66b1ab1 100644 (file)
@@ -4,7 +4,6 @@ import freenet.l10n.BaseL10n
 import freenet.l10n.BaseL10n.LANGUAGE.ENGLISH
 import net.pterodactylus.sone.test.mock
 import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.sone.web.WebInterface
 import net.pterodactylus.util.template.TemplateContext
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.equalTo
@@ -17,18 +16,12 @@ import org.mockito.ArgumentMatchers.anyString
  */
 class L10nFilterTest {
 
-       private val webInterface = mock<WebInterface>()
-       private val filter = L10nFilter(webInterface)
-       private val templateContext = mock<TemplateContext>()
        private val l10n = mock<BaseL10n>()
+       private val filter = L10nFilter(l10n)
+       private val templateContext = mock<TemplateContext>()
        private val translations = mutableMapOf<String, String>()
 
        @Before
-       fun setupWebInterface() {
-               whenever(webInterface.l10n).thenReturn(l10n)
-       }
-
-       @Before
        fun setupL10n() {
                whenever(l10n.selectedLanguage).thenReturn(ENGLISH)
                whenever(l10n.getString(anyString())).then { translations[it.arguments[0]] }
@@ -61,7 +54,7 @@ class L10nFilterTest {
        @Test
        fun `filter does not replace values if there are no parameters`() {
                translations["data"] = "{link}"
-               assertThat(filter.format(templateContext, "data", emptyMap()), equalTo("{link}"));
+               assertThat(filter.format(templateContext, "data", emptyMap()), equalTo("{link}"))
        }
 
 }
index d0dc7c4..2f55d52 100644 (file)
@@ -1,18 +1,15 @@
 package net.pterodactylus.sone.main
 
-import com.google.inject.Guice
-import freenet.client.HighLevelSimpleClient
-import freenet.clients.http.SessionManager
-import freenet.node.Node
-import freenet.pluginmanager.PluginRespirator
-import net.pterodactylus.sone.test.deepMock
-import net.pterodactylus.sone.test.getInstance
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.sameInstance
-import org.junit.Test
-import org.mockito.Mockito.verify
+import com.google.inject.*
+import freenet.client.*
+import freenet.clients.http.*
+import freenet.node.*
+import freenet.pluginmanager.*
+import net.pterodactylus.sone.test.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [FreenetModule].
@@ -25,10 +22,12 @@ class FreenetModuleTest {
        }
        private val node = pluginRespirator.node!!
        private val highLevelSimpleClient = pluginRespirator.hlSimpleClient!!
+       private val toadletContainer: ToadletContainer = pluginRespirator.toadletContainer
+       private val pageMaker: PageMaker = pluginRespirator.pageMaker
        private val module = FreenetModule(pluginRespirator)
        private val injector = Guice.createInjector(module)
 
-       private inline fun <reified T: Any> verifySingletonInstance() {
+       private inline fun <reified T : Any> verifySingletonInstance() {
                val firstInstance = injector.getInstance<T>()
                val secondInstance = injector.getInstance<T>()
                assertThat(firstInstance, sameInstance(secondInstance))
@@ -36,7 +35,7 @@ class FreenetModuleTest {
 
        @Test
        fun `plugin respirator is returned correctly`() {
-               assertThat(injector.getInstance<PluginRespirator>(), sameInstance(pluginRespirator))
+               assertThat(injector.getInstance(), sameInstance(pluginRespirator))
        }
 
        @Test
@@ -46,7 +45,7 @@ class FreenetModuleTest {
 
        @Test
        fun `node is returned correctly`() {
-               assertThat(injector.getInstance<Node>(), sameInstance(node))
+               assertThat(injector.getInstance(), sameInstance(node))
        }
 
        @Test
@@ -56,7 +55,7 @@ class FreenetModuleTest {
 
        @Test
        fun `high level simply client is returned correctly`() {
-               assertThat(injector.getInstance<HighLevelSimpleClient>(), sameInstance(highLevelSimpleClient))
+               assertThat(injector.getInstance(), sameInstance(highLevelSimpleClient))
        }
 
        @Test
@@ -66,7 +65,7 @@ class FreenetModuleTest {
 
        @Test
        fun `session manager is returned correctly`() {
-               assertThat(injector.getInstance<SessionManager>(), sameInstance(sessionManager))
+               assertThat(injector.getInstance(), sameInstance(sessionManager))
        }
 
        @Test
@@ -75,4 +74,24 @@ class FreenetModuleTest {
                verify(pluginRespirator).getSessionManager("Sone")
        }
 
+       @Test
+       fun `toadlet container is returned correctly`() {
+               assertThat(injector.getInstance(), sameInstance(toadletContainer))
+       }
+
+       @Test
+       fun `toadlet container is returned as singleten`() {
+               verifySingletonInstance<ToadletContainer>()
+       }
+
+       @Test
+       fun `page maker is returned correctly`() {
+               assertThat(injector.getInstance(), sameInstance(pageMaker))
+       }
+
+       @Test
+       fun `page maker is returned as singleten`() {
+               verifySingletonInstance<PageMaker>()
+       }
+
 }
diff --git a/src/test/kotlin/net/pterodactylus/sone/main/SoneModuleCreatorTest.kt b/src/test/kotlin/net/pterodactylus/sone/main/SoneModuleCreatorTest.kt
new file mode 100644 (file)
index 0000000..0286032
--- /dev/null
@@ -0,0 +1,176 @@
+package net.pterodactylus.sone.main
+
+import com.google.common.base.*
+import com.google.common.eventbus.*
+import com.google.inject.*
+import com.google.inject.name.Names.*
+import net.pterodactylus.sone.database.*
+import net.pterodactylus.sone.database.memory.*
+import net.pterodactylus.sone.freenet.wot.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.util.config.*
+import net.pterodactylus.util.version.Version
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import java.io.*
+import java.util.concurrent.atomic.*
+
+class SoneModuleCreatorTest {
+
+       private val currentDir: File = File(".")
+       private val pluginVersion = Version("", 0, 1, 2)
+       private val pluginYear = 2019
+       private val pluginHomepage = "home://page"
+       private val sonePlugin = mock<SonePlugin>().apply {
+               whenever(version).thenReturn(pluginVersion.toString())
+               whenever(year).thenReturn(pluginYear)
+               whenever(homepage).thenReturn(pluginHomepage)
+       }
+
+       @After
+       fun removePropertiesFromCurrentDirectory() {
+               File(currentDir, "sone.properties").delete()
+       }
+
+       @Test
+       fun `creator binds configuration when no file is present`() {
+               File(currentDir, "sone.properties").delete()
+               assertThat(getInstance<Configuration>(), notNullValue())
+       }
+
+       @Test
+       fun `creator binds first start to true when no file is present`() {
+               File(currentDir, "sone.properties").delete()
+               assertThat(getInstance(named("FirstStart")), equalTo(true))
+       }
+
+       @Test
+       fun `config file is created in current directory if not present`() {
+               File(currentDir, "sone.properties").delete()
+               val configuration = getInstance<Configuration>()
+               configuration.save()
+               assertThat(File(currentDir, "sone.properties").exists(), equalTo(true))
+       }
+
+       @Test
+       fun `creator binds configuration when file is present`() {
+               File(currentDir, "sone.properties").writeText("Option=old")
+               assertThat(getInstance<Configuration>().getStringValue("Option").value, equalTo("old"))
+       }
+
+       @Test
+       fun `creator binds first start to false when file is present`() {
+               File(currentDir, "sone.properties").writeText("Option=old")
+               assertThat(getInstance(named("FirstStart")), equalTo(false))
+       }
+
+       @Test
+       fun `invalid config file leads to new config being created`() {
+               File(currentDir, "sone.properties").writeText("Option=old\nbroken")
+               val configuration = getInstance<Configuration>()
+               assertThat(configuration.getStringValue("Option").getValue(null), nullValue())
+       }
+
+       @Test
+       fun `invalid config file leads to new config being set to true`() {
+               File(currentDir, "sone.properties").writeText("Option=old\nbroken")
+               assertThat(getInstance(named("NewConfig")), equalTo(true))
+       }
+
+       @Test
+       fun `valid config file leads to new config being set to false`() {
+               File(currentDir, "sone.properties").writeText("Option=old")
+               assertThat(getInstance(named("NewConfig")), equalTo(false))
+       }
+
+       @Test
+       fun `event bus is bound`() {
+               assertThat(getInstance<EventBus>(), notNullValue())
+       }
+
+       @Test
+       fun `context is bound`() {
+               assertThat(getInstance<Context>().context, equalTo("Sone"))
+       }
+
+       @Test
+       fun `optional context is bound`() {
+               assertThat(getInstance<Optional<Context>>().get().context, equalTo("Sone"))
+       }
+
+       @Test
+       fun `sone plugin is bound`() {
+               assertThat(getInstance(), sameInstance(sonePlugin))
+       }
+
+       @Test
+       fun `version is bound`() {
+               assertThat(getInstance(), equalTo(pluginVersion))
+       }
+
+       @Test
+       fun `plugin version is bound`() {
+               assertThat(getInstance(), equalTo(PluginVersion(pluginVersion.toString())))
+       }
+
+       @Test
+       fun `plugin year is bound`() {
+               assertThat(getInstance(), equalTo(PluginYear(pluginYear)))
+       }
+
+       @Test
+       fun `plugin homepage in bound`() {
+               assertThat(getInstance(), equalTo(PluginHomepage(pluginHomepage)))
+       }
+
+       @Test
+       fun `database is bound correctly`() {
+               assertThat(getInstance<Database>(), instanceOf(MemoryDatabase::class.java))
+       }
+
+       @Test
+       fun `default loader is used without dev options`() {
+               assertThat(getInstance<Loaders>(), instanceOf(DefaultLoaders::class.java))
+       }
+
+       @Test
+       fun `default loaders are used if no path is given`() {
+               File(currentDir, "sone.properties").writeText("Developer.LoadFromFilesystem=true")
+               assertThat(getInstance<Loaders>(), instanceOf(DefaultLoaders::class.java))
+       }
+
+       @Test
+       fun `debug loaders are used if path is given`() {
+               File(currentDir, "sone.properties").writeText("Developer.LoadFromFilesystem=true\nDeveloper.FilesystemPath=/tmp")
+               assertThat(getInstance<Loaders>(), instanceOf(DebugLoaders::class.java))
+       }
+
+       class TestObject {
+               val ref: AtomicReference<Any?> = AtomicReference()
+               @Subscribe
+               fun testEvent(event: Any?) {
+                       ref.set(event)
+               }
+       }
+
+       @Test
+       fun `created objects are registered with event bus`() {
+               val injector = createInjector()
+               val eventBus: EventBus = getInstance(injector = injector)
+               val testObject = getInstance<TestObject>(injector = injector)
+               val event = Any()
+               eventBus.post(event)
+               assertThat(testObject.ref.get(), sameInstance(event))
+       }
+
+       private fun createInjector(): Injector = SoneModuleCreator()
+                       .createModule(sonePlugin)
+                       .let { Guice.createInjector(it) }
+
+       private inline fun <reified R : Any> getInstance(annotation: Annotation? = null, injector: Injector = createInjector()): R =
+                       annotation
+                                       ?.let { injector.getInstance(Key.get(object : TypeLiteral<R>() {}, it)) }
+                                       ?: injector.getInstance(Key.get(object : TypeLiteral<R>() {}))
+
+}
diff --git a/src/test/kotlin/net/pterodactylus/sone/main/SonePluginTest.kt b/src/test/kotlin/net/pterodactylus/sone/main/SonePluginTest.kt
new file mode 100644 (file)
index 0000000..6d57b9f
--- /dev/null
@@ -0,0 +1,34 @@
+package net.pterodactylus.sone.main
+
+import freenet.client.async.USKManager
+import freenet.l10n.BaseL10n.LANGUAGE.ENGLISH
+import freenet.node.Node
+import freenet.node.NodeClientCore
+import freenet.pluginmanager.PluginRespirator
+import net.pterodactylus.sone.test.*
+import org.junit.Test
+
+/**
+ * Unit test for [SonePlugin].
+ */
+class SonePluginTest {
+
+       private val sonePlugin = SonePlugin()
+       private val pluginRespirator = deepMock<PluginRespirator>()
+       private val node = deepMock<Node>()
+       private val clientCore = deepMock<NodeClientCore>()
+       private val uskManager = deepMock<USKManager>()
+
+       init {
+               setField(node, "clientCore", clientCore)
+               whenever(pluginRespirator.node).thenReturn(node)
+               setField(clientCore, "uskManager", uskManager)
+       }
+
+       @Test
+       fun `sone plugin can be started`() {
+               sonePlugin.setLanguage(ENGLISH)
+               sonePlugin.runPlugin(pluginRespirator)
+       }
+
+}
index eb692ba..f7da592 100644 (file)
@@ -17,7 +17,7 @@ class ImageAccessorTest {
 
        private val accessor = ImageAccessor()
        private val album = mock<Album>()
-       private val images = listOf(mock<Image>(), mock<Image>())
+       private val images = listOf(mock<Image>(), mock())
 
        @Before
        fun setupImages() {
index 07cba3d..8363f6b 100644 (file)
@@ -1,35 +1,21 @@
 package net.pterodactylus.sone.template
 
-import com.google.inject.Guice
-import net.pterodactylus.sone.core.LinkedElement
-import net.pterodactylus.sone.test.getInstance
-import net.pterodactylus.sone.test.isProvidedByMock
-import net.pterodactylus.util.template.ClassPathTemplateProvider
-import net.pterodactylus.util.template.HtmlFilter
-import net.pterodactylus.util.template.TemplateContextFactory
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.`is`
-import org.hamcrest.Matchers.contains
-import org.hamcrest.Matchers.equalTo
-import org.hamcrest.Matchers.notNullValue
-import org.hamcrest.Matchers.nullValue
-import org.jsoup.Jsoup
-import org.jsoup.nodes.Element
-import org.junit.Test
+import com.google.inject.*
+import net.pterodactylus.sone.core.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.util.template.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.jsoup.*
+import org.jsoup.nodes.*
+import org.junit.*
 
 /**
  * Unit test for [LinkedElementRenderFilter].
  */
 class LinkedElementRenderFilterTest {
 
-       private val templateContextFactory = TemplateContextFactory()
-
-       init {
-               templateContextFactory.addFilter("html", HtmlFilter())
-               templateContextFactory.addProvider(ClassPathTemplateProvider(LinkedElementRenderFilter::class.java, "/templates/"))
-       }
-
-       private val filter = LinkedElementRenderFilter(templateContextFactory)
+       private val filter = LinkedElementRenderFilter()
 
        @Test
        fun `filter returns null for objects that are not linked elements`() {
@@ -38,36 +24,36 @@ class LinkedElementRenderFilterTest {
 
        @Test
        fun `filter renders empty span for not loaded elements`() {
-               val html = filter.format(null, LinkedElement("KSK@gpl.png", loading = true), emptyMap<String, Any?>()) as String
+               val html = filter.format(null, LinkedElement("KSK@gpl.png", loading = true), emptyMap()) as String
                val spanNode = Jsoup.parseBodyFragment(html).body().child(0)
-               assertThat(spanNode.nodeName(), `is`("span"))
-               assertThat(spanNode.attr("class"), `is`("linked-element not-loaded"))
-               assertThat(spanNode.attr("title"), `is`("KSK@gpl.png"))
-               assertThat(spanNode.hasAttr("style"), `is`(false))
-               assertThat(spanNode.children().isEmpty(), `is`(true))
+               assertThat(spanNode.nodeName(), equalTo("span"))
+               assertThat(spanNode.attr("class"), equalTo("linked-element not-loaded"))
+               assertThat(spanNode.attr("title"), equalTo("KSK@gpl.png"))
+               assertThat(spanNode.hasAttr("style"), equalTo(false))
+               assertThat(spanNode.children().isEmpty(), equalTo(true))
        }
 
        @Test
        fun `filter can render linked images`() {
-               val html = filter.format(null, LinkedElement("KSK@gpl.png", properties = mapOf("type" to "image")), emptyMap<String, Any?>()) as String
+               val html = filter.format(null, LinkedElement("KSK@gpl.png", properties = mapOf("type" to "image")), emptyMap()) as String
                val outerSpanNode = Jsoup.parseBodyFragment(html).body().child(0)
-               assertThat(outerSpanNode.nodeName(), `is`("span"))
-               assertThat(outerSpanNode.attr("class"), `is`("linked-element loaded"))
-               assertThat(outerSpanNode.attr("title"), `is`("KSK@gpl.png"))
+               assertThat(outerSpanNode.nodeName(), equalTo("span"))
+               assertThat(outerSpanNode.attr("class"), equalTo("linked-element loaded"))
+               assertThat(outerSpanNode.attr("title"), equalTo("KSK@gpl.png"))
                val linkNode = outerSpanNode.child(0)
-               assertThat(linkNode.nodeName(), `is`("a"))
-               assertThat(linkNode.attr("href"), `is`("/KSK@gpl.png"))
+               assertThat(linkNode.nodeName(), equalTo("a"))
+               assertThat(linkNode.attr("href"), equalTo("/KSK@gpl.png"))
                val innerSpanNode = linkNode.child(0)
-               assertThat(innerSpanNode.attr("style"), `is`("background-image: url('/KSK@gpl.png')"))
+               assertThat(innerSpanNode.attr("style"), equalTo("background-image: url('/KSK@gpl.png')"))
        }
 
        @Test
        fun `filter can render HTML pages`() {
-               val html = filter.format(null, LinkedElement("KSK@gpl.html", properties = mapOf("type" to "html", "title" to "Page Title", "description" to "This is the description.")), emptyMap<String, Any?>()) as String
+               val html = filter.format(null, LinkedElement("KSK@gpl.html", properties = mapOf("type" to "html", "title" to "Page Title", "description" to "This is the description.")), emptyMap()) as String
                val outerSpanNode = Jsoup.parseBodyFragment(html).body().child(0)
                assertThat(outerSpanNode.nodeName(), equalTo("span"))
-               assertThat(outerSpanNode.attr("class"), `is`("linked-element loaded"))
-               assertThat(outerSpanNode.attr("title"), `is`("KSK@gpl.html"))
+               assertThat(outerSpanNode.attr("class"), equalTo("linked-element loaded"))
+               assertThat(outerSpanNode.attr("title"), equalTo("KSK@gpl.html"))
                val linkNode = outerSpanNode.child(0)
                assertThat(linkNode.nodeName(), equalTo("a"))
                assertThat(linkNode.attr("href"), equalTo("/KSK@gpl.html"))
index 717d3c5..a2c4ad7 100644 (file)
@@ -61,7 +61,7 @@ class ProfileAccessorTest {
 
        @Before
        fun setupCore() {
-               whenever(core.getImage(eq("avatar-id"), anyBoolean())).thenReturn(mock<Image>())
+               whenever(core.getImage(eq("avatar-id"), anyBoolean())).thenReturn(mock())
        }
 
 
index 0920280..5592808 100644 (file)
@@ -1,29 +1,18 @@
 package net.pterodactylus.sone.template
 
-import net.pterodactylus.sone.core.Core
-import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.data.Profile
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.text.FreemailPart
-import net.pterodactylus.sone.text.FreenetLinkPart
-import net.pterodactylus.sone.text.LinkPart
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.database.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.text.*
 import net.pterodactylus.sone.text.Part
-import net.pterodactylus.sone.text.PlainTextPart
-import net.pterodactylus.sone.text.PostPart
-import net.pterodactylus.sone.text.SonePart
-import net.pterodactylus.util.template.HtmlFilter
-import net.pterodactylus.util.template.TemplateContext
-import net.pterodactylus.util.template.TemplateContextFactory
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.`is`
-import org.hamcrest.Matchers.containsInAnyOrder
-import org.jsoup.Jsoup
-import org.jsoup.nodes.Attribute
-import org.jsoup.nodes.Element
-import org.junit.Test
-import org.mockito.Mockito.`when`
-import java.net.URLEncoder
+import net.pterodactylus.util.template.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.jsoup.*
+import org.jsoup.nodes.*
+import org.junit.*
+import org.mockito.*
+import java.net.*
 
 /**
  * Unit test for [RenderFilter].
@@ -37,22 +26,18 @@ class RenderFilterTest {
                private const val POST_ID = "37a06250-6775-4b94-86ff-257ba690953c"
        }
 
-       private val core = mock<Core>()
-       private val templateContextFactory = TemplateContextFactory()
-       private val templateContext: TemplateContext
+       private val soneProvider = mock<SoneProvider>()
+       private val soneTextParser = mock<SoneTextParser>()
+       private val htmlFilter = HtmlFilter()
        private val sone = setupSone(SONE_IDENTITY, "Sone", "First")
        private val parameters = mutableMapOf<String, Any?>()
 
-       init {
-               templateContextFactory.addFilter("html", HtmlFilter())
-               templateContext = templateContextFactory.createTemplateContext()
-       }
-
-       private val filter = RenderFilter(core, templateContextFactory)
+       private val filter = RenderFilter(soneProvider, soneTextParser, htmlFilter)
+       private val templateContext = TemplateContext()
 
        @Test
        fun `plain text part is rendered correctly`() {
-               assertThat(renderParts(PlainTextPart("plain text")), `is`("plain text"))
+               assertThat(renderParts(PlainTextPart("plain text")), equalTo("plain text"))
        }
 
        private fun renderParts(vararg part: Part) = filter.format(templateContext, listOf(*part), parameters) as String
@@ -64,13 +49,13 @@ class RenderFilterTest {
        }
 
        private fun verifyLink(linkNode: Element, url: String, cssClass: String, tooltip: String, text: String) {
-               assertThat(linkNode.nodeName(), `is`("a"))
+               assertThat(linkNode.nodeName(), equalTo("a"))
                assertThat<List<Attribute>>(linkNode.attributes().asList(), containsInAnyOrder(
                                Attribute("href", url),
                                Attribute("class", cssClass),
                                Attribute("title", tooltip)
                ))
-               assertThat(linkNode.text(), `is`(text))
+               assertThat(linkNode.text(), equalTo(text))
        }
 
        @Test
@@ -96,11 +81,11 @@ class RenderFilterTest {
 
        private fun setupSone(identity: String, name: String?, firstName: String): Sone {
                val sone = mock<Sone>()
-               `when`(sone.id).thenReturn(identity)
-               `when`(sone.profile).thenReturn(Profile(sone))
-               `when`(sone.name).thenReturn(name)
+               whenever(sone.id).thenReturn(identity)
+               whenever(sone.profile).thenReturn(Profile(sone))
+               whenever(sone.name).thenReturn(name)
                sone.profile.firstName = firstName
-               `when`(core.getSone(identity)).thenReturn(sone)
+               whenever(soneProvider.getSone(identity)).thenReturn(sone)
                return sone
        }
 
@@ -114,21 +99,24 @@ class RenderFilterTest {
        @Test
        fun `post part is cut off correctly when there are spaces`() {
                val post = setupPost(sone, "1234 678901 345 789012 45678 01.")
+               whenever(soneTextParser.parse(eq("1234 678901 345 789012 45678 01."), ArgumentMatchers.any()))
+                               .thenReturn(listOf(PlainTextPart("1234 678901 345 789012 45678 01.")))
                val linkNode = renderParts(PostPart(post)).toLinkNode()
                verifyLink(linkNode, "viewPost.html?post=$POST_ID", "in-sone", "First", "1234 678901 345…")
        }
 
-       private fun setupPost(sone: Sone, value: String): Post {
-               val post = mock<Post>()
-               `when`(post.id).thenReturn(POST_ID)
-               `when`(post.sone).thenReturn(sone)
-               `when`(post.text).thenReturn(value)
-               return post
-       }
+       private fun setupPost(sone: Sone, value: String) =
+                       mock<Post>().apply {
+                               whenever(id).thenReturn(POST_ID)
+                               whenever(this.sone).thenReturn(this@RenderFilterTest.sone)
+                               whenever(text).thenReturn(value)
+                       }
 
        @Test
        fun `post part is cut off correctly when there are no spaces`() {
                val post = setupPost(sone, "1234567890123456789012345678901.")
+               whenever(soneTextParser.parse(eq("1234567890123456789012345678901."), ArgumentMatchers.any()))
+                               .thenReturn(listOf(PlainTextPart("1234567890123456789012345678901.")))
                val linkNode = renderParts(PostPart(post)).toLinkNode()
                verifyLink(linkNode, "viewPost.html?post=$POST_ID", "in-sone", "First", "12345678901234567890…")
        }
@@ -136,6 +124,8 @@ class RenderFilterTest {
        @Test
        fun `post part shorter than 21 chars is not cut off`() {
                val post = setupPost(sone, "12345678901234567890")
+               whenever(soneTextParser.parse(eq("12345678901234567890"), ArgumentMatchers.any()))
+                               .thenReturn(listOf(PlainTextPart("12345678901234567890")))
                val linkNode = renderParts(PostPart(post)).toLinkNode()
                verifyLink(linkNode, "viewPost.html?post=$POST_ID", "in-sone", "First", "12345678901234567890")
        }
@@ -143,7 +133,7 @@ class RenderFilterTest {
        @Test
        fun `multiple parts are rendered correctly`() {
                val parts = arrayOf(PlainTextPart("te"), PlainTextPart("xt"))
-               assertThat(renderParts(*parts), `is`("text"))
+               assertThat(renderParts(*parts), equalTo("text"))
        }
 
        @Test
index f06069b..f8ac06d 100644 (file)
@@ -34,7 +34,7 @@ class ReplyAccessorTest {
 
        @Test
        fun `returns the likes correctly`() {
-               val sones = setOf(mock<Sone>(), mock<Sone>(), mock<Sone>())
+               val sones = setOf(mock<Sone>(), mock(), mock())
                whenever(core.getLikes(reply)).thenReturn(sones)
                assertThat(accessor.get(templateContext, reply, "likes"), equalTo<Any>(sones))
        }
@@ -74,7 +74,7 @@ class ReplyAccessorTest {
 
        @Test
        fun `return that a reply is loaded if its sone is not null`() {
-               whenever(reply.sone).thenReturn(mock<Sone>())
+               whenever(reply.sone).thenReturn(mock())
                assertThat(accessor.get(templateContext, reply, "loaded"), equalTo<Any>(true))
        }
 
index 4614454..f1ee884 100644 (file)
@@ -26,7 +26,7 @@ class ReplyGroupFilterTest {
 
        @Before
        fun setupReplies() {
-               (0..4).forEach {
+               repeat(5) {
                        sones += mock<Sone>()
                }
                (0..7).forEach {
index 2fe40d4..87cfce0 100644 (file)
@@ -128,7 +128,7 @@ class SoneAccessorTest {
 
        @Test
        fun `accessor returns that the sone’s status is not unknown if it is not unknown`() {
-               whenever(sone.status).thenReturn(mock<SoneStatus>())
+               whenever(sone.status).thenReturn(mock())
                assertAccessorReturnValue("unknown", false)
        }
 
@@ -140,7 +140,7 @@ class SoneAccessorTest {
 
        @Test
        fun `accessor returns that the sone’s status is not idle if it is not idle`() {
-               whenever(sone.status).thenReturn(mock<SoneStatus>())
+               whenever(sone.status).thenReturn(mock())
                assertAccessorReturnValue("idle", false)
        }
 
@@ -152,7 +152,7 @@ class SoneAccessorTest {
 
        @Test
        fun `accessor returns that the sone’s status is not inserting if it is not inserting`() {
-               whenever(sone.status).thenReturn(mock<SoneStatus>())
+               whenever(sone.status).thenReturn(mock())
                assertAccessorReturnValue("inserting", false)
        }
 
@@ -164,7 +164,7 @@ class SoneAccessorTest {
 
        @Test
        fun `accessor returns that the sone’s status is not downloading if it is not downloading`() {
-               whenever(sone.status).thenReturn(mock<SoneStatus>())
+               whenever(sone.status).thenReturn(mock())
                assertAccessorReturnValue("downloading", false)
        }
 
@@ -217,7 +217,7 @@ class SoneAccessorTest {
 
        @Test
        fun `accessor returns all images in the correct order`() {
-               val images = listOf(mock<Image>(), mock<Image>(), mock<Image>(), mock<Image>(), mock<Image>())
+               val images = listOf(mock<Image>(), mock(), mock(), mock(), mock())
                val firstAlbum = createAlbum(listOf(), listOf(images[0], images[3]))
                val secondAlbum = createAlbum(listOf(), listOf(images[1], images[4], images[2]))
                val rootAlbum = createAlbum(listOf(firstAlbum, secondAlbum), listOf())
@@ -233,7 +233,7 @@ class SoneAccessorTest {
 
        @Test
        fun `accessor returns all albums in the correct order`() {
-               val albums = listOf(mock<Album>(), mock<Album>(), mock<Album>(), mock<Album>(), mock<Album>())
+               val albums = listOf(mock<Album>(), mock(), mock(), mock(), mock())
                val rootAlbum = createAlbum(albums, listOf())
                whenever(sone.rootAlbum).thenReturn(rootAlbum)
                assertAccessorReturnValueMatches("albums", contains(*albums.toTypedArray()))
index f5f52b8..2fc4a96 100644 (file)
@@ -2,21 +2,26 @@ package net.pterodactylus.sone.test
 
 import com.google.inject.Injector
 import com.google.inject.Module
+import com.google.inject.name.*
+import org.mockito.*
 import javax.inject.Provider
 import kotlin.reflect.KClass
 
-fun <T : Any> KClass<T>.isProvidedBy(instance: T) = Module { it.bind(this.java).toProvider { instance } }
+fun <T : Any> KClass<T>.isProvidedBy(instance: T) = Module { it.bind(this.java).toProvider(Provider<T> { instance }) }
+fun <T : Any> KClass<T>.withNameIsProvidedBy(instance: T, name: String) = Module { it.bind(this.java).annotatedWith(Names.named(name)).toProvider(Provider<T> { instance }) }
 fun <T : Any> KClass<T>.isProvidedBy(provider: com.google.inject.Provider<T>) = Module { it.bind(this.java).toProvider(provider) }
 fun <T : Any> KClass<T>.isProvidedBy(provider: KClass<out Provider<T>>) = Module { it.bind(this.java).toProvider(provider.java) }
-inline fun <reified T : Any> KClass<T>.isProvidedByMock() = Module { it.bind(this.java).toProvider { mock<T>() } }
+inline fun <reified T : Any> KClass<T>.isProvidedByMock() = Module { it.bind(this.java).toProvider(Provider<T> { mock() }) }
+inline fun <reified T : Any> KClass<T>.isProvidedByDeepMock() = Module { it.bind(this.java).toProvider(Provider<T> { deepMock() }) }
 
 inline fun <reified T : Any> Injector.getInstance() = getInstance(T::class.java)!!
 
 fun <T : Any> supply(javaClass: Class<T>): Source<T> = object : Source<T> {
        override fun fromInstance(instance: T) = Module { it.bind(javaClass).toInstance(instance) }
-       override fun byInstance(instance: T) = Module { it.bind(javaClass).toProvider { instance } }
+       override fun byInstance(instance: T) = Module { it.bind(javaClass).toProvider(Provider<T> { instance }) }
        override fun byProvider(provider: com.google.inject.Provider<T>) = Module { it.bind(javaClass).toProvider(provider) }
        override fun byProvider(provider: Class<Provider<T>>) = Module { it.bind(javaClass).toProvider(provider) }
+       override fun byMock() = Module { it.bind(javaClass).toInstance(Mockito.mock(javaClass)) }
 }
 
 interface Source<T : Any> {
@@ -24,4 +29,5 @@ interface Source<T : Any> {
        fun byInstance(instance: T): Module
        fun byProvider(provider: com.google.inject.Provider<T>): Module
        fun byProvider(provider: Class<Provider<T>>): Module
+       fun byMock(): Module
 }
diff --git a/src/test/kotlin/net/pterodactylus/sone/test/Matchers.kt b/src/test/kotlin/net/pterodactylus/sone/test/Matchers.kt
new file mode 100644 (file)
index 0000000..c084d35
--- /dev/null
@@ -0,0 +1,21 @@
+package net.pterodactylus.sone.test
+
+import net.pterodactylus.util.web.*
+import org.hamcrest.*
+
+fun hasHeader(name: String, value: String) = object : TypeSafeDiagnosingMatcher<Header>() {
+       override fun matchesSafely(item: Header, mismatchDescription: Description) =
+                       compare(item.name, { it.equals(name, ignoreCase = true) }) { mismatchDescription.appendText("name is ").appendValue(it) }
+                                       ?: compare(item.hasValue(value), { it }) { mismatchDescription.appendText("does not have value ").appendValue(value) }
+                                       ?: true
+
+       override fun describeTo(description: Description) {
+               description.appendText("name is ").appendValue(name)
+                               .appendText(", value is ").appendValue(value)
+       }
+}
+
+fun <T : Any> compare(value: T, comparison: (T) -> Boolean, onError: (T) -> Unit) =
+               false.takeUnless { comparison(value) }
+                               ?.also { onError(value) }
+
index c4fd7cb..85b86d5 100644 (file)
@@ -1,8 +1,7 @@
 package net.pterodactylus.sone.test
 
 import com.google.inject.Module
-import org.mockito.ArgumentCaptor
-import org.mockito.Mockito
+import org.mockito.*
 import org.mockito.invocation.InvocationOnMock
 import org.mockito.stubbing.OngoingStubbing
 
@@ -18,12 +17,17 @@ inline fun <reified T : Any> bind(implementation: T): Module =
                Module { it!!.bind(T::class.java).toInstance(implementation) }
 
 inline fun <reified T : Any> bindMock(): Module =
-               Module { it!!.bind(T::class.java).toInstance(mock<T>()) }
+               Module { it!!.bind(T::class.java).toInstance(mock()) }
 
 inline fun <reified T: Any?> whenever(methodCall: T) = Mockito.`when`(methodCall)!!
 
-inline fun <reified T : Any> OngoingStubbing<T>.thenReturnMock(): OngoingStubbing<T> = this.thenReturn(mock<T>())
+inline fun <reified T : Any> OngoingStubbing<T>.thenReturnMock(): OngoingStubbing<T> = this.thenReturn(mock())
 
 operator fun <T> InvocationOnMock.get(index: Int): T = getArgument(index)
 
 inline fun <reified T> argumentCaptor(): ArgumentCaptor<T> = ArgumentCaptor.forClass<T, T>(T::class.java)!!
+
+fun <T> eq(t: T): T {
+       ArgumentMatchers.eq(t)
+       return null as T
+}
index f0cceca..ac6e955 100644 (file)
@@ -9,7 +9,7 @@ class OneByOneMatcher<A> : TypeSafeDiagnosingMatcher<A>() {
        private val matchers = mutableListOf<Matcher<A, *>>()
 
        fun <V> expect(description: String, expected: V, actual: (A) -> V) {
-               matchers += Matcher<A, V>(expected, actual, description)
+               matchers += Matcher(expected, actual, description)
        }
 
        override fun describeTo(description: Description) {
diff --git a/src/test/kotlin/net/pterodactylus/sone/test/TestUtils.kt b/src/test/kotlin/net/pterodactylus/sone/test/TestUtils.kt
new file mode 100644 (file)
index 0000000..9996925
--- /dev/null
@@ -0,0 +1,19 @@
+package net.pterodactylus.sone.test
+
+import java.lang.reflect.*
+
+private val modifiers = Field::class.java.getDeclaredField("modifiers").apply {
+       isAccessible = true
+}
+
+fun setField(instance: Any, name: String, value: Any?) {
+       generateSequence<Class<*>>(instance.javaClass) { it.superclass }
+                       .flatMap { it.declaredFields.asSequence() }
+                       .filter { it.name == name }
+                       .toList()
+                       .forEach { field ->
+                               field.isAccessible = true
+                               modifiers.setInt(field, field.modifiers and Modifier.FINAL.inv())
+                               field.set(instance, value)
+                       }
+}
index 361bd19..31fca48 100644 (file)
@@ -16,7 +16,7 @@ class SonePartTest {
        private val sone = mock<Sone>()
 
        init {
-               `when`(sone.profile).thenReturn(mock<Profile>())
+               `when`(sone.profile).thenReturn(mock())
                `when`(sone.name).thenReturn("sone")
        }
 
diff --git a/src/test/kotlin/net/pterodactylus/sone/text/SoneTextParserTest.kt b/src/test/kotlin/net/pterodactylus/sone/text/SoneTextParserTest.kt
new file mode 100644 (file)
index 0000000..b20225a
--- /dev/null
@@ -0,0 +1,458 @@
+/*
+ * Sone - SoneTextParserTest.kt - Copyright © 2011–2019 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.text
+
+import com.google.inject.Guice.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.data.impl.*
+import net.pterodactylus.sone.database.*
+import net.pterodactylus.sone.test.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import kotlin.test.*
+
+/**
+ * JUnit test case for [SoneTextParser].
+ */
+class SoneTextParserTest {
+
+       private val soneTextParser = SoneTextParser(null, null)
+
+       @Test
+       fun `basic operation`() {
+               val parts = soneTextParser.parse("Test.", null)
+               assertThat("Part Text", convertText(parts, PlainTextPart::class.java), equalTo("Test."))
+       }
+
+       @Test
+       fun `empty lines at start and end are stripped`() {
+               val parts = soneTextParser.parse("\nTest.\n\n", null)
+               assertThat("Part Text", convertText(parts, PlainTextPart::class.java), equalTo("Test."))
+       }
+
+       @Test
+       fun `duplicate empty lines in the text are stripped`() {
+               val parts = soneTextParser.parse("\nTest.\n\n\nTest.", null)
+               assertThat("Part Text", convertText(parts, PlainTextPart::class.java), equalTo("Test.\n\nTest."))
+       }
+
+       @Test
+       fun `consecutive lines are separated by linefeed`() {
+               val parts = soneTextParser.parse("Text.\nText", null)
+               assertThat("Part Text", convertText(parts), equalTo("Text.\nText"))
+       }
+
+       @Test
+       fun `freenet links have the freenet prefix removed`() {
+               val parts = soneTextParser.parse("freenet:KSK@gpl.txt", null)
+               assertThat("Part Text", convertText(parts), equalTo("[KSK@gpl.txt|KSK@gpl.txt|gpl.txt]"))
+       }
+
+       @Test
+       fun `only the first item in a line is prefixed with a line break`() {
+               val parts = soneTextParser.parse("Text.\nKSK@gpl.txt and KSK@gpl.txt", null)
+               assertThat("Part Text", convertText(parts), equalTo("Text.\n[KSK@gpl.txt|KSK@gpl.txt|gpl.txt] and [KSK@gpl.txt|KSK@gpl.txt|gpl.txt]"))
+       }
+
+       @Test
+       fun `sone link with too short sone ID is rendered as plain text`() {
+               val parts = soneTextParser.parse("sone://too-short", null)
+               assertThat("Part Text", convertText(parts), equalTo("sone://too-short"))
+       }
+
+       @Test
+       fun `sone link is rendered correctly if sone is not present`() {
+               val parser = SoneTextParser(AbsentSoneProvider(), null)
+               val parts = parser.parse("sone://DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU", null)
+               assertThat("Part Text", convertText(parts), equalTo("[Sone|DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU]"))
+       }
+
+       @Test
+       fun `sone and post can be parsed from the same text`() {
+               val parser = SoneTextParser(TestSoneProvider(), TestPostProvider())
+               val parts = parser.parse("Text sone://DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU more text post://f3757817-b45a-497a-803f-9c5aafc10dc6 even more text", null)
+               assertThat("Part Text", convertText(parts), equalTo("Text [Sone|DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU] more text [Post|f3757817-b45a-497a-803f-9c5aafc10dc6|text] even more text"))
+       }
+
+       @Test
+       fun `post link is rendered as plain text if post ID is too short`() {
+               val parts = soneTextParser.parse("post://too-short", null)
+               assertThat("Part Text", convertText(parts), equalTo("post://too-short"))
+       }
+
+       @Test
+       fun `post link is rendered correctly if post is present`() {
+               val parser = SoneTextParser(null, TestPostProvider())
+               val parts = parser.parse("post://f3757817-b45a-497a-803f-9c5aafc10dc6", null)
+               assertThat("Part Text", convertText(parts), equalTo("[Post|f3757817-b45a-497a-803f-9c5aafc10dc6|text]"))
+       }
+
+       @Test
+       fun `post link is rendered as plain text if post is absent`() {
+               val parser = SoneTextParser(null, AbsentPostProvider())
+               val parts = parser.parse("post://f3757817-b45a-497a-803f-9c5aafc10dc6", null)
+               assertThat("Part Text", convertText(parts), equalTo("post://f3757817-b45a-497a-803f-9c5aafc10dc6"))
+       }
+
+       @Test
+       fun `name of freenet link does not contain url parameters`() {
+               val parts = soneTextParser.parse("KSK@gpl.txt?max-size=12345", null)
+               assertThat("Part Text", convertText(parts), equalTo("[KSK@gpl.txt?max-size=12345|KSK@gpl.txt|gpl.txt]"))
+       }
+
+       @Test
+       fun `trailing slash in freenet link is removed for name`() {
+               val parts = soneTextParser.parse("KSK@gpl.txt/", null)
+               assertThat("Part Text", convertText(parts), equalTo("[KSK@gpl.txt/|KSK@gpl.txt/|gpl.txt]"))
+       }
+
+       @Test
+       fun `last meta string of freenet link is used as name`() {
+               val parts = soneTextParser.parse("CHK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/COPYING", null)
+               assertThat("Part Text", convertText(parts), equalTo("[CHK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/COPYING|CHK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/COPYING|COPYING]"))
+       }
+
+       @Test
+       fun `freenet link without meta strings and doc name gets first nine characters of key as name`() {
+               val parts = soneTextParser.parse("CHK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8", null)
+               assertThat("Part Text", convertText(parts), equalTo("[CHK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8|CHK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8|CHK@qM1nm]"))
+       }
+
+       @Test
+       fun `malformed key is rendered as plain text`() {
+               val parts = soneTextParser.parse("CHK@qM1nmgU", null)
+               assertThat("Part Text", convertText(parts), equalTo("CHK@qM1nmgU"))
+       }
+
+       @Test
+       fun `https link has its paths shortened`() {
+               val parts = soneTextParser.parse("https://test.test/some-long-path/file.txt", null)
+               assertThat("Part Text", convertText(parts), equalTo("[https://test.test/some-long-path/file.txt|https://test.test/some-long-path/file.txt|test.test/…/file.txt]"))
+       }
+
+       @Test
+       fun `http links have their last slash removed`() {
+               val parts = soneTextParser.parse("http://test.test/test/", null)
+               assertThat("Part Text", convertText(parts), equalTo("[http://test.test/test/|http://test.test/test/|test.test/…]"))
+       }
+
+       @Test
+       fun `www prefix is removed for hostname with two dots and no path`() {
+               val parts = soneTextParser.parse("http://www.test.test", null)
+               assertThat("Part Text", convertText(parts), equalTo("[http://www.test.test|http://www.test.test|test.test]"))
+       }
+
+       @Test
+       fun `www prefix is removed for hostname with two dots and a path`() {
+               val parts = soneTextParser.parse("http://www.test.test/test.html", null)
+               assertThat("Part Text", convertText(parts), equalTo("[http://www.test.test/test.html|http://www.test.test/test.html|test.test/test.html]"))
+       }
+
+       @Test
+       fun `hostname is kept intact if not beginning with www`() {
+               val parts = soneTextParser.parse("http://test.test.test/test.html", null)
+               assertThat("Part Text", convertText(parts), equalTo("[http://test.test.test/test.html|http://test.test.test/test.html|test.test.test/test.html]"))
+       }
+
+       @Test
+       fun `hostname with one dot but no slash is kept intact`() {
+               val parts = soneTextParser.parse("http://test.test", null)
+               assertThat("Part Text", convertText(parts), equalTo("[http://test.test|http://test.test|test.test]"))
+       }
+
+       @Test
+       fun `url parameters are removed for http links`() {
+               val parts = soneTextParser.parse("http://test.test?foo=bar", null)
+               assertThat("Part Text", convertText(parts), equalTo("[http://test.test?foo=bar|http://test.test?foo=bar|test.test]"))
+       }
+
+       @Test
+       fun `empty string is parsed correctly`() {
+               val parts = soneTextParser.parse("", null)
+               assertThat("Part Text", convertText(parts), equalTo(""))
+       }
+
+       @Test
+       fun `links are parsed in correct order`() {
+               val parts = soneTextParser.parse("KSK@ CHK@", null)
+               assertThat("Part Text", convertText(parts), equalTo("KSK@ CHK@"))
+       }
+
+       @Test
+       fun `invalid ssk and usk link is parsed as text`() {
+               val parts = soneTextParser.parse("SSK@a USK@a", null)
+               assertThat("Part Text", convertText(parts), equalTo("SSK@a USK@a"))
+       }
+
+       @Test
+       fun `ssk without document name is parsed correctly`() {
+               val parts = soneTextParser.parse(
+                               "SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8", null)
+               assertThat("Part Text", convertText(parts),
+                               equalTo("[SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8|"
+                                               + "SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8|"
+                                               + "SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU]"))
+       }
+
+       @Test
+       fun `ssk link without context is not trusted`() {
+               val parts = soneTextParser.parse("SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test", null)
+               assertThat("Part Text", convertText(parts), equalTo("[SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test|SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test|test]"))
+       }
+
+       @Test
+       fun `ssk link with context without sone is not trusted`() {
+               val context = SoneTextParserContext(null)
+               val parts = soneTextParser.parse("SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test", context)
+               assertThat("Part Text", convertText(parts), equalTo("[SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test|SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test|test]"))
+       }
+
+       @Test
+       fun `ssk link with context with different sone is not trusted`() {
+               val context = SoneTextParserContext(IdOnlySone("DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU"))
+               val parts = soneTextParser.parse("SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test", context)
+               assertThat("Part Text", convertText(parts), equalTo("[SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test|SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test|test]"))
+       }
+
+       @Test
+       fun `ssk link with context with correct sone is trusted`() {
+               val context = SoneTextParserContext(IdOnlySone("qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU"))
+               val parts = soneTextParser.parse("SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test", context)
+               assertThat("Part Text", convertText(parts), equalTo("[SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test|trusted|SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test|test]"))
+       }
+
+       @Test
+       fun `usk link with context with correct sone is trusted`() {
+               val context = SoneTextParserContext(IdOnlySone("qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU"))
+               val parts = soneTextParser.parse("USK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test/0", context)
+               assertThat("Part Text", convertText(parts), equalTo("[USK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test/0|trusted|USK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test/0|test]"))
+       }
+
+       @Test
+       fun `usk links with backlinks is parsed correctly`() {
+               val context = SoneTextParserContext(IdOnlySone("qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU"))
+               val parts = soneTextParser.parse("USK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test/0/../../../USK@nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI,DuQSUZiI~agF8c-6tjsFFGuZ8eICrzWCILB60nT8KKo,AQACAAE/sone/78/", context)
+               assertThat("Part Text", convertText(parts), equalTo("[USK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test/0|trusted|USK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test/0|test]"))
+       }
+
+       @Test
+       fun `test basic ksk links`() {
+               val parts: Iterable<Part> = soneTextParser.parse("KSK@gpl.txt", null)
+               assertThat("Part Text", convertText(parts, FreenetLinkPart::class.java), equalTo("[KSK@gpl.txt|KSK@gpl.txt|gpl.txt]"))
+       }
+
+       @Test
+       fun `embedded ksk links are parsed correctly`() {
+               val parts = soneTextParser.parse("Link is KSK@gpl.txt\u200b.", null)
+               assertThat("Part Text", convertText(parts, PlainTextPart::class.java, FreenetLinkPart::class.java), equalTo("Link is [KSK@gpl.txt|KSK@gpl.txt|gpl.txt]\u200b."))
+       }
+
+       @Test
+       fun `embedded ksk links and line breaks are parsed correctly`() {
+               val parts = soneTextParser.parse("Link is KSK@gpl.txt\nKSK@test.dat\n", null)
+               assertThat("Part Text", convertText(parts, PlainTextPart::class.java, FreenetLinkPart::class.java), equalTo("Link is [KSK@gpl.txt|KSK@gpl.txt|gpl.txt]\n[KSK@test.dat|KSK@test.dat|test.dat]"))
+       }
+
+       @Test
+       fun `ksk links with backlinks are parsed correctly`() {
+               val parts = soneTextParser.parse("KSK@gallery/../Sone/imageBrowser.html?album=30c930ee-97cd-11e9-bd44-f3e595768b77", null)
+               assertThat("Part Text", convertText(parts, FreenetLinkPart::class.java), equalTo("[KSK@gallery|KSK@gallery|gallery]"))
+       }
+
+       @Test
+       fun `test empty lines and sone links`() {
+               val soneTextParser = SoneTextParser(TestSoneProvider(), null)
+
+               /* check basic links. */
+               val parts = soneTextParser.parse("Some text.\n\nLink to sone://DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU and stuff.", null)
+               assertThat("Part Text", convertText(parts, PlainTextPart::class.java, SonePart::class.java), equalTo("Some text.\n\nLink to [Sone|DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU] and stuff."))
+       }
+
+       @Test
+       fun `test empy http links`() {
+               val soneTextParser = SoneTextParser(TestSoneProvider(), null)
+
+               /* check empty http links. */
+               val parts = soneTextParser.parse("Some text. Empty link: http:// – nice!", null)
+               assertThat("Part Text", convertText(parts, PlainTextPart::class.java), equalTo("Some text. Empty link: http:// – nice!"))
+       }
+
+       @Test
+       fun `http link without parens ends at next closing paren`() {
+               val parts = soneTextParser.parse("Some text (and a link: http://example.sone/abc) – nice!", null)
+               assertThat("Part Text", convertText(parts, PlainTextPart::class.java, LinkPart::class.java), equalTo("Some text (and a link: [http://example.sone/abc|http://example.sone/abc|example.sone/abc]) – nice!"))
+       }
+
+       @Test
+       fun `usk link ends at first non numeric non slash character after version number`() {
+               val parts = soneTextParser.parse("Some link (USK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test/0). Nice", null)
+               assertThat("Part Text", convertText(parts), equalTo("Some link ([USK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test/0|USK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test/0|test]). Nice"))
+       }
+
+       @Test
+       fun `usk link with filename shows the filename`() {
+               val parts = soneTextParser.parse("Some link (USK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test/0/images/image.jpg). Nice", null)
+               assertThat("Part Text", convertText(parts), equalTo("Some link ([USK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test/0/images/image.jpg|USK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test/0/images/image.jpg|image.jpg]). Nice"))
+       }
+
+       @Test
+       fun `usk link without filename but ending in slash shows the path`() {
+               val parts = soneTextParser.parse("Some link (USK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test/0/). Nice", null)
+               assertThat("Part Text", convertText(parts), equalTo("Some link ([USK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test/0|USK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test/0|test]). Nice"))
+       }
+
+       @Test
+       fun `http link with opened and closed parens ends at next closing paren`() {
+               val parts = soneTextParser.parse("Some text (and a link: http://example.sone/abc_(def)) – nice!", null)
+               assertThat("Part Text", convertText(parts, PlainTextPart::class.java, LinkPart::class.java), equalTo("Some text (and a link: [http://example.sone/abc_(def)|http://example.sone/abc_(def)|example.sone/abc_(def)]) – nice!"))
+       }
+
+       @Test
+       fun `punctuation is ignored at end of link before whitespace`() {
+               val parts = soneTextParser.parse("Some text and a link: http://example.sone/abc. Nice!", null)
+               assertThat("Part Text", convertText(parts, PlainTextPart::class.java, LinkPart::class.java), equalTo("Some text and a link: [http://example.sone/abc|http://example.sone/abc|example.sone/abc]. Nice!"))
+       }
+
+       @Test
+       fun `multiple punctuation characters are ignored at end of link before whitespace`() {
+               val parts = soneTextParser.parse("Some text and a link: http://example.sone/abc... Nice!", null)
+               assertThat("Part Text", convertText(parts, PlainTextPart::class.java, LinkPart::class.java), equalTo("Some text and a link: [http://example.sone/abc|http://example.sone/abc|example.sone/abc]... Nice!"))
+       }
+
+       @Test
+       fun `commas are ignored at end of link before whitespace`() {
+               val parts = soneTextParser.parse("Some text and a link: http://example.sone/abc, nice!", null)
+               assertThat("Part Text", convertText(parts, PlainTextPart::class.java, LinkPart::class.java), equalTo("Some text and a link: [http://example.sone/abc|http://example.sone/abc|example.sone/abc], nice!"))
+       }
+
+       @Test
+       fun `exclamation marks are ignored at end of link before whitespace`() {
+               val parts = soneTextParser.parse("A link: http://example.sone/abc!", null)
+               assertThat("Part Text", convertText(parts, PlainTextPart::class.java, LinkPart::class.java), equalTo("A link: [http://example.sone/abc|http://example.sone/abc|example.sone/abc]!"))
+       }
+
+       @Test
+       fun `question marks are ignored at end of link before whitespace`() {
+               val parts = soneTextParser.parse("A link: http://example.sone/abc?", null)
+               assertThat("Part Text", convertText(parts, PlainTextPart::class.java, LinkPart::class.java), equalTo("A link: [http://example.sone/abc|http://example.sone/abc|example.sone/abc]?"))
+       }
+
+       @Test
+       fun `correct freemail address is linked to correctly`() {
+               val parts = soneTextParser.parse("Mail me at sone@t4dlzfdww3xvsnsc6j6gtliox6zaoak7ymkobbmcmdw527ubuqra.freemail!", null)
+               assertThat("Part Text", convertText(parts), equalTo("Mail me at [Freemail|sone|t4dlzfdww3xvsnsc6j6gtliox6zaoak7ymkobbmcmdw527ubuqra|nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI]!"))
+       }
+
+       @Test
+       fun `freemail address with invalid freemail id is parsed as text`() {
+               val parts = soneTextParser.parse("Mail me at sone@t4dlzfdww3xvsnsc6j6gtliox6zaoak7ymkobbmcmdw527ubuqr8.freemail!", null)
+               assertThat("Part Text", convertText(parts), equalTo("Mail me at sone@t4dlzfdww3xvsnsc6j6gtliox6zaoak7ymkobbmcmdw527ubuqr8.freemail!"))
+       }
+
+       @Test
+       fun `freemail address with invalid sized freemail id is parsed as text`() {
+               val parts = soneTextParser.parse("Mail me at sone@4dlzfdww3xvsnsc6j6gtliox6zaoak7ymkobbmcmdw527ubuqra.freemail!", null)
+               assertThat("Part Text", convertText(parts), equalTo("Mail me at sone@4dlzfdww3xvsnsc6j6gtliox6zaoak7ymkobbmcmdw527ubuqra.freemail!"))
+       }
+
+       @Test
+       fun `freemail address without local part is parsed as text`() {
+               val parts = soneTextParser.parse("     @t4dlzfdww3xvsnsc6j6gtliox6zaoak7ymkobbmcmdw527ubuqra.freemail!", null)
+               assertThat("Part Text", convertText(parts), equalTo("     @t4dlzfdww3xvsnsc6j6gtliox6zaoak7ymkobbmcmdw527ubuqra.freemail!"))
+       }
+
+       @Test
+       fun `local part of freemail address can contain letters digits minus dot underscore`() {
+               val parts = soneTextParser.parse("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._@t4dlzfdww3xvsnsc6j6gtliox6zaoak7ymkobbmcmdw527ubuqra.freemail", null)
+               assertThat("Part Text", convertText(parts), equalTo("[Freemail|ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._|t4dlzfdww3xvsnsc6j6gtliox6zaoak7ymkobbmcmdw527ubuqra|nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI]"))
+       }
+
+       private fun convertText(parts: Iterable<Part>, vararg validClasses: Class<*>): String {
+               if (validClasses.isNotEmpty()) {
+                       assertThat(parts.map { it.javaClass }.distinct() - validClasses.distinct(), empty())
+               }
+               return parts.joinToString("") { part ->
+                       when (part) {
+                               is PlainTextPart -> part.text
+                               is FreenetLinkPart -> "[${part.link}|${if (part.trusted) "trusted|" else ""}${part.title}|${part.text}]"
+                               is FreemailPart -> "[Freemail|${part.emailLocalPart}|${part.freemailId}|${part.identityId}]"
+                               is LinkPart -> "[${part.link}|${part.title}|${part.text}]"
+                               is SonePart -> "[Sone|${part.sone.id}]"
+                               is PostPart -> "[Post|${part.post.id}|${part.post.text}]"
+                               else -> throw NoSuchElementException()
+                       }
+               }
+       }
+
+       @Test
+       fun `parser can be created by guice`() {
+               val injector = createInjector(
+                               SoneProvider::class.isProvidedByMock(),
+                               PostProvider::class.isProvidedByMock()
+               )
+               assertThat(injector.getInstance<SoneTextParser>(), notNullValue())
+       }
+
+       /**
+        * Mock Sone provider.
+        */
+       private open class TestSoneProvider : SoneProvider {
+
+               override val soneLoader = this::getSone
+               override val sones: Collection<Sone> = emptySet()
+               override val localSones: Collection<Sone> = emptySet()
+               override val remoteSones: Collection<Sone> = emptySet()
+
+               override fun getSone(soneId: String): Sone? = IdOnlySone(soneId)
+
+       }
+
+       private class AbsentSoneProvider : TestSoneProvider() {
+
+               override fun getSone(soneId: String): Sone? = null
+
+       }
+
+       private open class TestPostProvider : PostProvider {
+
+               override fun getPost(postId: String): Post? {
+                       return object : Post {
+                               override val id = postId
+                               override fun isLoaded() = false
+                               override fun getSone() = null
+                               override fun getRecipientId() = null
+                               override fun getRecipient() = null
+                               override fun getTime() = 0L
+                               override fun getText() = "text"
+                               override fun isKnown() = false
+                               override fun setKnown(known: Boolean) = null
+                       }
+               }
+
+               override fun getPosts(soneId: String) = emptySet<Post>()
+               override fun getDirectedPosts(recipientId: String) = emptySet<Post>()
+
+       }
+
+       private class AbsentPostProvider : TestPostProvider() {
+
+               override fun getPost(postId: String): Post? = null
+
+       }
+
+}
index 1c2d7f5..9e96bfe 100644 (file)
@@ -1,8 +1,7 @@
 package net.pterodactylus.sone.utils
 
 import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.contains
-import org.hamcrest.Matchers.empty
+import org.hamcrest.Matchers.*
 import org.junit.Test
 
 /**
@@ -20,4 +19,26 @@ class ObjectsTest {
                assertThat(null.asList(), empty())
        }
 
+       @Test(expected = IllegalArgumentException::class)
+       fun `exception is thrown for null and true condition`() {
+               null.throwOnNullIf(true) { IllegalArgumentException() }
+       }
+
+       @Test
+       fun `exception is not thrown for null and false condition`() {
+               assertThat(null.throwOnNullIf(false) { IllegalArgumentException() }, nullValue())
+       }
+
+       @Test
+       fun `exception is not thrown for any and true condition`() {
+               val any = Any()
+               assertThat(any.throwOnNullIf(true) { IllegalArgumentException() }, equalTo(any))
+       }
+
+       @Test
+       fun `exception is not thrown for any and false condition`() {
+               val any = Any()
+               assertThat(any.throwOnNullIf(false) { IllegalArgumentException() }, equalTo(any))
+       }
+
 }
index 3ce20ce..d3f063d 100644 (file)
@@ -12,7 +12,7 @@ import org.junit.Test
 class PaginationTest {
 
        private val items = listOf(1, 2, 3, 4, 5)
-       private val pagination = Pagination<Int>(items, 2)
+       private val pagination = Pagination(items, 2)
 
        @Test
        fun `pagination can be created from iterable`() {
diff --git a/src/test/kotlin/net/pterodactylus/sone/web/AllPagesTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/AllPagesTest.kt
new file mode 100644 (file)
index 0000000..cf6ab15
--- /dev/null
@@ -0,0 +1,253 @@
+package net.pterodactylus.sone.web
+
+import com.google.inject.Guice.createInjector
+import net.pterodactylus.sone.core.Core
+import net.pterodactylus.sone.core.FreenetInterface
+import net.pterodactylus.sone.main.PluginHomepage
+import net.pterodactylus.sone.main.PluginVersion
+import net.pterodactylus.sone.main.PluginYear
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.sone.web.pages.*
+import net.pterodactylus.util.template.Template
+import org.hamcrest.Matcher
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers
+import org.junit.Test
+
+/**
+ * Test for [AllPages].
+ */
+class AllPagesTest {
+
+       private val injector by lazy {
+               baseInjector.createChildInjector(
+                               PluginVersion::class.isProvidedByMock(),
+                               PluginYear::class.isProvidedByMock(),
+                               PluginHomepage::class.isProvidedByMock()
+               )!!
+       }
+       private val allPages by lazy { injector.getInstance<AllPages>() }
+
+       private inline fun <reified T> instanceOf(): Matcher<T> = Matchers.instanceOf<T>(T::class.java)
+
+       @Test
+       fun `about page can be injected`() {
+               assertThat(allPages.aboutPage, instanceOf<AboutPage>())
+       }
+
+       @Test
+       fun `bookmark page can be injected`() {
+               assertThat(allPages.bookmarkPage, instanceOf<BookmarkPage>())
+       }
+
+       @Test
+       fun `bookmarks page can be injected`() {
+               assertThat(allPages.bookmarksPage, instanceOf<BookmarksPage>())
+       }
+
+       @Test
+       fun `create album page can be injected`() {
+               assertThat(allPages.createAlbumPage, instanceOf<CreateAlbumPage>())
+       }
+
+       @Test
+       fun `create post page can be injected`() {
+               assertThat(allPages.createPostPage, instanceOf<CreatePostPage>())
+       }
+
+       @Test
+       fun `create reply page can be injected`() {
+               assertThat(allPages.createReplyPage, instanceOf<CreateReplyPage>())
+       }
+
+       @Test
+       fun `create sone page can be injected`() {
+               assertThat(allPages.createSonePage, instanceOf<CreateSonePage>())
+       }
+
+       @Test
+       fun `delete album page can be injected`() {
+               assertThat(allPages.deleteAlbumPage, instanceOf<DeleteAlbumPage>())
+       }
+
+       @Test
+       fun `delete image page can be injected`() {
+               assertThat(allPages.deleteImagePage, instanceOf<DeleteImagePage>())
+       }
+
+       @Test
+       fun `delete post page can be injected`() {
+               assertThat(allPages.deletePostPage, instanceOf<DeletePostPage>())
+       }
+
+       @Test
+       fun `delete profile field page can be injected`() {
+               assertThat(allPages.deleteProfileFieldPage, instanceOf<DeleteProfileFieldPage>())
+       }
+
+       @Test
+       fun `delete reply page can be injected`() {
+               assertThat(allPages.deleteReplyPage, instanceOf<DeleteReplyPage>())
+       }
+
+       @Test
+       fun `delete sone page can be injected`() {
+               assertThat(allPages.deleteSonePage, instanceOf<DeleteSonePage>())
+       }
+
+       @Test
+       fun `dismiss notification page can be injected`() {
+               assertThat(allPages.dismissNotificationPage, instanceOf<DismissNotificationPage>())
+       }
+
+       @Test
+       fun `distrust page can be injected`() {
+               assertThat(allPages.distrustPage, instanceOf<DistrustPage>())
+       }
+
+       @Test
+       fun `edit album page can be injected`() {
+               assertThat(allPages.editAlbumPage, instanceOf<EditAlbumPage>())
+       }
+
+       @Test
+       fun `edit image page can be injected`() {
+               assertThat(allPages.editImagePage, instanceOf<EditImagePage>())
+       }
+
+       @Test
+       fun `edit profile field page can be injected`() {
+               assertThat(allPages.editProfileFieldPage, instanceOf<EditProfileFieldPage>())
+       }
+
+       @Test
+       fun `edit profile page can be injected`() {
+               assertThat(allPages.editProfilePage, instanceOf<EditProfilePage>())
+       }
+
+       @Test
+       fun `follow sone page can be injected`() {
+               assertThat(allPages.followSonePage, instanceOf<FollowSonePage>())
+       }
+
+       @Test
+       fun `get image page can be injected`() {
+               assertThat(allPages.getImagePage, instanceOf<GetImagePage>())
+       }
+
+       @Test
+       fun `image browser page can be injected`() {
+               assertThat(allPages.imageBrowserPage, instanceOf<ImageBrowserPage>())
+       }
+
+       @Test
+       fun `index page can be injected`() {
+               assertThat(allPages.indexPage, instanceOf<IndexPage>())
+       }
+
+       @Test
+       fun `known sones page can be injected`() {
+               assertThat(allPages.knownSonesPage, instanceOf<KnownSonesPage>())
+       }
+
+       @Test
+       fun `like page can be injected`() {
+               assertThat(allPages.likePage, instanceOf<LikePage>())
+       }
+
+       @Test
+       fun `lock sone page can be injected`() {
+               assertThat(allPages.lockSonePage, instanceOf<LockSonePage>())
+       }
+
+       @Test
+       fun `login page can be injected`() {
+               assertThat(allPages.loginPage, instanceOf<LoginPage>())
+       }
+
+       @Test
+       fun `logout page can be injected`() {
+               assertThat(allPages.logoutPage, instanceOf<LogoutPage>())
+       }
+
+       @Test
+       fun `mark as known page can be injected`() {
+               assertThat(allPages.markAsKnownPage, instanceOf<MarkAsKnownPage>())
+       }
+
+       @Test
+       fun `new page can be injected`() {
+               assertThat(allPages.newPage, instanceOf<NewPage>())
+       }
+
+       @Test
+       fun `options page can be injected`() {
+               assertThat(allPages.optionsPage, instanceOf<OptionsPage>())
+       }
+
+       @Test
+       fun `rescue page can be injected`() {
+               assertThat(allPages.rescuePage, instanceOf<RescuePage>())
+       }
+
+       @Test
+       fun `search page can be injected`() {
+               assertThat(allPages.searchPage, instanceOf<SearchPage>())
+       }
+
+       @Test
+       fun `trust page can be injected`() {
+               assertThat(allPages.trustPage, instanceOf<TrustPage>())
+       }
+
+       @Test
+       fun `unbookmark page can be injected`() {
+               assertThat(allPages.unbookmarkPage, instanceOf<UnbookmarkPage>())
+       }
+
+       @Test
+       fun `unfollow sone page can be injected`() {
+               assertThat(allPages.unfollowSonePage, instanceOf<UnfollowSonePage>())
+       }
+
+       @Test
+       fun `unlike page can be injected`() {
+               assertThat(allPages.unlikePage, instanceOf<UnlikePage>())
+       }
+
+       @Test
+       fun `unlock sone page can be injected`() {
+               assertThat(allPages.unlockSonePage, instanceOf<UnlockSonePage>())
+       }
+
+       @Test
+       fun `untrust page can be injected`() {
+               assertThat(allPages.untrustPage, instanceOf<UntrustPage>())
+       }
+
+       @Test
+       fun `upload image page can be injected`() {
+               assertThat(allPages.uploadImagePage, instanceOf<UploadImagePage>())
+       }
+
+       @Test
+       fun `view post page can be injected`() {
+               assertThat(allPages.viewPostPage, instanceOf<ViewPostPage>())
+       }
+
+       @Test
+       fun `view sone page can be injected`() {
+               assertThat(allPages.viewSonePage, instanceOf<ViewSonePage>())
+       }
+}
+
+val baseInjector by lazy {
+       createInjector(
+                       Core::class.isProvidedByMock(),
+                       FreenetInterface::class.isProvidedByMock(),
+                       Template::class.isProvidedByMock(),
+                       WebInterface::class.isProvidedByDeepMock(),
+                       TemplateRenderer::class.isProvidedByMock()
+       )!!
+}
diff --git a/src/test/kotlin/net/pterodactylus/sone/web/PageToadletRegistryTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/PageToadletRegistryTest.kt
new file mode 100644 (file)
index 0000000..d242b32
--- /dev/null
@@ -0,0 +1,95 @@
+package net.pterodactylus.sone.web
+
+import com.google.inject.*
+import freenet.clients.http.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.web.*
+import org.junit.*
+import org.junit.rules.*
+import org.mockito.Mockito.*
+
+class PageToadletRegistryTest {
+
+       private val pageMaker = mock<PageMaker>()
+       private val toadletContainer = mock<ToadletContainer>()
+       private val pageToadletFactory = mock<PageToadletFactory>()
+       private val sonePlugin = mock<SonePlugin>()
+
+       private val injector = Guice.createInjector(
+                       PageMaker::class.isProvidedBy(pageMaker),
+                       ToadletContainer::class.isProvidedBy(toadletContainer),
+                       PageToadletFactory::class.isProvidedBy(pageToadletFactory),
+                       SonePlugin::class.isProvidedBy(sonePlugin)
+       )
+
+       @JvmField
+       @Rule
+       val expectedException: ExpectedException = ExpectedException.none()
+       private val pageToadletRegistry = injector.getInstance<PageToadletRegistry>()
+
+       @Test
+       fun `registry adds navigation category to page maker`() {
+               pageToadletRegistry.registerToadlets()
+               verify(pageMaker).addNavigationCategory("/Sone/index.html", "Navigation.Menu.Sone.Name", "Navigation.Menu.Sone.Tooltip", sonePlugin)
+       }
+
+       private val page = TestPage()
+
+       @Test
+       fun `adding a page without menuname will add it correctly`() {
+               val toadletWithoutMenuname = createPageToadlet()
+               whenever(pageToadletFactory.createPageToadlet(page)).thenReturn(toadletWithoutMenuname)
+               pageToadletRegistry.addPage(page)
+               pageToadletRegistry.registerToadlets()
+               verify(toadletContainer).register(toadletWithoutMenuname, null, "/Sone/", true, false)
+       }
+
+       @Test
+       fun `adding a page with menuname will add it correctly`() {
+               val toadletWithMenuname = createPageToadlet("Test")
+               whenever(pageToadletFactory.createPageToadlet(page)).thenReturn(toadletWithMenuname)
+               pageToadletRegistry.addPage(page)
+               pageToadletRegistry.registerToadlets()
+               verify(toadletContainer).register(toadletWithMenuname, "Navigation.Menu.Sone.Name", "/Sone/", true, "Navigation.Menu.Sone.Item.Test.Name", "Navigation.Menu.Sone.Item.Test.Tooltip", false, toadletWithMenuname)
+       }
+
+       @Test
+       fun `adding a page after registering will throw an exception`() {
+               val toadletWithMenuname = createPageToadlet("Test")
+               whenever(pageToadletFactory.createPageToadlet(page)).thenReturn(toadletWithMenuname)
+               pageToadletRegistry.registerToadlets()
+               expectedException.expect(IllegalStateException::class.java)
+               pageToadletRegistry.addPage(page)
+       }
+
+       @Test
+       fun `unregistering toadlets will remove category link`() {
+               pageToadletRegistry.unregisterToadlets()
+               verify(pageMaker).removeNavigationCategory("Navigation.Menu.Sone.Name")
+       }
+
+       @Test
+       fun `unregistering toadlets will unregister them from the container`() {
+               val toadletWithMenuname = createPageToadlet("Test")
+               whenever(pageToadletFactory.createPageToadlet(page)).thenReturn(toadletWithMenuname)
+               pageToadletRegistry.addPage(page)
+               pageToadletRegistry.registerToadlets()
+               pageToadletRegistry.unregisterToadlets()
+               verify(toadletContainer).unregister(toadletWithMenuname)
+       }
+
+       private fun createPageToadlet(menuName: String? = null) =
+                       mock<PageToadlet>().apply {
+                               whenever(this.path()).thenReturn("/Sone/")
+                               whenever(this.menuName).thenReturn(menuName)
+                       }
+
+       private class TestPage : Page<FreenetRequest> {
+               override fun getPath() = ""
+               override fun isPrefixPage() = false
+               override fun handleRequest(freenetRequest: FreenetRequest, response: Response) = response
+       }
+
+}
diff --git a/src/test/kotlin/net/pterodactylus/sone/web/WebInterfaceModuleTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/WebInterfaceModuleTest.kt
new file mode 100644 (file)
index 0000000..9137da3
--- /dev/null
@@ -0,0 +1,272 @@
+package net.pterodactylus.sone.web
+
+import com.google.inject.Guice.*
+import freenet.client.*
+import freenet.clients.http.*
+import freenet.l10n.*
+import freenet.support.api.*
+import net.pterodactylus.sone.core.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.database.*
+import net.pterodactylus.sone.freenet.*
+import net.pterodactylus.sone.freenet.wot.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.template.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.text.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.template.*
+import net.pterodactylus.util.web.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+
+class WebInterfaceModuleTest {
+
+       private val webInterfaceModule = WebInterfaceModule()
+       private val l10n = mock<BaseL10n>()
+       private val loaders = mock<Loaders>()
+       private val additionalModules = arrayOf(
+                       Core::class.isProvidedByMock(),
+                       SoneProvider::class.isProvidedByMock(),
+                       BaseL10n::class.isProvidedBy(l10n),
+                       SoneTextParser::class.isProvidedByMock(),
+                       ElementLoader::class.isProvidedByMock(),
+                       Loaders::class.isProvidedBy(loaders),
+                       HighLevelSimpleClient::class.isProvidedByMock(),
+                       SessionManager::class.isProvidedByMock()
+       )
+       private val injector = createInjector(webInterfaceModule, *additionalModules)!!
+       private val templateContext by lazy { injector.getInstance<TemplateContextFactory>().createTemplateContext()!! }
+
+       @Test
+       fun `template context factory creates template with reflection accessor for objects`() {
+               verifyAccessor<Any, ReflectionAccessor>()
+       }
+
+       @Test
+       fun `template context factory creates template with collection accessor for collections`() {
+               verifyAccessor<Collection<*>, CollectionAccessor>()
+       }
+
+       @Test
+       fun `template context contains sone accessor for sones`() {
+               verifyAccessor<Sone, SoneAccessor>()
+       }
+
+       @Test
+       fun `template context contains post accessor for posts`() {
+               verifyAccessor<Post, PostAccessor>()
+       }
+
+       @Test
+       fun `template context contains reply accessor for replies`() {
+               verifyAccessor<Reply<*>, ReplyAccessor>()
+       }
+
+       @Test
+       fun `template context contains album accessor for albums`() {
+               verifyAccessor<Album, AlbumAccessor>()
+       }
+
+       @Test
+       fun `template context contains image accessor for images`() {
+               verifyAccessor<Image, ImageAccessor>()
+       }
+
+       @Test
+       fun `template context contains identity accessor for identities`() {
+               verifyAccessor<Identity, IdentityAccessor>()
+       }
+
+       @Test
+       fun `template context contains trust accessor for trusts`() {
+               verifyAccessor<Trust, TrustAccessor>()
+       }
+
+       @Test
+       fun `template context contains http request accessor for http requests`() {
+               verifyAccessor<HTTPRequest, HttpRequestAccessor>()
+       }
+
+       @Test
+       fun `template context contains profile accessor for profiles`() {
+               verifyAccessor<Profile, ProfileAccessor>()
+       }
+
+       private inline fun <reified O, reified A : Accessor> verifyAccessor() {
+               assertThat(templateContext.getAccessor(O::class.java), instanceOf(A::class.java))
+       }
+
+       @Test
+       fun `template context contains date filter`() {
+               verifyFilter<DateFilter>("date")
+       }
+
+       @Test
+       fun `template context contains html filter`() {
+               verifyFilter<HtmlFilter>("html")
+       }
+
+       @Test
+       fun `template context contains replace filter`() {
+               verifyFilter<ReplaceFilter>("replace")
+       }
+
+       @Test
+       fun `template context contains store filter`() {
+               verifyFilter<StoreFilter>("store")
+       }
+
+       @Test
+       fun `template context contains l10n filter`() {
+               verifyFilter<L10nFilter>("l10n")
+       }
+
+       @Test
+       fun `template context contains substring filter`() {
+               verifyFilter<SubstringFilter>("substring")
+       }
+
+       @Test
+       fun `template context contains xml filter`() {
+               verifyFilter<XmlFilter>("xml")
+       }
+
+       @Test
+       fun `template context contains change filter`() {
+               verifyFilter<RequestChangeFilter>("change")
+       }
+
+       @Test
+       fun `template context contains match filter`() {
+               verifyFilter<MatchFilter>("match")
+       }
+
+       @Test
+       fun `template context contains css filter`() {
+               verifyFilter<CssClassNameFilter>("css")
+       }
+
+       @Test
+       fun `template context contains js filter`() {
+               verifyFilter<JavascriptFilter>("js")
+       }
+
+       @Test
+       fun `template context contains parser filter`() {
+               verifyFilter<ParserFilter>("parse")
+       }
+
+       @Test
+       fun `template context contains shorten filter`() {
+               verifyFilter<ShortenFilter>("shorten")
+       }
+
+       @Test
+       fun `template context contains render filter`() {
+               verifyFilter<RenderFilter>("render")
+       }
+
+       @Test
+       fun `template context contains linked elements filter`() {
+               verifyFilter<LinkedElementsFilter>("linked-elements")
+       }
+
+       @Test
+       fun `template context contains linked elements render filter`() {
+               verifyFilter<LinkedElementRenderFilter>("render-linked-element")
+       }
+
+       @Test
+       fun `template context contains reparse filter`() {
+               verifyFilter<ReparseFilter>("reparse")
+       }
+
+       @Test
+       fun `template context contains unknown date filter`() {
+               verifyFilter<UnknownDateFilter>("unknown")
+       }
+
+       @Test
+       fun `unknown date filter uses correct l10n key`() {
+               whenever(l10n.getString("View.Sone.Text.UnknownDate")).thenReturn("unknown")
+               assertThat(getFilter("unknown")!!.format(null, 0L, emptyMap()), equalTo<Any>("unknown"))
+       }
+
+       @Test
+       fun `template context contains format filter`() {
+               verifyFilter<FormatFilter>("format")
+       }
+
+       @Test
+       fun `template context contains collection sort filter`() {
+               verifyFilter<CollectionSortFilter>("sort")
+       }
+
+       @Test
+       fun `template context contains image link filter`() {
+               verifyFilter<ImageLinkFilter>("image-link")
+       }
+
+       @Test
+       fun `template context contains reply group filter`() {
+               verifyFilter<ReplyGroupFilter>("replyGroup")
+       }
+
+       @Test
+       fun `template context contains contains filter`() {
+               verifyFilter<ContainsFilter>("in")
+       }
+
+       @Test
+       fun `template context unique elements filter`() {
+               verifyFilter<UniqueElementFilter>("unique")
+       }
+
+       @Test
+       fun `template context mod filter`() {
+               verifyFilter<ModFilter>("mod")
+       }
+
+       @Test
+       fun `template context pagination filter`() {
+               verifyFilter<PaginationFilter>("paginate")
+       }
+
+       private inline fun <reified F : Filter> verifyFilter(name: String) {
+               assertThat(getFilter(name), instanceOf(F::class.java))
+       }
+
+       private fun getFilter(name: String): Filter? = templateContext.getFilter(name)
+
+       @Test
+       fun `template context factory is created as singleton`() {
+           val factory1 = injector.getInstance<TemplateContextFactory>()
+           val factory2 = injector.getInstance<TemplateContextFactory>()
+               assertThat(factory1, sameInstance(factory2))
+       }
+
+       @Test
+       fun `template from classpath is returned`() {
+               val template = Template()
+               templateContext["testTemplate"] = template
+               assertThat(templateContext.getTemplate("testTemplate"), sameInstance(template))
+       }
+
+       @Test
+       fun `template from loaders’ provider is returned`() {
+               val template = Template()
+               whenever(loaders.templateProvider).thenReturn(TemplateProvider { _, templateName ->
+                       template.takeIf { templateName == "testTemplate" }
+               })
+               assertThat(templateContext.getTemplate("testTemplate"), sameInstance(template))
+       }
+
+       @Test
+       fun `page toadlet factory is created with correct prefix`() {
+               val page = mock<Page<FreenetRequest>>()
+           assertThat(injector.getInstance<PageToadletFactory>().createPageToadlet(page).path(), startsWith("/Sone/"))
+       }
+
+}
index 17c334e..801ea28 100644 (file)
@@ -1,9 +1,11 @@
 package net.pterodactylus.sone.web.ajax
 
-import net.pterodactylus.sone.data.Post
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
 import org.mockito.ArgumentMatchers.any
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
@@ -39,4 +41,9 @@ class BookmarkAjaxPageTest : JsonPageTest("bookmark.ajax", requiresLogin = false
                verify(core).bookmarkPost(post)
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+           assertThat(baseInjector.getInstance<BookmarkAjaxPage>(), notNullValue())
+       }
+
 }
index 8449453..821198f 100644 (file)
@@ -3,11 +3,14 @@ package net.pterodactylus.sone.web.ajax
 import com.google.common.base.Optional
 import net.pterodactylus.sone.data.Post
 import net.pterodactylus.sone.data.Sone
+import net.pterodactylus.sone.test.getInstance
 import net.pterodactylus.sone.test.mock
 import net.pterodactylus.sone.test.whenever
 import net.pterodactylus.sone.utils.asOptional
+import net.pterodactylus.sone.web.baseInjector
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.equalTo
+import org.hamcrest.Matchers.notNullValue
 import org.hamcrest.Matchers.nullValue
 import org.junit.Test
 
@@ -89,4 +92,9 @@ class CreatePostAjaxPageTest : JsonPageTest("createPost.ajax", pageSupplier = ::
                                whenever(this.recipientId).thenReturn(recipientId.asOptional())
                        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+           assertThat(baseInjector.getInstance<CreatePostAjaxPage>(), notNullValue())
+       }
+
 }
index 829167e..dfbc1db 100644 (file)
@@ -3,10 +3,13 @@ package net.pterodactylus.sone.web.ajax
 import net.pterodactylus.sone.data.Post
 import net.pterodactylus.sone.data.PostReply
 import net.pterodactylus.sone.data.Sone
+import net.pterodactylus.sone.test.getInstance
 import net.pterodactylus.sone.test.mock
 import net.pterodactylus.sone.test.whenever
+import net.pterodactylus.sone.web.baseInjector
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.equalTo
+import org.hamcrest.Matchers.notNullValue
 import org.junit.Test
 
 /**
@@ -61,4 +64,9 @@ class CreateReplyAjaxPageTest : JsonPageTest("createReply.ajax", pageSupplier =
                assertThat(json["sone"]?.asText(), equalTo("local-sone"))
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+           assertThat(baseInjector.getInstance<CreateReplyAjaxPage>(), notNullValue())
+       }
+
 }
index cee50b9..5ad9531 100644 (file)
@@ -1,13 +1,12 @@
 package net.pterodactylus.sone.web.ajax
 
-import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [DeletePostAjaxPage].
@@ -40,4 +39,9 @@ class DeletePostAjaxPageTest : JsonPageTest("deletePost.ajax", pageSupplier = ::
                verify(core).deletePost(post)
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+           assertThat(baseInjector.getInstance<DeletePostAjaxPage>(), notNullValue())
+       }
+
 }
index 330bedf..9e4a8e0 100644 (file)
@@ -1,10 +1,11 @@
 package net.pterodactylus.sone.web.ajax
 
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.hamcrest.Matchers.nullValue
-import org.junit.Test
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [DeleteProfileFieldAjaxPage].
@@ -27,4 +28,9 @@ class DeleteProfileFieldAjaxPageTest : JsonPageTest("deleteProfileField.ajax", p
                verify(core).touchConfiguration()
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+           assertThat(baseInjector.getInstance<DeleteProfileFieldAjaxPage>(), notNullValue())
+       }
+
 }
index e39332c..0f765dc 100644 (file)
@@ -1,13 +1,12 @@
 package net.pterodactylus.sone.web.ajax
 
-import net.pterodactylus.sone.data.PostReply
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [DeleteReplyAjaxPage].
@@ -41,4 +40,9 @@ class DeleteReplyAjaxPageTest : JsonPageTest("deleteReply.ajax", pageSupplier =
                verify(core).deleteReply(reply)
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+           assertThat(baseInjector.getInstance<DeleteReplyAjaxPage>(), notNullValue())
+       }
+
 }
index ff603d2..a43a742 100644 (file)
@@ -1,12 +1,12 @@
 package net.pterodactylus.sone.web.ajax
 
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.util.notify.Notification
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.util.notify.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [DismissNotificationAjaxPage].
@@ -35,4 +35,9 @@ class DismissNotificationAjaxPageTest : JsonPageTest("dismissNotification.ajax",
                verify(notification).dismiss()
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+           assertThat(baseInjector.getInstance<DismissNotificationAjaxPage>(), notNullValue())
+       }
+
 }
index 78930e5..868819e 100644 (file)
@@ -1,9 +1,12 @@
 package net.pterodactylus.sone.web.ajax
 
 import net.pterodactylus.sone.data.Sone
+import net.pterodactylus.sone.test.getInstance
 import net.pterodactylus.sone.test.mock
+import net.pterodactylus.sone.web.baseInjector
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.equalTo
+import org.hamcrest.Matchers.notNullValue
 import org.junit.Test
 import org.mockito.Mockito.verify
 
@@ -34,7 +37,7 @@ class DistrustAjaxPageTest : JsonPageTest("distrustSone.ajax", pageSupplier = ::
 
        @Test
        fun `request with valid sone results in correct trust value being sent back`() {
-               core.preferences.negativeTrust = -33
+               core.preferences.newNegativeTrust = -33
                val sone = mock<Sone>()
                addSone(sone, "sone-id")
                addRequestParameter("sone", "sone-id")
@@ -42,4 +45,9 @@ class DistrustAjaxPageTest : JsonPageTest("distrustSone.ajax", pageSupplier = ::
                assertThat(json["trustValue"]?.asInt(), equalTo(-33))
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+           assertThat(baseInjector.getInstance<DistrustAjaxPage>(), notNullValue())
+       }
+
 }
index 098185b..1528d60 100644 (file)
@@ -5,10 +5,13 @@ import net.pterodactylus.sone.data.Album.Modifier.AlbumTitleMustNotBeEmpty
 import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.data.impl.AlbumImpl
 import net.pterodactylus.sone.test.deepMock
+import net.pterodactylus.sone.test.getInstance
 import net.pterodactylus.sone.test.mock
 import net.pterodactylus.sone.test.whenever
+import net.pterodactylus.sone.web.baseInjector
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.equalTo
+import org.hamcrest.Matchers.notNullValue
 import org.junit.Test
 
 /**
@@ -87,4 +90,9 @@ class EditAlbumAjaxPageTest : JsonPageTest("editAlbum.ajax", pageSupplier = ::Ed
                assertThat(json["description"]?.asText(), equalTo("foo KSK@foo.html link"))
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+           assertThat(baseInjector.getInstance<EditAlbumAjaxPage>(), notNullValue())
+       }
+
 }
index dad6c02..26436a3 100644 (file)
@@ -8,10 +8,14 @@ import net.pterodactylus.sone.template.ParserFilter
 import net.pterodactylus.sone.template.RenderFilter
 import net.pterodactylus.sone.template.ShortenFilter
 import net.pterodactylus.sone.test.argumentCaptor
+import net.pterodactylus.sone.test.getInstance
+import net.pterodactylus.sone.test.isProvidedByMock
 import net.pterodactylus.sone.test.mock
 import net.pterodactylus.sone.test.whenever
+import net.pterodactylus.sone.web.baseInjector
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.equalTo
+import org.hamcrest.Matchers.notNullValue
 import org.junit.Test
 import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.eq
@@ -113,4 +117,13 @@ class EditImageAjaxPageTest : JsonPageTest("editImage.ajax") {
                assertThat(parameterCaptor.value["sone"], equalTo<Any>(sone))
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.createChildInjector(
+                               ParserFilter::class.isProvidedByMock(),
+                               ShortenFilter::class.isProvidedByMock(),
+                               RenderFilter::class.isProvidedByMock()
+               ).getInstance<EditImageAjaxPage>(), notNullValue())
+       }
+
 }
index 7eed9ac..6e00c29 100644 (file)
@@ -1,7 +1,10 @@
 package net.pterodactylus.sone.web.ajax
 
+import net.pterodactylus.sone.test.getInstance
+import net.pterodactylus.sone.web.baseInjector
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.equalTo
+import org.hamcrest.Matchers.notNullValue
 import org.junit.Test
 import org.mockito.Mockito.verify
 
@@ -43,4 +46,9 @@ class EditProfileFieldAjaxPageTest : JsonPageTest("editProfileField.ajax", pageS
                verify(currentSone).profile = profile
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+           assertThat(baseInjector.getInstance<EditProfileFieldAjaxPage>(), notNullValue())
+       }
+
 }
index c649e75..cd4a932 100644 (file)
@@ -1,12 +1,12 @@
 package net.pterodactylus.sone.web.ajax
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [FollowSoneAjaxPage].
@@ -35,4 +35,9 @@ class FollowSoneAjaxPageTest : JsonPageTest("followSone.ajax", pageSupplier = ::
                verify(core).markSoneKnown(sone)
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<FollowSoneAjaxPage>(), notNullValue())
+       }
+
 }
index 3a75ded..5f89713 100644 (file)
@@ -4,11 +4,14 @@ import net.pterodactylus.sone.data.Post
 import net.pterodactylus.sone.data.PostReply
 import net.pterodactylus.sone.data.Profile
 import net.pterodactylus.sone.data.Sone
+import net.pterodactylus.sone.test.getInstance
 import net.pterodactylus.sone.test.mock
 import net.pterodactylus.sone.test.whenever
+import net.pterodactylus.sone.web.baseInjector
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.contains
 import org.hamcrest.Matchers.equalTo
+import org.hamcrest.Matchers.notNullValue
 import org.junit.Test
 
 /**
@@ -86,6 +89,11 @@ class GetLikesAjaxPageTest : JsonPageTest("getLikes.ajax", needsFormPassword = f
                assertThatJsonFailed("invalid-type")
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+           assertThat(baseInjector.getInstance<GetLikesAjaxPage>(), notNullValue())
+       }
+       
 }
 
 private fun createSone(index: Int) = mock<Sone>().apply {
index f49f9f3..fb8b527 100644 (file)
@@ -1,15 +1,19 @@
 package net.pterodactylus.sone.web.ajax
 
 import com.fasterxml.jackson.databind.JsonNode
+import net.pterodactylus.sone.core.ElementLoader
 import net.pterodactylus.sone.core.LinkedElement
 import net.pterodactylus.sone.template.LinkedElementRenderFilter
+import net.pterodactylus.sone.test.getInstance
+import net.pterodactylus.sone.test.isProvidedByMock
 import net.pterodactylus.sone.test.mock
 import net.pterodactylus.sone.test.whenever
 import net.pterodactylus.sone.utils.jsonArray
+import net.pterodactylus.sone.web.baseInjector
 import net.pterodactylus.util.template.TemplateContext
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers
-import org.hamcrest.Matchers.equalTo
+import org.hamcrest.Matchers.notNullValue
 import org.junit.Test
 import org.mockito.ArgumentMatchers
 
@@ -40,6 +44,14 @@ class GetLinkedElementAjaxPageTest: JsonPageTest("getLinkedElement.ajax", requir
                ))
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+           assertThat(baseInjector.createChildInjector(
+                           ElementLoader::class.isProvidedByMock(),
+                           LinkedElementRenderFilter::class.isProvidedByMock()
+           ).getInstance<GetLinkedElementAjaxPage>(), notNullValue())
+       }
+
        private fun JsonNode.toMap() = fields().asSequence().map { it.key!! to if (it.value.isNull) null else it.value.asText()!! }.toMap()
 
 }
index 8084b65..8f67ecf 100644 (file)
@@ -3,8 +3,10 @@ package net.pterodactylus.sone.web.ajax
 import net.pterodactylus.sone.main.SonePlugin
 import net.pterodactylus.sone.test.argumentCaptor
 import net.pterodactylus.sone.test.get
+import net.pterodactylus.sone.test.getInstance
 import net.pterodactylus.sone.test.mock
 import net.pterodactylus.sone.test.whenever
+import net.pterodactylus.sone.web.baseInjector
 import net.pterodactylus.util.notify.Notification
 import net.pterodactylus.util.notify.TemplateNotification
 import net.pterodactylus.util.template.TemplateContext
@@ -14,6 +16,7 @@ import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.containsInAnyOrder
 import org.hamcrest.Matchers.empty
 import org.hamcrest.Matchers.equalTo
+import org.hamcrest.Matchers.notNullValue
 import org.junit.Test
 import org.mockito.ArgumentMatchers.any
 import org.mockito.Mockito.verify
@@ -115,4 +118,9 @@ class GetNotificationsAjaxPageTest : JsonPageTest("getNotifications.ajax", requi
                assertThat(templateContext.value["notification"], equalTo<Any>(templateNotification))
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+           assertThat(baseInjector.getInstance<GetNotificationsAjaxPage>(), notNullValue())
+       }
+
 }
index 8d33542..130bdc2 100644 (file)
@@ -2,13 +2,16 @@ package net.pterodactylus.sone.web.ajax
 
 import net.pterodactylus.sone.data.Post
 import net.pterodactylus.sone.data.Sone
+import net.pterodactylus.sone.test.getInstance
 import net.pterodactylus.sone.test.mock
 import net.pterodactylus.sone.test.whenever
 import net.pterodactylus.sone.utils.asOptional
 import net.pterodactylus.sone.utils.asTemplate
+import net.pterodactylus.sone.web.baseInjector
 import net.pterodactylus.util.template.ReflectionAccessor
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.equalTo
+import org.hamcrest.Matchers.notNullValue
 import org.junit.Test
 
 /**
@@ -51,4 +54,9 @@ class GetPostAjaxPageTest : JsonPageTest("getPost.ajax", needsFormPassword = fal
                ).joinToString("\n")))
        }
 
+       @Test
+       fun `page can be created dependency injection`() {
+           assertThat(baseInjector.getInstance<GetPostAjaxPage>(), notNullValue())
+       }
+
 }
index 6af8776..6010e9f 100644 (file)
@@ -2,12 +2,15 @@ package net.pterodactylus.sone.web.ajax
 
 import net.pterodactylus.sone.data.PostReply
 import net.pterodactylus.sone.data.Sone
+import net.pterodactylus.sone.test.getInstance
 import net.pterodactylus.sone.test.mock
 import net.pterodactylus.sone.test.whenever
 import net.pterodactylus.sone.utils.asTemplate
+import net.pterodactylus.sone.web.baseInjector
 import net.pterodactylus.util.template.ReflectionAccessor
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.equalTo
+import org.hamcrest.Matchers.notNullValue
 import org.junit.Test
 
 /**
@@ -49,4 +52,9 @@ class GetReplyAjaxPageTest : JsonPageTest("getReply.ajax", needsFormPassword = f
                ).joinToString("\n")))
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+           assertThat(baseInjector.getInstance<GetReplyAjaxPage>(), notNullValue())
+       }
+
 }
index 9576c7c..f270d71 100644 (file)
@@ -1,17 +1,21 @@
 package net.pterodactylus.sone.web.ajax
 
 import com.fasterxml.jackson.databind.JsonNode
+import net.pterodactylus.sone.core.ElementLoader
 import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.data.Sone.SoneStatus.downloading
 import net.pterodactylus.sone.data.Sone.SoneStatus.inserting
 import net.pterodactylus.sone.freenet.L10nFilter
 import net.pterodactylus.sone.freenet.L10nText
 import net.pterodactylus.sone.test.deepMock
+import net.pterodactylus.sone.test.getInstance
+import net.pterodactylus.sone.test.isProvidedByMock
 import net.pterodactylus.sone.test.mock
 import net.pterodactylus.sone.test.whenever
 import net.pterodactylus.sone.text.TimeText
 import net.pterodactylus.sone.text.TimeTextConverter
 import net.pterodactylus.sone.utils.jsonArray
+import net.pterodactylus.sone.web.baseInjector
 import net.pterodactylus.util.notify.Notification
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.allOf
@@ -19,6 +23,7 @@ import org.hamcrest.Matchers.containsInAnyOrder
 import org.hamcrest.Matchers.emptyIterable
 import org.hamcrest.Matchers.equalTo
 import org.hamcrest.Matchers.hasEntry
+import org.hamcrest.Matchers.notNullValue
 import org.junit.Before
 import org.junit.Test
 import org.mockito.ArgumentMatchers.any
@@ -134,6 +139,15 @@ class GetStatusAjaxPageTest: JsonPageTest("getStatus.ajax", requiresLogin = fals
                ))
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+           assertThat(baseInjector.createChildInjector(
+                           ElementLoader::class.isProvidedByMock(),
+                           TimeTextConverter::class.isProvidedByMock(),
+                           L10nFilter::class.isProvidedByMock()
+           ).getInstance<GetStatusAjaxPage>(), notNullValue())
+       }
+
        private fun JsonNode.toMap() = fields().asSequence().map { it.key!! to if (it.value.isNull) null else it.value.asText()!! }.toMap()
 
 }
index fff438b..62a34d1 100644 (file)
@@ -6,19 +6,23 @@ import net.pterodactylus.sone.data.PostReply
 import net.pterodactylus.sone.freenet.L10nFilter
 import net.pterodactylus.sone.freenet.L10nText
 import net.pterodactylus.sone.test.get
+import net.pterodactylus.sone.test.getInstance
+import net.pterodactylus.sone.test.isProvidedByMock
 import net.pterodactylus.sone.test.mock
 import net.pterodactylus.sone.test.whenever
 import net.pterodactylus.sone.text.TimeText
 import net.pterodactylus.sone.text.TimeTextConverter
 import net.pterodactylus.sone.utils.jsonObject
+import net.pterodactylus.sone.web.baseInjector
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.containsInAnyOrder
 import org.hamcrest.Matchers.emptyIterable
-import org.hamcrest.Matchers.equalTo
+import org.hamcrest.Matchers.notNullValue
 import org.junit.Before
 import org.junit.Test
 import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.anyLong
+import java.util.TimeZone
 import java.util.TimeZone.getTimeZone
 
 /**
@@ -107,4 +111,13 @@ class GetTimesAjaxPageTest : JsonPageTest("getTimes.ajax", needsFormPassword = f
                ))
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.createChildInjector(
+                               TimeTextConverter::class.isProvidedByMock(),
+                               L10nFilter::class.isProvidedByMock(),
+                               TimeZone::class.isProvidedByMock()
+               ).getInstance<GetTimesAjaxPage>(), notNullValue())
+       }
+
 }
index 0d58ca2..ebf4edd 100644 (file)
@@ -1,7 +1,10 @@
 package net.pterodactylus.sone.web.ajax
 
+import net.pterodactylus.sone.test.getInstance
+import net.pterodactylus.sone.web.baseInjector
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.equalTo
+import org.hamcrest.Matchers.notNullValue
 import org.junit.Test
 
 /**
@@ -17,4 +20,9 @@ class GetTranslationAjaxPageTest : JsonPageTest("getTranslation.ajax", requiresL
                assertThat(json["value"]?.asText(), equalTo("bar"))
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+           assertThat(baseInjector.getInstance<GetTranslationAjaxPage>(), notNullValue())
+       }
+
 }
index 75a0cd4..f9554f3 100644 (file)
@@ -23,7 +23,7 @@ class JsonPageBaseTest : TestObjects() {
        private val outputStream = ByteArrayOutputStream()
        private val response = Response(outputStream)
 
-       private val page = object : JsonPage("path.html", webInterface) {
+       private val page = object : JsonPage(webInterface) {
 
                override val needsFormPassword get() = this@JsonPageBaseTest.needsFormPassword
 
@@ -49,7 +49,7 @@ class JsonPageBaseTest : TestObjects() {
 
        @Test
        fun `page returns 403 is full access is required but request is not full access`() {
-               core.preferences.isRequireFullAccess = true
+               core.preferences.newRequireFullAccess = true
                page.handleRequest(freenetRequest, response)
                assertThat(response.statusCode, equalTo(403))
                assertThat(response.statusText, equalTo("Forbidden"))
index 457aac4..c7da213 100644 (file)
@@ -2,10 +2,13 @@ package net.pterodactylus.sone.web.ajax
 
 import net.pterodactylus.sone.data.Post
 import net.pterodactylus.sone.data.PostReply
+import net.pterodactylus.sone.test.getInstance
 import net.pterodactylus.sone.test.mock
 import net.pterodactylus.sone.test.whenever
+import net.pterodactylus.sone.web.baseInjector
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.equalTo
+import org.hamcrest.Matchers.notNullValue
 import org.junit.Test
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
@@ -60,4 +63,9 @@ class LikeAjaxPageTest : JsonPageTest("like.ajax", pageSupplier = ::LikeAjaxPage
                verify(core, never()).touchConfiguration()
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+           assertThat(baseInjector.getInstance<LikeAjaxPage>(), notNullValue())
+       }
+
 }
index 3a2a524..e3fe5c6 100644 (file)
@@ -1,11 +1,12 @@
 package net.pterodactylus.sone.web.ajax
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.test.mock
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [LockSoneAjaxPage].
@@ -26,4 +27,9 @@ class LockSoneAjaxPageTest : JsonPageTest("lockSone.ajax", requiresLogin = false
                verify(core).lockSone(sone)
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+           assertThat(baseInjector.getInstance<LockSoneAjaxPage>(), notNullValue())
+       }
+
 }
index 9578d45..edc8c25 100644 (file)
@@ -1,6 +1,12 @@
 package net.pterodactylus.sone.web.ajax
 
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+
 /**
  * Unit test for [LoggedInJsonPageTest].
  */
-class LoggedInJsonPageTest : JsonPageTest("path", requiresLogin = true, pageSupplier = { webInterface -> LoggedInJsonPage("path", webInterface) })
+class LoggedInJsonPageTest : JsonPageTest("path", requiresLogin = true, pageSupplier = ::TestPage)
+
+@ToadletPath("path")
+class TestPage(webInterface: WebInterface) : LoggedInJsonPage(webInterface)
index a3ab0ff..9c37a3d 100644 (file)
@@ -1,16 +1,12 @@
 package net.pterodactylus.sone.web.ajax
 
-import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.data.PostReply
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
-import org.mockito.Mockito.any
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [MarkAsKnownAjaxPage].
@@ -69,4 +65,9 @@ class MarkAsKnownAjaxPageTest : JsonPageTest("markAsKnown.ajax", requiresLogin =
                verify(core).markReplyKnown(reply2)
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+           assertThat(baseInjector.getInstance<MarkAsKnownAjaxPage>(), notNullValue())
+       }
+
 }
index aab1bbe..847b82c 100644 (file)
@@ -1,7 +1,10 @@
 package net.pterodactylus.sone.web.ajax
 
+import net.pterodactylus.sone.test.getInstance
+import net.pterodactylus.sone.web.baseInjector
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.equalTo
+import org.hamcrest.Matchers.notNullValue
 import org.junit.Test
 import org.mockito.Mockito.verify
 
@@ -62,4 +65,9 @@ class MoveProfileFieldAjaxPageTest : JsonPageTest("moveProfileField.ajax", true,
                verify(currentSone).profile = profile
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+           assertThat(baseInjector.getInstance<MoveProfileFieldAjaxPage>(), notNullValue())
+       }
+
 }
index 64a66a3..d557790 100644 (file)
@@ -1,9 +1,12 @@
 package net.pterodactylus.sone.web.ajax
 
 import net.pterodactylus.sone.data.Sone
+import net.pterodactylus.sone.test.getInstance
 import net.pterodactylus.sone.test.mock
+import net.pterodactylus.sone.web.baseInjector
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.equalTo
+import org.hamcrest.Matchers.notNullValue
 import org.junit.Test
 import org.mockito.Mockito.verify
 
@@ -31,9 +34,14 @@ class TrustAjaxPageTest : JsonPageTest("trustSone.ajax", requiresLogin = true, n
        fun `request with valid sone returns positive trust value`() {
                addSone(sone, "sone-id")
                addRequestParameter("sone", "sone-id")
-               core.preferences.positiveTrust = 31
+               core.preferences.newPositiveTrust = 31
                assertThatJsonIsSuccessful()
                assertThat(json["trustValue"]?.asInt(), equalTo(31))
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+           assertThat(baseInjector.getInstance<TrustAjaxPage>(), notNullValue())
+       }
+
 }
index 65c5beb..b54981f 100644 (file)
@@ -1,9 +1,12 @@
 package net.pterodactylus.sone.web.ajax
 
 import net.pterodactylus.sone.data.Post
+import net.pterodactylus.sone.test.getInstance
 import net.pterodactylus.sone.test.mock
+import net.pterodactylus.sone.web.baseInjector
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.equalTo
+import org.hamcrest.Matchers.notNullValue
 import org.junit.Test
 import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.eq
@@ -42,4 +45,9 @@ class UnbookmarkAjaxPageTest : JsonPageTest("unbookmark.ajax", requiresLogin = f
                verify(core).unbookmarkPost(eq(post))
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+           assertThat(baseInjector.getInstance<UnbookmarkAjaxPage>(), notNullValue())
+       }
+
 }
index b53cc8e..788a398 100644 (file)
@@ -1,12 +1,12 @@
 package net.pterodactylus.sone.web.ajax
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [UnfollowSoneAjaxPage].
@@ -32,4 +32,9 @@ class UnfollowSoneAjaxPageTest : JsonPageTest("unfollowSone.ajax", pageSupplier
                verify(core).unfollowSone(currentSone, "sone-id")
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+           assertThat(baseInjector.getInstance<UnfollowSoneAjaxPage>(), notNullValue())
+       }
+
 }
index 4661892..488aacd 100644 (file)
@@ -1,7 +1,10 @@
 package net.pterodactylus.sone.web.ajax
 
+import net.pterodactylus.sone.test.getInstance
+import net.pterodactylus.sone.web.baseInjector
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.equalTo
+import org.hamcrest.Matchers.notNullValue
 import org.junit.Test
 import org.mockito.Mockito.verify
 
@@ -52,4 +55,9 @@ class UnlikeAjaxPageTest : JsonPageTest("unlike.ajax", pageSupplier = ::UnlikeAj
                verify(core).touchConfiguration()
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+           assertThat(baseInjector.getInstance<UnlikeAjaxPage>(), notNullValue())
+       }
+
 }
index 81a54e3..bce6db1 100644 (file)
@@ -1,11 +1,12 @@
 package net.pterodactylus.sone.web.ajax
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.test.mock
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [UnlockSoneAjaxPage].
@@ -32,4 +33,9 @@ class UnlockSoneAjaxPageTest : JsonPageTest("unlockSone.ajax", requiresLogin = f
                verify(core).unlockSone(sone)
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+           assertThat(baseInjector.getInstance<UnlockSoneAjaxPage>(), notNullValue())
+       }
+
 }
index f2de48d..b693194 100644 (file)
@@ -1,12 +1,12 @@
 package net.pterodactylus.sone.web.ajax
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.test.mock
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.hamcrest.Matchers.nullValue
-import org.junit.Test
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [UntrustAjaxPage].
@@ -42,4 +42,9 @@ class UntrustAjaxPageTest : JsonPageTest("untrustSone.ajax", pageSupplier = ::Un
                assertThat(json["trustValue"], nullValue())
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+           assertThat(baseInjector.getInstance<UntrustAjaxPage>(), notNullValue())
+       }
+
 }
diff --git a/src/test/kotlin/net/pterodactylus/sone/web/page/FreenetRequestTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/page/FreenetRequestTest.kt
new file mode 100644 (file)
index 0000000..e05ad45
--- /dev/null
@@ -0,0 +1,77 @@
+package net.pterodactylus.sone.web.page
+
+import freenet.clients.http.*
+import freenet.clients.http.SessionManager.*
+import freenet.l10n.*
+import freenet.support.api.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.util.web.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.*
+import org.mockito.Mockito.*
+import java.net.*
+
+class FreenetRequestTest {
+
+       private val uri = URI(".")
+       private val method = Method.GET
+       private val httpRequest = mock(HTTPRequest::class.java)
+       private val toadletContext = mock(ToadletContext::class.java)
+       private val l10n = mock<BaseL10n>()
+       private val sessionManager = mock<SessionManager>()
+       private val request = FreenetRequest(uri, method, httpRequest, toadletContext, l10n, sessionManager)
+
+       @Test
+       fun `uri is retained correctly`() {
+               assertThat(request.uri, equalTo(uri))
+       }
+
+       @Test
+       fun `method is retained correctly`() {
+               assertThat(request.method, equalTo(method))
+       }
+
+       @Test
+       fun `http request is retained correctly`() {
+               assertThat(request.httpRequest, equalTo(httpRequest))
+       }
+
+       @Test
+       fun `toadlet context is retained correctly`() {
+               assertThat(request.toadletContext, equalTo(toadletContext))
+       }
+
+       @Test
+       fun `l10n is retained correctly`() {
+               assertThat(request.l10n, equalTo(l10n))
+       }
+
+       @Test
+       fun `null is returned if no session exists`() {
+               assertThat(request.existingSession, nullValue())
+       }
+
+       @Test
+       fun `existing session can be retrieved`() {
+               val session = mock<Session>()
+               whenever(sessionManager.useSession(toadletContext)).thenReturn(session)
+               assertThat(request.existingSession, sameInstance(session))
+       }
+
+       @Test
+       fun `existing session is returned if it exists`() {
+               val session = mock<Session>()
+               whenever(sessionManager.useSession(toadletContext)).thenReturn(session)
+               assertThat(request.session, sameInstance(session))
+       }
+
+       @Test
+       fun `new session is returned if none exists`() {
+               val session = mock<Session>()
+               whenever(sessionManager.createSession(anyString(), ArgumentMatchers.eq(toadletContext))).thenReturn(session)
+               assertThat(request.session, sameInstance(session))
+       }
+
+}
diff --git a/src/test/kotlin/net/pterodactylus/sone/web/page/FreenetTemplatePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/page/FreenetTemplatePageTest.kt
new file mode 100644 (file)
index 0000000..2474d08
--- /dev/null
@@ -0,0 +1,149 @@
+package net.pterodactylus.sone.web.page
+
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.util.web.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
+import java.io.*
+import java.nio.charset.StandardCharsets.*
+
+class FreenetTemplatePageTest {
+
+       private val templateRenderer = deepMock<TemplateRenderer>()
+       private val loaders = mock<Loaders>()
+       private val page = TestPage(templateRenderer, loaders)
+
+       @Test
+       fun `path is exposed correctly`() {
+               assertThat(page.path, equalTo("/test/path"))
+       }
+
+       @Test
+       fun `getPageTitle() default implementation returns empty string`() {
+               assertThat(page.getPageTitle(mock()), equalTo(""))
+       }
+
+       @Test
+       fun `isPrefixPage() default implementation returns false`() {
+               assertThat(page.isPrefixPage, equalTo(false))
+       }
+
+       @Test
+       fun `getStylesheets() default implementation returns empty collection`() {
+               assertThat(page.styleSheets, empty())
+       }
+
+       @Test
+       fun `getShortcutIcon() default implementation returns null`() {
+               assertThat(page.shortcutIcon, nullValue())
+       }
+
+       @Test
+       fun `getRedirectTarget() default implementation returns null`() {
+               assertThat(page.getRedirectTarget(mock()), nullValue())
+       }
+
+       @Test
+       fun `getAdditionalLinkNodes() default implementation returns empty collection`() {
+               assertThat(page.getAdditionalLinkNodes(mock()), empty())
+       }
+
+       @Test
+       fun `isFullAccessOnly() default implementation returns false`() {
+               assertThat(page.isFullAccessOnly, equalTo(false))
+       }
+
+       @Test
+       fun `isLinkExcepted() default implementation returns false`() {
+               assertThat(page.isLinkExcepted(mock()), equalTo(false))
+       }
+
+       @Test
+       fun `isEnabled() returns true if full access only is false`() {
+               assertThat(page.isEnabled(mock()), equalTo(true))
+       }
+
+       @Test
+       fun `isEnabled() returns false if full access only is true`() {
+               val page = object : TestPage(templateRenderer, loaders) {
+                       override val isFullAccessOnly = true
+               }
+               assertThat(page.isEnabled(mock()), equalTo(false))
+       }
+
+       @Test
+       fun `page with redirect target throws redirect exception on handleRequest`() {
+               val page = object : TestPage(templateRenderer, loaders) {
+                       override fun getRedirectTarget(request: FreenetRequest) = "foo"
+               }
+               val request = mock<FreenetRequest>()
+               val response = mock<Response>()
+               val pageResponse = page.handleRequest(request, response)
+               assertThat(pageResponse.statusCode, anyOf(equalTo(302), equalTo(307)))
+               assertThat(pageResponse.headers, contains(hasHeader("location", "foo")))
+       }
+
+       @Test
+       fun `page with full access only returns unauthorized on handleRequest with non-full access request`() {
+               val page = object : TestPage(templateRenderer, loaders) {
+                       override val isFullAccessOnly = true
+               }
+               val request = deepMock<FreenetRequest>()
+               val response = Response(null)
+               val pageResponse = page.handleRequest(request, response)
+               assertThat(pageResponse.statusCode, equalTo(401))
+       }
+
+       @Test
+       fun `page redirects on POST without form password`() {
+               val request = deepMock<FreenetRequest>().apply {
+                       whenever(httpRequest.getPartAsStringFailsafe(any(), anyInt())).thenReturn("")
+                       whenever(method).thenReturn(POST)
+               }
+               val response = Response(null)
+               val pageResponse = page.handleRequest(request, response)
+               assertThat(pageResponse.statusCode, anyOf(equalTo(302), equalTo(307)))
+               assertThat(pageResponse.headers, contains(hasHeader("location", "invalid-form-password")))
+       }
+
+       @Test
+       fun `page redirects on POST with invalid password`() {
+               val request = deepMock<FreenetRequest>().apply {
+                       whenever(httpRequest.getPartAsStringFailsafe(any(), anyInt())).thenReturn("invalid")
+                       whenever(method).thenReturn(POST)
+               }
+               val response = Response(null)
+               val pageResponse = page.handleRequest(request, response)
+               assertThat(pageResponse.statusCode, anyOf(equalTo(302), equalTo(307)))
+               assertThat(pageResponse.headers, contains(hasHeader("location", "invalid-form-password")))
+       }
+
+       @Test
+       @Dirty
+       fun `freenet template page creates page with correct title`() {
+               val page = object : TestPage(templateRenderer, loaders) {
+                       override fun getPageTitle(request: FreenetRequest) = "page title"
+               }
+               val request = deepMock<FreenetRequest>()
+               val pageMakerInteractionFactory = deepMock<PageMakerInteractionFactory>()
+               whenever(pageMakerInteractionFactory.createPageMaker(request.toadletContext, "page title").renderPage()).thenReturn("<page>")
+               setField(page, "pageMakerInteractionFactory", pageMakerInteractionFactory)
+               val response = page.handleRequest(request, Response(ByteArrayOutputStream()))
+               assertThat(response.statusCode, equalTo(200))
+               assertThat((response.content as ByteArrayOutputStream).toString(UTF_8.name()), equalTo("<page>"))
+       }
+
+       @Test
+       fun `template from annotation is loaded`() {
+               verify(loaders).loadTemplate("template-path")
+       }
+
+       @TemplatePath("template-path")
+       @ToadletPath("/test/path")
+       private open class TestPage(templateRenderer: TemplateRenderer, loaders: Loaders) : FreenetTemplatePage(templateRenderer, loaders, "invalid-form-password")
+
+}
diff --git a/src/test/kotlin/net/pterodactylus/sone/web/page/PageMakerInteractionFactoryTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/page/PageMakerInteractionFactoryTest.kt
new file mode 100644 (file)
index 0000000..8e16094
--- /dev/null
@@ -0,0 +1,28 @@
+package net.pterodactylus.sone.web.page
+
+import com.google.inject.*
+import freenet.clients.http.*
+import net.pterodactylus.sone.test.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
+
+class PageMakerInteractionFactoryTest {
+
+       private val pageMakerInteractionFactory: PageMakerInteractionFactory = DefaultPageMakerInteractionFactory()
+
+       @Test
+       fun `page maker interaction factory can be created by guice`() {
+               val injector = Guice.createInjector()
+               assertThat(injector.getInstance<PageMakerInteractionFactory>(), notNullValue())
+       }
+
+       @Test
+       fun `page maker interaction sets page title correctly`() {
+               val toadletContext = deepMock<ToadletContext>()
+               pageMakerInteractionFactory.createPageMaker(toadletContext, "page title")
+               verify(toadletContext.pageMaker).getPageNode("page title", toadletContext)
+       }
+
+}
diff --git a/src/test/kotlin/net/pterodactylus/sone/web/page/PageMakerInteractionTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/page/PageMakerInteractionTest.kt
new file mode 100644 (file)
index 0000000..178ef09
--- /dev/null
@@ -0,0 +1,67 @@
+package net.pterodactylus.sone.web.page
+
+import freenet.clients.http.*
+import freenet.support.*
+import freenet.support.HTMLNode.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.test.TestUtil.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+
+@Dirty
+class PageMakerInteractionTest {
+
+       private val toadletContext = deepMock<ToadletContext>()
+       private val pageMaker: PageMaker = toadletContext.pageMaker
+       private val outerNode = HTMLDoctype("html", "-//W3C//DTD XHTML 1.1//EN")
+       private val htmlNode: HTMLNode = outerNode.addChild("html")
+       private val headNode: HTMLNode = htmlNode.addChild("head")
+       private val contentNode: HTMLNode = htmlNode.addChild("body").addChild("div")
+       private val pageNode: PageNode = createObject(PageNode::class.java, arrayOf(HTMLNode::class.java, HTMLNode::class.java, HTMLNode::class.java), outerNode, headNode, contentNode)
+
+       init {
+               whenever(pageMaker.getPageNode("page title", toadletContext)).thenReturn(pageNode)
+       }
+
+       private val pageMakerInteractions = PageMakerInteraction(toadletContext, "page title")
+
+       @Test
+       fun `interactions can add style sheet`() {
+               pageMakerInteractions.addStyleSheet("style.sheet")
+               assertThat(headNode.children.filter { it.name == "link" }.map { it.attributes }, contains(
+                               mapOf("rel" to "stylesheet", "href" to "style.sheet", "type" to "text/css", "media" to "screen")
+               ))
+       }
+
+       @Test
+       fun `link nodes can be added`() {
+               pageMakerInteractions.addLinkNode(mapOf("foo" to "bar"))
+               assertThat(headNode.children.filter { it.name == "link" }.map { it.attributes }, contains(
+                               mapOf("foo" to "bar")
+               ))
+       }
+
+       @Test
+       fun `shortcut icon can be added`() {
+               pageMakerInteractions.addShortcutIcon("shortcut.icon")
+               assertThat(headNode.children.filter { it.name == "link" }.map { it.attributes }, contains(
+                               mapOf("rel" to "icon", "href" to "shortcut.icon")
+               ))
+       }
+
+       @Test
+       fun `content can be set`() {
+               pageMakerInteractions.setContent("foo<bar")
+               assertThat(contentNode.generate(), containsString("foo<bar"))
+       }
+
+       @Test
+       fun `whole page can be rendered`() {
+               pageMakerInteractions.setContent("foo<bar")
+               assertThat(pageMakerInteractions.renderPage(), containsString("foo<bar"))
+       }
+
+       private val HTMLNode.name: String get() = firstTag
+
+}
diff --git a/src/test/kotlin/net/pterodactylus/sone/web/page/PageToadletFactoryTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/page/PageToadletFactoryTest.kt
new file mode 100644 (file)
index 0000000..7ac8c01
--- /dev/null
@@ -0,0 +1,77 @@
+package net.pterodactylus.sone.web.page
+
+import com.google.inject.*
+import freenet.client.*
+import freenet.clients.http.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.util.web.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+
+private val highLevelSimpleClient = mock<HighLevelSimpleClient>()
+private val sessionManager = mock<SessionManager>()
+private const val pathPrefix = "/some/prefix/"
+
+class PageToadletFactoryTest {
+
+       private val pageToadletFactory = PageToadletFactory(highLevelSimpleClient, sessionManager, pathPrefix)
+
+       @Test
+       fun `page toadlet without menu name is created without menu name`() {
+               val page = mock<Page<FreenetRequest>>()
+               val pageToadlet = pageToadletFactory.createPageToadlet(page)
+               assertThat(pageToadlet.menuName, nullValue())
+       }
+
+       @Test
+       fun `page toadlet with menu name is created with menu name`() {
+               val page = mock<Page<FreenetRequest>>()
+               val pageToadlet = pageToadletFactory.createPageToadlet(page, "testName")
+               assertThat(pageToadlet.menuName, equalTo("testName"))
+       }
+
+       @Test
+       fun `path prefix is handed down correctly`() {
+               val page = mock<Page<FreenetRequest>>().apply {
+                       whenever(path).thenReturn("path")
+               }
+               val pageToadlet = pageToadletFactory.createPageToadlet(page)
+               assertThat(pageToadlet.path(), equalTo("/some/prefix/path"))
+       }
+
+       @Test
+       fun `menu name is added from annotation when no menu name is given`() {
+               val page = TestPageWithMenuName()
+               val pageToadlet = pageToadletFactory.createPageToadlet(page)
+               assertThat(pageToadlet.menuName, equalTo("testName"))
+       }
+
+       @Test
+       fun `menu name from annotation is ignored when menu name is given`() {
+               val page = TestPageWithMenuName()
+               val pageToadlet = pageToadletFactory.createPageToadlet(page, "foo")
+               assertThat(pageToadlet.menuName, equalTo("foo"))
+       }
+
+       @Test
+       fun `page toadlet factory can be created by guice`() {
+               val injector = Guice.createInjector(
+                               HighLevelSimpleClient::class.isProvidedBy(highLevelSimpleClient),
+                               SessionManager::class.isProvidedBy(sessionManager),
+                               String::class.withNameIsProvidedBy("/Sone/", "toadletPathPrefix")
+               )
+           assertThat(injector.getInstance<PageToadletFactory>(), notNullValue())
+       }
+
+}
+
+@MenuName("testName")
+private class TestPageWithMenuName : Page<FreenetRequest> {
+
+       override fun getPath() = ""
+       override fun isPrefixPage() = false
+       override fun handleRequest(request: FreenetRequest, response: Response) = response
+
+}
diff --git a/src/test/kotlin/net/pterodactylus/sone/web/page/SoneRequestTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/page/SoneRequestTest.kt
new file mode 100644 (file)
index 0000000..50c6ce7
--- /dev/null
@@ -0,0 +1,62 @@
+package net.pterodactylus.sone.web.page
+
+import freenet.clients.http.*
+import freenet.l10n.*
+import freenet.support.api.*
+import net.pterodactylus.sone.core.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.util.web.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.*
+import java.net.*
+
+class SoneRequestTest {
+
+       private val uri = URI(".")
+       private val method = Method.GET
+       private val httpRequest = Mockito.mock(HTTPRequest::class.java)
+       private val toadletContext = Mockito.mock(ToadletContext::class.java)
+       private val l10n = mock<BaseL10n>()
+       private val sessionManager = mock<SessionManager>()
+       private val core = mock<Core>()
+       private val webInterface = mock<WebInterface>()
+       private val soneRequest = SoneRequest(uri, method, httpRequest, toadletContext, l10n, sessionManager, core, webInterface)
+
+       @Test
+       fun `freenet request properties are retained correctly`() {
+               assertThat(soneRequest.uri, equalTo(uri))
+               assertThat(soneRequest.method, equalTo(method))
+               assertThat(soneRequest.httpRequest, equalTo(httpRequest))
+               assertThat(soneRequest.toadletContext, equalTo(toadletContext))
+               assertThat(soneRequest.l10n, equalTo(l10n))
+               assertThat(soneRequest.sessionManager, equalTo(sessionManager))
+       }
+
+       @Test
+       fun `core is retained correctly`() {
+               assertThat(soneRequest.core, sameInstance(core))
+       }
+
+       @Test
+       fun `web interface is retained correctly`() {
+               assertThat(soneRequest.webInterface, sameInstance(webInterface))
+       }
+
+       @Test
+       fun `freenet request is wrapped correctly`() {
+           val freenetRequest = FreenetRequest(uri, method, httpRequest, toadletContext, l10n, sessionManager)
+               val wrappedSoneRequest = freenetRequest.toSoneRequest(core, webInterface)
+               assertThat(wrappedSoneRequest.uri, equalTo(uri))
+               assertThat(wrappedSoneRequest.method, equalTo(method))
+               assertThat(wrappedSoneRequest.httpRequest, equalTo(httpRequest))
+               assertThat(wrappedSoneRequest.toadletContext, equalTo(toadletContext))
+               assertThat(wrappedSoneRequest.l10n, equalTo(l10n))
+               assertThat(wrappedSoneRequest.sessionManager, equalTo(sessionManager))
+               assertThat(wrappedSoneRequest.core, sameInstance(core))
+               assertThat(wrappedSoneRequest.webInterface, sameInstance(webInterface))
+       }
+
+}
diff --git a/src/test/kotlin/net/pterodactylus/sone/web/page/TemplateRendererTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/page/TemplateRendererTest.kt
new file mode 100644 (file)
index 0000000..cb03c61
--- /dev/null
@@ -0,0 +1,60 @@
+package net.pterodactylus.sone.web.page
+
+import com.google.inject.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.util.template.*
+import net.pterodactylus.util.web.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.junit.rules.*
+
+class TemplateRendererTest {
+
+       @Rule
+       @JvmField
+       val expectedException: ExpectedException = ExpectedException.none()
+       private val templateContextFactory = TemplateContextFactory()
+       private val templateRenderer = TemplateRenderer(templateContextFactory)
+
+       @Test
+       fun `renderer can render template`() {
+               val template = "foo".asTemplate()
+               val rendered = templateRenderer.render(template)
+               assertThat(rendered, equalTo("foo"))
+       }
+
+       @Test
+       fun `renderer merges template contexts from template and context factory`() {
+               templateContextFactory.addTemplateObject("a", 1)
+               val template = "<%a><%b>".asTemplate()
+               template.initialContext.set("b", 2)
+               val rendered = templateRenderer.render(template)
+               assertThat(rendered, equalTo("12"))
+       }
+
+       @Test
+       fun `template context can be processed`() {
+               templateContextFactory.addTemplateObject("a", 1)
+               val template = "<%a><%b><%c>".asTemplate()
+               template.initialContext.set("b", 2)
+               val rendered = templateRenderer.render(template) { templateContext -> templateContext.set("c", 3) }
+               assertThat(rendered, equalTo("123"))
+       }
+
+       @Test
+       fun `redirect exceptions are thrown`() {
+               expectedException.expect(RedirectException::class.java)
+               templateRenderer.render(Template()) { _ -> throw RedirectException("foo") }
+       }
+
+       @Test
+       fun `template renderer can be created by guice`() {
+               val injector = Guice.createInjector(
+                               TemplateContextFactory::class.isProvidedByMock()
+               )
+               assertThat(injector.getInstance<TemplateRenderer>(), notNullValue())
+       }
+
+}
index 142bf81..c3f0f76 100644 (file)
@@ -1,16 +1,17 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.main.SonePlugin.PluginHomepage
-import net.pterodactylus.sone.main.SonePlugin.PluginVersion
-import net.pterodactylus.sone.main.SonePlugin.PluginYear
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
 
 /**
  * Unit test for [AboutPage].
  */
-class AboutPageTest: WebPageTest({ template, webInterface -> AboutPage(template, webInterface, PluginVersion(version), PluginYear(year), PluginHomepage(homepage)) }) {
+class AboutPageTest : WebPageTest({ webInterface, loaders, templateRenderer -> AboutPage(webInterface, loaders, templateRenderer, PluginVersion(version), PluginYear(year), PluginHomepage(homepage)) }) {
 
        companion object {
                private const val version = "0.1.2"
@@ -46,4 +47,24 @@ class AboutPageTest: WebPageTest({ template, webInterface -> AboutPage(template,
                assertThat(templateContext["year"], equalTo<Any>(year))
        }
 
+       @Test
+       fun `about page can be created by dependency injection`() {
+               val injector = baseInjector.createChildInjector(
+                               PluginVersion::class.isProvidedByMock(),
+                               PluginYear::class.isProvidedByMock(),
+                               PluginHomepage::class.isProvidedByMock()
+               )
+               assertThat(injector.getInstance<AboutPage>(), notNullValue())
+       }
+
+       @Test
+       fun `page is annotated with correct menuname`() {
+               assertThat(page.menuName, equalTo("About"))
+       }
+
+       @Test
+       fun `page is annotated with correct template path`() {
+               assertThat(page.templatePath, equalTo("/templates/about.html"))
+       }
+
 }
index a805843..292c4a7 100644 (file)
@@ -1,11 +1,12 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
 import org.mockito.ArgumentMatchers.any
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
@@ -13,7 +14,7 @@ import org.mockito.Mockito.verify
 /**
  * Unit test for [BookmarkPage].
  */
-class BookmarkPageTest: WebPageTest(::BookmarkPage) {
+class BookmarkPageTest : WebPageTest(::BookmarkPage) {
 
        @Test
        fun `path is set correctly`() {
@@ -51,4 +52,9 @@ class BookmarkPageTest: WebPageTest(::BookmarkPage) {
                }
        }
 
+       @Test
+       fun `bookmark page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<BookmarkPage>(), notNullValue())
+       }
+
 }
index e853199..3668755 100644 (file)
@@ -1,19 +1,18 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.sone.utils.Pagination
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.contains
-import org.hamcrest.Matchers.equalTo
-import org.junit.Before
-import org.junit.Test
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
 
 /**
  * Unit test for [BookmarksPage].
  */
-class BookmarksPageTest: WebPageTest(::BookmarksPage) {
+class BookmarksPageTest : WebPageTest(::BookmarksPage) {
 
        private val post1 = createLoadedPost(1000)
        private val post2 = createLoadedPost(3000)
@@ -27,7 +26,7 @@ class BookmarksPageTest: WebPageTest(::BookmarksPage) {
        @Before
        fun setupBookmarkedPostsAndPagination() {
                whenever(core.bookmarkedPosts).thenReturn(setOf(post1, post2, post3))
-               core.preferences.postsPerPage = 5
+               core.preferences.newPostsPerPage = 5
        }
 
        @Test
@@ -56,4 +55,19 @@ class BookmarksPageTest: WebPageTest(::BookmarksPage) {
                }
        }
 
+       @Test
+       fun `bookmarks page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<BookmarksPage>(), notNullValue())
+       }
+
+       @Test
+       fun `page is annotated with correct menuname`() {
+               assertThat(page.menuName, equalTo("Bookmarks"))
+       }
+
+       @Test
+       fun `page is annotated with correct template path`() {
+               assertThat(page.templatePath, equalTo("/templates/bookmarks.html"))
+       }
+
 }
index 5cc463a..224791f 100644 (file)
@@ -1,22 +1,21 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Album
-import net.pterodactylus.sone.data.Album.Modifier
-import net.pterodactylus.sone.data.Album.Modifier.AlbumTitleMustNotBeEmpty
-import net.pterodactylus.sone.test.deepMock
-import net.pterodactylus.sone.test.selfMock
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.data.Album.*
+import net.pterodactylus.sone.data.Album.Modifier.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [CreateAlbumPage].
  */
-class CreateAlbumPageTest: WebPageTest(::CreateAlbumPage) {
+class CreateAlbumPageTest : WebPageTest(::CreateAlbumPage) {
 
        private val parentAlbum = createAlbum("parent-id")
        private val newAlbum = createAlbum("album-id")
@@ -96,4 +95,14 @@ class CreateAlbumPageTest: WebPageTest(::CreateAlbumPage) {
                }
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<CreateAlbumPage>(), notNullValue())
+       }
+
+       @Test
+       fun `page is annotated with correct template path`() {
+               assertThat(page.templatePath, equalTo("/templates/createAlbum.html"))
+       }
+
 }
index 3b1cc8c..2bf0c05 100644 (file)
@@ -1,19 +1,21 @@
 package net.pterodactylus.sone.web.pages
 
-import com.google.common.base.Optional.absent
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.utils.asOptional
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
-import org.mockito.Mockito.verify
+import com.google.common.base.Optional.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [CreatePostPage].
  */
-class CreatePostPageTest: WebPageTest(::CreatePostPage) {
+class CreatePostPageTest : WebPageTest(::CreatePostPage) {
 
        @Test
        fun `page returns correct path`() {
@@ -88,4 +90,14 @@ class CreatePostPageTest: WebPageTest(::CreatePostPage) {
                }
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<CreatePostPage>(), notNullValue())
+       }
+
+       @Test
+       fun `page is annotated with correct template path`() {
+               assertThat(page.templatePath, equalTo("/templates/createPost.html"))
+       }
+
 }
index 71e83bc..062a010 100644 (file)
@@ -1,18 +1,19 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [CreateReplyPage].
  */
-class CreateReplyPageTest: WebPageTest(::CreateReplyPage) {
+class CreateReplyPageTest : WebPageTest(::CreateReplyPage) {
 
        @Test
        fun `page returns correct path`() {
@@ -85,4 +86,14 @@ class CreateReplyPageTest: WebPageTest(::CreateReplyPage) {
                verifyRedirect("noPermission.html")
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<CreateReplyPage>(), notNullValue())
+       }
+
+       @Test
+       fun `page is annotated with correct template path`() {
+               assertThat(page.templatePath, equalTo("/templates/createReply.html"))
+       }
+
 }
index 9cbf2c3..6a4a2b0 100644 (file)
@@ -1,22 +1,21 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Profile
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.freenet.wot.OwnIdentity
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.contains
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.freenet.wot.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mockito.verify
 
 /**
  * Unit test for [CreateSonePage].
  */
-class CreateSonePageTest: WebPageTest(::CreateSonePage) {
+class CreateSonePageTest : WebPageTest(::CreateSonePage) {
 
        private val localSones_ = listOf(
                        createSone("local-sone1"),
@@ -111,7 +110,7 @@ class CreateSonePageTest: WebPageTest(::CreateSonePage) {
 
        @Test
        fun `create sone is not shown in menu if full access is required but client doesn’t have full access`() {
-               core.preferences.isRequireFullAccess = true
+               core.preferences.newRequireFullAccess = true
                assertThat(page.isEnabled(toadletContext), equalTo(false))
        }
 
@@ -136,10 +135,25 @@ class CreateSonePageTest: WebPageTest(::CreateSonePage) {
 
        @Test
        fun `create sone is shown in menu if no sone is logged in and client has full access`() {
-               core.preferences.isRequireFullAccess = true
+               core.preferences.newRequireFullAccess = true
                whenever(toadletContext.isAllowedFullAccess).thenReturn(true)
                unsetCurrentSone()
                assertThat(page.isEnabled(toadletContext), equalTo(true))
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<CreateSonePage>(), notNullValue())
+       }
+
+       @Test
+       fun `page is annotated with the correct menuname`() {
+               assertThat(page.menuName, equalTo("CreateSone"))
+       }
+
+       @Test
+       fun `page is annotated with the correct template path`() {
+               assertThat(page.templatePath, equalTo("/templates/createSone.html"))
+       }
+
 }
index 969a133..55b3d70 100644 (file)
@@ -1,21 +1,20 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Album
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Before
-import org.junit.Test
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mockito.verify
 
 /**
  * Unit test for [DeleteAlbumPage].
  */
-class DeleteAlbumPageTest: WebPageTest(::DeleteAlbumPage) {
+class DeleteAlbumPageTest : WebPageTest(::DeleteAlbumPage) {
 
        private val sone = mock<Sone>()
        private val album = mock<Album>()
@@ -96,7 +95,7 @@ class DeleteAlbumPageTest: WebPageTest(::DeleteAlbumPage) {
        fun `album is deleted and page redirects to album if parent album is not root album`() {
                setMethod(POST)
                whenever(parentAlbum.isRoot).thenReturn(false)
-               whenever(sone.rootAlbum).thenReturn(mock<Album>())
+               whenever(sone.rootAlbum).thenReturn(mock())
                addAlbum("album-id", album)
                addHttpRequestPart("album", "album-id")
                verifyRedirect("imageBrowser.html?album=parent-id") {
@@ -104,4 +103,14 @@ class DeleteAlbumPageTest: WebPageTest(::DeleteAlbumPage) {
                }
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<DeleteAlbumPage>(), notNullValue())
+       }
+
+       @Test
+       fun `page is annotated with correct template path`() {
+               assertThat(page.templatePath, equalTo("/templates/deleteAlbum.html"))
+       }
+
 }
index 1b90c4a..cecde50 100644 (file)
@@ -1,21 +1,19 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Album
-import net.pterodactylus.sone.data.Image
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [DeleteImagePage].
  */
-class DeleteImagePageTest: WebPageTest(::DeleteImagePage) {
+class DeleteImagePageTest : WebPageTest(::DeleteImagePage) {
 
        private val image = mock<Image>()
        private val sone = mock<Sone>()
@@ -80,4 +78,14 @@ class DeleteImagePageTest: WebPageTest(::DeleteImagePage) {
                }
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<DeleteImagePage>(), notNullValue())
+       }
+
+       @Test
+       fun `page is annotated with correct template path`() {
+               assertThat(page.templatePath, equalTo("/templates/deleteImage.html"))
+       }
+
 }
index 5ccd5c2..9464920 100644 (file)
@@ -1,21 +1,19 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [DeletePostPage].
  */
-class DeletePostPageTest: WebPageTest(::DeletePostPage) {
+class DeletePostPageTest : WebPageTest(::DeletePostPage) {
 
        private val post = mock<Post>()
        private val sone = mock<Sone>()
@@ -28,12 +26,12 @@ class DeletePostPageTest: WebPageTest(::DeletePostPage) {
 
        @Test
        fun `page returns correct path`() {
-           assertThat(page.path, equalTo("deletePost.html"))
+               assertThat(page.path, equalTo("deletePost.html"))
        }
 
        @Test
        fun `page requires login`() {
-           assertThat(page.requiresLogin(), equalTo(true))
+               assertThat(page.requiresLogin(), equalTo(true))
        }
 
        @Test
@@ -102,4 +100,14 @@ class DeletePostPageTest: WebPageTest(::DeletePostPage) {
                assertThat(templateContext["returnPage"], equalTo<Any>("return.html"))
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<DeletePostPage>(), notNullValue())
+       }
+
+       @Test
+       fun `page is annotated with correct template path`() {
+               assertThat(page.templatePath, equalTo("/templates/deletePost.html"))
+       }
+
 }
index 8f344eb..728966c 100644 (file)
@@ -1,21 +1,19 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Profile
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.hamcrest.Matchers.nullValue
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mockito.any
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [DeleteProfileFieldPage].
  */
-class DeleteProfileFieldPageTest: WebPageTest(::DeleteProfileFieldPage) {
+class DeleteProfileFieldPageTest : WebPageTest(::DeleteProfileFieldPage) {
 
        private val profile = Profile(currentSone)
        private val field = profile.addField("name")
@@ -75,4 +73,14 @@ class DeleteProfileFieldPageTest: WebPageTest(::DeleteProfileFieldPage) {
                }
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<DeleteProfileFieldPage>(), notNullValue())
+       }
+
+       @Test
+       fun `page is annotated with correct template path`() {
+               assertThat(page.templatePath, equalTo("/templates/deleteProfileField.html"))
+       }
+
 }
index c332557..f9a07c2 100644 (file)
@@ -1,21 +1,19 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.PostReply
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [DeleteReplyPage].
  */
-class DeleteReplyPageTest: WebPageTest(::DeleteReplyPage) {
+class DeleteReplyPageTest : WebPageTest(::DeleteReplyPage) {
 
        private val sone = mock<Sone>()
        private val reply = mock<PostReply>()
@@ -28,12 +26,12 @@ class DeleteReplyPageTest: WebPageTest(::DeleteReplyPage) {
 
        @Test
        fun `page returns correct path`() {
-           assertThat(page.path, equalTo("deleteReply.html"))
+               assertThat(page.path, equalTo("deleteReply.html"))
        }
 
        @Test
        fun `page requires login`() {
-           assertThat(page.requiresLogin(), equalTo(true))
+               assertThat(page.requiresLogin(), equalTo(true))
        }
 
        @Test
@@ -95,4 +93,14 @@ class DeleteReplyPageTest: WebPageTest(::DeleteReplyPage) {
                }
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<DeleteReplyPage>(), notNullValue())
+       }
+
+       @Test
+       fun `page is annotated with correct template path`() {
+               assertThat(page.templatePath, equalTo("/templates/deleteReply.html"))
+       }
+
 }
index 415adf6..21cfdfc 100644 (file)
@@ -1,33 +1,33 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
-import org.mockito.Mockito.any
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [DeleteSonePage].
  */
-class DeleteSonePageTest: WebPageTest(::DeleteSonePage) {
+class DeleteSonePageTest : WebPageTest(::DeleteSonePage) {
 
        @Test
        fun `page returns correct path`() {
-           assertThat(page.path, equalTo("deleteSone.html"))
+               assertThat(page.path, equalTo("deleteSone.html"))
        }
 
        @Test
        fun `page requires login`() {
-           assertThat(page.requiresLogin(), equalTo(true))
+               assertThat(page.requiresLogin(), equalTo(true))
        }
 
        @Test
        fun `page returns correct title`() {
-           whenever(l10n.getString("Page.DeleteSone.Title")).thenReturn("delete sone page")
-               assertThat(page.getPageTitle(freenetRequest), equalTo("delete sone page"))
+               whenever(l10n.getString("Page.DeleteSone.Title")).thenReturn("delete sone page")
+               assertThat(page.getPageTitle(soneRequest), equalTo("delete sone page"))
        }
 
        @Test
@@ -52,4 +52,19 @@ class DeleteSonePageTest: WebPageTest(::DeleteSonePage) {
                }
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<DeleteSonePage>(), notNullValue())
+       }
+
+       @Test
+       fun `page is annotated with correct menuname`() {
+               assertThat(page.menuName, equalTo("DeleteSone"))
+       }
+
+       @Test
+       fun `page is annotated with correct template path`() {
+               assertThat(page.templatePath, equalTo("/templates/deleteSone.html"))
+       }
+
 }
index 41f47aa..e8583df 100644 (file)
@@ -1,19 +1,18 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.util.notify.Notification
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.util.notify.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [DismissNotificationPage].
  */
-class DismissNotificationPageTest: WebPageTest(::DismissNotificationPage) {
+class DismissNotificationPageTest : WebPageTest(::DismissNotificationPage) {
 
        private val notification = mock<Notification>()
 
@@ -30,7 +29,7 @@ class DismissNotificationPageTest: WebPageTest(::DismissNotificationPage) {
        @Test
        fun `page returns correct title`() {
                whenever(l10n.getString("Page.DismissNotification.Title")).thenReturn("dismiss notification page")
-               assertThat(page.getPageTitle(freenetRequest), equalTo("dismiss notification page"))
+               assertThat(page.getPageTitle(soneRequest), equalTo("dismiss notification page"))
        }
 
        @Test
@@ -63,4 +62,9 @@ class DismissNotificationPageTest: WebPageTest(::DismissNotificationPage) {
                }
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<DismissNotificationPage>(), notNullValue())
+       }
+
 }
index 3261df6..d706e05 100644 (file)
@@ -1,18 +1,18 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [DistrustPage].
  */
-class DistrustPageTest: WebPageTest(::DistrustPage) {
+class DistrustPageTest : WebPageTest(::DistrustPage) {
 
        @Test
        fun `page returns correct path`() {
@@ -27,7 +27,7 @@ class DistrustPageTest: WebPageTest(::DistrustPage) {
        @Test
        fun `page returns correct title`() {
                whenever(l10n.getString("Page.Distrust.Title")).thenReturn("distrust page title")
-               assertThat(page.getPageTitle(freenetRequest), equalTo("distrust page title"))
+               assertThat(page.getPageTitle(soneRequest), equalTo("distrust page title"))
        }
 
        @Test
@@ -54,4 +54,9 @@ class DistrustPageTest: WebPageTest(::DistrustPage) {
                }
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<DistrustPage>(), notNullValue())
+       }
+
 }
index b4328c6..4d435b4 100644 (file)
@@ -1,23 +1,20 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Album
-import net.pterodactylus.sone.data.Album.Modifier
-import net.pterodactylus.sone.data.Album.Modifier.AlbumTitleMustNotBeEmpty
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.mockBuilder
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.data.Album.*
+import net.pterodactylus.sone.data.Album.Modifier.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [EditAlbumPage].
  */
-class EditAlbumPageTest: WebPageTest(::EditAlbumPage) {
+class EditAlbumPageTest : WebPageTest(::EditAlbumPage) {
 
        private val album = mock<Album>()
        private val parentAlbum = mock<Album>()
@@ -49,7 +46,7 @@ class EditAlbumPageTest: WebPageTest(::EditAlbumPage) {
        @Test
        fun `page returns correct title`() {
                whenever(l10n.getString("Page.EditAlbum.Title")).thenReturn("edit album page")
-               assertThat(page.getPageTitle(freenetRequest), equalTo("edit album page"))
+               assertThat(page.getPageTitle(soneRequest), equalTo("edit album page"))
        }
 
        @Test
@@ -120,4 +117,9 @@ class EditAlbumPageTest: WebPageTest(::EditAlbumPage) {
                }
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<EditAlbumPage>(), notNullValue())
+       }
+
 }
index f6eb02e..0961622 100644 (file)
@@ -1,26 +1,20 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Album
-import net.pterodactylus.sone.data.Image
-import net.pterodactylus.sone.data.Image.Modifier
-import net.pterodactylus.sone.data.Image.Modifier.ImageTitleMustNotBeEmpty
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.test.doThrow
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.mockBuilder
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.data.Image.*
+import net.pterodactylus.sone.data.Image.Modifier.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [EditImagePage].
  */
-class EditImagePageTest: WebPageTest(::EditImagePage) {
+class EditImagePageTest : WebPageTest(::EditImagePage) {
 
        private val image = mock<Image>()
        private val modifier = mockBuilder<Modifier>()
@@ -44,13 +38,13 @@ class EditImagePageTest: WebPageTest(::EditImagePage) {
 
        @Test
        fun `page requires login`() {
-           assertThat(page.requiresLogin(), equalTo(true))
+               assertThat(page.requiresLogin(), equalTo(true))
        }
 
        @Test
        fun `page returns correct title`() {
                whenever(l10n.getString("Page.EditImage.Title")).thenReturn("edit image page title")
-           assertThat(page.getPageTitle(freenetRequest), equalTo("edit image page title"))
+               assertThat(page.getPageTitle(soneRequest), equalTo("edit image page title"))
        }
 
        @Test
@@ -145,4 +139,9 @@ class EditImagePageTest: WebPageTest(::EditImagePage) {
                }
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<EditImagePage>(), notNullValue())
+       }
+
 }
index b4acc7e..7731fa2 100644 (file)
@@ -1,19 +1,19 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Profile
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [EditProfileFieldPage].
  */
-class EditProfileFieldPageTest: WebPageTest(::EditProfileFieldPage) {
+class EditProfileFieldPageTest : WebPageTest(::EditProfileFieldPage) {
 
        private val profile = Profile(currentSone)
        private val field = profile.addField("Name")
@@ -25,18 +25,18 @@ class EditProfileFieldPageTest: WebPageTest(::EditProfileFieldPage) {
 
        @Test
        fun `page returns correct path`() {
-           assertThat(page.path, equalTo("editProfileField.html"))
+               assertThat(page.path, equalTo("editProfileField.html"))
        }
 
        @Test
        fun `page requires login`() {
-           assertThat(page.requiresLogin(), equalTo(true))
+               assertThat(page.requiresLogin(), equalTo(true))
        }
 
        @Test
        fun `page returns correct title`() {
                whenever(l10n.getString("Page.EditProfileField.Title")).thenReturn("edit profile field title")
-           assertThat(page.getPageTitle(freenetRequest), equalTo("edit profile field title"))
+               assertThat(page.getPageTitle(soneRequest), equalTo("edit profile field title"))
        }
 
        @Test
@@ -93,4 +93,14 @@ class EditProfileFieldPageTest: WebPageTest(::EditProfileFieldPage) {
                assertThat(templateContext["duplicateFieldName"], equalTo<Any>(true))
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<EditProfileFieldPage>(), notNullValue())
+       }
+
+       @Test
+       fun `page is annotated with correct template path`() {
+               assertThat(page.templatePath, equalTo("/templates/editProfileField.html"))
+       }
+
 }
index b4e6570..a31f04c 100644 (file)
@@ -1,23 +1,19 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Image
-import net.pterodactylus.sone.data.Profile
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.contains
-import org.hamcrest.Matchers.equalTo
-import org.hamcrest.Matchers.notNullValue
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [EditProfilePage].
  */
-class EditProfilePageTest: WebPageTest(::EditProfilePage) {
+class EditProfilePageTest : WebPageTest(::EditProfilePage) {
 
        private val profile = Profile(currentSone)
        private val firstField = profile.addField("First Field")
@@ -40,18 +36,18 @@ class EditProfilePageTest: WebPageTest(::EditProfilePage) {
 
        @Test
        fun `page returns correct path`() {
-           assertThat(page.path, equalTo("editProfile.html"))
+               assertThat(page.path, equalTo("editProfile.html"))
        }
 
        @Test
        fun `page requires login`() {
-           assertThat(page.requiresLogin(), equalTo(true))
+               assertThat(page.requiresLogin(), equalTo(true))
        }
 
        @Test
        fun `page returns correct title`() {
-           whenever(l10n.getString("Page.EditProfile.Title")).thenReturn("edit profile page title")
-               assertThat(page.getPageTitle(freenetRequest), equalTo("edit profile page title"))
+               whenever(l10n.getString("Page.EditProfile.Title")).thenReturn("edit profile page title")
+               assertThat(page.getPageTitle(soneRequest), equalTo("edit profile page title"))
        }
 
        @Test
@@ -218,4 +214,19 @@ class EditProfilePageTest: WebPageTest(::EditProfilePage) {
                verifyRedirect("editProfileField.html?field=${firstField.id}")
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<EditProfilePage>(), notNullValue())
+       }
+
+       @Test
+       fun `page is annotated with correct menuname`() {
+               assertThat(page.menuName, equalTo("EditProfile"))
+       }
+
+       @Test
+       fun `page is annotated with correct template path`() {
+               assertThat(page.templatePath, equalTo("/templates/editProfile.html"))
+       }
+
 }
diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/ErrorPagesTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/ErrorPagesTest.kt
new file mode 100644 (file)
index 0000000..6f8bfa1
--- /dev/null
@@ -0,0 +1,103 @@
+package net.pterodactylus.sone.web.pages
+
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+
+class ErrorPagesTest {
+
+       private val webPageTest = WebPageTest()
+
+       private fun testPage(page: (WebInterface, Loaders, TemplateRenderer) -> FreenetTemplatePage, test: (FreenetTemplatePage) -> Unit) =
+                       with(webPageTest) {
+                               test(page(webInterface, loaders, templateRenderer))
+                       }
+
+       @Test
+       fun `invalid page returns correct path`() {
+               testPage(::InvalidPage) { page ->
+                       assertThat(page.path, equalTo("invalid.html"))
+               }
+       }
+
+       @Test
+       fun `invalid page returns correct title`() {
+               testPage(::InvalidPage) { page ->
+                       assertThat(page.getPageTitle(webPageTest.freenetRequest), equalTo("Page.Invalid.Title"))
+               }
+       }
+
+       @Test
+       fun `invalid page is annotated with correct template path`() {
+               testPage(::InvalidPage) { page ->
+                       assertThat(page.templatePath, equalTo("/templates/invalid.html"))
+               }
+       }
+
+       @Test
+       fun `no permission page returns correct path`() {
+               testPage(::NoPermissionPage) { page ->
+                       assertThat(page.path, equalTo("noPermission.html"))
+               }
+       }
+
+       @Test
+       fun `no permission page returns correct title`() {
+               testPage(::NoPermissionPage) { page ->
+                       assertThat(page.getPageTitle(webPageTest.freenetRequest), equalTo("Page.NoPermission.Title"))
+               }
+       }
+
+       @Test
+       fun `no permission page is annotated with correct template path`() {
+               testPage(::NoPermissionPage) { page ->
+                       assertThat(page.templatePath, equalTo("/templates/noPermission.html"))
+               }
+       }
+
+       @Test
+       fun `empty image title page returns correct path`() {
+               testPage(::EmptyImageTitlePage) { page ->
+                       assertThat(page.path, equalTo("emptyImageTitle.html"))
+               }
+       }
+
+       @Test
+       fun `empty image title page returns correct page title`() {
+               testPage(::EmptyImageTitlePage) { page ->
+                       assertThat(page.getPageTitle(webPageTest.freenetRequest), equalTo("Page.EmptyImageTitle.Title"))
+               }
+       }
+
+       @Test
+       fun `empty image title page is annotated with correct template path`() {
+               testPage(::EmptyImageTitlePage) { page ->
+                       assertThat(page.templatePath, equalTo("/templates/emptyImageTitle.html"))
+               }
+       }
+
+       @Test
+       fun `empty album title page returns correct path`() {
+               testPage(::EmptyAlbumTitlePage) { page ->
+                       assertThat(page.path, equalTo("emptyAlbumTitle.html"))
+               }
+       }
+
+       @Test
+       fun `empty album title page returns correct page title`() {
+               testPage(::EmptyAlbumTitlePage) { page ->
+                       assertThat(page.getPageTitle(webPageTest.freenetRequest), equalTo("Page.EmptyAlbumTitle.Title"))
+               }
+       }
+
+       @Test
+       fun `empty album title page is annotated with correct template path`() {
+               testPage(::EmptyAlbumTitlePage) { page ->
+                       assertThat(page.templatePath, equalTo("/templates/emptyAlbumTitle.html"))
+               }
+       }
+
+}
index 21964aa..ba1dc42 100644 (file)
@@ -1,13 +1,13 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
-import org.mockito.ArgumentMatchers
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.*
 import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mockito.never
@@ -16,22 +16,22 @@ import org.mockito.Mockito.verify
 /**
  * Unit test for [FollowSonePage].
  */
-class FollowSonePageTest: WebPageTest(::FollowSonePage) {
+class FollowSonePageTest : WebPageTest(::FollowSonePage) {
 
        @Test
        fun `page returns correct path`() {
-           assertThat(page.path, equalTo("followSone.html"))
+               assertThat(page.path, equalTo("followSone.html"))
        }
 
        @Test
        fun `page requires login`() {
-           assertThat(page.requiresLogin(), equalTo(true))
+               assertThat(page.requiresLogin(), equalTo(true))
        }
 
        @Test
        fun `page returns correct title`() {
-           whenever(l10n.getString("Page.FollowSone.Title")).thenReturn("follow sone page title")
-               assertThat(page.getPageTitle(freenetRequest), equalTo("follow sone page title"))
+               whenever(l10n.getString("Page.FollowSone.Title")).thenReturn("follow sone page title")
+               assertThat(page.getPageTitle(soneRequest), equalTo("follow sone page title"))
        }
 
        @Test
@@ -80,4 +80,9 @@ class FollowSonePageTest: WebPageTest(::FollowSonePage) {
                }
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<FollowSonePage>(), notNullValue())
+       }
+
 }
index b5efeac..f4428bd 100644 (file)
@@ -1,10 +1,12 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.TemporaryImage
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.contains
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import java.net.*
 
 /**
  * Unit test for [GetImagePage].
@@ -29,7 +31,7 @@ class GetImagePageTest {
 
        @Test
        fun `page is not link-excepted`() {
-               assertThat(page.isLinkExcepted(null), equalTo(false))
+               assertThat(page.isLinkExcepted(URI("")), equalTo(false))
        }
 
        @Test
@@ -59,4 +61,9 @@ class GetImagePageTest {
                ))
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<GetImagePage>(), notNullValue())
+       }
+
 }
index 4d0b2c7..a0e9372 100644 (file)
@@ -1,34 +1,33 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Album
-import net.pterodactylus.sone.data.Image
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.contains
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import java.net.*
 
 /**
  * Unit test for [ImageBrowserPage].
  */
-class ImageBrowserPageTest: WebPageTest(::ImageBrowserPage) {
+class ImageBrowserPageTest : WebPageTest(::ImageBrowserPage) {
 
        @Test
        fun `page returns correct path`() {
-           assertThat(page.path, equalTo("imageBrowser.html"))
+               assertThat(page.path, equalTo("imageBrowser.html"))
        }
 
        @Test
        fun `page requires login`() {
-           assertThat(page.requiresLogin(), equalTo(true))
+               assertThat(page.requiresLogin(), equalTo(true))
        }
 
        @Test
        fun `page returns correct title`() {
                whenever(l10n.getString("Page.ImageBrowser.Title")).thenReturn("image browser page title")
-           assertThat(page.getPageTitle(freenetRequest), equalTo("image browser page title"))
+               assertThat(page.getPageTitle(soneRequest), equalTo("image browser page title"))
        }
 
        @Test
@@ -87,7 +86,7 @@ class ImageBrowserPageTest: WebPageTest(::ImageBrowserPage) {
 
        @Test
        fun `get request for gallery can show second page`() {
-               core.preferences.imagesPerPage = 2
+               core.preferences.newImagesPerPage = 2
                val firstSone = createSone("first album", "second album")
                addSone("sone1", firstSone)
                val secondSone = createSone("third album", "fourth album")
@@ -130,7 +129,22 @@ class ImageBrowserPageTest: WebPageTest(::ImageBrowserPage) {
 
        @Test
        fun `page is link-excepted`() {
-           assertThat(page.isLinkExcepted(null), equalTo(true))
+               assertThat(page.isLinkExcepted(URI("")), equalTo(true))
+       }
+
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<ImageBrowserPage>(), notNullValue())
+       }
+
+       @Test
+       fun `page is annotated with correct menuname`() {
+               assertThat(page.menuName, equalTo("ImageBrowser"))
+       }
+
+       @Test
+       fun `page is annotated with correct template path`() {
+               assertThat(page.templatePath, equalTo("/templates/imageBrowser.html"))
        }
 
 }
index b51627d..9104127 100644 (file)
@@ -1,25 +1,22 @@
 package net.pterodactylus.sone.web.pages
 
-import com.google.common.base.Optional.fromNullable
-import com.google.common.base.Predicate
-import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.notify.PostVisibilityFilter
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.sone.utils.Pagination
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.contains
-import org.hamcrest.Matchers.emptyIterable
-import org.hamcrest.Matchers.equalTo
-import org.junit.Before
-import org.junit.Test
-import org.mockito.ArgumentMatchers
+import com.google.common.base.*
+import com.google.common.base.Optional.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.notify.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.*
 
 /**
  * Unit test for [IndexPage].
  */
-class IndexPageTest: WebPageTest({ template, webInterface -> IndexPage(template, webInterface, postVisibilityFilter) }) {
+class IndexPageTest : WebPageTest({ webInterface, loaders, templateRenderer -> IndexPage(webInterface, loaders, templateRenderer, postVisibilityFilter) }) {
 
        companion object {
                private val postVisibilityFilter = mock<PostVisibilityFilter>()
@@ -27,18 +24,18 @@ class IndexPageTest: WebPageTest({ template, webInterface -> IndexPage(template,
 
        @Test
        fun `page returns correct path`() {
-           assertThat(page.path, equalTo("index.html"))
+               assertThat(page.path, equalTo("index.html"))
        }
 
        @Test
        fun `page requires login`() {
-           assertThat(page.requiresLogin(), equalTo(true))
+               assertThat(page.requiresLogin(), equalTo(true))
        }
 
        @Test
        fun `page returns correct title`() {
                whenever(l10n.getString("Page.Index.Title")).thenReturn("index page title")
-           assertThat(page.getPageTitle(freenetRequest), equalTo("index page title"))
+               assertThat(page.getPageTitle(soneRequest), equalTo("index page title"))
        }
 
        @Before
@@ -134,7 +131,7 @@ class IndexPageTest: WebPageTest({ template, webInterface -> IndexPage(template,
        fun `index page sets page correctly`() {
                val posts = listOf(createPost(3000), createPost(2000), createPost(1000))
                whenever(currentSone.posts).thenReturn(posts)
-               core.preferences.postsPerPage = 1
+               core.preferences.newPostsPerPage = 1
                addHttpRequestParameter("page", "2")
                page.processTemplate(freenetRequest, templateContext)
                @Suppress("UNCHECKED_CAST")
@@ -143,7 +140,7 @@ class IndexPageTest: WebPageTest({ template, webInterface -> IndexPage(template,
 
        @Test
        fun `index page without posts sets correct pagination`() {
-               core.preferences.postsPerPage = 1
+               core.preferences.newPostsPerPage = 1
                page.processTemplate(freenetRequest, templateContext)
                @Suppress("UNCHECKED_CAST")
                (templateContext["pagination"] as Pagination<Post>).let { pagination ->
@@ -151,4 +148,19 @@ class IndexPageTest: WebPageTest({ template, webInterface -> IndexPage(template,
                }
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<IndexPage>(), notNullValue())
+       }
+
+       @Test
+       fun `page is annotated with correct menuname`() {
+               assertThat(page.menuName, equalTo("Index"))
+       }
+
+       @Test
+       fun `page is annotated with correct template path`() {
+               assertThat(page.templatePath, equalTo("/templates/index.html"))
+       }
+
 }
index 70a5eea..37e0e95 100644 (file)
@@ -1,26 +1,19 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Album
-import net.pterodactylus.sone.data.Image
-import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.data.PostReply
-import net.pterodactylus.sone.data.Profile
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.freenet.wot.Identity
-import net.pterodactylus.sone.freenet.wot.OwnIdentity
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.sone.utils.Pagination
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.contains
-import org.hamcrest.Matchers.equalTo
-import org.junit.Before
-import org.junit.Test
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.freenet.wot.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
 
 /**
  * Unit test for [KnownSonesPage].
  */
-class KnownSonesPageTest: WebPageTest(::KnownSonesPage) {
+class KnownSonesPageTest : WebPageTest(::KnownSonesPage) {
 
        private val sones = listOf(
                        createSone(1000, 4, 7, 2, "sone2", true, true),
@@ -50,7 +43,7 @@ class KnownSonesPageTest: WebPageTest(::KnownSonesPage) {
                        whenever(albums).thenReturn(listOf(album))
                }
                whenever(this.rootAlbum).thenReturn(rootAlbum)
-               whenever(this.profile).thenReturn(mock<Profile>())
+               whenever(this.profile).thenReturn(mock())
                whenever(id).thenReturn(name.toLowerCase())
                whenever(this.name).thenReturn(name)
        }
@@ -81,7 +74,7 @@ class KnownSonesPageTest: WebPageTest(::KnownSonesPage) {
        @Test
        fun `page returns correct title`() {
                whenever(l10n.getString("Page.KnownSones.Title")).thenReturn("known sones page title")
-               assertThat(page.getPageTitle(freenetRequest), equalTo("known sones page title"))
+               assertThat(page.getPageTitle(soneRequest), equalTo("known sones page title"))
        }
 
        @Test
@@ -242,4 +235,19 @@ class KnownSonesPageTest: WebPageTest(::KnownSonesPage) {
                }
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<KnownSonesPage>(), notNullValue())
+       }
+
+       @Test
+       fun `page is annotated with the correct menuname`() {
+               assertThat(page.menuName, equalTo("KnownSones"))
+       }
+
+       @Test
+       fun `page is annotated with corrrect template path`() {
+               assertThat(page.templatePath, equalTo("/templates/knownSones.html"))
+       }
+
 }
index 9a08737..4140644 100644 (file)
@@ -1,31 +1,32 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [LikePage].
  */
-class LikePageTest: WebPageTest(::LikePage) {
+class LikePageTest : WebPageTest(::LikePage) {
 
        @Test
        fun `page returns correct path`() {
-           assertThat(page.path, equalTo("like.html"))
+               assertThat(page.path, equalTo("like.html"))
        }
 
        @Test
        fun `page requires login`() {
-           assertThat(page.requiresLogin(), equalTo(true))
+               assertThat(page.requiresLogin(), equalTo(true))
        }
 
        @Test
        fun `page returns correct title`() {
-           addTranslation("Page.Like.Title", "like page title")
-               assertThat(page.getPageTitle(freenetRequest), equalTo("like page title"))
+               addTranslation("Page.Like.Title", "like page title")
+               assertThat(page.getPageTitle(soneRequest), equalTo("like page title"))
        }
 
        @Test
@@ -65,4 +66,9 @@ class LikePageTest: WebPageTest(::LikePage) {
                }
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<LikePage>(), notNullValue())
+       }
+
 }
index 2a9b4ed..de42141 100644 (file)
@@ -1,11 +1,12 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
 import org.mockito.ArgumentMatchers.any
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
@@ -13,7 +14,7 @@ import org.mockito.Mockito.verify
 /**
  * Unit test for [LockSonePage].
  */
-class LockSonePageTest: WebPageTest(::LockSonePage) {
+class LockSonePageTest : WebPageTest(::LockSonePage) {
 
        @Test
        fun `page returns correct path`() {
@@ -22,13 +23,13 @@ class LockSonePageTest: WebPageTest(::LockSonePage) {
 
        @Test
        fun `page does not require login`() {
-           assertThat(page.requiresLogin(), equalTo(false))
+               assertThat(page.requiresLogin(), equalTo(false))
        }
 
        @Test
        fun `page returns correct title`() {
-           addTranslation("Page.LockSone.Title", "lock Sone page title")
-               assertThat(page.getPageTitle(freenetRequest), equalTo("lock Sone page title"))
+               addTranslation("Page.LockSone.Title", "lock Sone page title")
+               assertThat(page.getPageTitle(soneRequest), equalTo("lock Sone page title"))
        }
 
        @Test
@@ -52,4 +53,9 @@ class LockSonePageTest: WebPageTest(::LockSonePage) {
                }
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<LockSonePage>(), notNullValue())
+       }
+
 }
index 1b409b7..067857e 100644 (file)
@@ -1,25 +1,20 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.freenet.wot.Identity
-import net.pterodactylus.sone.freenet.wot.OwnIdentity
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.thenReturnMock
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.contains
-import org.hamcrest.Matchers.containsInAnyOrder
-import org.hamcrest.Matchers.equalTo
-import org.hamcrest.Matchers.nullValue
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.freenet.wot.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [LoginPage].
  */
-class LoginPageTest: WebPageTest(::LoginPage) {
+class LoginPageTest : WebPageTest(::LoginPage) {
 
        private val sones = listOf(createSone("Sone", "Test"), createSone("Test"), createSone("Sone"))
 
@@ -45,12 +40,12 @@ class LoginPageTest: WebPageTest(::LoginPage) {
 
        @Test
        fun `page returns correct path`() {
-           assertThat(page.path, equalTo("login.html"))
+               assertThat(page.path, equalTo("login.html"))
        }
 
        @Test
        fun `page does not require login`() {
-           assertThat(page.requiresLogin(), equalTo(false))
+               assertThat(page.requiresLogin(), equalTo(false))
        }
 
        @Test
@@ -108,7 +103,7 @@ class LoginPageTest: WebPageTest(::LoginPage) {
 
        @Test
        fun `page is not enabled if full access required and request is not full access`() {
-               core.preferences.isRequireFullAccess = true
+               core.preferences.newRequireFullAccess = true
                assertThat(page.isEnabled(toadletContext), equalTo(false))
        }
 
@@ -125,7 +120,7 @@ class LoginPageTest: WebPageTest(::LoginPage) {
 
        @Test
        fun `page is enabled if full access required and request is full access and there is no current sone`() {
-               core.preferences.isRequireFullAccess = true
+               core.preferences.newRequireFullAccess = true
                unsetCurrentSone()
                whenever(toadletContext.isAllowedFullAccess).thenReturn(true)
                assertThat(page.isEnabled(toadletContext), equalTo(true))
@@ -133,9 +128,24 @@ class LoginPageTest: WebPageTest(::LoginPage) {
 
        @Test
        fun `page is not enabled if full access required and request is full access but there is a current sone`() {
-               core.preferences.isRequireFullAccess = true
+               core.preferences.newRequireFullAccess = true
                whenever(toadletContext.isAllowedFullAccess).thenReturn(true)
                assertThat(page.isEnabled(toadletContext), equalTo(false))
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<LoginPage>(), notNullValue())
+       }
+
+       @Test
+       fun `page is annotated with correct menuname`() {
+               assertThat(page.menuName, equalTo("Login"))
+       }
+
+       @Test
+       fun `page is annotated with correct template path`() {
+               assertThat(page.templatePath, equalTo("/templates/login.html"))
+       }
+
 }
index 9dbc82a..fae95d2 100644 (file)
@@ -1,15 +1,17 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.test.whenever
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [LogoutPage].
  */
-class LogoutPageTest: WebPageTest(::LogoutPage) {
+class LogoutPageTest : WebPageTest(::LogoutPage) {
 
        @Test
        fun `page returns correct path`() {
@@ -24,7 +26,7 @@ class LogoutPageTest: WebPageTest(::LogoutPage) {
        @Test
        fun `page returns correct title`() {
                addTranslation("Page.Logout.Title", "logout page title")
-               assertThat(page.getPageTitle(freenetRequest), equalTo("logout page title"))
+               assertThat(page.getPageTitle(soneRequest), equalTo("logout page title"))
        }
 
        @Test
@@ -36,7 +38,7 @@ class LogoutPageTest: WebPageTest(::LogoutPage) {
 
        @Test
        fun `page is not enabled if sone requires full access and request does not have full access`() {
-               core.preferences.isRequireFullAccess = true
+               core.preferences.newRequireFullAccess = true
                assertThat(page.isEnabled(toadletContext), equalTo(false))
        }
 
@@ -60,10 +62,20 @@ class LogoutPageTest: WebPageTest(::LogoutPage) {
 
        @Test
        fun `page is enabled if full access is required and present and sone is logged in and there is more than one sone`() {
-               core.preferences.isRequireFullAccess = true
+               core.preferences.newRequireFullAccess = true
                whenever(toadletContext.isAllowedFullAccess).thenReturn(true)
                whenever(core.localSones).thenReturn(listOf(currentSone, currentSone))
                assertThat(page.isEnabled(toadletContext), equalTo(true))
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<LogoutPage>(), notNullValue())
+       }
+
+       @Test
+       fun `page is annotated with correct menuname`() {
+               assertThat(page.menuName, equalTo("Logout"))
+       }
+
 }
index d9a132a..6142e3a 100644 (file)
@@ -1,19 +1,18 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.data.PostReply
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [MarkAsKnownPage].
  */
-class MarkAsKnownPageTest: WebPageTest(::MarkAsKnownPage) {
+class MarkAsKnownPageTest : WebPageTest(::MarkAsKnownPage) {
 
        @Test
        fun `page returns correct path`() {
@@ -28,7 +27,7 @@ class MarkAsKnownPageTest: WebPageTest(::MarkAsKnownPage) {
        @Test
        fun `page returns correct title`() {
                addTranslation("Page.MarkAsKnown.Title", "mark as known page title")
-               assertThat(page.getPageTitle(freenetRequest), equalTo("mark as known page title"))
+               assertThat(page.getPageTitle(soneRequest), equalTo("mark as known page title"))
        }
 
        @Test
@@ -37,7 +36,7 @@ class MarkAsKnownPageTest: WebPageTest(::MarkAsKnownPage) {
                addHttpRequestPart("returnPage", "return.html")
                addHttpRequestPart("type", "post")
                addHttpRequestPart("id", "post1 post2 post3")
-               val posts = listOf(mock<Post>(), mock<Post>())
+               val posts = listOf(mock<Post>(), mock())
                addPost("post1", posts[0])
                addPost("post3", posts[1])
                verifyRedirect("return.html") {
@@ -52,7 +51,7 @@ class MarkAsKnownPageTest: WebPageTest(::MarkAsKnownPage) {
                addHttpRequestPart("returnPage", "return.html")
                addHttpRequestPart("type", "reply")
                addHttpRequestPart("id", "reply1 reply2 reply3")
-               val replies = listOf(mock<PostReply>(), mock<PostReply>())
+               val replies = listOf(mock<PostReply>(), mock())
                addPostReply("reply1", replies[0])
                addPostReply("reply3", replies[1])
                verifyRedirect("return.html") {
@@ -67,7 +66,7 @@ class MarkAsKnownPageTest: WebPageTest(::MarkAsKnownPage) {
                addHttpRequestPart("returnPage", "return.html")
                addHttpRequestPart("type", "sone")
                addHttpRequestPart("id", "sone1 sone2 sone3")
-               val sones = listOf(mock<Sone>(), mock<Sone>())
+               val sones = listOf(mock<Sone>(), mock())
                addSone("sone1", sones[0])
                addSone("sone3", sones[1])
                verifyRedirect("return.html") {
@@ -83,4 +82,9 @@ class MarkAsKnownPageTest: WebPageTest(::MarkAsKnownPage) {
                verifyRedirect("invalid.html")
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<MarkAsKnownPage>(), notNullValue())
+       }
+
 }
index 96427e7..a4d7617 100644 (file)
@@ -1,27 +1,23 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.data.PostReply
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.sone.utils.Pagination
-import net.pterodactylus.sone.utils.asOptional
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.contains
-import org.hamcrest.Matchers.containsInAnyOrder
-import org.hamcrest.Matchers.equalTo
-import org.junit.Before
-import org.junit.Test
-import java.util.Arrays.asList
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import java.util.Arrays.*
 
 /**
  * Unit test for [NewPage].
  */
-class NewPageTest: WebPageTest(::NewPage) {
+class NewPageTest : WebPageTest(::NewPage) {
 
        @Before
        fun setupNumberOfPostsPerPage() {
-               webInterface.core.preferences.postsPerPage = 5
+               webInterface.core.preferences.newPostsPerPage = 5
        }
 
        @Test
@@ -37,14 +33,14 @@ class NewPageTest: WebPageTest(::NewPage) {
        @Test
        fun `page returns correct title`() {
                addTranslation("Page.New.Title", "new page title")
-               assertThat(page.getPageTitle(freenetRequest), equalTo("new page title"))
+               assertThat(page.getPageTitle(soneRequest), equalTo("new page title"))
        }
 
        @Test
        fun `posts are not duplicated when they come from both new posts and new replies notifications`() {
                val extraPost = mock<Post>().withTime(2000)
                val posts = asList(mock<Post>().withTime(1000), mock<Post>().withTime(3000))
-               val postReplies = asList(mock<PostReply>(), mock<PostReply>())
+               val postReplies = asList(mock<PostReply>(), mock())
                whenever(postReplies[0].post).thenReturn(posts[0].asOptional())
                whenever(postReplies[1].post).thenReturn(extraPost.asOptional())
                whenever(webInterface.getNewPosts(currentSone)).thenReturn(posts)
@@ -61,7 +57,7 @@ class NewPageTest: WebPageTest(::NewPage) {
        @Test
        @Suppress("UNCHECKED_CAST")
        fun `posts are paginated properly`() {
-               webInterface.core.preferences.postsPerPage = 2
+               webInterface.core.preferences.newPostsPerPage = 2
                val posts = listOf(mock<Post>().withTime(2000), mock<Post>().withTime(3000), mock<Post>().withTime(1000))
                whenever(webInterface.getNewPosts(currentSone)).thenReturn(posts)
                verifyNoRedirect {
@@ -72,7 +68,7 @@ class NewPageTest: WebPageTest(::NewPage) {
        @Test
        @Suppress("UNCHECKED_CAST")
        fun `posts are paginated properly on second page`() {
-               webInterface.core.preferences.postsPerPage = 2
+               webInterface.core.preferences.newPostsPerPage = 2
                addHttpRequestParameter("page", "1")
                val posts = listOf(mock<Post>().withTime(2000), mock<Post>().withTime(3000), mock<Post>().withTime(1000))
                whenever(webInterface.getNewPosts(currentSone)).thenReturn(posts)
@@ -81,4 +77,19 @@ class NewPageTest: WebPageTest(::NewPage) {
                }
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<NewPage>(), notNullValue())
+       }
+
+       @Test
+       fun `page is annotated with the correct menuname`() {
+               assertThat(page.menuName, equalTo("New"))
+       }
+
+       @Test
+       fun `page is annotated with correct template path`() {
+               assertThat(page.templatePath, equalTo("/templates/new.html"))
+       }
+
 }
index 4e4c02e..71e1b7b 100644 (file)
@@ -1,38 +1,35 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.SoneOptions.DefaultSoneOptions
-import net.pterodactylus.sone.data.SoneOptions.LoadExternalContent.FOLLOWED
-import net.pterodactylus.sone.data.SoneOptions.LoadExternalContent.TRUSTED
+import net.pterodactylus.sone.data.SoneOptions.*
+import net.pterodactylus.sone.data.SoneOptions.LoadExternalContent.*
+import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.*
 import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.ALWAYS
-import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.NO
-import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.WRITING
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.hamcrest.Matchers.hasItem
-import org.hamcrest.Matchers.nullValue
-import org.junit.Before
-import org.junit.Test
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
 
 /**
  * Unit test for [OptionsPage].
  */
-class OptionsPageTest: WebPageTest(::OptionsPage) {
+class OptionsPageTest : WebPageTest(::OptionsPage) {
 
        @Before
        fun setupPreferences() {
-               core.preferences.insertionDelay = 1
-               core.preferences.charactersPerPost = 50
-               core.preferences.fcpFullAccessRequired = WRITING
-               core.preferences.imagesPerPage = 4
-               core.preferences.isFcpInterfaceActive = true
-               core.preferences.isRequireFullAccess = true
-               core.preferences.negativeTrust = 7
-               core.preferences.positiveTrust = 8
-               core.preferences.postCutOffLength = 51
-               core.preferences.postsPerPage = 10
-               core.preferences.trustComment = "11"
+               core.preferences.newInsertionDelay = 1
+               core.preferences.newCharactersPerPost = 50
+               core.preferences.newFcpFullAccessRequired = WRITING
+               core.preferences.newImagesPerPage = 4
+               core.preferences.newFcpInterfaceActive = true
+               core.preferences.newRequireFullAccess = true
+               core.preferences.newNegativeTrust = 7
+               core.preferences.newPositiveTrust = 8
+               core.preferences.newPostCutOffLength = 51
+               core.preferences.newPostsPerPage = 10
+               core.preferences.newTrustComment = "11"
        }
 
        @Before
@@ -61,7 +58,7 @@ class OptionsPageTest: WebPageTest(::OptionsPage) {
        @Test
        fun `page returns correct title`() {
                addTranslation("Page.Options.Title", "options page title")
-               assertThat(page.getPageTitle(freenetRequest), equalTo("options page title"))
+               assertThat(page.getPageTitle(soneRequest), equalTo("options page title"))
        }
 
        @Test
@@ -268,22 +265,22 @@ class OptionsPageTest: WebPageTest(::OptionsPage) {
 
        @Test
        fun `fcp interface can be set to true`() {
-               verifyThatPreferencesCanBeSet("fcp-interface-active", "checked", true) { core.preferences.isFcpInterfaceActive }
+               verifyThatPreferencesCanBeSet("fcp-interface-active", "checked", true) { core.preferences.fcpInterfaceActive }
        }
 
        @Test
        fun `fcp interface can be set to false`() {
-               verifyThatPreferencesCanBeSet("fcp-interface-active", null, false) { core.preferences.isFcpInterfaceActive }
+               verifyThatPreferencesCanBeSet("fcp-interface-active", null, false) { core.preferences.fcpInterfaceActive }
        }
 
        @Test
        fun `require full access can be set to true`() {
-               verifyThatPreferencesCanBeSet("require-full-access", "checked", true) { core.preferences.isRequireFullAccess }
+               verifyThatPreferencesCanBeSet("require-full-access", "checked", true) { core.preferences.requireFullAccess }
        }
 
        @Test
        fun `require full access can be set to false`() {
-               verifyThatPreferencesCanBeSet("require-full-access", null, false) { core.preferences.isRequireFullAccess }
+               verifyThatPreferencesCanBeSet("require-full-access", null, false) { core.preferences.requireFullAccess }
        }
 
        @Test
@@ -376,4 +373,19 @@ class OptionsPageTest: WebPageTest(::OptionsPage) {
                verifyThatPreferencesCanBeSet("trust-comment", "", "Set from Sone Web Interface") { core.preferences.trustComment }
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<OptionsPage>(), notNullValue())
+       }
+
+       @Test
+       fun `page is annotated with correct menuname`() {
+               assertThat(page.menuName, equalTo("Options"))
+       }
+
+       @Test
+       fun `page is annotated with correct template path`() {
+               assertThat(page.templatePath, equalTo("/templates/options.html"))
+       }
+
 }
index 7ebb052..99cf8bf 100644 (file)
@@ -1,13 +1,13 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.web.page.FreenetRequest
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Rule
-import org.junit.Test
-import org.junit.rules.TemporaryFolder
-import java.nio.file.Files
-import java.nio.file.Paths
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.junit.rules.*
+import java.nio.file.*
 import kotlin.text.Charsets.UTF_8
 
 /**
@@ -15,7 +15,9 @@ import kotlin.text.Charsets.UTF_8
  */
 class ReloadingPageTest {
 
-       @Rule @JvmField val tempFolder = TemporaryFolder()
+       @Rule
+       @JvmField
+       val tempFolder = TemporaryFolder()
        private val folder by lazy { tempFolder.newFolder()!! }
        private val page by lazy { ReloadingPage<FreenetRequest>("/prefix/", folder.path, "text/plain") }
        private val webPageTest = WebPageTest()
@@ -52,4 +54,9 @@ class ReloadingPageTest {
                assertThat(responseBytes.toByteArray(), equalTo("Hello\nWorld\n".toByteArray()))
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<ReloadingPage<*>>(), notNullValue())
+       }
+
 }
index e8ec10a..d11352f 100644 (file)
@@ -1,13 +1,13 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.core.SoneRescuer
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Before
-import org.junit.Test
+import net.pterodactylus.sone.core.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
 import org.mockito.ArgumentMatchers.anyLong
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
@@ -15,7 +15,7 @@ import org.mockito.Mockito.verify
 /**
  * Unit test for [RescuePage].
  */
-class RescuePageTest: WebPageTest(::RescuePage) {
+class RescuePageTest : WebPageTest(::RescuePage) {
 
        private val soneRescuer = mock<SoneRescuer>()
 
@@ -37,7 +37,7 @@ class RescuePageTest: WebPageTest(::RescuePage) {
        @Test
        fun `page returns correct title`() {
                addTranslation("Page.Rescue.Title", "rescue page title")
-               assertThat(page.getPageTitle(freenetRequest), equalTo("rescue page title"))
+               assertThat(page.getPageTitle(soneRequest), equalTo("rescue page title"))
        }
 
        @Test
@@ -85,4 +85,19 @@ class RescuePageTest: WebPageTest(::RescuePage) {
                }
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<RescuePage>(), notNullValue())
+       }
+
+       @Test
+       fun `page is annotated with correct menuname`() {
+               assertThat(page.menuName, equalTo("Rescue"))
+       }
+
+       @Test
+       fun `page is annotated with correct template path`() {
+               assertThat(page.templatePath, equalTo("/templates/rescue.html"))
+       }
+
 }
index 94af5da..329f458 100644 (file)
@@ -1,28 +1,22 @@
 package net.pterodactylus.sone.web.pages
 
-import com.google.common.base.Optional.absent
-import com.google.common.base.Ticker
-import net.pterodactylus.sone.data.Album
-import net.pterodactylus.sone.data.Image
-import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.data.PostReply
-import net.pterodactylus.sone.data.Profile
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.test.isOnPage
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.sone.utils.asOptional
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.contains
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
-import java.util.concurrent.TimeUnit
-import java.util.concurrent.atomic.AtomicInteger
+import com.google.common.base.*
+import com.google.common.base.Optional.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import java.util.concurrent.*
+import java.util.concurrent.atomic.*
 
 /**
  * Unit test for [SearchPage].
  */
-class SearchPageTest: WebPageTest({ template, webInterface -> SearchPage(template, webInterface, ticker) }) {
+class SearchPageTest : WebPageTest({ webInterface, loaders, templateRenderer -> SearchPage(webInterface, loaders, templateRenderer, ticker) }) {
 
        companion object {
                val ticker = mock<Ticker>()
@@ -41,7 +35,7 @@ class SearchPageTest: WebPageTest({ template, webInterface -> SearchPage(templat
        @Test
        fun `page returns correct title`() {
                addTranslation("Page.Search.Title", "search page title")
-               assertThat(page.getPageTitle(freenetRequest), equalTo("search page title"))
+               assertThat(page.getPageTitle(soneRequest), equalTo("search page title"))
        }
 
        @Test
@@ -63,28 +57,28 @@ class SearchPageTest: WebPageTest({ template, webInterface -> SearchPage(templat
 
        @Test
        fun `searching for sone link redirects to view sone page`() {
-               addSone("sone-id", mock<Sone>())
-               addHttpRequestParameter("query", "sone://sone-id")
-               verifyRedirect("viewSone.html?sone=sone-id")
+               addSone("Sone-ID", mock())
+               addHttpRequestParameter("query", "sone://Sone-ID")
+               verifyRedirect("viewSone.html?sone=Sone-ID")
        }
 
        @Test
        fun `searching for sone link without prefix redirects to view sone page`() {
-               addSone("sone-id", mock<Sone>())
+               addSone("sone-id", mock())
                addHttpRequestParameter("query", "sone-id")
                verifyRedirect("viewSone.html?sone=sone-id")
        }
 
        @Test
        fun `searching for a post link redirects to post page`() {
-               addPost("post-id", mock<Post>())
-               addHttpRequestParameter("query", "post://post-id")
-               verifyRedirect("viewPost.html?post=post-id")
+               addPost("Post-id", mock())
+               addHttpRequestParameter("query", "post://Post-id")
+               verifyRedirect("viewPost.html?post=Post-id")
        }
 
        @Test
        fun `searching for a post ID without prefix redirects to post page`() {
-               addPost("post-id", mock<Post>())
+               addPost("post-id", mock())
                addHttpRequestParameter("query", "post-id")
                verifyRedirect("viewPost.html?post=post-id")
        }
@@ -92,8 +86,8 @@ class SearchPageTest: WebPageTest({ template, webInterface -> SearchPage(templat
        @Test
        fun `searching for a reply link redirects to the post page`() {
                val postReply = mock<PostReply>().apply { whenever(postId).thenReturn("post-id") }
-               addPostReply("reply-id", postReply)
-               addHttpRequestParameter("query", "reply://reply-id")
+               addPostReply("Reply-id", postReply)
+               addHttpRequestParameter("query", "reply://Reply-id")
                verifyRedirect("viewPost.html?post=post-id")
        }
 
@@ -107,28 +101,28 @@ class SearchPageTest: WebPageTest({ template, webInterface -> SearchPage(templat
 
        @Test
        fun `searching for an album link redirects to the image browser`() {
-               addAlbum("album-id", mock<Album>())
+               addAlbum("album-id", mock())
                addHttpRequestParameter("query", "album://album-id")
                verifyRedirect("imageBrowser.html?album=album-id")
        }
 
        @Test
        fun `searching for an album ID redirects to the image browser`() {
-               addAlbum("album-id", mock<Album>())
+               addAlbum("album-id", mock())
                addHttpRequestParameter("query", "album-id")
                verifyRedirect("imageBrowser.html?album=album-id")
        }
 
        @Test
        fun `searching for an image link redirects to the image browser`() {
-               addImage("image-id", mock<Image>())
+               addImage("image-id", mock())
                addHttpRequestParameter("query", "image://image-id")
                verifyRedirect("imageBrowser.html?image=image-id")
        }
 
        @Test
        fun `searching for an image ID redirects to the image browser`() {
-               addImage("image-id", mock<Image>())
+               addImage("image-id", mock())
                addHttpRequestParameter("query", "image-id")
                verifyRedirect("imageBrowser.html?image=image-id")
        }
@@ -282,7 +276,7 @@ class SearchPageTest: WebPageTest({ template, webInterface -> SearchPage(templat
 
        @Test
        fun `sone hits are paginated correctly`() {
-               core.preferences.postsPerPage = 2
+               core.preferences.newPostsPerPage = 2
                val sones = listOf(createSone("1Sone"), createSone("Other1"), createSone("22Sone"), createSone("333Sone"), createSone("Other2"))
                                .onEach { addSone(it.id, it) }
                addHttpRequestParameter("query", "sone")
@@ -294,7 +288,7 @@ class SearchPageTest: WebPageTest({ template, webInterface -> SearchPage(templat
 
        @Test
        fun `sone hits page 2 is shown correctly`() {
-               core.preferences.postsPerPage = 2
+               core.preferences.newPostsPerPage = 2
                val sones = listOf(createSone("1Sone"), createSone("Other1"), createSone("22Sone"), createSone("333Sone"), createSone("Other2"))
                                .onEach { addSone(it.id, it) }
                addHttpRequestParameter("query", "sone")
@@ -307,7 +301,7 @@ class SearchPageTest: WebPageTest({ template, webInterface -> SearchPage(templat
 
        @Test
        fun `post hits are paginated correctly`() {
-               core.preferences.postsPerPage = 2
+               core.preferences.newPostsPerPage = 2
                val sones = listOf(createSoneWithPost("match1", "1Sone"), createSoneWithPost("no-match1", "Other1"), createSoneWithPost("match2", "22Sone"), createSoneWithPost("match3", "333Sone"), createSoneWithPost("no-match2", "Other2"))
                addHttpRequestParameter("query", "sone")
                verifyNoRedirect {
@@ -318,7 +312,7 @@ class SearchPageTest: WebPageTest({ template, webInterface -> SearchPage(templat
 
        @Test
        fun `post hits page 2 is shown correctly`() {
-               core.preferences.postsPerPage = 2
+               core.preferences.newPostsPerPage = 2
                val sones = listOf(createSoneWithPost("match1", "1Sone"), createSoneWithPost("no-match1", "Other1"), createSoneWithPost("match2", "22Sone"), createSoneWithPost("match3", "333Sone"), createSoneWithPost("no-match2", "Other2"))
                addHttpRequestParameter("query", "sone")
                addHttpRequestParameter("postPage", "1")
@@ -364,4 +358,14 @@ class SearchPageTest: WebPageTest({ template, webInterface -> SearchPage(templat
        @Suppress("UNCHECKED_CAST")
        private operator fun <T> get(key: String): T? = templateContext[key] as? T
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<SearchPage>(), notNullValue())
+       }
+
+       @Test
+       fun `page is annotated with correct template path`() {
+               assertThat(page.templatePath, equalTo("/templates/search.html"))
+       }
+
 }
index 3cb08e6..8e29df1 100644 (file)
@@ -1,39 +1,38 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.main.SonePlugin
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.sone.web.page.FreenetRequest
-import net.pterodactylus.util.notify.Notification
-import net.pterodactylus.util.template.TemplateContext
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.main.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.notify.*
+import net.pterodactylus.util.template.*
 import net.pterodactylus.util.version.Version
-import org.hamcrest.Matcher
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.anyOf
-import org.hamcrest.Matchers.contains
-import org.hamcrest.Matchers.containsInAnyOrder
-import org.hamcrest.Matchers.equalTo
-import org.hamcrest.Matchers.nullValue
-import org.junit.Test
+import org.hamcrest.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
 
 /**
  * Unit test for [SoneTemplatePage].
  */
-class SoneTemplatePageTest : WebPageTest({ template, webInterface -> object : SoneTemplatePage("path.html", template, webInterface, true) {} }) {
+class SoneTemplatePageTest : WebPageTest({ webInterface, loaders, templateRenderer -> object : SoneTemplatePage(webInterface, loaders, templateRenderer, requiresLogin = true) {} }) {
+
+       init {
+               request("index.html")
+       }
 
        @Test
        fun `page title is empty string if no page title key was given`() {
-               SoneTemplatePage("path.html", template, null, webInterface).let { page ->
-                       assertThat(page.getPageTitle(freenetRequest), equalTo(""))
+               SoneTemplatePage(webInterface, loaders, templateRenderer, requiresLogin = false).let { page ->
+                       assertThat(page.getPageTitle(soneRequest), equalTo(""))
                }
        }
 
        @Test
        fun `page title is retrieved from l10n if page title key is given`() {
-               SoneTemplatePage("path.html", template, "page.title", webInterface).let { page ->
+               SoneTemplatePage(webInterface, loaders, templateRenderer, pageTitleKey = "page.title", requiresLogin = false).let { page ->
                        whenever(l10n.getString("page.title")).thenReturn("Page Title")
-                       assertThat(page.getPageTitle(freenetRequest), equalTo("Page Title"))
+                       assertThat(page.getPageTitle(soneRequest), equalTo("Page Title"))
                }
        }
 
@@ -78,7 +77,7 @@ class SoneTemplatePageTest : WebPageTest({ template, webInterface -> object : So
 
        @Test
        fun `local sones are set in template context`() {
-               val localSones = listOf(mock<Sone>(), mock<Sone>())
+               val localSones = listOf(mock<Sone>(), mock())
                whenever(core.localSones).thenReturn(localSones)
                verifyVariableMatches("localSones", containsInAnyOrder(*localSones.toTypedArray()))
        }
@@ -144,7 +143,7 @@ class SoneTemplatePageTest : WebPageTest({ template, webInterface -> object : So
        @Test
        fun `handleRequest method is called`() {
                var called = false
-               val page = object : SoneTemplatePage("path.html", template, webInterface, true) {
+               val page = object : SoneTemplatePage(webInterface, loaders, templateRenderer, requiresLogin = true) {
                        override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
                                called = true
                        }
@@ -155,7 +154,7 @@ class SoneTemplatePageTest : WebPageTest({ template, webInterface -> object : So
 
        @Test
        fun `redirect does not happen if login is not required`() {
-               val page = SoneTemplatePage("page.html", template, webInterface, false)
+               val page = SoneTemplatePage(webInterface, loaders, templateRenderer, requiresLogin = false)
                assertThat(page.getRedirectTarget(freenetRequest), nullValue())
        }
 
@@ -167,14 +166,12 @@ class SoneTemplatePageTest : WebPageTest({ template, webInterface -> object : So
        @Test
        fun `redirect does happen if sone is not logged in`() {
                unsetCurrentSone()
-               request("index.html")
                assertThat(page.getRedirectTarget(freenetRequest), equalTo("login.html?target=index.html"))
        }
 
        @Test
        fun `redirect does happen with parameters encoded correctly if sone is not logged in`() {
                unsetCurrentSone()
-               request("index.html")
                addHttpRequestParameter("foo", "b=r")
                addHttpRequestParameter("baz", "q&o")
                assertThat(page.getRedirectTarget(freenetRequest), anyOf(
@@ -185,7 +182,7 @@ class SoneTemplatePageTest : WebPageTest({ template, webInterface -> object : So
 
        @Test
        fun `page is disabled if full access is required but request does not have full access`() {
-               core.preferences.isRequireFullAccess = true
+               core.preferences.newRequireFullAccess = true
                assertThat(page.isEnabled(toadletContext), equalTo(false))
        }
 
@@ -202,16 +199,28 @@ class SoneTemplatePageTest : WebPageTest({ template, webInterface -> object : So
 
        @Test
        fun `page is enabled if full access is required and request has full access and login is required and there is a current sone`() {
-               core.preferences.isRequireFullAccess = true
+               core.preferences.newRequireFullAccess = true
                whenever(toadletContext.isAllowedFullAccess).thenReturn(true)
                assertThat(page.isEnabled(toadletContext), equalTo(true))
        }
 
        @Test
        fun `page is enabled if no full access is required and login is not required`() {
-               SoneTemplatePage("path.html", template, webInterface, false).let { page ->
+               SoneTemplatePage(webInterface, loaders, templateRenderer, requiresLogin = false).let { page ->
                        assertThat(page.isEnabled(toadletContext), equalTo(true))
                }
        }
 
+       @Test
+       fun `handle request with sone request is called`() {
+               var called = false
+               val page = object : SoneTemplatePage(webInterface, loaders, templateRenderer) {
+                       override fun handleRequest(soneRequest: SoneRequest, templateContext: TemplateContext) {
+                               called = true
+                       }
+               }
+               page.processTemplate(freenetRequest, templateContext)
+               assertThat(called, equalTo(true))
+       }
+
 }
index cc77203..990c1b7 100644 (file)
@@ -1,11 +1,13 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Sone
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.getInstance
 import net.pterodactylus.sone.test.mock
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
 import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mockito.never
@@ -14,22 +16,22 @@ import org.mockito.Mockito.verify
 /**
  * Unit test for [TrustPage].
  */
-class TrustPageTest: WebPageTest(::TrustPage) {
+class TrustPageTest : WebPageTest(::TrustPage) {
 
        @Test
        fun `page returns correct path`() {
-           assertThat(page.path, equalTo("trust.html"))
+               assertThat(page.path, equalTo("trust.html"))
        }
 
        @Test
        fun `page requires login`() {
-           assertThat(page.requiresLogin(), equalTo(true))
+               assertThat(page.requiresLogin(), equalTo(true))
        }
 
        @Test
        fun `page returns correct title`() {
-           addTranslation("Page.Trust.Title", "title trust page")
-               assertThat(page.getPageTitle(freenetRequest), equalTo("title trust page"))
+               addTranslation("Page.Trust.Title", "title trust page")
+               assertThat(page.getPageTitle(soneRequest), equalTo("title trust page"))
        }
 
        @Test
@@ -68,4 +70,9 @@ class TrustPageTest: WebPageTest(::TrustPage) {
                }
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<TrustPage>(), notNullValue())
+       }
+
 }
index a9f784e..4025fe4 100644 (file)
@@ -1,23 +1,18 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.test.capture
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.contains
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
-import org.mockito.Mockito.any
-import org.mockito.Mockito.never
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [UnbookmarkPage].
  */
-class UnbookmarkPageTest: WebPageTest(::UnbookmarkPage) {
+class UnbookmarkPageTest : WebPageTest(::UnbookmarkPage) {
 
        @Test
        fun `page returns correct path`() {
@@ -32,7 +27,7 @@ class UnbookmarkPageTest: WebPageTest(::UnbookmarkPage) {
        @Test
        fun `page returns correct title`() {
                addTranslation("Page.Unbookmark.Title", "unbookmark page title")
-               assertThat(page.getPageTitle(freenetRequest), equalTo("unbookmark page title"))
+               assertThat(page.getPageTitle(soneRequest), equalTo("unbookmark page title"))
        }
 
        @Test
@@ -77,4 +72,9 @@ class UnbookmarkPageTest: WebPageTest(::UnbookmarkPage) {
                }
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<UnbookmarkPage>(), notNullValue())
+       }
+
 }
index 01721cd..39f4fd5 100644 (file)
@@ -1,30 +1,32 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
-import org.mockito.Mockito.verify
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 
 /**
  * Unit test for [UnfollowSonePage].
  */
-class UnfollowSonePageTest: WebPageTest(::UnfollowSonePage) {
+class UnfollowSonePageTest : WebPageTest(::UnfollowSonePage) {
 
        @Test
        fun `page returns correct path`() {
-           assertThat(page.path, equalTo("unfollowSone.html"))
+               assertThat(page.path, equalTo("unfollowSone.html"))
        }
 
        @Test
        fun `page requires login`() {
-           assertThat(page.requiresLogin(), equalTo(true))
+               assertThat(page.requiresLogin(), equalTo(true))
        }
 
        @Test
        fun `page returns correct page title`() {
-           addTranslation("Page.UnfollowSone.Title", "unfollow page title")
-               assertThat(page.getPageTitle(freenetRequest), equalTo("unfollow page title"))
+               addTranslation("Page.UnfollowSone.Title", "unfollow page title")
+               assertThat(page.getPageTitle(soneRequest), equalTo("unfollow page title"))
        }
 
        @Test
@@ -53,4 +55,9 @@ class UnfollowSonePageTest: WebPageTest(::UnfollowSonePage) {
                }
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<UnfollowSonePage>(), notNullValue())
+       }
+
 }
index fe00729..2d8ef89 100644 (file)
@@ -1,9 +1,11 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
 import org.mockito.ArgumentMatchers.any
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
@@ -11,22 +13,22 @@ import org.mockito.Mockito.verify
 /**
  * Unit test for [UnlikePage].
  */
-class UnlikePageTest: WebPageTest(::UnlikePage) {
+class UnlikePageTest : WebPageTest(::UnlikePage) {
 
        @Test
        fun `page returns correct path`() {
-           assertThat(page.path, equalTo("unlike.html"))
+               assertThat(page.path, equalTo("unlike.html"))
        }
 
        @Test
        fun `page requires login`() {
-           assertThat(page.requiresLogin(), equalTo(true))
+               assertThat(page.requiresLogin(), equalTo(true))
        }
 
        @Test
        fun `page returns correct title`() {
                addTranslation("Page.Unlike.Title", "unlike page title")
-           assertThat(page.getPageTitle(freenetRequest), equalTo("unlike page title"))
+               assertThat(page.getPageTitle(soneRequest), equalTo("unlike page title"))
        }
 
        @Test
@@ -68,4 +70,9 @@ class UnlikePageTest: WebPageTest(::UnlikePage) {
                }
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<UnlikePage>(), notNullValue())
+       }
+
 }
index 6d46922..8627fd4 100644 (file)
@@ -1,12 +1,12 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
 import org.mockito.ArgumentMatchers.any
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
@@ -14,22 +14,22 @@ import org.mockito.Mockito.verify
 /**
  * Unit test for [UnlockSonePage].
  */
-class UnlockSonePageTest: WebPageTest(::UnlockSonePage) {
+class UnlockSonePageTest : WebPageTest(::UnlockSonePage) {
 
        @Test
        fun `page returns correct path`() {
-           assertThat(page.path, equalTo("unlockSone.html"))
+               assertThat(page.path, equalTo("unlockSone.html"))
        }
 
        @Test
        fun `page does not require login`() {
-           assertThat(page.requiresLogin(), equalTo(false))
+               assertThat(page.requiresLogin(), equalTo(false))
        }
 
        @Test
        fun `page returns correct title`() {
                addTranslation("Page.UnlockSone.Title", "unlock page title")
-           assertThat(page.getPageTitle(freenetRequest), equalTo("unlock page title"))
+               assertThat(page.getPageTitle(soneRequest), equalTo("unlock page title"))
        }
 
        @Test
@@ -56,7 +56,7 @@ class UnlockSonePageTest: WebPageTest(::UnlockSonePage) {
                setMethod(POST)
                addHttpRequestPart("returnPage", "return.html")
                addHttpRequestPart("sone", "remote-sone")
-               addSone("remote-sone", mock<Sone>())
+               addSone("remote-sone", mock())
                verifyRedirect("return.html") {
                        verify(core, never()).unlockSone(any())
                }
@@ -74,4 +74,9 @@ class UnlockSonePageTest: WebPageTest(::UnlockSonePage) {
                }
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<UnlockSonePage>(), notNullValue())
+       }
+
 }
index 9d18162..eb0ebdd 100644 (file)
@@ -1,11 +1,13 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Sone
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.getInstance
 import net.pterodactylus.sone.test.mock
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
 import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mockito.never
@@ -14,22 +16,22 @@ import org.mockito.Mockito.verify
 /**
  * Unit test for [UntrustPage].
  */
-class UntrustPageTest: WebPageTest(::UntrustPage) {
+class UntrustPageTest : WebPageTest(::UntrustPage) {
 
        @Test
        fun `page returns correct path`() {
-           assertThat(page.path, equalTo("untrust.html"))
+               assertThat(page.path, equalTo("untrust.html"))
        }
 
        @Test
        fun `page requires login`() {
-           assertThat(page.requiresLogin(), equalTo(true))
+               assertThat(page.requiresLogin(), equalTo(true))
        }
 
        @Test
        fun `page returns correct title`() {
                addTranslation("Page.Untrust.Title", "untrust page title")
-           assertThat(page.getPageTitle(freenetRequest), equalTo("untrust page title"))
+               assertThat(page.getPageTitle(soneRequest), equalTo("untrust page title"))
        }
 
        @Test
@@ -70,4 +72,9 @@ class UntrustPageTest: WebPageTest(::UntrustPage) {
                }
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<UntrustPage>(), notNullValue())
+       }
+
 }
index 58e63a2..370c0a7 100644 (file)
@@ -1,26 +1,24 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Album
-import net.pterodactylus.sone.data.Image
-import net.pterodactylus.sone.data.Image.Modifier
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.data.TemporaryImage
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.data.Image.*
+import net.pterodactylus.sone.test.getInstance
 import net.pterodactylus.sone.test.mock
 import net.pterodactylus.sone.test.mockBuilder
 import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.util.web.Method.POST
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
-import org.mockito.Mockito.any
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import net.pterodactylus.util.web.Method.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.Mockito.*
 import org.mockito.Mockito.eq
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
 
 /**
  * Unit test for [UploadImagePage].
  */
-class UploadImagePageTest: WebPageTest(::UploadImagePage) {
+class UploadImagePageTest : WebPageTest(::UploadImagePage) {
 
        private val parentAlbum = mock<Album>().apply {
                whenever(id).thenReturn("parent-id")
@@ -29,18 +27,18 @@ class UploadImagePageTest: WebPageTest(::UploadImagePage) {
 
        @Test
        fun `page returns correct path`() {
-           assertThat(page.path, equalTo("uploadImage.html"))
+               assertThat(page.path, equalTo("uploadImage.html"))
        }
 
        @Test
        fun `page requires login`() {
-           assertThat(page.requiresLogin(), equalTo(true))
+               assertThat(page.requiresLogin(), equalTo(true))
        }
 
        @Test
        fun `page returns correct title`() {
-           addTranslation("Page.UploadImage.Title", "upload image page title")
-               assertThat(page.getPageTitle(freenetRequest), equalTo("upload image page title"))
+               addTranslation("Page.UploadImage.Title", "upload image page title")
+               assertThat(page.getPageTitle(soneRequest), equalTo("upload image page title"))
        }
 
        @Test
@@ -61,7 +59,7 @@ class UploadImagePageTest: WebPageTest(::UploadImagePage) {
        fun `post request with parent that is not the current sone results in no permission error page`() {
                setMethod(POST)
                addHttpRequestPart("parent", "parent-id")
-               whenever(parentAlbum.sone).thenReturn(mock<Sone>())
+               whenever(parentAlbum.sone).thenReturn(mock())
                addAlbum("parent-id", parentAlbum)
                verifyRedirect("noPermission.html")
        }
@@ -115,4 +113,14 @@ class UploadImagePageTest: WebPageTest(::UploadImagePage) {
                }
        }
 
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<UploadImagePage>(), notNullValue())
+       }
+
+       @Test
+       fun `page is annotated with correct template path`() {
+               assertThat(page.templatePath, equalTo("/templates/invalid.html"))
+       }
+
 }
index b66f6cc..6ba1550 100644 (file)
@@ -1,18 +1,18 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.data.Profile
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.hamcrest.Matchers.nullValue
-import org.junit.Test
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import java.net.*
 
 /**
  * Unit test for [ViewPostPage].
  */
-class ViewPostPageTest: WebPageTest(::ViewPostPage) {
+class ViewPostPageTest : WebPageTest(::ViewPostPage) {
 
        private val post = mock<Post>()
 
@@ -28,7 +28,7 @@ class ViewPostPageTest: WebPageTest(::ViewPostPage) {
 
        @Test
        fun `the view post page is link-excepted`() {
-               assertThat(page.isLinkExcepted(null), equalTo(true))
+               assertThat(page.isLinkExcepted(URI("")), equalTo(true))
        }
 
        @Test
@@ -72,14 +72,14 @@ class ViewPostPageTest: WebPageTest(::ViewPostPage) {
        @Test
        fun `page title for request without parameters is default title`() {
                addTranslation("Page.ViewPost.Title", "view post title")
-               assertThat(page.getPageTitle(freenetRequest), equalTo("view post title"))
+               assertThat(page.getPageTitle(soneRequest), equalTo("view post title"))
        }
 
        @Test
        fun `page title for request with invalid post is default title`() {
                addHttpRequestParameter("post", "invalid-post-id")
                addTranslation("Page.ViewPost.Title", "view post title")
-               assertThat(page.getPageTitle(freenetRequest), equalTo("view post title"))
+               assertThat(page.getPageTitle(soneRequest), equalTo("view post title"))
        }
 
        @Test
@@ -94,7 +94,17 @@ class ViewPostPageTest: WebPageTest(::ViewPostPage) {
                addPost("post-id", post)
                addHttpRequestParameter("post", "post-id")
                addTranslation("Page.ViewPost.Title", "view post title")
-               assertThat(page.getPageTitle(freenetRequest), equalTo("This is a text that … - First M. Last - view post title"))
+               assertThat(page.getPageTitle(soneRequest), equalTo("This is a text that … - First M. Last - view post title"))
+       }
+
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<ViewPostPage>(), notNullValue())
+       }
+
+       @Test
+       fun `page is annotated with correct template path`() {
+               assertThat(page.templatePath, equalTo("/templates/viewPost.html"))
        }
 
 }
index d7ff46d..6e97e36 100644 (file)
@@ -1,25 +1,19 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.data.PostReply
-import net.pterodactylus.sone.data.Profile
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.test.isOnPage
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.sone.utils.Pagination
-import net.pterodactylus.sone.utils.asOptional
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.contains
-import org.hamcrest.Matchers.equalTo
-import org.hamcrest.Matchers.nullValue
-import org.junit.Before
-import org.junit.Test
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import java.net.*
 
 /**
  * Unit test for [ViewSonePage].
  */
-class ViewSonePageTest: WebPageTest(::ViewSonePage) {
+class ViewSonePageTest : WebPageTest(::ViewSonePage) {
 
        init {
                whenever(currentSone.id).thenReturn("sone-id")
@@ -27,17 +21,17 @@ class ViewSonePageTest: WebPageTest(::ViewSonePage) {
 
        private val post1 = createPost("post1", "First Post.", 1000, currentSone)
        private val post2 = createPost("post2", "Second Post.", 2000, currentSone)
-       private val foreignPost1 = createPost("foreign-post1", "First Foreign Post.", 1000, mock<Sone>())
-       private val foreignPost2 = createPost("foreign-post2", "Second Foreign Post.", 2000, mock<Sone>())
-       private val foreignPost3 = createPost("foreign-post3", "Third Foreign Post.", 3000, mock<Sone>())
-       private val directed1 = createPost("post3", "First directed.", 1500, mock<Sone>(), recipient = currentSone)
-       private val directed2 = createPost("post4", "Second directed.", 2500, mock<Sone>(), recipient = currentSone)
+       private val foreignPost1 = createPost("foreign-post1", "First Foreign Post.", 1000, mock())
+       private val foreignPost2 = createPost("foreign-post2", "Second Foreign Post.", 2000, mock())
+       private val foreignPost3 = createPost("foreign-post3", "Third Foreign Post.", 3000, mock())
+       private val directed1 = createPost("post3", "First directed.", 1500, mock(), recipient = currentSone)
+       private val directed2 = createPost("post4", "Second directed.", 2500, mock(), recipient = currentSone)
 
        @Before
        fun setup() {
                whenever(currentSone.posts).thenReturn(mutableListOf(post2, post1))
                whenever(core.getDirectedPosts("sone-id")).thenReturn(setOf(directed1, directed2))
-               core.preferences.postsPerPage = 2
+               core.preferences.newPostsPerPage = 2
        }
 
        @Test
@@ -176,14 +170,14 @@ class ViewSonePageTest: WebPageTest(::ViewSonePage) {
        @Test
        fun `page title is default for request without parameters`() {
                addTranslation("Page.ViewSone.Page.TitleWithoutSone", "view sone page without sone")
-               assertThat(page.getPageTitle(freenetRequest), equalTo("view sone page without sone"))
+               assertThat(page.getPageTitle(soneRequest), equalTo("view sone page without sone"))
        }
 
        @Test
        fun `page title is default for request with invalid sone parameters`() {
                addHttpRequestParameter("sone", "invalid-sone-id")
                addTranslation("Page.ViewSone.Page.TitleWithoutSone", "view sone page without sone")
-               assertThat(page.getPageTitle(freenetRequest), equalTo("view sone page without sone"))
+               assertThat(page.getPageTitle(soneRequest), equalTo("view sone page without sone"))
        }
 
        @Test
@@ -196,12 +190,22 @@ class ViewSonePageTest: WebPageTest(::ViewSonePage) {
                        lastName = "Last"
                })
                addTranslation("Page.ViewSone.Title", "view sone page")
-               assertThat(page.getPageTitle(freenetRequest), equalTo("First M. Last - view sone page"))
+               assertThat(page.getPageTitle(soneRequest), equalTo("First M. Last - view sone page"))
        }
 
        @Test
        fun `page is link-excepted`() {
-               assertThat(page.isLinkExcepted(null), equalTo(true))
+               assertThat(page.isLinkExcepted(URI("")), equalTo(true))
+       }
+
+       @Test
+       fun `page can be created by dependency injection`() {
+               assertThat(baseInjector.getInstance<ViewSonePage>(), notNullValue())
+       }
+
+       @Test
+       fun `page is annotated with correct template path`() {
+               assertThat(page.templatePath, equalTo("/templates/viewSone.html"))
        }
 
 }
index ce16c85..f51bece 100644 (file)
@@ -1,60 +1,60 @@
 package net.pterodactylus.sone.web.pages
 
-import com.google.common.eventbus.EventBus
-import freenet.clients.http.ToadletContext
-import freenet.support.SimpleReadOnlyArrayBucket
-import freenet.support.api.HTTPRequest
-import freenet.support.api.HTTPUploadedFile
-import net.pterodactylus.sone.core.Preferences
-import net.pterodactylus.sone.data.Album
-import net.pterodactylus.sone.data.Image
-import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.data.PostReply
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.data.TemporaryImage
-import net.pterodactylus.sone.freenet.wot.OwnIdentity
+import com.google.common.eventbus.*
+import freenet.clients.http.*
+import freenet.support.*
+import freenet.support.api.*
+import net.pterodactylus.sone.core.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.freenet.wot.*
+import net.pterodactylus.sone.main.*
 import net.pterodactylus.sone.test.deepMock
 import net.pterodactylus.sone.test.get
 import net.pterodactylus.sone.test.mock
 import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.sone.utils.asList
-import net.pterodactylus.sone.utils.asOptional
-import net.pterodactylus.sone.web.WebInterface
-import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.sone.web.*
+import net.pterodactylus.sone.web.page.*
 import net.pterodactylus.sone.web.page.FreenetTemplatePage.RedirectException
-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 net.pterodactylus.util.web.Method.GET
-import net.pterodactylus.util.web.Response
-import org.junit.Assert.fail
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.anyLong
-import org.mockito.ArgumentMatchers.anyString
+import net.pterodactylus.util.notify.*
+import net.pterodactylus.util.template.*
+import net.pterodactylus.util.web.*
+import net.pterodactylus.util.web.Method.*
+import org.junit.Assert.*
+import org.mockito.ArgumentMatchers.*
 import org.mockito.ArgumentMatchers.eq
-import java.io.ByteArrayOutputStream
-import java.net.URI
-import java.nio.charset.Charset
+import java.io.*
+import java.net.*
+import java.nio.charset.*
 import kotlin.text.Charsets.UTF_8
 
 /**
  * Base class for web page tests.
  */
-open class WebPageTest(pageSupplier: (Template, WebInterface) -> SoneTemplatePage = { _, _ -> mock<SoneTemplatePage>() }) {
+open class WebPageTest(pageSupplier: (WebInterface, Loaders, TemplateRenderer) -> SoneTemplatePage = { _, _, _ -> mock() }) {
 
        val currentSone = mock<Sone>()
-       val template = mock<Template>()
+       val loaders = mock<Loaders>()
+       val templateRenderer = mock<TemplateRenderer>()
        val webInterface = deepMock<WebInterface>()
        val core = webInterface.core
        val eventBus = mock<EventBus>()
        val preferences = Preferences(eventBus)
        val l10n = webInterface.l10n!!
+       val sessionManager = mock<SessionManager>()
+
+       val page by lazy { pageSupplier(webInterface, loaders, templateRenderer) }
 
-       val page by lazy { pageSupplier(template, webInterface) }
        val httpRequest = mock<HTTPRequest>()
        val freenetRequest = mock<FreenetRequest>()
+
+       init {
+               whenever(freenetRequest.l10n).thenReturn(l10n)
+               whenever(freenetRequest.sessionManager).thenReturn(sessionManager)
+               whenever(freenetRequest.uri).thenReturn(mock())
+       }
+
+       val soneRequest by lazy { freenetRequest.toSoneRequest(core, webInterface) }
        val templateContext = TemplateContext()
        val toadletContext = deepMock<ToadletContext>()
        val responseContent = ByteArrayOutputStream()
@@ -96,12 +96,13 @@ open class WebPageTest(pageSupplier: (Template, WebInterface) -> SoneTemplatePag
                whenever(core.getPostReply(anyString())).then { allPostReplies[it[0]] }
                whenever(core.getReplies(anyString())).then { perPostReplies[it[0]].asList() }
                whenever(core.getAlbum(anyString())).then { allAlbums[it[0]] }
-               whenever(core.getImage(anyString())).then { allImages[it[0]]}
-               whenever(core.getImage(anyString(), anyBoolean())).then { allImages[it[0]]}
+               whenever(core.getImage(anyString())).then { allImages[it[0]] }
+               whenever(core.getImage(anyString(), anyBoolean())).then { allImages[it[0]] }
                whenever(core.getTemporaryImage(anyString())).thenReturn(null)
        }
 
        private fun setupWebInterface() {
+               whenever(webInterface.sessionManager).thenReturn(sessionManager)
                whenever(webInterface.getCurrentSoneCreatingSession(eq(toadletContext))).thenReturn(currentSone)
                whenever(webInterface.getCurrentSone(eq(toadletContext), anyBoolean())).thenReturn(currentSone)
                whenever(webInterface.getCurrentSoneWithoutCreatingSession(eq(toadletContext))).thenReturn(currentSone)
@@ -126,12 +127,13 @@ open class WebPageTest(pageSupplier: (Template, WebInterface) -> SoneTemplatePag
                whenever(httpRequest.getPartAsStringFailsafe(anyString(), anyInt())).then { postRequestParameters[it[0]]?.decode()?.take(it[1]) ?: "" }
                whenever(httpRequest.getUploadedFile(anyString())).then {
                        it.get<String>(0).takeIf { it in uploadedFileNames }
-                                       ?.let { name -> UploadedFile(uploadedFileNames[name]!!, uploadedFileContentTypes[name]!!, uploadedFileResources[name]!!)
-                       }
+                                       ?.let { name ->
+                                               UploadedFile(uploadedFileNames[name]!!, uploadedFileContentTypes[name]!!, uploadedFileResources[name]!!)
+                                       }
                }
        }
 
-       private class UploadedFile(private val filename: String, private val contentType: String, private val resourceName: String): HTTPUploadedFile {
+       private class UploadedFile(private val filename: String, private val contentType: String, private val resourceName: String) : HTTPUploadedFile {
                override fun getFilename() = filename
                override fun getContentType() = contentType
                override fun getData() = javaClass.getResourceAsStream(resourceName).readBytes().let(::SimpleReadOnlyArrayBucket)
@@ -164,7 +166,7 @@ open class WebPageTest(pageSupplier: (Template, WebInterface) -> SoneTemplatePag
        }
 
        fun addHttpRequestParameter(name: String, value: String) {
-               getRequestParameters[name] = getRequestParameters.getOrElse(name) { mutableListOf<String>() }.apply { add(value) }
+               getRequestParameters[name] = getRequestParameters.getOrElse(name) { mutableListOf() }.apply { add(value) }
        }
 
        fun addHttpRequestPart(name: String, value: String) {
index 95ae89c..88ff71e 100644 (file)
@@ -9,17 +9,19 @@ buildscript {
 
 import org.ajoberstar.grgit.Grgit
 
-task(writeVersion) << {
-       def grgit = Grgit.open(dir: project.rootDir)
-       def version = grgit.resolve.toCommit("HEAD").id
-       def niceVersion = grgit.describe()
-       grgit.close()
+task(writeVersion) {
+    doLast {
+        def grgit = Grgit.open(dir: project.rootDir)
+        def version = grgit.resolve.toCommit("HEAD").id
+        def niceVersion = grgit.describe()
+        grgit.close()
 
-    new File("src/generated/resources").mkdirs()
-       project.file("src/generated/resources/version.yaml").withWriter { out ->
-               out.println "id: ${version}"
-               out.println "nice: ${niceVersion}"
-       }
+        new File("src/generated/resources").mkdirs()
+        project.file("src/generated/resources/version.yaml").withWriter { out ->
+            out.println "id: ${version}"
+            out.println "nice: ${niceVersion}"
+        }
+    }
 }
 
 sourceSets {