Merge branch 'release-0.9.7' 0.9.7
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Sat, 7 Oct 2017 16:55:06 +0000 (18:55 +0200)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Sat, 7 Oct 2017 16:55:06 +0000 (18:55 +0200)
455 files changed:
.gitignore
README.md
build.gradle
src/main/java/net/pterodactylus/sone/core/Core.java
src/main/java/net/pterodactylus/sone/core/FreenetInterface.java
src/main/java/net/pterodactylus/sone/data/Profile.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/impl/IdOnlySone.java
src/main/java/net/pterodactylus/sone/data/impl/SoneImpl.java
src/main/java/net/pterodactylus/sone/database/SoneProvider.java
src/main/java/net/pterodactylus/sone/fcp/AbstractSoneCommand.java
src/main/java/net/pterodactylus/sone/fcp/CreatePostCommand.java
src/main/java/net/pterodactylus/sone/fcp/CreateReplyCommand.java
src/main/java/net/pterodactylus/sone/fcp/DeletePostCommand.java
src/main/java/net/pterodactylus/sone/fcp/DeleteReplyCommand.java
src/main/java/net/pterodactylus/sone/fcp/FcpInterface.java
src/main/java/net/pterodactylus/sone/fcp/GetLocalSonesCommand.java
src/main/java/net/pterodactylus/sone/fcp/GetPostCommand.java
src/main/java/net/pterodactylus/sone/fcp/GetPostFeedCommand.java
src/main/java/net/pterodactylus/sone/fcp/GetPostsCommand.java
src/main/java/net/pterodactylus/sone/fcp/GetSoneCommand.java
src/main/java/net/pterodactylus/sone/fcp/GetSonesCommand.java
src/main/java/net/pterodactylus/sone/fcp/LikePostCommand.java
src/main/java/net/pterodactylus/sone/fcp/LikeReplyCommand.java
src/main/java/net/pterodactylus/sone/fcp/LockSoneCommand.java
src/main/java/net/pterodactylus/sone/fcp/UnlockSoneCommand.java
src/main/java/net/pterodactylus/sone/fcp/VersionCommand.java
src/main/java/net/pterodactylus/sone/freenet/L10nFilter.java
src/main/java/net/pterodactylus/sone/freenet/fcp/Command.java
src/main/java/net/pterodactylus/sone/freenet/wot/IdentityChangeDetector.java
src/main/java/net/pterodactylus/sone/main/DebugLoaders.java
src/main/java/net/pterodactylus/sone/main/SonePlugin.java
src/main/java/net/pterodactylus/sone/template/FilesystemTemplate.java
src/main/java/net/pterodactylus/sone/template/IdentityAccessor.java
src/main/java/net/pterodactylus/sone/template/ParserFilter.java [deleted file]
src/main/java/net/pterodactylus/sone/template/ProfileAccessor.java
src/main/java/net/pterodactylus/sone/template/ReplyGroupFilter.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/text/FreemailPart.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/text/FreenetLinkPart.java [deleted file]
src/main/java/net/pterodactylus/sone/text/LinkPart.java [deleted file]
src/main/java/net/pterodactylus/sone/text/Part.java [deleted file]
src/main/java/net/pterodactylus/sone/text/PartContainer.java [deleted file]
src/main/java/net/pterodactylus/sone/text/PlainTextPart.java [deleted file]
src/main/java/net/pterodactylus/sone/text/SonePart.java [deleted file]
src/main/java/net/pterodactylus/sone/text/SoneTextParser.java
src/main/java/net/pterodactylus/sone/utils/NumberParsers.java
src/main/java/net/pterodactylus/sone/web/AboutPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/BookmarkPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/BookmarksPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/CreateAlbumPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/CreatePostPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/CreateReplyPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/CreateSonePage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/DeleteAlbumPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/DeleteImagePage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/DeletePostPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/DeleteProfileFieldPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/DeleteReplyPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/DeleteSonePage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/DismissNotificationPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/DistrustPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/EditAlbumPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/EditImagePage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/EditProfileFieldPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/EditProfilePage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/FollowSonePage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/GetImagePage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ImageBrowserPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/IndexPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/KnownSonesPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/LikePage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/LockSonePage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/LoginPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/LogoutPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/MarkAsKnownPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/NewPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/OptionsPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ReloadingPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/RescuePage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/SearchPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/SoneTemplatePage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/TrustPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/UnbookmarkPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/UnfollowSonePage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/UnlikePage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/UnlockSonePage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/UntrustPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/UploadImagePage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ViewPostPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ViewSonePage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/WebInterface.java
src/main/java/net/pterodactylus/sone/web/ajax/BookmarkAjaxPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/CreatePostAjaxPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/CreateReplyAjaxPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/DeletePostAjaxPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/DeleteProfileFieldAjaxPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/DeleteReplyAjaxPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/DismissNotificationAjaxPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/DistrustAjaxPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/EditAlbumAjaxPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/EditImageAjaxPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/EditProfileFieldAjaxPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/FollowSoneAjaxPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/GetLikesAjaxPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/GetNotificationsAjaxPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/GetPostAjaxPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/GetReplyAjaxPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/GetStatusAjaxPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/GetTimesAjaxPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/GetTranslationPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/JsonErrorReturnObject.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/JsonPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/JsonReturnObject.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/LikeAjaxPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/LockSoneAjaxPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/MarkAsKnownAjaxPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/MoveProfileFieldAjaxPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/TrustAjaxPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/UnbookmarkAjaxPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/UnfollowSoneAjaxPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/UnlikeAjaxPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/UnlockSoneAjaxPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/ajax/UntrustAjaxPage.java [deleted file]
src/main/java/net/pterodactylus/sone/web/page/FreenetTemplatePage.java
src/main/java/net/pterodactylus/sone/web/page/PageToadlet.java
src/main/kotlin/net/pterodactylus/sone/core/DefaultElementLoader.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/core/ElementLoader.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/freenet/L10nText.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/main/FreenetModule.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/main/NoArg.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/main/VersionParser.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/template/LinkedElementRenderFilter.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/template/LinkedElementsFilter.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/template/ParserFilter.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/template/RenderFilter.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/template/ShortenFilter.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/text/FreenetLinkPart.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/text/LinkPart.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/text/Part.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/text/PlainTextPart.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/text/SonePart.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/text/TimeText.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/text/TimeTextConverter.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/utils/AutoCloseableBucket.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/utils/Booleans.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/utils/Buckets.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/utils/Json.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/utils/Objects.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/utils/Optionals.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/utils/Pagination.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/utils/Requests.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/utils/Strings.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/utils/Templates.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/SessionProvider.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/BookmarkAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/CreatePostAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/CreateReplyAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/DeletePostAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/DeleteProfileFieldAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/DeleteReplyAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/DismissNotificationAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/DistrustAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/EditAlbumAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/EditImageAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/EditProfileFieldAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/FollowSoneAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/GetLikesAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/GetLinkedElementAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/GetNotificationsAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/GetPostAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/GetReplyAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/GetStatusAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/GetTimesAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/GetTranslationAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/JsonErrorReturnObject.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/JsonPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/JsonReturnObject.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/LikeAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/LockSoneAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/LoggedInJsonPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/MarkAsKnownAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/MoveProfileFieldAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/TrustAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/UnbookmarkAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/UnfollowSoneAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/UnlikeAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/UnlockSoneAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/UntrustAjaxPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/AboutPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/BookmarkPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/BookmarksPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/CreateAlbumPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/CreatePostPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/CreateReplyPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/CreateSonePage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteAlbumPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteImagePage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/DeletePostPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteProfileFieldPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteReplyPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteSonePage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/DismissNotificationPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/DistrustPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/EditAlbumPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/EditImagePage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/EditProfileFieldPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/EditProfilePage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/FollowSonePage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/GetImagePage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/ImageBrowserPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/IndexPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/KnownSonesPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/LikePage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/LockSonePage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/LoginPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/LogoutPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/MarkAsKnownPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/NewPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/OptionsPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/ReloadingPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/RescuePage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/SearchPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/SoneTemplatePage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/TrustPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/UnbookmarkPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/UnfollowSonePage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/UnlikePage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/UnlockSonePage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/UntrustPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/UploadImagePage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/ViewPostPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/ViewSonePage.kt [new file with mode: 0644]
src/main/resources/i18n/sone.de.properties
src/main/resources/i18n/sone.en.properties
src/main/resources/i18n/sone.es.properties
src/main/resources/i18n/sone.fr.properties
src/main/resources/i18n/sone.ja.properties
src/main/resources/i18n/sone.no.properties
src/main/resources/i18n/sone.pl.properties
src/main/resources/i18n/sone.ru.properties
src/main/resources/static/css/sone.css
src/main/resources/static/javascript/sone.js
src/main/resources/templates/imageBrowser.html
src/main/resources/templates/include/browseAlbums.html
src/main/resources/templates/include/viewPost.html
src/main/resources/templates/include/viewReply.html
src/main/resources/templates/include/viewSone.html
src/main/resources/templates/insert/sone.xml
src/main/resources/templates/invalid.html
src/main/resources/templates/linked/html-page.html [new file with mode: 0644]
src/main/resources/templates/linked/image.html [new file with mode: 0644]
src/main/resources/templates/linked/notLoaded.html [new file with mode: 0644]
src/main/resources/templates/notify/newVersionNotification.html
src/main/resources/templates/notify/soneInsertNotification.html
src/main/resources/templates/options.html
src/main/resources/templates/viewSone.html
src/test/java/net/pterodactylus/sone/Matchers.java [deleted file]
src/test/java/net/pterodactylus/sone/TestAlbumBuilder.java [deleted file]
src/test/java/net/pterodactylus/sone/TestImageBuilder.java [deleted file]
src/test/java/net/pterodactylus/sone/TestPostBuilder.java [deleted file]
src/test/java/net/pterodactylus/sone/TestPostReplyBuilder.java [deleted file]
src/test/java/net/pterodactylus/sone/TestUtil.java [deleted file]
src/test/java/net/pterodactylus/sone/TestValue.java [deleted file]
src/test/java/net/pterodactylus/sone/core/ConfigurationSoneParserTest.java
src/test/java/net/pterodactylus/sone/core/FreenetInterfaceTest.java
src/test/java/net/pterodactylus/sone/core/PreferencesLoaderTest.java
src/test/java/net/pterodactylus/sone/database/memory/ConfigurationLoaderTest.java
src/test/java/net/pterodactylus/sone/database/memory/MemoryBookmarkDatabaseTest.java
src/test/java/net/pterodactylus/sone/database/memory/MemoryDatabaseTest.java
src/test/java/net/pterodactylus/sone/fcp/FcpInterfaceTest.java [deleted file]
src/test/java/net/pterodactylus/sone/fcp/LockSoneCommandTest.java [deleted file]
src/test/java/net/pterodactylus/sone/fcp/UnlockSoneCommandTest.java [deleted file]
src/test/java/net/pterodactylus/sone/freenet/wot/DefaultIdentityTest.java
src/test/java/net/pterodactylus/sone/notify/ListNotificationTest.java
src/test/java/net/pterodactylus/sone/template/AlbumAccessorTest.java
src/test/java/net/pterodactylus/sone/template/FilesystemTemplateTest.java
src/test/java/net/pterodactylus/sone/template/IdentityAccessorTest.java
src/test/java/net/pterodactylus/sone/template/PostAccessorTest.java [new file with mode: 0644]
src/test/java/net/pterodactylus/sone/test/Matchers.java [new file with mode: 0644]
src/test/java/net/pterodactylus/sone/test/TestAlbumBuilder.java [new file with mode: 0644]
src/test/java/net/pterodactylus/sone/test/TestImageBuilder.java [new file with mode: 0644]
src/test/java/net/pterodactylus/sone/test/TestPostBuilder.java [new file with mode: 0644]
src/test/java/net/pterodactylus/sone/test/TestPostReplyBuilder.java [new file with mode: 0644]
src/test/java/net/pterodactylus/sone/test/TestUtil.java [new file with mode: 0644]
src/test/java/net/pterodactylus/sone/test/TestValue.java [new file with mode: 0644]
src/test/java/net/pterodactylus/sone/text/FreemailPartTest.java [new file with mode: 0644]
src/test/java/net/pterodactylus/sone/text/FreenetLinkPartTest.java [deleted file]
src/test/java/net/pterodactylus/sone/text/LinkPartTest.java [deleted file]
src/test/java/net/pterodactylus/sone/text/PartContainerTest.java [deleted file]
src/test/java/net/pterodactylus/sone/text/PlainTextPartTest.java [deleted file]
src/test/java/net/pterodactylus/sone/text/SonePartTest.java [deleted file]
src/test/java/net/pterodactylus/sone/text/SoneTextParserTest.java
src/test/java/net/pterodactylus/sone/utils/IntegerRangePredicateTest.java
src/test/java/net/pterodactylus/sone/web/AboutPageTest.java [deleted file]
src/test/java/net/pterodactylus/sone/web/BookmarkPageTest.java [deleted file]
src/test/java/net/pterodactylus/sone/web/BookmarksPageTest.java [deleted file]
src/test/java/net/pterodactylus/sone/web/CreateAlbumPageTest.java [deleted file]
src/test/java/net/pterodactylus/sone/web/CreatePostPageTest.java [deleted file]
src/test/java/net/pterodactylus/sone/web/CreateReplyPageTest.java [deleted file]
src/test/java/net/pterodactylus/sone/web/CreateSonePageTest.java [deleted file]
src/test/java/net/pterodactylus/sone/web/DeleteReplyPageTest.java [deleted file]
src/test/java/net/pterodactylus/sone/web/NewPageTest.java [deleted file]
src/test/java/net/pterodactylus/sone/web/UploadImagePageTest.java [deleted file]
src/test/java/net/pterodactylus/sone/web/WebPageTest.java [deleted file]
src/test/java/net/pterodactylus/sone/web/ajax/BookmarkAjaxPageTest.java [deleted file]
src/test/java/net/pterodactylus/sone/web/ajax/GetTimesAjaxPageTest.java [deleted file]
src/test/kotlin/net/pterodactylus/sone/core/DefaultElementLoaderTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/core/ElementLoaderTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/fcp/CreatePostCommandTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/fcp/CreateReplyCommandTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/fcp/DeletePostCommandTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/fcp/DeleteReplyCommandTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/fcp/FcpInterfaceTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/fcp/GetLocalSonesCommandTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/fcp/GetPostCommandTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/fcp/GetPostFeedCommandTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/fcp/GetPostsCommandTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/fcp/GetSoneCommandTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/fcp/GetSonesCommandTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/fcp/LikePostCommandTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/fcp/LikeReplyCommandTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/fcp/LockSoneCommandTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/fcp/SoneCommandTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/fcp/UnlockSoneCommandTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/fcp/VersionCommandTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/freenet/L10nFilterTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/main/FreenetModuleTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/main/VersionParserTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/template/ImageAccessorTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/template/LinkedElementRenderFilterTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/template/LinkedElementsFilterTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/template/ParserFilterTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/template/ProfileAccessorTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/template/RenderFilterTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/template/ReplyAccessorTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/template/ReplyGroupFilterTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/template/RequestChangeFilterTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/template/ShortenFilterTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/template/SoneAccessorTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/template/SubstringFilterTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/template/TrustAccessorTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/template/UniqueElementFilterTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/template/UnknownDateFilterTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/test/Guice.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/test/Mockotlin.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/test/OneByOneMatcher.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/test/PaginationMatcher.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/text/FreenetLinkPartTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/text/LinkPartTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/text/SonePartTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/text/TimeTextConverterTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/utils/AutoCloseableBucketTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/utils/BooleansTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/utils/BucketsTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/utils/JsonTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/utils/ObjectsTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/utils/OptionalsTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/utils/PaginationTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/utils/RequestsTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/utils/StringsTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/BookmarkAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/CreatePostAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/CreateReplyAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/DeletePostAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/DeleteProfileFieldAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/DeleteReplyAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/DismissNotificationAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/DistrustAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/EditAlbumAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/EditImageAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/EditProfileFieldAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/FollowSoneAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/GetLikesAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/GetLinkedElementAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/GetNotificationsAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/GetPostAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/GetReplyAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/GetStatusAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/GetTimesAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/GetTranslationAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/JsonErrorReturnObjectTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/JsonPageBaseTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/JsonPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/JsonReturnObjectTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/LikeAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/LockSoneAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/LoggedInJsonPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/MarkAsKnownAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/MoveProfileFieldAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/TestObjects.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/TrustAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/UnbookmarkAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/UnfollowSoneAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/UnlikeAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/UnlockSoneAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/ajax/UntrustAjaxPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/AboutPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/BookmarkPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/BookmarksPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/CreateAlbumPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/CreatePostPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/CreateReplyPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/CreateSonePageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteAlbumPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteImagePageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/DeletePostPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteProfileFieldPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteReplyPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteSonePageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/DismissNotificationPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/DistrustPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/EditAlbumPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/EditImagePageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/EditProfileFieldPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/EditProfilePageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/FollowSonePageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/GetImagePageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/ImageBrowserPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/IndexPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/KnownSonesPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/LikePageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/LockSonePageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/LoginPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/LogoutPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/MarkAsKnownPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/NewPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/OptionsPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/ReloadingPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/RescuePageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/SearchPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/SoneTemplatePageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/TrustPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/UnbookmarkPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/UnfollowSonePageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/UnlikePageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/UnlockSonePageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/UntrustPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/UploadImagePageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/ViewPostPageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/ViewSonePageTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/web/pages/WebPageTest.kt [new file with mode: 0644]
src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker [new file with mode: 0644]
src/test/resources/net/pterodactylus/sone/core/element-loader.html [new file with mode: 0644]
src/test/resources/net/pterodactylus/sone/core/element-loader2.html [new file with mode: 0644]
src/test/resources/net/pterodactylus/sone/core/element-loader3.html [new file with mode: 0644]
src/test/resources/net/pterodactylus/sone/core/element-loader4.html [new file with mode: 0644]
src/test/resources/net/pterodactylus/sone/main/custom-version.yaml [new file with mode: 0644]
src/test/resources/net/pterodactylus/sone/web/pages/upload-image-invalid-image.png [new file with mode: 0644]
src/test/resources/net/pterodactylus/sone/web/pages/upload-image-value-image.png [new file with mode: 0644]
src/test/resources/version.yaml [new file with mode: 0644]
version.gradle [new file with mode: 0644]

index ea8c4bf..99b31c6 100644 (file)
@@ -1 +1,2 @@
 /target
+/src/generated
index c8e8a00..f3a7df1 100644 (file)
--- a/README.md
+++ b/README.md
@@ -2,19 +2,31 @@
 
 Sone aims to provide social network functionality for [Freenet](https://freenetproject.org/) (also here [on GitHub](https://github.com/freenet/)).
 
+## Compiling
+
+Sone’s build process is handled by [Gradle](https://gradle.org/). Just use the Gradle wrapper that comes with Sone:
+
+    # ./gradlew clean build fatJar
+
+This will resolve Sone’s dependencies, compile Sone, run all the tests, and put the file `sone-jar-with-dependencies.jar` into the `build/libs` directory. This is the file that you can load from Freenet’s plugin manager to run Sone.
+
 ## Installing
 
 ### Prerequisites
 
 For Sone to work you will need a running Freenet node, of course. You will also need the Web of Trust plugin from the official plugins listed on your node’s plugin manager page (*Configuration → Plugins* in the menu).
 
-If you already have a web of trust identity, you can skip the next section.
-
-You will also need to create an identity in the web of trust. Select *Community* from the menu, choose “Generate” and follow the instructions on-screen until your identity has been created.
+You will also need a web of trust identity to use Sone. If you do not already have a web of trust identity, select *Community* from the menu, choose “Generate” and follow the instructions on-screen until your identity has been created.
 
 ### Loading/Installing
 
-For Sone to work you will need a running Freenet node and the WebOfTrust plugin. Loading Sone happens from Freenet’s web interface using the “Add an Unofficial Plugin from Freenet” section from the node’s plugin manager at *Configuration → Plugins*, at the bottom of the page. Enter the key of the plugin (starting with “USK@”) into the text field and press the “Load” button. The plugin should then be downloaded from Freenet and started once it’s ready.
+#### From Freenet
+
+If you just want to run the latest version of Sone you first have to obtain the plugin key from [Sone’s homepage in Freenet](USK@nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI,DuQSUZiI~agF8c-6tjsFFGuZ8eICrzWCILB60nT8KKo,AQACAAE/sone/75/). Now head over to your node’s plugin manager which you can reach from the menu at *Configuration → Plugins*. At the bottom of the page there’s a section called “Add an Unofficial Plugin from Freenet.” Enter the key of the plugin into the text field and press the “Load” button. The plugin should then be downloaded from Freenet and started once it’s ready.
+
+#### From Disk
+
+If you have compiled Sone yourself, there’s a different section on the plugin manager page, called “Add an Unofficial Plugin.” Enter the full path name of the JAR file created by Gradle and press the “Load” button. Sone should then be ready instantly.
 
 The node will remember which plugins you loaded so that you don’t need to do that again after your node restarts (e.g. for updates).
 
@@ -48,6 +60,6 @@ Now, a social network wouldn’t be much fun if you couldn’t talk with other p
 
 When displaying posts and replies, Sone first parses the text. Special elements, such as Freenet URIs and Sone elements with a special syntax, are replaced with formatting that allow your browser to navigate the elements. Sone recognizes the following elements:
 
-* Links to Freenet URIs are linked to as-is. Make sure to separate the URI from surrounding text by whitespace, such as space or line breaks.
+* Links to Freenet URIs are linked to as-is. Make sure to separate the URI from surrounding text by whitespace, such as space or line breaks, or interpunction, such as commas and periods.
 * Links to other Sone’s profiles are added by the prefix “sone://” followed by the ID of the Sone. It is also possible to get the link for a Sone from a post or reply by that Sone; just copy the URL behind the “[link author]” link.
 * Links to other posts are added by the prefix “post://” followed by the ID of the post. You can also find the post ID behind the “[link post]” link below a post.
index 9221dd3..ecdbfdd 100644 (file)
@@ -1,12 +1,15 @@
 group = 'net.pterodactylus'
-version = '0.9.6'
+version = '0.9.7'
 
 buildscript {
+    ext.kotlinVersion = '1.1.51'
     repositories {
         mavenCentral()
     }
     dependencies {
-        classpath group: 'info.solidsoft.gradle.pitest', name: 'gradle-pitest-plugin', version: '1.1.10'
+        classpath group: 'info.solidsoft.gradle.pitest', name: 'gradle-pitest-plugin', version: '1.1.11'
+        classpath group: 'org.jetbrains.kotlin', name: 'kotlin-gradle-plugin', version: kotlinVersion
+        classpath group: 'org.jetbrains.kotlin', name: 'kotlin-noarg', version: kotlinVersion
     }
 }
 
@@ -24,6 +27,8 @@ tasks.withType(JavaCompile) {
        options.encoding = 'UTF-8'
 }
 
+apply plugin: 'kotlin'
+
 configurations {
     provided {
         dependencies.all { dep ->
@@ -38,18 +43,23 @@ dependencies {
     provided group: 'org.freenetproject', name: 'freenet-ext', version: '29'
     provided group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.54'
 
+    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.fasterxml.jackson.core', name: 'jackson-databind', version: '2.1.2'
-    compile group: 'com.google.code.findbugs', name: 'jsr305', version: '2.0.1'
+    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: 'junit', name: 'junit', version: '4.11'
     testCompile group: 'org.mockito', name: 'mockito-core', version: '2.1.0'
-    testCompile group: 'org.jsoup', name: 'jsoup', version: '1.7.1'
     testCompile group: 'org.hamcrest', name: 'hamcrest-all', version: '1.3'
 }
 
+apply from: 'version.gradle'
+
 task fatJar(type: Jar) {
     archiveName = project.name + '-jar-with-dependencies.jar'
     from { (configurations.runtime - configurations.provided).collect { it.isDirectory() ? it : zipTree(it) } }
@@ -72,7 +82,7 @@ javadoc {
 apply plugin: 'jacoco'
 
 jacoco {
-    toolVersion = '0.7.7.201606060606'
+    toolVersion = '0.7.9'
 }
 
 jacocoTestReport.dependsOn test
@@ -92,3 +102,27 @@ findbugs {
 }
 
 apply plugin: 'idea'
+
+task countLinesMain(type: Exec) {
+    executable = 'cloc'
+    args = ['--by-file', '--xml', '--report-file=build/reports/cloc/main.xml', 'src/main']
+    standardOutput = new ByteArrayOutputStream()
+}
+
+task countLinesTest(type: Exec) {
+    executable = 'cloc'
+    args = ['--by-file', '--xml', '--report-file=build/reports/cloc/test.xml', 'src/test']
+    standardOutput = new ByteArrayOutputStream()
+}
+
+task countLines {
+    new File(buildDir, "reports/cloc").mkdirs()
+    dependsOn tasks.countLinesMain
+    dependsOn tasks.countLinesTest
+}
+
+apply plugin: 'kotlin-noarg'
+
+noArg {
+    annotation('net.pterodactylus.sone.main.NoArg')
+}
index 91825a6..66677de 100644 (file)
@@ -40,6 +40,9 @@ import java.util.concurrent.TimeUnit;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
 import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidAlbumFound;
 import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidImageFound;
 import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidParentAlbumFound;
@@ -68,8 +71,8 @@ import net.pterodactylus.sone.data.Profile;
 import net.pterodactylus.sone.data.Profile.Field;
 import net.pterodactylus.sone.data.Reply;
 import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.sone.data.Sone.ShowCustomAvatars;
 import net.pterodactylus.sone.data.Sone.SoneStatus;
+import net.pterodactylus.sone.data.SoneOptions.LoadExternalContent;
 import net.pterodactylus.sone.data.TemporaryImage;
 import net.pterodactylus.sone.database.AlbumBuilder;
 import net.pterodactylus.sone.database.Database;
@@ -314,6 +317,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
        /**
         * {@inheritDocs}
         */
+       @Nonnull
        @Override
        public Collection<Sone> getSones() {
                return database.getSones();
@@ -534,7 +538,8 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         * @return The album with the given ID, or {@code null} if no album with the
         *         given ID exists
         */
-       public Album getAlbum(String albumId) {
+       @Nullable
+       public Album getAlbum(@Nonnull String albumId) {
                return database.getAlbum(albumId).orNull();
        }
 
@@ -549,6 +554,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         *            The ID of the image
         * @return The image with the given ID
         */
+       @Nullable
        public Image getImage(String imageId) {
                return getImage(imageId, true);
        }
@@ -565,6 +571,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         * @return The image with the given ID, or {@code null} if none exists and
         *         none was created
         */
+       @Nullable
        public Image getImage(String imageId, boolean create) {
                Optional<Image> image = database.getImage(imageId);
                if (image.isPresent()) {
@@ -1070,7 +1077,8 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                sone.getOptions().setShowNewSoneNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewSones").getValue(true));
                sone.getOptions().setShowNewPostNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").getValue(true));
                sone.getOptions().setShowNewReplyNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").getValue(true));
-               sone.getOptions().setShowCustomAvatars(ShowCustomAvatars.valueOf(configuration.getStringValue(sonePrefix + "/Options/ShowCustomAvatars").getValue(ShowCustomAvatars.NEVER.name())));
+               sone.getOptions().setShowCustomAvatars(LoadExternalContent.valueOf(configuration.getStringValue(sonePrefix + "/Options/ShowCustomAvatars").getValue(LoadExternalContent.NEVER.name())));
+               sone.getOptions().setLoadLinkedImages(LoadExternalContent.valueOf(configuration.getStringValue(sonePrefix + "/Options/LoadLinkedImages").getValue(LoadExternalContent.NEVER.name())));
 
                /* if we’re still here, Sone was loaded successfully. */
                synchronized (sone) {
@@ -1548,6 +1556,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").setValue(sone.getOptions().isShowNewPostNotifications());
                        configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").setValue(sone.getOptions().isShowNewReplyNotifications());
                        configuration.getStringValue(sonePrefix + "/Options/ShowCustomAvatars").setValue(sone.getOptions().getShowCustomAvatars().name());
+                       configuration.getStringValue(sonePrefix + "/Options/LoadLinkedImages").setValue(sone.getOptions().getLoadLinkedImages().name());
 
                        configuration.save();
 
index a06b5b0..946371b 100644 (file)
@@ -23,6 +23,7 @@ import static java.util.logging.Level.WARNING;
 import static java.util.logging.Logger.getLogger;
 import static net.pterodactylus.sone.freenet.Key.routingKey;
 
+import java.io.IOException;
 import java.net.MalformedURLException;
 import java.util.Collections;
 import java.util.HashMap;
@@ -30,6 +31,8 @@ import java.util.Map;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import javax.annotation.Nonnull;
+
 import net.pterodactylus.sone.core.event.ImageInsertAbortedEvent;
 import net.pterodactylus.sone.core.event.ImageInsertFailedEvent;
 import net.pterodactylus.sone.core.event.ImageInsertFinishedEvent;
@@ -44,6 +47,7 @@ import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
 import freenet.client.ClientMetadata;
+import freenet.client.FetchContext;
 import freenet.client.FetchException;
 import freenet.client.FetchException.FetchExceptionMode;
 import freenet.client.FetchResult;
@@ -51,16 +55,21 @@ import freenet.client.HighLevelSimpleClient;
 import freenet.client.InsertBlock;
 import freenet.client.InsertContext;
 import freenet.client.InsertException;
+import freenet.client.Metadata;
 import freenet.client.async.BaseClientPutter;
 import freenet.client.async.ClientContext;
+import freenet.client.async.ClientGetCallback;
+import freenet.client.async.ClientGetter;
 import freenet.client.async.ClientPutCallback;
 import freenet.client.async.ClientPutter;
+import freenet.client.async.SnoopMetadata;
 import freenet.client.async.USKCallback;
 import freenet.keys.FreenetURI;
 import freenet.keys.InsertableClientSSK;
 import freenet.keys.USK;
 import freenet.node.Node;
 import freenet.node.RequestClient;
+import freenet.node.RequestClientBuilder;
 import freenet.node.RequestStarter;
 import freenet.support.api.Bucket;
 import freenet.support.api.RandomAccessBucket;
@@ -93,17 +102,8 @@ public class FreenetInterface {
        /** The not-Sone-related USK callbacks. */
        private final Map<FreenetURI, USKCallback> uriUskCallbacks = Collections.synchronizedMap(new HashMap<FreenetURI, USKCallback>());
 
-       private final RequestClient imageInserts = new RequestClient() {
-               @Override
-               public boolean persistent() {
-                       return false;
-               }
-
-               @Override
-               public boolean realTimeFlag() {
-                       return true;
-               }
-       };
+       private final RequestClient imageInserts = new RequestClientBuilder().realTime().build();
+       private final RequestClient imageLoader = new RequestClientBuilder().realTime().build();
 
        /**
         * Creates a new Freenet interface.
@@ -148,6 +148,59 @@ public class FreenetInterface {
                }
        }
 
+       public void startFetch(final FreenetURI uri, final BackgroundFetchCallback backgroundFetchCallback) {
+               ClientGetCallback callback = new ClientGetCallback() {
+                       @Override
+                       public void onSuccess(FetchResult result, ClientGetter state) {
+                               try {
+                                       backgroundFetchCallback.loaded(uri, result.getMimeType(), result.asByteArray());
+                               } catch (IOException e) {
+                                       backgroundFetchCallback.failed(uri);
+                               }
+                       }
+
+                       @Override
+                       public void onFailure(FetchException e, ClientGetter state) {
+                               backgroundFetchCallback.failed(uri);
+                       }
+
+                       @Override
+                       public void onResume(ClientContext context) throws ResumeFailedException {
+                               /* do nothing. */
+                       }
+
+                       @Override
+                       public RequestClient getRequestClient() {
+                               return imageLoader;
+                       }
+               };
+               SnoopMetadata snoop = new SnoopMetadata() {
+                       @Override
+                       public boolean snoopMetadata(Metadata meta, ClientContext context) {
+                               String mimeType = meta.getMIMEType();
+                               boolean cancel = (mimeType == null) || backgroundFetchCallback.shouldCancel(uri, mimeType, meta.dataLength());
+                               if (cancel) {
+                                       backgroundFetchCallback.failed(uri);
+                               }
+                               return cancel;
+                       }
+               };
+               FetchContext fetchContext = client.getFetchContext();
+               try {
+                       ClientGetter clientGetter = client.fetch(uri, 2097152, callback, fetchContext, RequestStarter.INTERACTIVE_PRIORITY_CLASS);
+                       clientGetter.setMetaSnoop(snoop);
+                       clientGetter.restart(uri, fetchContext.filterData, node.clientCore.clientContext);
+               } catch (FetchException fe) {
+                       /* stupid exception that can not actually be thrown! */
+               }
+       }
+
+       public interface BackgroundFetchCallback {
+               boolean shouldCancel(@Nonnull FreenetURI uri, @Nonnull String mimeType, long size);
+               void loaded(@Nonnull FreenetURI uri, @Nonnull String mimeType, @Nonnull byte[] data);
+               void failed(@Nonnull FreenetURI uri);
+       }
+
        /**
         * Inserts the image data of the given {@link TemporaryImage} and returns
         * the given insert token that can be used to add listeners or cancel the
index 0f9b8ff..a0ebeba 100644 (file)
@@ -26,6 +26,9 @@ import java.util.Collections;
 import java.util.List;
 import java.util.UUID;
 
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
 import com.google.common.hash.Hasher;
 import com.google.common.hash.Hashing;
 
@@ -80,7 +83,7 @@ public class Profile implements Fingerprintable {
         * @param profile
         *            The profile to copy
         */
-       public Profile(Profile profile) {
+       public Profile(@Nonnull Profile profile) {
                this.sone = profile.sone;
                this.firstName = profile.firstName;
                this.middleName = profile.middleName;
@@ -101,6 +104,7 @@ public class Profile implements Fingerprintable {
         *
         * @return The Sone this profile belongs to
         */
+       @Nonnull
        public Sone getSone() {
                return sone;
        }
@@ -110,6 +114,7 @@ public class Profile implements Fingerprintable {
         *
         * @return The first name
         */
+       @Nullable
        public String getFirstName() {
                return firstName;
        }
@@ -121,7 +126,8 @@ public class Profile implements Fingerprintable {
         *            The first name to set
         * @return This profile (for method chaining)
         */
-       public Profile setFirstName(String firstName) {
+       @Nonnull
+       public Profile setFirstName(@Nullable String firstName) {
                this.firstName = firstName;
                return this;
        }
@@ -131,6 +137,7 @@ public class Profile implements Fingerprintable {
         *
         * @return The middle name
         */
+       @Nullable
        public String getMiddleName() {
                return middleName;
        }
@@ -142,7 +149,8 @@ public class Profile implements Fingerprintable {
         *            The middle name to set
         * @return This profile (for method chaining)
         */
-       public Profile setMiddleName(String middleName) {
+       @Nonnull
+       public Profile setMiddleName(@Nullable String middleName) {
                this.middleName = middleName;
                return this;
        }
@@ -152,6 +160,7 @@ public class Profile implements Fingerprintable {
         *
         * @return The last name
         */
+       @Nullable
        public String getLastName() {
                return lastName;
        }
@@ -163,7 +172,8 @@ public class Profile implements Fingerprintable {
         *            The last name to set
         * @return This profile (for method chaining)
         */
-       public Profile setLastName(String lastName) {
+       @Nonnull
+       public Profile setLastName(@Nullable String lastName) {
                this.lastName = lastName;
                return this;
        }
@@ -173,6 +183,7 @@ public class Profile implements Fingerprintable {
         *
         * @return The day of the birth date (from 1 to 31)
         */
+       @Nullable
        public Integer getBirthDay() {
                return birthDay;
        }
@@ -184,7 +195,8 @@ public class Profile implements Fingerprintable {
         *            The day of the birth date (from 1 to 31)
         * @return This profile (for method chaining)
         */
-       public Profile setBirthDay(Integer birthDay) {
+       @Nonnull
+       public Profile setBirthDay(@Nullable Integer birthDay) {
                this.birthDay = birthDay;
                return this;
        }
@@ -194,6 +206,7 @@ public class Profile implements Fingerprintable {
         *
         * @return The month of the birth date (from 1 to 12)
         */
+       @Nullable
        public Integer getBirthMonth() {
                return birthMonth;
        }
@@ -205,7 +218,8 @@ public class Profile implements Fingerprintable {
         *            The month of the birth date (from 1 to 12)
         * @return This profile (for method chaining)
         */
-       public Profile setBirthMonth(Integer birthMonth) {
+       @Nonnull
+       public Profile setBirthMonth(@Nullable Integer birthMonth) {
                this.birthMonth = birthMonth;
                return this;
        }
@@ -215,6 +229,7 @@ public class Profile implements Fingerprintable {
         *
         * @return The year of the birth date
         */
+       @Nullable
        public Integer getBirthYear() {
                return birthYear;
        }
@@ -225,6 +240,7 @@ public class Profile implements Fingerprintable {
         * @return The ID of the currently selected avatar image, or {@code null} if
         *         no avatar is selected.
         */
+       @Nullable
        public String getAvatar() {
                return avatar;
        }
@@ -237,7 +253,8 @@ public class Profile implements Fingerprintable {
         *            image.
         * @return This Sone
         */
-       public Profile setAvatar(Image avatar) {
+       @Nonnull
+       public Profile setAvatar(@Nullable Image avatar) {
                if (avatar == null) {
                        this.avatar = null;
                        return this;
@@ -254,7 +271,8 @@ public class Profile implements Fingerprintable {
         *            The year of the birth date
         * @return This profile (for method chaining)
         */
-       public Profile setBirthYear(Integer birthYear) {
+       @Nonnull
+       public Profile setBirthYear(@Nullable Integer birthYear) {
                this.birthYear = birthYear;
                return this;
        }
@@ -264,6 +282,7 @@ public class Profile implements Fingerprintable {
         *
         * @return The fields of this profile
         */
+       @Nonnull
        public List<Field> getFields() {
                return new ArrayList<Field>(fields);
        }
@@ -275,7 +294,7 @@ public class Profile implements Fingerprintable {
         *            The field to check for
         * @return {@code true} if this profile contains the field, false otherwise
         */
-       public boolean hasField(Field field) {
+       public boolean hasField(@Nonnull Field field) {
                return fields.contains(field);
        }
 
@@ -287,7 +306,8 @@ public class Profile implements Fingerprintable {
         * @return The field, or {@code null} if this profile does not contain a
         *         field with the given ID
         */
-       public Field getFieldById(String fieldId) {
+       @Nullable
+       public Field getFieldById(@Nonnull String fieldId) {
                checkNotNull(fieldId, "fieldId must not be null");
                for (Field field : fields) {
                        if (field.getId().equals(fieldId)) {
@@ -305,7 +325,8 @@ public class Profile implements Fingerprintable {
         * @return The field, or {@code null} if this profile does not contain a
         *         field with the given name
         */
-       public Field getFieldByName(String fieldName) {
+       @Nullable
+       public Field getFieldByName(@Nonnull String fieldName) {
                for (Field field : fields) {
                        if (field.getName().equals(fieldName)) {
                                return field;
@@ -323,7 +344,8 @@ public class Profile implements Fingerprintable {
         * @throws IllegalArgumentException
         *             if the name is not valid
         */
-       public Field addField(String fieldName) throws IllegalArgumentException {
+       @Nonnull
+       public Field addField(@Nonnull String fieldName) throws IllegalArgumentException {
                checkNotNull(fieldName, "fieldName must not be null");
                if (fieldName.length() == 0) {
                        throw new EmptyFieldName();
@@ -345,7 +367,7 @@ public class Profile implements Fingerprintable {
         * @param field
         *            The field to move up
         */
-       public void moveFieldUp(Field field) {
+       public void moveFieldUp(@Nonnull Field field) {
                checkNotNull(field, "field must not be null");
                checkArgument(hasField(field), "field must belong to this profile");
                checkArgument(getFieldIndex(field) > 0, "field index must be > 0");
@@ -362,7 +384,7 @@ public class Profile implements Fingerprintable {
         * @param field
         *            The field to move down
         */
-       public void moveFieldDown(Field field) {
+       public void moveFieldDown(@Nonnull Field field) {
                checkNotNull(field, "field must not be null");
                checkArgument(hasField(field), "field must belong to this profile");
                checkArgument(getFieldIndex(field) < fields.size() - 1, "field index must be < " + (fields.size() - 1));
@@ -377,7 +399,7 @@ public class Profile implements Fingerprintable {
         * @param field
         *            The field to remove
         */
-       public void removeField(Field field) {
+       public void removeField(@Nonnull Field field) {
                checkNotNull(field, "field must not be null");
                checkArgument(hasField(field), "field must belong to this profile");
                fields.remove(field);
@@ -395,7 +417,7 @@ public class Profile implements Fingerprintable {
         * @return The index of the field, or {@code -1} if there is no field with
         *         the given name
         */
-       private int getFieldIndex(Field field) {
+       private int getFieldIndex(@Nonnull Field field) {
                return fields.indexOf(field);
        }
 
@@ -470,7 +492,7 @@ public class Profile implements Fingerprintable {
                 * @param id
                 *            The ID of the field
                 */
-               private Field(String id) {
+               private Field(@Nonnull String id) {
                        this.id = checkNotNull(id, "id must not be null");
                }
 
@@ -479,6 +501,7 @@ public class Profile implements Fingerprintable {
                 *
                 * @return The ID of this field
                 */
+               @Nonnull
                public String getId() {
                        return id;
                }
@@ -488,6 +511,7 @@ public class Profile implements Fingerprintable {
                 *
                 * @return The name of this field
                 */
+               @Nonnull
                public String getName() {
                        return name;
                }
@@ -501,7 +525,8 @@ public class Profile implements Fingerprintable {
                 *            The new name of this field
                 * @return This field
                 */
-               public Field setName(String name) {
+               @Nonnull
+               public Field setName(@Nonnull String name) {
                        checkNotNull(name, "name must not be null");
                        checkArgument(getFieldByName(name) == null, "name must be unique");
                        this.name = name;
@@ -513,6 +538,7 @@ public class Profile implements Fingerprintable {
                 *
                 * @return The value of this field
                 */
+               @Nullable
                public String getValue() {
                        return value;
                }
@@ -526,7 +552,8 @@ public class Profile implements Fingerprintable {
                 *            The new value of this field
                 * @return This field
                 */
-               public Field setValue(String value) {
+               @Nonnull
+               public Field setValue(@Nullable String value) {
                        this.value = value;
                        return this;
                }
index 68c8114..b761c7b 100644 (file)
@@ -69,30 +69,6 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
                downloading,
        }
 
-       /**
-        * The possible values for the “show custom avatars” option.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
-        */
-       public static enum ShowCustomAvatars {
-
-               /** Never show custom avatars. */
-               NEVER,
-
-               /** Only show custom avatars of followed Sones. */
-               FOLLOWED,
-
-               /** Only show custom avatars of Sones you manually trust. */
-               MANUALLY_TRUSTED,
-
-               /** Only show custom avatars of automatically trusted Sones. */
-               TRUSTED,
-
-               /** Always show custom avatars. */
-               ALWAYS,
-
-       }
-
        /** comparator that sorts Sones by their nice name. */
        public static final Comparator<Sone> NICE_NAME_COMPARATOR = new Comparator<Sone>() {
 
@@ -193,6 +169,7 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
         *
         * @return The identity of this Sone
         */
+       @Nonnull
        Identity getIdentity();
 
        /**
@@ -200,6 +177,7 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
         *
         * @return The name of this Sone
         */
+       @Nonnull
        String getName();
 
        /**
@@ -214,6 +192,7 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
         *
         * @return The request URI of this Sone
         */
+       @Nonnull
        FreenetURI getRequestUri();
 
        /**
@@ -221,6 +200,7 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
         *
         * @return The insert URI of this Sone
         */
+       @Nullable
        FreenetURI getInsertUri();
 
        /**
@@ -254,6 +234,7 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
         *              The time of the update (in milliseconds since Jan 1, 1970 UTC)
         * @return This Sone (for method chaining)
         */
+       @Nonnull
        Sone setTime(long time);
 
        /**
@@ -261,6 +242,7 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
         *
         * @return The status of this Sone
         */
+       @Nonnull
        SoneStatus getStatus();
 
        /**
@@ -272,7 +254,8 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
         * @throws IllegalArgumentException
         *              if {@code status} is {@code null}
         */
-       Sone setStatus(SoneStatus status);
+       @Nonnull
+       Sone setStatus(@Nonnull SoneStatus status);
 
        /**
         * Returns a copy of the profile. If you want to update values in the profile
@@ -281,6 +264,7 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
         *
         * @return A copy of the profile
         */
+       @Nonnull
        Profile getProfile();
 
        /**
@@ -291,13 +275,14 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
         * @param profile
         *              The profile to set
         */
-       void setProfile(Profile profile);
+       void setProfile(@Nonnull Profile profile);
 
        /**
         * Returns the client used by this Sone.
         *
         * @return The client used by this Sone, or {@code null}
         */
+       @Nullable
        Client getClient();
 
        /**
@@ -307,7 +292,8 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
         *              The client used by this Sone, or {@code null}
         * @return This Sone (for method chaining)
         */
-       Sone setClient(Client client);
+       @Nonnull
+       Sone setClient(@Nullable Client client);
 
        /**
         * Returns whether this Sone is known.
@@ -323,6 +309,7 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
         *              {@code true} if this Sone is known, {@code false} otherwise
         * @return This Sone
         */
+       @Nonnull
        Sone setKnown(boolean known);
 
        /**
@@ -330,6 +317,7 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
         *
         * @return The friend Sones of this Sone
         */
+       @Nonnull
        Collection<String> getFriends();
 
        /**
@@ -340,13 +328,14 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
         * @return {@code true} if this Sone has the given Sone as a friend, {@code
         *         false} otherwise
         */
-       boolean hasFriend(String friendSoneId);
+       boolean hasFriend(@Nonnull String friendSoneId);
 
        /**
         * Returns the list of posts of this Sone, sorted by time, newest first.
         *
         * @return All posts of this Sone
         */
+       @Nonnull
        List<Post> getPosts();
 
        /**
@@ -356,7 +345,8 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
         *              The new (and only) posts of this Sone
         * @return This Sone (for method chaining)
         */
-       Sone setPosts(Collection<Post> posts);
+       @Nonnull
+       Sone setPosts(@Nonnull Collection<Post> posts);
 
        /**
         * Adds the given post to this Sone. The post will not be added if its {@link
@@ -365,7 +355,7 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
         * @param post
         *              The post to add
         */
-       void addPost(Post post);
+       void addPost(@Nonnull Post post);
 
        /**
         * Removes the given post from this Sone.
@@ -373,13 +363,14 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
         * @param post
         *              The post to remove
         */
-       void removePost(Post post);
+       void removePost(@Nonnull Post post);
 
        /**
         * Returns all replies this Sone made.
         *
         * @return All replies this Sone made
         */
+       @Nonnull
        Set<PostReply> getReplies();
 
        /**
@@ -389,7 +380,8 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
         *              The new (and only) replies of this Sone
         * @return This Sone (for method chaining)
         */
-       Sone setReplies(Collection<PostReply> replies);
+       @Nonnull
+       Sone setReplies(@Nonnull Collection<PostReply> replies);
 
        /**
         * Adds a reply to this Sone. If the given reply was not made by this Sone,
@@ -398,7 +390,7 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
         * @param reply
         *              The reply to add
         */
-       void addReply(PostReply reply);
+       void addReply(@Nonnull PostReply reply);
 
        /**
         * Removes a reply from this Sone.
@@ -406,13 +398,14 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
         * @param reply
         *              The reply to remove
         */
-       void removeReply(PostReply reply);
+       void removeReply(@Nonnull PostReply reply);
 
        /**
         * Returns the IDs of all liked posts.
         *
         * @return All liked posts’ IDs
         */
+       @Nonnull
        Set<String> getLikedPostIds();
 
        /**
@@ -422,7 +415,8 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
         *              All liked posts’ IDs
         * @return This Sone (for method chaining)
         */
-       Sone setLikePostIds(Set<String> likedPostIds);
+       @Nonnull
+       Sone setLikePostIds(@Nonnull Set<String> likedPostIds);
 
        /**
         * Checks whether the given post ID is liked by this Sone.
@@ -432,7 +426,7 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
         * @return {@code true} if this Sone likes the given post, {@code false}
         *         otherwise
         */
-       boolean isLikedPostId(String postId);
+       boolean isLikedPostId(@Nonnull String postId);
 
        /**
         * Adds the given post ID to the list of posts this Sone likes.
@@ -441,22 +435,23 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
         *              The ID of the post
         * @return This Sone (for method chaining)
         */
-       Sone addLikedPostId(String postId);
+       @Nonnull
+       Sone addLikedPostId(@Nonnull String postId);
 
        /**
         * Removes the given post ID from the list of posts this Sone likes.
         *
         * @param postId
         *              The ID of the post
-        * @return This Sone (for method chaining)
         */
-       Sone removeLikedPostId(String postId);
+       void removeLikedPostId(@Nonnull String postId);
 
        /**
         * Returns the IDs of all liked replies.
         *
         * @return All liked replies’ IDs
         */
+       @Nonnull
        Set<String> getLikedReplyIds();
 
        /**
@@ -466,7 +461,8 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
         *              All liked replies’ IDs
         * @return This Sone (for method chaining)
         */
-       Sone setLikeReplyIds(Set<String> likedReplyIds);
+       @Nonnull
+       Sone setLikeReplyIds(@Nonnull Set<String> likedReplyIds);
 
        /**
         * Checks whether the given reply ID is liked by this Sone.
@@ -476,7 +472,7 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
         * @return {@code true} if this Sone likes the given reply, {@code false}
         *         otherwise
         */
-       boolean isLikedReplyId(String replyId);
+       boolean isLikedReplyId(@Nonnull String replyId);
 
        /**
         * Adds the given reply ID to the list of replies this Sone likes.
@@ -485,22 +481,23 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
         *              The ID of the reply
         * @return This Sone (for method chaining)
         */
-       Sone addLikedReplyId(String replyId);
+       @Nonnull
+       Sone addLikedReplyId(@Nonnull String replyId);
 
        /**
         * Removes the given post ID from the list of replies this Sone likes.
         *
         * @param replyId
         *              The ID of the reply
-        * @return This Sone (for method chaining)
         */
-       Sone removeLikedReplyId(String replyId);
+       void removeLikedReplyId(@Nonnull String replyId);
 
        /**
         * Returns the root album that contains all visible albums of this Sone.
         *
         * @return The root album of this Sone
         */
+       @Nonnull
        Album getRootAlbum();
 
        /**
@@ -508,6 +505,7 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
         *
         * @return The options of this Sone
         */
+       @Nonnull
        SoneOptions getOptions();
 
        /**
@@ -517,6 +515,6 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
         *              The options of this Sone
         */
        /* TODO - remove this method again, maybe add an option provider */
-       void setOptions(SoneOptions options);
+       void setOptions(@Nonnull SoneOptions options);
 
 }
index 819e94a..5f05447 100644 (file)
@@ -1,8 +1,8 @@
 package net.pterodactylus.sone.data;
 
-import static net.pterodactylus.sone.data.Sone.ShowCustomAvatars.NEVER;
+import static net.pterodactylus.sone.data.SoneOptions.LoadExternalContent.NEVER;
 
-import net.pterodactylus.sone.data.Sone.ShowCustomAvatars;
+import javax.annotation.Nonnull;
 
 /**
  * All Sone-specific options.
@@ -26,8 +26,35 @@ public interface SoneOptions {
        boolean isShowNewReplyNotifications();
        void setShowNewReplyNotifications(boolean showNewReplyNotifications);
 
-       ShowCustomAvatars getShowCustomAvatars();
-       void setShowCustomAvatars(ShowCustomAvatars showCustomAvatars);
+       LoadExternalContent getShowCustomAvatars();
+       void setShowCustomAvatars(LoadExternalContent showCustomAvatars);
+
+       @Nonnull LoadExternalContent getLoadLinkedImages();
+       void setLoadLinkedImages(@Nonnull LoadExternalContent loadLinkedImages);
+
+       /**
+        * 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 {
+
+               /** Never show custom avatars. */
+               NEVER,
+
+               /** Only show custom avatars of followed Sones. */
+               FOLLOWED,
+
+               /** Only show custom avatars of Sones you manually trust. */
+               MANUALLY_TRUSTED,
+
+               /** Only show custom avatars of automatically trusted Sones. */
+               TRUSTED,
+
+               /** Always show custom avatars. */
+               ALWAYS,
+
+       }
 
        /**
         * {@link SoneOptions} implementation.
@@ -41,7 +68,8 @@ public interface SoneOptions {
                private boolean showNewSoneNotifications = true;
                private boolean showNewPostNotifications = true;
                private boolean showNewReplyNotifications = true;
-               private ShowCustomAvatars showCustomAvatars = NEVER;
+               private LoadExternalContent showCustomAvatars = NEVER;
+               private LoadExternalContent loadLinkedImages = NEVER;
 
                @Override
                public boolean isAutoFollow() {
@@ -94,15 +122,26 @@ public interface SoneOptions {
                }
 
                @Override
-               public ShowCustomAvatars getShowCustomAvatars() {
+               public LoadExternalContent getShowCustomAvatars() {
                        return showCustomAvatars;
                }
 
                @Override
-               public void setShowCustomAvatars(ShowCustomAvatars showCustomAvatars) {
+               public void setShowCustomAvatars(LoadExternalContent showCustomAvatars) {
                        this.showCustomAvatars = showCustomAvatars;
                }
 
+               @Nonnull
+               @Override
+               public LoadExternalContent getLoadLinkedImages() {
+                       return loadLinkedImages;
+               }
+
+               @Override
+               public void setLoadLinkedImages(@Nonnull LoadExternalContent loadLinkedImages) {
+                       this.loadLinkedImages = loadLinkedImages;
+               }
+
        }
 
 }
index e9a0c57..79766b5 100644 (file)
@@ -184,8 +184,7 @@ public class IdOnlySone implements Sone {
        }
 
        @Override
-       public Sone removeLikedPostId(String postId) {
-               return this;
+       public void removeLikedPostId(String postId) {
        }
 
        @Override
@@ -209,8 +208,7 @@ public class IdOnlySone implements Sone {
        }
 
        @Override
-       public Sone removeLikedReplyId(String replyId) {
-               return this;
+       public void removeLikedReplyId(String replyId) {
        }
 
        @Override
index 04a5bcc..0494607 100644 (file)
@@ -31,6 +31,9 @@ import java.util.concurrent.CopyOnWriteArraySet;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
 import net.pterodactylus.sone.data.Album;
 import net.pterodactylus.sone.data.Client;
 import net.pterodactylus.sone.data.Post;
@@ -134,6 +137,7 @@ public class SoneImpl implements Sone {
         *
         * @return The identity of this Sone
         */
+       @Nonnull
        public String getId() {
                return id;
        }
@@ -143,6 +147,7 @@ public class SoneImpl implements Sone {
         *
         * @return The identity of this Sone
         */
+       @Nonnull
        public Identity getIdentity() {
                return identity;
        }
@@ -152,6 +157,7 @@ public class SoneImpl implements Sone {
         *
         * @return The name of this Sone
         */
+       @Nonnull
        public String getName() {
                return (identity != null) ? identity.getNickname() : null;
        }
@@ -170,6 +176,7 @@ public class SoneImpl implements Sone {
         *
         * @return The request URI of this Sone
         */
+       @Nonnull
        public FreenetURI getRequestUri() {
                try {
                        return new FreenetURI(getIdentity().getRequestUri())
@@ -189,6 +196,7 @@ public class SoneImpl implements Sone {
         *
         * @return The insert URI of this Sone
         */
+       @Nullable
        public FreenetURI getInsertUri() {
                if (!isLocal()) {
                        return null;
@@ -244,6 +252,7 @@ public class SoneImpl implements Sone {
         *              The time of the update (in milliseconds since Jan 1, 1970 UTC)
         * @return This Sone (for method chaining)
         */
+       @Nonnull
        public Sone setTime(long time) {
                this.time = time;
                return this;
@@ -254,6 +263,7 @@ public class SoneImpl implements Sone {
         *
         * @return The status of this Sone
         */
+       @Nonnull
        public SoneStatus getStatus() {
                return status;
        }
@@ -267,7 +277,8 @@ public class SoneImpl implements Sone {
         * @throws IllegalArgumentException
         *              if {@code status} is {@code null}
         */
-       public Sone setStatus(SoneStatus status) {
+       @Nonnull
+       public Sone setStatus(@Nonnull SoneStatus status) {
                this.status = checkNotNull(status, "status must not be null");
                return this;
        }
@@ -279,6 +290,7 @@ public class SoneImpl implements Sone {
         *
         * @return A copy of the profile
         */
+       @Nonnull
        public Profile getProfile() {
                return new Profile(profile);
        }
@@ -291,7 +303,7 @@ public class SoneImpl implements Sone {
         * @param profile
         *              The profile to set
         */
-       public void setProfile(Profile profile) {
+       public void setProfile(@Nonnull Profile profile) {
                this.profile = new Profile(profile);
        }
 
@@ -300,6 +312,7 @@ public class SoneImpl implements Sone {
         *
         * @return The client used by this Sone, or {@code null}
         */
+       @Nullable
        public Client getClient() {
                return client;
        }
@@ -311,7 +324,8 @@ public class SoneImpl implements Sone {
         *              The client used by this Sone, or {@code null}
         * @return This Sone (for method chaining)
         */
-       public Sone setClient(Client client) {
+       @Nonnull
+       public Sone setClient(@Nullable Client client) {
                this.client = client;
                return this;
        }
@@ -332,6 +346,7 @@ public class SoneImpl implements Sone {
         *              {@code true} if this Sone is known, {@code false} otherwise
         * @return This Sone
         */
+       @Nonnull
        public Sone setKnown(boolean known) {
                this.known = known;
                return this;
@@ -342,6 +357,7 @@ public class SoneImpl implements Sone {
         *
         * @return The friend Sones of this Sone
         */
+       @Nonnull
        public Collection<String> getFriends() {
                return database.getFriends(this);
        }
@@ -354,7 +370,7 @@ public class SoneImpl implements Sone {
         * @return {@code true} if this Sone has the given Sone as a friend, {@code
         *         false} otherwise
         */
-       public boolean hasFriend(String friendSoneId) {
+       public boolean hasFriend(@Nonnull String friendSoneId) {
                return database.isFriend(this, friendSoneId);
        }
 
@@ -363,6 +379,7 @@ public class SoneImpl implements Sone {
         *
         * @return All posts of this Sone
         */
+       @Nonnull
        public List<Post> getPosts() {
                List<Post> sortedPosts;
                synchronized (this) {
@@ -379,7 +396,8 @@ public class SoneImpl implements Sone {
         *              The new (and only) posts of this Sone
         * @return This Sone (for method chaining)
         */
-       public Sone setPosts(Collection<Post> posts) {
+       @Nonnull
+       public Sone setPosts(@Nonnull Collection<Post> posts) {
                synchronized (this) {
                        this.posts.clear();
                        this.posts.addAll(posts);
@@ -394,7 +412,7 @@ public class SoneImpl implements Sone {
         * @param post
         *              The post to add
         */
-       public void addPost(Post post) {
+       public void addPost(@Nonnull Post post) {
                if (post.getSone().equals(this) && posts.add(post)) {
                        logger.log(Level.FINEST, String.format("Adding %s to “%s”.", post, getName()));
                }
@@ -406,7 +424,7 @@ public class SoneImpl implements Sone {
         * @param post
         *              The post to remove
         */
-       public void removePost(Post post) {
+       public void removePost(@Nonnull Post post) {
                if (post.getSone().equals(this)) {
                        posts.remove(post);
                }
@@ -417,6 +435,7 @@ public class SoneImpl implements Sone {
         *
         * @return All replies this Sone made
         */
+       @Nonnull
        public Set<PostReply> getReplies() {
                return Collections.unmodifiableSet(replies);
        }
@@ -428,7 +447,8 @@ public class SoneImpl implements Sone {
         *              The new (and only) replies of this Sone
         * @return This Sone (for method chaining)
         */
-       public Sone setReplies(Collection<PostReply> replies) {
+       @Nonnull
+       public Sone setReplies(@Nonnull Collection<PostReply> replies) {
                this.replies.clear();
                this.replies.addAll(replies);
                return this;
@@ -441,7 +461,7 @@ public class SoneImpl implements Sone {
         * @param reply
         *              The reply to add
         */
-       public void addReply(PostReply reply) {
+       public void addReply(@Nonnull PostReply reply) {
                if (reply.getSone().equals(this)) {
                        replies.add(reply);
                }
@@ -453,7 +473,7 @@ public class SoneImpl implements Sone {
         * @param reply
         *              The reply to remove
         */
-       public void removeReply(PostReply reply) {
+       public void removeReply(@Nonnull PostReply reply) {
                if (reply.getSone().equals(this)) {
                        replies.remove(reply);
                }
@@ -464,6 +484,7 @@ public class SoneImpl implements Sone {
         *
         * @return All liked posts’ IDs
         */
+       @Nonnull
        public Set<String> getLikedPostIds() {
                return Collections.unmodifiableSet(likedPostIds);
        }
@@ -475,7 +496,8 @@ public class SoneImpl implements Sone {
         *              All liked posts’ IDs
         * @return This Sone (for method chaining)
         */
-       public Sone setLikePostIds(Set<String> likedPostIds) {
+       @Nonnull
+       public Sone setLikePostIds(@Nonnull Set<String> likedPostIds) {
                this.likedPostIds.clear();
                this.likedPostIds.addAll(likedPostIds);
                return this;
@@ -489,7 +511,7 @@ public class SoneImpl implements Sone {
         * @return {@code true} if this Sone likes the given post, {@code false}
         *         otherwise
         */
-       public boolean isLikedPostId(String postId) {
+       public boolean isLikedPostId(@Nonnull String postId) {
                return likedPostIds.contains(postId);
        }
 
@@ -500,7 +522,8 @@ public class SoneImpl implements Sone {
         *              The ID of the post
         * @return This Sone (for method chaining)
         */
-       public Sone addLikedPostId(String postId) {
+       @Nonnull
+       public Sone addLikedPostId(@Nonnull String postId) {
                likedPostIds.add(postId);
                return this;
        }
@@ -510,11 +533,9 @@ public class SoneImpl implements Sone {
         *
         * @param postId
         *              The ID of the post
-        * @return This Sone (for method chaining)
         */
-       public Sone removeLikedPostId(String postId) {
+       public void removeLikedPostId(@Nonnull String postId) {
                likedPostIds.remove(postId);
-               return this;
        }
 
        /**
@@ -522,6 +543,7 @@ public class SoneImpl implements Sone {
         *
         * @return All liked replies’ IDs
         */
+       @Nonnull
        public Set<String> getLikedReplyIds() {
                return Collections.unmodifiableSet(likedReplyIds);
        }
@@ -533,7 +555,8 @@ public class SoneImpl implements Sone {
         *              All liked replies’ IDs
         * @return This Sone (for method chaining)
         */
-       public Sone setLikeReplyIds(Set<String> likedReplyIds) {
+       @Nonnull
+       public Sone setLikeReplyIds(@Nonnull Set<String> likedReplyIds) {
                this.likedReplyIds.clear();
                this.likedReplyIds.addAll(likedReplyIds);
                return this;
@@ -547,7 +570,7 @@ public class SoneImpl implements Sone {
         * @return {@code true} if this Sone likes the given reply, {@code false}
         *         otherwise
         */
-       public boolean isLikedReplyId(String replyId) {
+       public boolean isLikedReplyId(@Nonnull String replyId) {
                return likedReplyIds.contains(replyId);
        }
 
@@ -558,7 +581,8 @@ public class SoneImpl implements Sone {
         *              The ID of the reply
         * @return This Sone (for method chaining)
         */
-       public Sone addLikedReplyId(String replyId) {
+       @Nonnull
+       public Sone addLikedReplyId(@Nonnull String replyId) {
                likedReplyIds.add(replyId);
                return this;
        }
@@ -568,11 +592,9 @@ public class SoneImpl implements Sone {
         *
         * @param replyId
         *              The ID of the reply
-        * @return This Sone (for method chaining)
         */
-       public Sone removeLikedReplyId(String replyId) {
+       public void removeLikedReplyId(@Nonnull String replyId) {
                likedReplyIds.remove(replyId);
-               return this;
        }
 
        /**
@@ -580,6 +602,7 @@ public class SoneImpl implements Sone {
         *
         * @return The root album of this Sone
         */
+       @Nonnull
        public Album getRootAlbum() {
                return rootAlbum;
        }
@@ -589,6 +612,7 @@ public class SoneImpl implements Sone {
         *
         * @return The options of this Sone
         */
+       @Nonnull
        public SoneOptions getOptions() {
                return options;
        }
@@ -600,7 +624,7 @@ public class SoneImpl implements Sone {
         *              The options of this Sone
         */
        /* TODO - remove this method again, maybe add an option provider */
-       public void setOptions(SoneOptions options) {
+       public void setOptions(@Nonnull SoneOptions options) {
                this.options = options;
        }
 
index a39ceff..69f7eaf 100644 (file)
@@ -19,6 +19,8 @@ package net.pterodactylus.sone.database;
 
 import java.util.Collection;
 
+import javax.annotation.Nonnull;
+
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Sone;
 
@@ -51,6 +53,7 @@ public interface SoneProvider {
         *
         * @return All Sones
         */
+       @Nonnull
        public Collection<Sone> getSones();
 
        /**
index b2264a5..98a43ab 100644 (file)
@@ -25,7 +25,6 @@ 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.Reply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.freenet.SimpleFieldSetBuilder;
 import net.pterodactylus.sone.freenet.fcp.AbstractCommand;
@@ -33,12 +32,11 @@ import net.pterodactylus.sone.freenet.fcp.Command;
 import net.pterodactylus.sone.freenet.fcp.FcpException;
 import net.pterodactylus.sone.template.SoneAccessor;
 
-import com.google.common.base.Optional;
-import com.google.common.collect.Collections2;
-
 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.
@@ -114,7 +112,7 @@ public abstract class AbstractSoneCommand extends AbstractCommand {
         * @return The encoded text
         */
        protected static String encodeString(String text) {
-               return text.replaceAll("\\\\", "\\\\").replaceAll("\n", "\\\\n").replaceAll("\r", "\\\\r");
+               return text.replaceAll("\\\\", "\\\\\\\\").replaceAll("\n", "\\\\n").replaceAll("\r", "\\\\r");
        }
 
        /**
@@ -164,7 +162,7 @@ public abstract class AbstractSoneCommand extends AbstractCommand {
                        throw new FcpException("Could not load Sone ID from “" + parameterName + "”.");
                }
                Optional<Sone> sone = core.getSone(soneId);
-               if ((mandatory && !sone.isPresent()) || (mandatory && sone.isPresent() && (localOnly && !sone.get().isLocal()))) {
+               if ((mandatory && !sone.isPresent()) || (sone.isPresent() && localOnly && !sone.get().isLocal())) {
                        throw new FcpException("Could not load Sone from “" + soneId + "”.");
                }
                return sone;
@@ -239,6 +237,7 @@ public abstract class AbstractSoneCommand extends AbstractCommand {
        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());
@@ -274,10 +273,7 @@ public abstract class AbstractSoneCommand extends AbstractCommand {
                soneBuilder.put(prefix + "Count", sones.size());
                for (Sone sone : sones) {
                        String sonePrefix = prefix + soneIndex++ + ".";
-                       soneBuilder.put(sonePrefix + "ID", sone.getId());
-                       soneBuilder.put(sonePrefix + "Name", sone.getName());
-                       soneBuilder.put(sonePrefix + "NiceName", SoneAccessor.getNiceName(sone));
-                       soneBuilder.put(sonePrefix + "Time", sone.getTime());
+                       soneBuilder.put(encodeSone(sone, sonePrefix, Optional.<Sone>absent()));
                }
 
                return soneBuilder.get();
@@ -337,9 +333,6 @@ public abstract class AbstractSoneCommand extends AbstractCommand {
                for (Post post : posts) {
                        String postPrefix = prefix + postIndex++;
                        postBuilder.put(encodePost(post, postPrefix + ".", includeReplies));
-                       if (includeReplies) {
-                               postBuilder.put(encodeReplies(Collections2.filter(core.getReplies(post.getId()), Reply.FUTURE_REPLY_FILTER), postPrefix + "."));
-                       }
                }
 
                return postBuilder.get();
@@ -355,7 +348,7 @@ public abstract class AbstractSoneCommand extends AbstractCommand {
         *            {@code null})
         * @return The simple field set containing the replies
         */
-       protected static SimpleFieldSet encodeReplies(Collection<? extends PostReply> replies, String prefix) {
+       protected SimpleFieldSet encodeReplies(Collection<? extends PostReply> replies, String prefix) {
                SimpleFieldSetBuilder replyBuilder = new SimpleFieldSetBuilder();
 
                int replyIndex = 0;
@@ -366,6 +359,7 @@ public abstract class AbstractSoneCommand extends AbstractCommand {
                        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();
index 6f1e82c..a3f709e 100644 (file)
@@ -25,12 +25,11 @@ import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.freenet.SimpleFieldSetBuilder;
 import net.pterodactylus.sone.freenet.fcp.FcpException;
 import freenet.support.SimpleFieldSet;
-import freenet.support.api.Bucket;
 
 /**
  * FCP command that creates a new {@link Post}.
  *
- * @see Core#createPost(Sone, Sone, String)
+ * @see Core#createPost(Sone, Optional, String)
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class CreatePostCommand extends AbstractSoneCommand {
@@ -49,7 +48,7 @@ public class CreatePostCommand extends AbstractSoneCommand {
         * {@inheritDoc}
         */
        @Override
-       public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException {
+       public Response execute(SimpleFieldSet parameters) throws FcpException {
                Sone sone = getSone(parameters, "Sone", true);
                String text = getString(parameters, "Text");
                Sone recipient = null;
index 9369529..d268a07 100644 (file)
@@ -25,7 +25,6 @@ import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.freenet.SimpleFieldSetBuilder;
 import net.pterodactylus.sone.freenet.fcp.FcpException;
 import freenet.support.SimpleFieldSet;
-import freenet.support.api.Bucket;
 
 /**
  * FCP command that creates a new {@link Reply}.
@@ -49,7 +48,7 @@ public class CreateReplyCommand extends AbstractSoneCommand {
         * {@inheritDoc}
         */
        @Override
-       public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException {
+       public Response execute(SimpleFieldSet parameters) throws FcpException {
                Sone sone = getSone(parameters, "Sone", true);
                Post post = getPost(parameters, "Post");
                String text = getString(parameters, "Text");
index e68cbb5..b5ace1d 100644 (file)
@@ -22,7 +22,6 @@ import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.freenet.SimpleFieldSetBuilder;
 import net.pterodactylus.sone.freenet.fcp.FcpException;
 import freenet.support.SimpleFieldSet;
-import freenet.support.api.Bucket;
 
 /**
  * FCP command that deletes a {@link Post}.
@@ -46,11 +45,12 @@ public class DeletePostCommand extends AbstractSoneCommand {
         * {@inheritDoc}
         */
        @Override
-       public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException {
+       public Response execute(SimpleFieldSet parameters) throws FcpException {
                Post post = getPost(parameters, "Post");
                if (!post.getSone().isLocal()) {
                        return new ErrorResponse(401, "Not allowed.");
                }
+               getCore().deletePost(post);
                return new Response("PostDeleted", new SimpleFieldSetBuilder().get());
        }
 
index 00a5b97..d75603e 100644 (file)
@@ -22,7 +22,6 @@ import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.freenet.SimpleFieldSetBuilder;
 import net.pterodactylus.sone.freenet.fcp.FcpException;
 import freenet.support.SimpleFieldSet;
-import freenet.support.api.Bucket;
 
 /**
  * FCP command that deletes a {@link PostReply}.
@@ -46,11 +45,12 @@ public class DeleteReplyCommand extends AbstractSoneCommand {
         * {@inheritDoc}
         */
        @Override
-       public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException {
+       public Response execute(SimpleFieldSet parameters) throws FcpException {
                PostReply reply = getReply(parameters, "Reply");
                if (!reply.getSone().isLocal()) {
                        return new ErrorResponse(401, "Not allowed.");
                }
+               getCore().deleteReply(reply);
                return new Response("ReplyDeleted", new SimpleFieldSetBuilder().get());
        }
 
index 440cb4c..6f28518 100644 (file)
@@ -19,8 +19,10 @@ package net.pterodactylus.sone.fcp;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static java.util.logging.Logger.getLogger;
+import static net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.NO;
+import static net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.WRITING;
+import static net.pterodactylus.sone.freenet.fcp.Command.AccessType.RESTRICTED_FCP;
 
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -28,6 +30,9 @@ import java.util.concurrent.atomic.AtomicReference;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import javax.annotation.Nonnull;
+import javax.inject.Singleton;
+
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.fcp.event.FcpInterfaceActivatedEvent;
 import net.pterodactylus.sone.fcp.event.FcpInterfaceDeactivatedEvent;
@@ -45,7 +50,6 @@ import freenet.support.api.Bucket;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.eventbus.Subscribe;
 import com.google.inject.Inject;
-import com.google.inject.Singleton;
 
 /**
  * Implementation of an FCP interface for other clients or plugins to
@@ -84,7 +88,8 @@ public class FcpInterface {
        private final AtomicReference<FullAccessRequired> fullAccessRequired = new AtomicReference<FullAccessRequired>(FullAccessRequired.ALWAYS);
 
        /** All available FCP commands. */
-       private final Map<String, AbstractSoneCommand> commands = Collections.synchronizedMap(new HashMap<String, AbstractSoneCommand>());
+       private final Map<String, AbstractSoneCommand> commands;
+       private final AccessAuthorizer accessAuthorizer;
 
        /**
         * Creates a new FCP interface.
@@ -93,22 +98,9 @@ public class FcpInterface {
         *            The core
         */
        @Inject
-       public FcpInterface(Core core) {
-               commands.put("Version", new VersionCommand(core));
-               commands.put("GetLocalSones", new GetLocalSonesCommand(core));
-               commands.put("GetSones", new GetSonesCommand(core));
-               commands.put("GetSone", new GetSoneCommand(core));
-               commands.put("GetPost", new GetPostCommand(core));
-               commands.put("GetPosts", new GetPostsCommand(core));
-               commands.put("GetPostFeed", new GetPostFeedCommand(core));
-               commands.put("LockSone", new LockSoneCommand(core));
-               commands.put("UnlockSone", new UnlockSoneCommand(core));
-               commands.put("LikePost", new LikePostCommand(core));
-               commands.put("LikeReply", new LikeReplyCommand(core));
-               commands.put("CreatePost", new CreatePostCommand(core));
-               commands.put("CreateReply", new CreateReplyCommand(core));
-               commands.put("DeletePost", new DeletePostCommand(core));
-               commands.put("DeleteReply", new DeleteReplyCommand(core));
+       public FcpInterface(Core core, CommandSupplier commandSupplier, AccessAuthorizer accessAuthorizer) {
+               commands = commandSupplier.supplyCommands(core);
+               this.accessAuthorizer = accessAuthorizer;
        }
 
        //
@@ -152,42 +144,38 @@ public class FcpInterface {
         *            {@link FredPluginFCP#ACCESS_FCP_RESTRICTED}
         */
        public void handle(PluginReplySender pluginReplySender, SimpleFieldSet parameters, Bucket data, int accessType) {
+               String identifier = parameters.get("Identifier");
+               if ((identifier == null) || (identifier.length() == 0)) {
+                       sendErrorReply(pluginReplySender, null, 400, "Missing Identifier.");
+                       return;
+               }
                if (!active.get()) {
-                       try {
-                               sendReply(pluginReplySender, null, new ErrorResponse(400, "FCP Interface deactivated"));
-                       } catch (PluginNotFoundException pnfe1) {
-                               logger.log(Level.FINE, "Could not set error to plugin.", pnfe1);
-                       }
+                       sendErrorReply(pluginReplySender, identifier, 503, "FCP Interface deactivated");
                        return;
                }
                AbstractSoneCommand command = commands.get(parameters.get("Message"));
-               if ((accessType == FredPluginFCP.ACCESS_FCP_RESTRICTED) && (((fullAccessRequired.get() == FullAccessRequired.WRITING) && command.requiresWriteAccess()) || (fullAccessRequired.get() == FullAccessRequired.ALWAYS))) {
-                       try {
-                               sendReply(pluginReplySender, null, new ErrorResponse(401, "Not authorized"));
-                       } catch (PluginNotFoundException pnfe1) {
-                               logger.log(Level.FINE, "Could not set error to plugin.", pnfe1);
-                       }
+               if (command == null) {
+                       sendErrorReply(pluginReplySender, identifier, 404, "Unrecognized Message: " + parameters.get("Message"));
+                       return;
+               }
+               if (!accessAuthorizer.authorized(AccessType.values()[accessType], fullAccessRequired.get(), command.requiresWriteAccess())) {
+                       sendErrorReply(pluginReplySender, identifier, 401, "Not authorized");
                        return;
                }
                try {
-                       if (command == null) {
-                               sendReply(pluginReplySender, null, new ErrorResponse("Unrecognized Message: " + parameters.get("Message")));
-                               return;
-                       }
-                       String identifier = parameters.get("Identifier");
-                       if ((identifier == null) || (identifier.length() == 0)) {
-                               sendReply(pluginReplySender, null, new ErrorResponse("Missing Identifier."));
-                               return;
-                       }
-                       try {
-                               Response response = command.execute(parameters, data, AccessType.values()[accessType]);
-                               sendReply(pluginReplySender, identifier, response);
-                       } catch (Exception e1) {
-                               logger.log(Level.WARNING, "Could not process FCP command “%s”.", command);
-                               sendReply(pluginReplySender, identifier, new ErrorResponse("Error executing command: " + e1.getMessage()));
-                       }
+                       Response response = command.execute(parameters);
+                       sendReply(pluginReplySender, identifier, response);
+               } catch (Exception e1) {
+                       logger.log(Level.WARNING, "Could not process FCP command “%s”.", command);
+                       sendErrorReply(pluginReplySender, identifier, 500, "Error executing command: " + e1.getMessage());
+               }
+       }
+
+       private void sendErrorReply(PluginReplySender pluginReplySender, String identifier, int errorCode, String message) {
+               try {
+                       sendReply(pluginReplySender, identifier, new ErrorResponse(errorCode, message));
                } catch (PluginNotFoundException pnfe1) {
-                       logger.log(Level.WARNING, "Could not find destination plugin: " + pluginReplySender);
+                       logger.log(Level.FINE, "Could not send error to plugin.", pnfe1);
                }
        }
 
@@ -212,13 +200,7 @@ public class FcpInterface {
                if (identifier != null) {
                        replyParameters.putOverwrite("Identifier", identifier);
                }
-               if (response.hasData()) {
-                       pluginReplySender.send(replyParameters, response.getData());
-               } else if (response.hasBucket()) {
-                       pluginReplySender.send(replyParameters, response.getBucket());
-               } else {
-                       pluginReplySender.send(replyParameters);
-               }
+               pluginReplySender.send(replyParameters);
        }
 
        @Subscribe
@@ -236,4 +218,38 @@ public class FcpInterface {
                setFullAccessRequired(fullAccessRequiredChanged.getFullAccessRequired());
        }
 
+       @Singleton
+       public static class CommandSupplier {
+
+               public Map<String, AbstractSoneCommand> supplyCommands(Core core) {
+                       Map<String, AbstractSoneCommand> commands = new HashMap<>();
+                       commands.put("Version", new VersionCommand(core));
+                       commands.put("GetLocalSones", new GetLocalSonesCommand(core));
+                       commands.put("GetSones", new GetSonesCommand(core));
+                       commands.put("GetSone", new GetSoneCommand(core));
+                       commands.put("GetPost", new GetPostCommand(core));
+                       commands.put("GetPosts", new GetPostsCommand(core));
+                       commands.put("GetPostFeed", new GetPostFeedCommand(core));
+                       commands.put("LockSone", new LockSoneCommand(core));
+                       commands.put("UnlockSone", new UnlockSoneCommand(core));
+                       commands.put("LikePost", new LikePostCommand(core));
+                       commands.put("LikeReply", new LikeReplyCommand(core));
+                       commands.put("CreatePost", new CreatePostCommand(core));
+                       commands.put("CreateReply", new CreateReplyCommand(core));
+                       commands.put("DeletePost", new DeletePostCommand(core));
+                       commands.put("DeleteReply", new DeleteReplyCommand(core));
+                       return commands;
+               }
+
+       }
+
+       @Singleton
+       public static class AccessAuthorizer {
+
+               public boolean authorized(@Nonnull AccessType accessType, @Nonnull FullAccessRequired fullAccessRequired, boolean commandRequiresWriteAccess) {
+                       return (accessType != RESTRICTED_FCP) || (fullAccessRequired == NO) || ((fullAccessRequired == WRITING) && !commandRequiresWriteAccess);
+               }
+
+       }
+
 }
index 8ac80cc..a55a81c 100644 (file)
@@ -19,7 +19,6 @@ package net.pterodactylus.sone.fcp;
 
 import net.pterodactylus.sone.core.Core;
 import freenet.support.SimpleFieldSet;
-import freenet.support.api.Bucket;
 
 /**
  * Implements the “GetLocalSones” FCP command that returns the list of local
@@ -43,7 +42,7 @@ public class GetLocalSonesCommand extends AbstractSoneCommand {
         * {@inheritDoc}
         */
        @Override
-       public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) {
+       public Response execute(SimpleFieldSet parameters) {
                return new Response("ListLocalSones", encodeSones(getCore().getLocalSones(), "LocalSones."));
        }
 
index 1e02dae..a6f1ae8 100644 (file)
@@ -21,7 +21,6 @@ import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.freenet.fcp.FcpException;
 import freenet.support.SimpleFieldSet;
-import freenet.support.api.Bucket;
 
 /**
  * The “GetPost” FCP command returns a single {@link Post} to an FCP client.
@@ -44,7 +43,7 @@ public class GetPostCommand extends AbstractSoneCommand {
         * {@inheritDoc}
         */
        @Override
-       public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException {
+       public Response execute(SimpleFieldSet parameters) throws FcpException {
                Post post = getPost(parameters, "Post");
                boolean includeReplies = getBoolean(parameters, "IncludeReplies", true);
 
index dea5156..7b804fb 100644 (file)
@@ -32,7 +32,6 @@ import com.google.common.base.Optional;
 import com.google.common.collect.Collections2;
 
 import freenet.support.SimpleFieldSet;
-import freenet.support.api.Bucket;
 
 /**
  * Implementation of an FCP interface for other clients or plugins to
@@ -56,7 +55,7 @@ public class GetPostFeedCommand extends AbstractSoneCommand {
         * {@inheritDoc}
         */
        @Override
-       public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException {
+       public Response execute(SimpleFieldSet parameters) throws FcpException {
                Sone sone = getSone(parameters, "Sone", true);
                int startPost = getInt(parameters, "StartPost", 0);
                int maxPosts = getInt(parameters, "MaxPosts", -1);
index 4059bf5..b9932a6 100644 (file)
@@ -25,7 +25,6 @@ import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.freenet.fcp.FcpException;
 import freenet.support.SimpleFieldSet;
-import freenet.support.api.Bucket;
 
 /**
  * Implements the “GetPosts” FCP command that returns the list of posts a Sone
@@ -49,7 +48,7 @@ public class GetPostsCommand extends AbstractSoneCommand {
         * {@inheritDoc}
         */
        @Override
-       public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException {
+       public Response execute(SimpleFieldSet parameters) throws FcpException {
                Sone sone = getSone(parameters, "Sone", false);
                int startPost = getInt(parameters, "StartPost", 0);
                int maxPosts = getInt(parameters, "MaxPosts", -1);
index f23be19..9d0e0b7 100644 (file)
@@ -25,7 +25,6 @@ import net.pterodactylus.sone.freenet.fcp.FcpException;
 import com.google.common.base.Optional;
 
 import freenet.support.SimpleFieldSet;
-import freenet.support.api.Bucket;
 
 /**
  * Implements the “GetSone“ FCP command which returns {@link Profile}
@@ -49,10 +48,10 @@ public class GetSoneCommand extends AbstractSoneCommand {
         * {@inheritDoc}
         */
        @Override
-       public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException {
+       public Response execute(SimpleFieldSet parameters) throws FcpException {
                Sone sone = getSone(parameters, "Sone", false);
-               Optional<Sone> localSone = getSone(parameters, "LocalSone", false, false);
-               return new Response("Sone", encodeSone(sone, "", localSone));
+               Optional<Sone> localSone = getSone(parameters, "LocalSone", true, false);
+               return new Response("Sone", encodeSone(sone, "Sone.", localSone));
        }
 
 }
index 97c8842..fa805e1 100644 (file)
@@ -24,7 +24,6 @@ import java.util.List;
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Sone;
 import freenet.support.SimpleFieldSet;
-import freenet.support.api.Bucket;
 
 /**
  * Implements the “GetSones” FCP command that returns the list of known Sones.
@@ -47,15 +46,15 @@ public class GetSonesCommand extends AbstractSoneCommand {
         * {@inheritDoc}
         */
        @Override
-       public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) {
+       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());
                if (sones.size() < startSone) {
-                       return new Response("Sones", encodeSones(Collections.<Sone> emptyList(), ""));
+                       return new Response("Sones", encodeSones(Collections.<Sone> emptyList(), "Sones."));
                }
                Collections.sort(sones, Sone.NICE_NAME_COMPARATOR);
-               return new Response("Sones", encodeSones(sones.subList(startSone, (maxSones == -1) ? sones.size() : Math.min(startSone + maxSones, sones.size())), ""));
+               return new Response("Sones", encodeSones(sones.subList(startSone, (maxSones == -1) ? sones.size() : Math.min(startSone + maxSones, sones.size())), "Sones."));
        }
 
 }
index 583b8e4..a2fdd18 100644 (file)
@@ -23,7 +23,6 @@ import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.freenet.SimpleFieldSetBuilder;
 import net.pterodactylus.sone.freenet.fcp.FcpException;
 import freenet.support.SimpleFieldSet;
-import freenet.support.api.Bucket;
 
 /**
  * Implements the “LikePost” FCP command which allows the user to like a post.
@@ -46,7 +45,7 @@ public class LikePostCommand extends AbstractSoneCommand {
         * {@inheritDoc}
         */
        @Override
-       public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException {
+       public Response execute(SimpleFieldSet parameters) throws FcpException {
                Post post = getPost(parameters, "Post");
                Sone sone = getSone(parameters, "Sone", true);
                sone.addLikedPostId(post.getId());
index 63d8893..6fba11b 100644 (file)
@@ -23,7 +23,6 @@ import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.freenet.SimpleFieldSetBuilder;
 import net.pterodactylus.sone.freenet.fcp.FcpException;
 import freenet.support.SimpleFieldSet;
-import freenet.support.api.Bucket;
 
 /**
  * Implements the “LikeReply” FCP command which allows the user to like a reply.
@@ -46,7 +45,7 @@ public class LikeReplyCommand extends AbstractSoneCommand {
         * {@inheritDoc}
         */
        @Override
-       public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException {
+       public Response execute(SimpleFieldSet parameters) throws FcpException {
                PostReply reply = getReply(parameters, "Reply");
                Sone sone = getSone(parameters, "Sone", true);
                sone.addLikedReplyId(reply.getId());
index e05ac93..f39a18c 100644 (file)
@@ -23,7 +23,6 @@ import net.pterodactylus.sone.freenet.SimpleFieldSetBuilder;
 import net.pterodactylus.sone.freenet.fcp.FcpException;
 
 import freenet.support.SimpleFieldSet;
-import freenet.support.api.Bucket;
 
 import com.google.common.base.Optional;
 
@@ -51,7 +50,7 @@ public class LockSoneCommand extends AbstractSoneCommand {
        //
 
        @Override
-       public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException {
+       public Response execute(SimpleFieldSet parameters) throws FcpException {
                Optional<Sone> sone = getSone(parameters, "Sone", true, true);
                getCore().lockSone(sone.get());
                return new Response("SoneLocked", new SimpleFieldSetBuilder().put("Sone", sone.get().getId()).get());
index 311a7ca..bc78441 100644 (file)
@@ -23,7 +23,6 @@ import net.pterodactylus.sone.freenet.SimpleFieldSetBuilder;
 import net.pterodactylus.sone.freenet.fcp.FcpException;
 
 import freenet.support.SimpleFieldSet;
-import freenet.support.api.Bucket;
 
 import com.google.common.base.Optional;
 
@@ -51,7 +50,7 @@ public class UnlockSoneCommand extends AbstractSoneCommand {
        //
 
        @Override
-       public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException {
+       public Response execute(SimpleFieldSet parameters) throws FcpException {
                Optional<Sone> sone = getSone(parameters, "Sone", true, true);
                getCore().unlockSone(sone.get());
                return new Response("SoneUnlocked", new SimpleFieldSetBuilder().put("Sone", sone.get().getId()).get());
index ee0f2e5..5f50b4d 100644 (file)
@@ -21,7 +21,6 @@ import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.freenet.SimpleFieldSetBuilder;
 import net.pterodactylus.sone.main.SonePlugin;
 import freenet.support.SimpleFieldSet;
-import freenet.support.api.Bucket;
 
 /**
  * Returns version information about the Sone plugin.
@@ -44,7 +43,7 @@ public class VersionCommand extends AbstractSoneCommand {
         * {@inheritDoc}
         */
        @Override
-       public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) {
+       public Response execute(SimpleFieldSet parameters) {
                return new Response("Version", new SimpleFieldSetBuilder().put("Version", SonePlugin.getPluginVersion()).put("ProtocolVersion", 1).get());
        }
 
index 3b4af2a..7ca9a5c 100644 (file)
@@ -23,6 +23,8 @@ import java.util.List;
 import java.util.Locale;
 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;
@@ -53,8 +55,23 @@ public class L10nFilter implements Filter {
         */
        @Override
        public String format(TemplateContext templateContext, Object data, Map<String, Object> parameters) {
-               if (parameters.isEmpty()) {
-                       return webInterface.getL10n().getString(String.valueOf(data));
+               List<Object> parameterValues = getParameters(data, parameters);
+               String text = getText(data);
+               if (parameterValues.isEmpty()) {
+                       return webInterface.getL10n().getString(text);
+               }
+               return new MessageFormat(webInterface.getL10n().getString(text), new Locale(webInterface.getL10n().getSelectedLanguage().shortCode)).format(parameterValues.toArray());
+       }
+
+       @Nonnull
+       private String getText(Object data) {
+               return (data instanceof L10nText) ? ((L10nText) data).getText() : String.valueOf(data);
+       }
+
+       @Nonnull
+       private List<Object> getParameters(Object data, Map<String, Object> parameters) {
+               if (data instanceof L10nText) {
+                       return ((L10nText) data).getParameters();
                }
                List<Object> parameterValues = new ArrayList<Object>();
                int parameterIndex = 0;
@@ -63,7 +80,7 @@ public class L10nFilter implements Filter {
                        parameterValues.add(value);
                        ++parameterIndex;
                }
-               return new MessageFormat(webInterface.getL10n().getString(String.valueOf(data)), new Locale(webInterface.getL10n().getSelectedLanguage().shortCode)).format(parameterValues.toArray());
+               return parameterValues;
        }
 
 }
index bafa764..17f8370 100644 (file)
@@ -19,7 +19,6 @@ package net.pterodactylus.sone.freenet.fcp;
 
 import net.pterodactylus.sone.freenet.SimpleFieldSetBuilder;
 import freenet.support.SimpleFieldSet;
-import freenet.support.api.Bucket;
 
 /**
  * Implementation of an FCP interface for other clients or plugins to
@@ -35,15 +34,11 @@ public interface Command {
         *
         * @param parameters
         *            The parameters of the comand
-        * @param data
-        *            The data of the command (may be {@code null})
-        * @param accessType
-        *            The access type
         * @return A reply to send back to the plugin
         * @throws FcpException
         *             if an error processing the parameters occurs
         */
-       public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException;
+       public Response execute(SimpleFieldSet parameters) throws FcpException;
 
        /**
         * The access type of the request.
@@ -76,12 +71,6 @@ public interface Command {
                /** The reply parameters. */
                private final SimpleFieldSet replyParameters;
 
-               /** The reply data, may be {@code null}. */
-               private final byte[] data;
-
-               /** The data bucket, may be {@code null}. */
-               private final Bucket bucket;
-
                /**
                 * Creates a new reply with the given parameters.
                 *
@@ -91,54 +80,8 @@ public interface Command {
                 *            The reply parameters
                 */
                public Response(String messageName, SimpleFieldSet replyParameters) {
-                       this(messageName, replyParameters, null, null);
-               }
-
-               /**
-                * Creates a new reply with the given parameters.
-                *
-                * @param messageName
-                *            The message name
-                * @param replyParameters
-                *            The reply parameters
-                * @param data
-                *            The data of the reply (may be {@code null})
-                */
-               public Response(String messageName, SimpleFieldSet replyParameters, byte[] data) {
-                       this(messageName, replyParameters, data, null);
-               }
-
-               /**
-                * Creates a new reply with the given parameters.
-                *
-                * @param messageName
-                *            The message name
-                * @param replyParameters
-                *            The reply parameters
-                * @param bucket
-                *            The bucket of the reply (may be {@code null})
-                */
-               public Response(String messageName, SimpleFieldSet replyParameters, Bucket bucket) {
-                       this(messageName, replyParameters, null, bucket);
-               }
-
-               /**
-                * Creates a new reply with the given parameters.
-                *
-                * @param messageName
-                *            The message name
-                * @param replyParameters
-                *            The reply parameters
-                * @param data
-                *            The data of the reply (may be {@code null})
-                * @param bucket
-                *            The bucket of the reply (may be {@code null})
-                */
-               private Response(String messageName, SimpleFieldSet replyParameters, byte[] data, Bucket bucket) {
                        this.messageName = messageName;
                        this.replyParameters = replyParameters;
-                       this.data = data;
-                       this.bucket = bucket;
                }
 
                /**
@@ -150,45 +93,6 @@ public interface Command {
                        return new SimpleFieldSetBuilder(replyParameters).put("Message", messageName).get();
                }
 
-               /**
-                * Returns whether the reply has reply data.
-                *
-                * @see #getData()
-                * @return {@code true} if this reply has data, {@code false} otherwise
-                */
-               public boolean hasData() {
-                       return data != null;
-               }
-
-               /**
-                * Returns the data of the reply.
-                *
-                * @return The data of the reply
-                */
-               public byte[] getData() {
-                       return data;
-               }
-
-               /**
-                * Returns whether the reply has a data bucket.
-                *
-                * @see #getBucket()
-                * @return {@code true} if the reply has a data bucket, {@code false}
-                *         otherwise
-                */
-               public boolean hasBucket() {
-                       return bucket != null;
-               }
-
-               /**
-                * Returns the data bucket of the reply.
-                *
-                * @return The data bucket of the reply
-                */
-               public Bucket getBucket() {
-                       return bucket;
-               }
-
        }
 
        /**
index cfce632..f014de5 100644 (file)
@@ -105,7 +105,7 @@ public class IdentityChangeDetector {
                return new Predicate<Identity>() {
                        @Override
                        public boolean apply(Identity identity) {
-                               return (identity == null) ? false : identityHasChanged(oldIdentities.get(identity.getId()), identity);
+                               return (identity != null) && identityHasChanged(oldIdentities.get(identity.getId()), identity);
                        }
                };
        }
@@ -151,7 +151,7 @@ public class IdentityChangeDetector {
                return new Predicate<String>() {
                        @Override
                        public boolean apply(String context) {
-                               return (identity == null) ? false : !identity.getContexts().contains(context);
+                               return (identity != null) && !identity.getContexts().contains(context);
                        }
                };
        }
@@ -160,7 +160,7 @@ public class IdentityChangeDetector {
                return new Predicate<Identity>() {
                        @Override
                        public boolean apply(Identity identity) {
-                               return (identity == null) ? false : !newIdentities.contains(identity);
+                               return (identity != null) && !newIdentities.contains(identity);
                        }
                };
        }
@@ -169,7 +169,7 @@ public class IdentityChangeDetector {
                return new Predicate<Entry<String, String>>() {
                        @Override
                        public boolean apply(Entry<String, String> property) {
-                               return (property == null) ? false : !identity.getProperties().containsKey(property.getKey());
+                               return (property != null) && !identity.getProperties().containsKey(property.getKey());
                        }
                };
        }
@@ -178,7 +178,7 @@ public class IdentityChangeDetector {
                return new Predicate<Entry<String, String>>() {
                        @Override
                        public boolean apply(Entry<String, String> property) {
-                               return (property == null) ? false : !newIdentity.getProperty(property.getKey()).equals(property.getValue());
+                               return (property != null) && !newIdentity.getProperty(property.getKey()).equals(property.getValue());
                        }
                };
        }
index 11755e0..d0168ae 100644 (file)
@@ -3,7 +3,7 @@ package net.pterodactylus.sone.main;
 import java.io.File;
 
 import net.pterodactylus.sone.template.FilesystemTemplate;
-import net.pterodactylus.sone.web.ReloadingPage;
+import net.pterodactylus.sone.web.pages.ReloadingPage;
 import net.pterodactylus.util.template.FilesystemTemplateProvider;
 import net.pterodactylus.util.template.Template;
 import net.pterodactylus.util.template.TemplateProvider;
index 61bd6ce..c1628eb 100644 (file)
@@ -54,7 +54,6 @@ import com.google.inject.spi.TypeListener;
 import freenet.client.async.PersistenceDisabledException;
 import freenet.l10n.BaseL10n.LANGUAGE;
 import freenet.l10n.PluginL10n;
-import freenet.node.Node;
 import freenet.pluginmanager.FredPlugin;
 import freenet.pluginmanager.FredPluginBaseL10n;
 import freenet.pluginmanager.FredPluginFCP;
@@ -115,13 +114,10 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr
                });
        }
 
-       /** The version. */
-       private static final Version VERSION = new Version(0, 9, 6);
-
        /** The current year at time of release. */
-       private static final int YEAR = 2016;
+       private static final int YEAR = 2017;
        private static final String SONE_HOMEPAGE = "USK@nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI,DuQSUZiI~agF8c-6tjsFFGuZ8eICrzWCILB60nT8KKo,AQACAAE/sone/";
-       private static final int LATEST_EDITION = 73;
+       private static final int LATEST_EDITION = 76;
 
        /** The logger. */
        private static final Logger logger = getLogger(SonePlugin.class.getName());
@@ -176,7 +172,8 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr
        }
 
        public static String getPluginVersion() {
-               return VERSION.toString();
+               net.pterodactylus.sone.main.Version version = VersionParserKt.getParsedVersion();
+               return (version == null) ? "unknown" : version.getNice();
        }
 
        public static int getYear() {
@@ -238,15 +235,8 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr
                final EventBus eventBus = new EventBus();
 
                /* Freenet injector configuration. */
-               AbstractModule freenetModule = new AbstractModule() {
+               FreenetModule freenetModule =  new FreenetModule(pluginRespirator);
 
-                       @Override
-                       @SuppressWarnings("synthetic-access")
-                       protected void configure() {
-                               bind(PluginRespirator.class).toInstance(SonePlugin.this.pluginRespirator);
-                               bind(Node.class).toInstance(SonePlugin.this.pluginRespirator.getNode());
-                       }
-               };
                /* Sone injector configuration. */
                AbstractModule soneModule = new AbstractModule() {
 
@@ -258,7 +248,10 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr
                                bind(Context.class).toInstance(context);
                                bind(getOptionalContextTypeLiteral()).toInstance(of(context));
                                bind(SonePlugin.class).toInstance(SonePlugin.this);
-                               bind(Version.class).toInstance(VERSION);
+                               bind(Version.class).toInstance(Version.parse(getVersion()));
+                               bind(PluginVersion.class).toInstance(new PluginVersion(getVersion()));
+                               bind(PluginYear.class).toInstance(new PluginYear(getYear()));
+                               bind(PluginHomepage.class).toInstance(new PluginHomepage(getHomepage()));
                                if (startConfiguration.getBooleanValue("Developer.LoadFromFilesystem").getValue(false)) {
                                        String path = startConfiguration.getStringValue("Developer.FilesystemPath").getValue(null);
                                        if (path != null) {
@@ -409,7 +402,49 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr
         */
        @Override
        public String getVersion() {
-               return VERSION.toString();
+               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;
+               }
+
        }
 
 }
index 473c191..ad753a7 100644 (file)
@@ -2,7 +2,7 @@ package net.pterodactylus.sone.template;
 
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.Reader;
@@ -18,8 +18,6 @@ import net.pterodactylus.util.template.TemplateContext;
 import net.pterodactylus.util.template.TemplateException;
 import net.pterodactylus.util.template.TemplateParser;
 
-import freenet.support.io.Closer;
-
 import com.google.common.base.Charsets;
 
 /**
@@ -46,28 +44,19 @@ public class FilesystemTemplate extends Template {
 
        private void loadTemplate() {
                File templateFile = new File(filename);
-               if (!templateFile.exists()) {
-                       throw new TemplateFileNotFoundException(filename);
-               }
                if (templateWasLoaded() && !templateFileHasBeenModifiedAfterLoading(templateFile)) {
                        return;
                }
-               InputStream templateInputStream = null;
-               Reader templateReader = null;
-               try {
-                       templateInputStream = new FileInputStream(templateFile);
-                       templateReader = new InputStreamReader(templateInputStream, Charsets.UTF_8);
+               try (InputStream templateInputStream = new FileInputStream(templateFile);
+                               Reader templateReader = new InputStreamReader(templateInputStream, Charsets.UTF_8)) {
                        Template template = TemplateParser.parse(templateReader);
                        lastTemplate.set(new LastLoadedTemplate(template));
                        template.getInitialContext().mergeContext(initialContext);
                        for (Part part : parts) {
                                template.add(part);
                        }
-               } catch (FileNotFoundException e) {
+               } catch (IOException e) {
                        throw new TemplateFileNotFoundException(filename);
-               } finally {
-                       Closer.close(templateReader);
-                       Closer.close(templateInputStream);
                }
        }
 
index 74c1a77..7766af3 100644 (file)
@@ -19,6 +19,9 @@ package net.pterodactylus.sone.template;
 
 import java.util.Set;
 
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.freenet.wot.Identity;
 import net.pterodactylus.sone.freenet.wot.OwnIdentity;
@@ -32,6 +35,7 @@ import net.pterodactylus.util.template.TemplateContext;
  *
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
+@Singleton
 public class IdentityAccessor extends ReflectionAccessor {
 
        /** The core. */
@@ -43,6 +47,7 @@ public class IdentityAccessor extends ReflectionAccessor {
         * @param core
         *            The core
         */
+       @Inject
        public IdentityAccessor(Core core) {
                this.core = core;
        }
diff --git a/src/main/java/net/pterodactylus/sone/template/ParserFilter.java b/src/main/java/net/pterodactylus/sone/template/ParserFilter.java
deleted file mode 100644 (file)
index 7cf8c70..0000000
+++ /dev/null
@@ -1,289 +0,0 @@
-/*
- * Sone - ParserFilter.java - Copyright © 2011–2016 David Roden
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * 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.template;
-
-import static java.lang.String.valueOf;
-import static net.pterodactylus.sone.utils.NumberParsers.parseInt;
-
-import java.io.StringReader;
-import java.io.StringWriter;
-import java.io.UnsupportedEncodingException;
-import java.io.Writer;
-import java.net.URLEncoder;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import net.pterodactylus.sone.core.Core;
-import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.sone.text.FreenetLinkPart;
-import net.pterodactylus.sone.text.LinkPart;
-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.util.template.Filter;
-import net.pterodactylus.util.template.Template;
-import net.pterodactylus.util.template.TemplateContext;
-import net.pterodactylus.util.template.TemplateContextFactory;
-import net.pterodactylus.util.template.TemplateParser;
-
-/**
- * Filter that filters a given text through a {@link SoneTextParser}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class ParserFilter implements Filter {
-
-       /** The core. */
-       private final Core core;
-
-       /** The link parser. */
-       private final SoneTextParser soneTextParser;
-
-       /** The template context factory. */
-       private final TemplateContextFactory templateContextFactory;
-
-       /** The template for {@link PlainTextPart}s. */
-       private static final Template plainTextTemplate = TemplateParser.parse(new StringReader("<%text|html>"));
-
-       /** The template for {@link FreenetLinkPart}s. */
-       private static final Template linkTemplate = TemplateParser.parse(new StringReader("<a class=\"<%cssClass|html>\" href=\"<%link|html>\" title=\"<%title|html>\"><%text|html></a>"));
-
-       /**
-        * Creates a new filter that runs its input through a {@link SoneTextParser}
-        * .
-        *
-        * @param core
-        *            The core
-        * @param templateContextFactory
-        *            The context factory for rendering the parts
-        * @param soneTextParser
-        *            The Sone text parser
-        */
-       public ParserFilter(Core core, TemplateContextFactory templateContextFactory, SoneTextParser soneTextParser) {
-               this.core = core;
-               this.templateContextFactory = templateContextFactory;
-               this.soneTextParser = soneTextParser;
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public Object format(TemplateContext templateContext, Object data, Map<String, Object> parameters) {
-               String text = valueOf(data);
-               int length = parseInt(valueOf(parameters.get("length")), -1);
-               int cutOffLength = parseInt(valueOf(parameters.get("cut-off-length")), length);
-               Object sone = parameters.get("sone");
-               if (sone instanceof String) {
-                       sone = core.getSone((String) sone).orNull();
-               }
-               SoneTextParserContext context = new SoneTextParserContext((Sone) sone);
-               StringWriter parsedTextWriter = new StringWriter();
-               Iterable<Part> parts = soneTextParser.parse(text, context);
-               if (length > -1) {
-                       int allPartsLength = 0;
-                       List<Part> shortenedParts = new ArrayList<Part>();
-                       for (Part part : parts) {
-                               if (part instanceof PlainTextPart) {
-                                       String longText = part.getText();
-                                       if (allPartsLength < cutOffLength) {
-                                               if ((allPartsLength + longText.length()) > cutOffLength) {
-                                                       shortenedParts.add(new PlainTextPart(longText.substring(0, cutOffLength - allPartsLength) + "…"));
-                                               } else {
-                                                       shortenedParts.add(part);
-                                               }
-                                       }
-                                       allPartsLength += longText.length();
-                               } else if (part instanceof LinkPart) {
-                                       if (allPartsLength < cutOffLength) {
-                                               shortenedParts.add(part);
-                                       }
-                                       allPartsLength += part.getText().length();
-                               } else {
-                                       if (allPartsLength < cutOffLength) {
-                                               shortenedParts.add(part);
-                                       }
-                               }
-                       }
-                       if (allPartsLength >= length) {
-                               parts = shortenedParts;
-                       }
-               }
-               render(parsedTextWriter, parts);
-               return parsedTextWriter.toString();
-       }
-
-       //
-       // PRIVATE METHODS
-       //
-
-       /**
-        * Renders the given parts.
-        *
-        * @param writer
-        *            The writer to render the parts to
-        * @param parts
-        *            The parts to render
-        */
-       private void render(Writer writer, Iterable<Part> parts) {
-               for (Part part : parts) {
-                       render(writer, part);
-               }
-       }
-
-       /**
-        * Renders the given part.
-        *
-        * @param writer
-        *            The writer to render the part to
-        * @param part
-        *            The part to render
-        */
-       @SuppressWarnings("unchecked")
-       private void render(Writer writer, Part part) {
-               if (part instanceof PlainTextPart) {
-                       render(writer, (PlainTextPart) part);
-               } else if (part instanceof FreenetLinkPart) {
-                       render(writer, (FreenetLinkPart) part);
-               } else if (part instanceof LinkPart) {
-                       render(writer, (LinkPart) part);
-               } else if (part instanceof SonePart) {
-                       render(writer, (SonePart) part);
-               } else if (part instanceof PostPart) {
-                       render(writer, (PostPart) part);
-               } else if (part instanceof Iterable<?>) {
-                       render(writer, (Iterable<Part>) part);
-               }
-       }
-
-       /**
-        * Renders the given plain-text part.
-        *
-        * @param writer
-        *            The writer to render the part to
-        * @param plainTextPart
-        *            The part to render
-        */
-       private void render(Writer writer, PlainTextPart plainTextPart) {
-               TemplateContext templateContext = templateContextFactory.createTemplateContext();
-               templateContext.set("text", plainTextPart.getText());
-               plainTextTemplate.render(templateContext, writer);
-       }
-
-       /**
-        * Renders the given freenet link part.
-        *
-        * @param writer
-        *            The writer to render the part to
-        * @param freenetLinkPart
-        *            The part to render
-        */
-       private void render(Writer writer, FreenetLinkPart freenetLinkPart) {
-               renderLink(writer, "/" + freenetLinkPart.getLink(), freenetLinkPart.getText(), freenetLinkPart.getTitle(), freenetLinkPart.isTrusted() ? "freenet-trusted" : "freenet");
-       }
-
-       /**
-        * Renders the given link part.
-        *
-        * @param writer
-        *            The writer to render the part to
-        * @param linkPart
-        *            The part to render
-        */
-       private void render(Writer writer, LinkPart linkPart) {
-               try {
-                       renderLink(writer, "/external-link/?_CHECKED_HTTP_=" + URLEncoder.encode(linkPart.getLink(), "UTF-8"), linkPart.getText(), linkPart.getTitle(), "internet");
-               } catch (UnsupportedEncodingException uee1) {
-                       /* not possible for UTF-8. */
-                       throw new RuntimeException("The JVM does not support UTF-8 encoding!", uee1);
-               }
-       }
-
-       /**
-        * Renders the given Sone part.
-        *
-        * @param writer
-        *            The writer to render the part to
-        * @param sonePart
-        *            The part to render
-        */
-       private void render(Writer writer, SonePart sonePart) {
-               if ((sonePart.getSone() != null) && (sonePart.getSone().getName() != null)) {
-                       renderLink(writer, "viewSone.html?sone=" + sonePart.getSone().getId(), SoneAccessor.getNiceName(sonePart.getSone()), SoneAccessor.getNiceName(sonePart.getSone()), "in-sone");
-               } else {
-                       renderLink(writer, "/WebOfTrust/ShowIdentity?id=" + sonePart.getSone().getId(), sonePart.getSone().getId(), sonePart.getSone().getId(), "in-sone");
-               }
-       }
-
-       /**
-        * Renders the given post part.
-        *
-        * @param writer
-        *            The writer to render the part to
-        * @param postPart
-        *            The part to render
-        */
-       private void render(Writer writer, PostPart postPart) {
-               SoneTextParser parser = new SoneTextParser(core, core);
-               SoneTextParserContext parserContext = new SoneTextParserContext(postPart.getPost().getSone());
-               Iterable<Part> parts = parser.parse(postPart.getPost().getText(), parserContext);
-               StringBuilder excerpt = new StringBuilder();
-               for (Part part : parts) {
-                       excerpt.append(part.getText());
-                       if (excerpt.length() > 20) {
-                               int lastSpace = excerpt.lastIndexOf(" ", 20);
-                               if (lastSpace > -1) {
-                                       excerpt.setLength(lastSpace);
-                               } else {
-                                       excerpt.setLength(20);
-                               }
-                               excerpt.append("…");
-                               break;
-                       }
-               }
-               renderLink(writer, "viewPost.html?post=" + postPart.getPost().getId(), excerpt.toString(), SoneAccessor.getNiceName(postPart.getPost().getSone()), "in-sone");
-       }
-
-       /**
-        * Renders the given link.
-        *
-        * @param writer
-        *            The writer to render the link to
-        * @param link
-        *            The link to render
-        * @param text
-        *            The text of the link
-        * @param title
-        *            The title of the link
-        * @param cssClass
-        *            The CSS class of the link
-        */
-       private void renderLink(Writer writer, String link, String text, String title, String cssClass) {
-               TemplateContext templateContext = templateContextFactory.createTemplateContext();
-               templateContext.set("cssClass", cssClass);
-               templateContext.set("link", link);
-               templateContext.set("text", text);
-               templateContext.set("title", title);
-               linkTemplate.render(templateContext, writer);
-       }
-
-}
index 980d960..fafba86 100644 (file)
@@ -20,7 +20,7 @@ package net.pterodactylus.sone.template;
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Profile;
 import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.sone.data.Sone.ShowCustomAvatars;
+import net.pterodactylus.sone.data.SoneOptions.LoadExternalContent;
 import net.pterodactylus.sone.freenet.wot.OwnIdentity;
 import net.pterodactylus.sone.freenet.wot.Trust;
 import net.pterodactylus.util.template.Accessor;
@@ -74,24 +74,24 @@ public class ProfileAccessor extends ReflectionAccessor {
                                /* always show your own avatars. */
                                return avatarId;
                        }
-                       ShowCustomAvatars showCustomAvatars = currentSone.getOptions().getShowCustomAvatars();
-                       if (showCustomAvatars == ShowCustomAvatars.NEVER) {
+                       LoadExternalContent showCustomAvatars = currentSone.getOptions().getShowCustomAvatars();
+                       if (showCustomAvatars == LoadExternalContent.NEVER) {
                                return null;
                        }
-                       if (showCustomAvatars == ShowCustomAvatars.ALWAYS) {
+                       if (showCustomAvatars == LoadExternalContent.ALWAYS) {
                                return avatarId;
                        }
-                       if (showCustomAvatars == ShowCustomAvatars.FOLLOWED) {
+                       if (showCustomAvatars == LoadExternalContent.FOLLOWED) {
                                return currentSone.hasFriend(remoteSone.getId()) ? avatarId : null;
                        }
                        Trust trust = remoteSone.getIdentity().getTrust((OwnIdentity) currentSone.getIdentity());
                        if (trust == null) {
                                return null;
                        }
-                       if ((showCustomAvatars == ShowCustomAvatars.MANUALLY_TRUSTED) && (trust.getExplicit() != null) && (trust.getExplicit() > 0)) {
+                       if ((showCustomAvatars == LoadExternalContent.MANUALLY_TRUSTED) && (trust.getExplicit() != null) && (trust.getExplicit() > 0)) {
                                return avatarId;
                        }
-                       if ((showCustomAvatars == ShowCustomAvatars.TRUSTED) && (((trust.getExplicit() != null) && (trust.getExplicit() > 0)) || ((trust.getImplicit() != null) && (trust.getImplicit() > 0)))) {
+                       if ((showCustomAvatars == LoadExternalContent.TRUSTED) && (((trust.getExplicit() != null) && (trust.getExplicit() > 0)) || ((trust.getImplicit() != null) && (trust.getImplicit() > 0)))) {
                                return avatarId;
                        }
                        return null;
index 9bb5b81..da12ca8 100644 (file)
@@ -30,8 +30,6 @@ import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.util.template.Filter;
 import net.pterodactylus.util.template.TemplateContext;
 
-import com.google.common.base.Optional;
-
 /**
  * {@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
@@ -48,33 +46,30 @@ public class ReplyGroupFilter implements Filter {
        public Object format(TemplateContext templateContext, Object data, Map<String, Object> parameters) {
                @SuppressWarnings("unchecked")
                List<PostReply> allReplies = (List<PostReply>) data;
-               Map<Post, Set<Sone>> postSones = new HashMap<Post, Set<Sone>>();
-               Map<Post, Set<PostReply>> postReplies = new HashMap<Post, Set<PostReply>>();
+               Map<Post, Set<Sone>> postSones = new HashMap<>();
+               Map<Post, Set<PostReply>> postReplies = new HashMap<>();
                for (PostReply reply : allReplies) {
                        /*
                         * All replies from a new-reply notification have posts,
                         * ListNotificationFilters takes care of that.
                         */
-                       Optional<Post> post = reply.getPost();
-                       Set<Sone> sones = postSones.get(post.get());
+                       Post post = reply.getPost().get();
+                       Set<Sone> sones = postSones.get(post);
                        if (sones == null) {
-                               sones = new HashSet<Sone>();
-                               postSones.put(post.get(), sones);
+                               sones = new HashSet<>();
+                               postSones.put(post, sones);
                        }
                        sones.add(reply.getSone());
-                       Set<PostReply> replies = postReplies.get(post.get());
+                       Set<PostReply> replies = postReplies.get(post);
                        if (replies == null) {
-                               replies = new HashSet<PostReply>();
-                               postReplies.put(post.get(), replies);
+                               replies = new HashSet<>();
+                               postReplies.put(post, replies);
                        }
                        replies.add(reply);
                }
-               Map<Post, Map<String, Set<?>>> result = new HashMap<Post, Map<String, Set<?>>>();
+               Map<Post, Map<String, Set<?>>> result = new HashMap<>();
                for (Entry<Post, Set<Sone>> postEntry : postSones.entrySet()) {
-                       if (result.containsKey(postEntry.getKey())) {
-                               continue;
-                       }
-                       Map<String, Set<?>> postResult = new HashMap<String, Set<?>>();
+                       Map<String, Set<?>> postResult = new HashMap<>();
                        postResult.put("sones", postEntry.getValue());
                        postResult.put("replies", postReplies.get(postEntry.getKey()));
                        result.put(postEntry.getKey(), postResult);
index 50fa272..5f4ab0c 100644 (file)
@@ -32,8 +32,7 @@ import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.data.Sone.SoneStatus;
 import net.pterodactylus.sone.freenet.wot.OwnIdentity;
 import net.pterodactylus.sone.freenet.wot.Trust;
-import net.pterodactylus.sone.web.WebInterface;
-import net.pterodactylus.sone.web.ajax.GetTimesAjaxPage;
+import net.pterodactylus.sone.text.TimeTextConverter;
 import net.pterodactylus.util.template.Accessor;
 import net.pterodactylus.util.template.ReflectionAccessor;
 import net.pterodactylus.util.template.TemplateContext;
@@ -62,6 +61,7 @@ public class SoneAccessor extends ReflectionAccessor {
 
        /** The core. */
        private final Core core;
+       private final TimeTextConverter timeTextConverter;
 
        /**
         * Creates a new Sone accessor.
@@ -69,8 +69,9 @@ public class SoneAccessor extends ReflectionAccessor {
         * @param core
         *            The Sone core
         */
-       public SoneAccessor(Core core) {
+       public SoneAccessor(Core core, TimeTextConverter timeTextConverter) {
                this.core = core;
+               this.timeTextConverter = timeTextConverter;
        }
 
        /**
@@ -104,7 +105,7 @@ public class SoneAccessor extends ReflectionAccessor {
                } else if (member.equals("locked")) {
                        return core.isLocked(sone);
                } else if (member.equals("lastUpdatedText")) {
-                       return GetTimesAjaxPage.getTime((WebInterface) templateContext.get("webInterface"), sone.getTime());
+                       return timeTextConverter.getTimeText(sone.getTime()).getL10nText();
                } else if (member.equals("trust")) {
                        Sone currentSone = (Sone) templateContext.get("currentSone");
                        if (currentSone == null) {
index 2104888..f14d0ff 100644 (file)
@@ -48,7 +48,7 @@ public class SubstringFilter implements Filter {
                }
                String dataString = String.valueOf(data);
                int dataLength = dataString.length();
-               int length = Integer.MAX_VALUE;
+               int length = dataLength;
                try {
                        length = Integer.parseInt(lengthString);
                } catch (NumberFormatException nfe1) {
index fee047d..32650f1 100644 (file)
@@ -43,10 +43,6 @@ public class TrustAccessor extends ReflectionAccessor {
                Trust trust = (Trust) object;
                if ("assigned".equals(member)) {
                        return trust.getExplicit() != null;
-               } else if ("maximum".equals(member)) {
-                       return ((trust.getExplicit() != null) && (trust.getExplicit() >= 100)) || ((trust.getImplicit() != null) && (trust.getImplicit() >= 100));
-               } else if ("hasDistance".equals(member)) {
-                       return (trust.getDistance() != null) && (trust.getDistance() != Integer.MAX_VALUE);
                }
                return super.get(templateContext, object, member);
        }
diff --git a/src/main/java/net/pterodactylus/sone/text/FreemailPart.java b/src/main/java/net/pterodactylus/sone/text/FreemailPart.java
new file mode 100644 (file)
index 0000000..69e6bfe
--- /dev/null
@@ -0,0 +1,37 @@
+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 {
+
+       private final String emailLocalPart;
+       private final String freemailId;
+       private final String identityId;
+
+       public FreemailPart(String emailLocalPart, String freemailId, String identityId) {
+               this.emailLocalPart = emailLocalPart;
+               this.freemailId = freemailId;
+               this.identityId = identityId;
+       }
+
+       @Override
+       public String getText() {
+               return String.format("%s@%s.freemail", emailLocalPart, freemailId);
+       }
+
+       public String getEmailLocalPart() {
+               return emailLocalPart;
+       }
+
+       public String getFreemailId() {
+               return freemailId;
+       }
+
+       public String getIdentityId() {
+               return identityId;
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/text/FreenetLinkPart.java b/src/main/java/net/pterodactylus/sone/text/FreenetLinkPart.java
deleted file mode 100644 (file)
index 493880f..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Sone - FreenetLinkPart.java - Copyright © 2011–2016 David Roden
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * 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 javax.annotation.Nonnull;
-
-/**
- * {@link LinkPart} implementation that stores an additional attribute: if the
- * link is an SSK or USK link and the post was created by an identity that owns
- * the keyspace in question.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class FreenetLinkPart extends LinkPart {
-
-       private final boolean trusted;
-
-       public FreenetLinkPart(@Nonnull String link, @Nonnull String text, boolean trusted) {
-               this(link, text, text, trusted);
-       }
-
-       public FreenetLinkPart(@Nonnull String link, @Nonnull String text, @Nonnull String title, boolean trusted) {
-               super(link, text, title);
-               this.trusted = trusted;
-       }
-
-       public boolean isTrusted() {
-               return trusted;
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/text/LinkPart.java b/src/main/java/net/pterodactylus/sone/text/LinkPart.java
deleted file mode 100644 (file)
index 9f889ef..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Sone - LinkPart.java - Copyright © 2011–2016 David Roden
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * 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 java.util.Objects;
-
-import javax.annotation.Nonnull;
-
-/**
- * {@link Part} implementation that can hold a link. A link contains of three
- * attributes: the link itself, the text that is shown instead of the link, and
- * an explanatory text that can be displayed e.g. as a tooltip.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class LinkPart implements Part {
-
-       private final String link;
-       private final String text;
-       private final String title;
-
-       public LinkPart(@Nonnull String link, @Nonnull String text) {
-               this(link, text, text);
-       }
-
-       public LinkPart(@Nonnull String link, @Nonnull String text, @Nonnull String title) {
-               this.link = Objects.requireNonNull(link);
-               this.text = Objects.requireNonNull(text);
-               this.title = Objects.requireNonNull(title);
-       }
-
-       @Nonnull
-       public String getLink() {
-               return link;
-       }
-
-       @Nonnull
-       public String getTitle() {
-               return title;
-       }
-
-       @Override
-       @Nonnull
-       public String getText() {
-               return text;
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/text/Part.java b/src/main/java/net/pterodactylus/sone/text/Part.java
deleted file mode 100644 (file)
index e516f0a..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Sone - Part.java - Copyright © 2010–2016 David Roden
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * 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;
-
-/**
- * A part is a single piece of information that can be displayed as a single
- * element. How the part is displayed is not part of the {@code Part}
- * specification.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface Part {
-
-       /**
-        * Returns the text contained in this part. This should return plain text
-        * without any format information.
-        *
-        * @return The plain text of this part
-        */
-       public String getText();
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/text/PartContainer.java b/src/main/java/net/pterodactylus/sone/text/PartContainer.java
deleted file mode 100644 (file)
index 3444d19..0000000
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Sone - PartContainer.java - Copyright © 2010–2016 David Roden
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * 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 java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Deque;
-import java.util.Iterator;
-import java.util.List;
-import java.util.NoSuchElementException;
-import java.util.Objects;
-
-import javax.annotation.Nonnull;
-
-/**
- * Part implementation that can contain an arbitrary amount of other parts.
- * Parts are added using the {@link #add(Part)} method and will be rendered in
- * the order they are added.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class PartContainer implements Part, Iterable<Part> {
-
-       private final List<Part> parts = new ArrayList<Part>();
-
-       public void add(@Nonnull Part part) {
-               parts.add(Objects.requireNonNull(part));
-       }
-
-       @Nonnull
-       public Part getPart(int index) {
-               return parts.get(index);
-       }
-
-       public void removePart(int index) {
-               parts.remove(index);
-       }
-
-       public int size() {
-               return parts.size();
-       }
-
-       @Override
-       @Nonnull
-       public String getText() {
-               StringBuilder partText = new StringBuilder();
-               for (Part part : parts) {
-                       partText.append(part.getText());
-               }
-               return partText.toString();
-       }
-
-       @Override
-       @Nonnull
-       @SuppressWarnings("synthetic-access")
-       public Iterator<Part> iterator() {
-               return new Iterator<Part>() {
-
-                       private Deque<Iterator<Part>> partStack = new ArrayDeque<Iterator<Part>>();
-                       private Part nextPart;
-                       private boolean foundNextPart;
-                       private boolean noNextPart;
-
-                       {
-                               partStack.push(parts.iterator());
-                       }
-
-                       private void findNext() {
-                               if (foundNextPart) {
-                                       return;
-                               }
-                               noNextPart = true;
-                               while (!partStack.isEmpty()) {
-                                       Iterator<Part> parts = partStack.pop();
-                                       if (parts.hasNext()) {
-                                               nextPart = parts.next();
-                                               partStack.push(parts);
-                                               if (nextPart instanceof PartContainer) {
-                                                       partStack.push(((PartContainer) nextPart).iterator());
-                                               } else {
-                                                       noNextPart = false;
-                                                       break;
-                                               }
-                                       }
-                               }
-                               foundNextPart = true;
-                       }
-
-                       @Override
-                       public boolean hasNext() {
-                               findNext();
-                               return !noNextPart;
-                       }
-
-                       @Override
-                       public Part next() {
-                               findNext();
-                               if (noNextPart) {
-                                       throw new NoSuchElementException();
-                               }
-                               foundNextPart = false;
-                               return nextPart;
-                       }
-
-                       @Override
-                       public void remove() {
-                               /* ignore. */
-                       }
-
-               };
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/text/PlainTextPart.java b/src/main/java/net/pterodactylus/sone/text/PlainTextPart.java
deleted file mode 100644 (file)
index 7bdbee9..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Sone - PlainTextPart.java - Copyright © 2011–2016 David Roden
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * 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 java.util.Objects;
-
-import javax.annotation.Nonnull;
-
-/**
- * {@link Part} implementation that holds a single piece of text.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class PlainTextPart implements Part {
-
-       private final String text;
-
-       public PlainTextPart(@Nonnull String text) {
-               this.text = Objects.requireNonNull(text);
-       }
-
-       @Override
-       @Nonnull
-       public String getText() {
-               return text;
-       }
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/text/SonePart.java b/src/main/java/net/pterodactylus/sone/text/SonePart.java
deleted file mode 100644 (file)
index 576eb7b..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Sone - SonePart.java - Copyright © 2011–2016 David Roden
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * 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 java.util.Objects;
-
-import javax.annotation.Nonnull;
-
-import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.sone.template.SoneAccessor;
-
-/**
- * {@link Part} implementation that stores a reference to a {@link Sone}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class SonePart implements Part {
-
-       private final Sone sone;
-
-       public SonePart(@Nonnull Sone sone) {
-               this.sone = Objects.requireNonNull(sone);
-       }
-
-       @Nonnull
-       public Sone getSone() {
-               return sone;
-       }
-
-       @Override
-       public String getText() {
-               return SoneAccessor.getNiceName(sone);
-       }
-
-}
index 81ac751..39d26db 100644 (file)
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.text;
 
+import static com.google.common.base.Optional.absent;
+import static com.google.common.base.Optional.of;
 import static java.util.logging.Logger.getLogger;
 
 import java.io.BufferedReader;
@@ -24,6 +26,8 @@ import java.io.IOException;
 import java.io.Reader;
 import java.io.StringReader;
 import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.regex.Matcher;
@@ -39,8 +43,10 @@ import net.pterodactylus.sone.database.PostProvider;
 import net.pterodactylus.sone.database.SoneProvider;
 
 import com.google.common.base.Optional;
+import org.bitpedia.util.Base32;
 
 import freenet.keys.FreenetURI;
+import freenet.support.Base64;
 
 /**
  * {@link Parser} implementation that can recognize Freenet URIs.
@@ -55,6 +61,38 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
        /** Pattern to detect whitespace. */
        private static final Pattern whitespacePattern = Pattern.compile("[\\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 static class NextLink {
+
+               private final int position;
+               private final String link;
+               private final String remainder;
+               private final LinkType linkType;
+
+               private NextLink(int position, String link, String remainder, LinkType linkType) {
+                       this.position = position;
+                       this.link = link;
+                       this.remainder = remainder;
+                       this.linkType = linkType;
+               }
+
+               public int getPosition() {
+                       return position;
+               }
+
+               public String getLink() {
+                       return link;
+               }
+
+               public String getRemainder() {
+                       return remainder;
+               }
+
+               public LinkType getLinkType() {
+                       return linkType;
+               }
+
+       }
+
        /**
         * Enumeration for all recognized link types.
         *
@@ -69,7 +107,39 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                HTTP("http://", false),
                HTTPS("https://", false),
                SONE("sone://", false),
-               POST("post://", false);
+               POST("post://", false),
+
+               FREEMAIL("", true) {
+                       @Override
+                       public Optional<NextLink> findNext(String line) {
+                               int nextFreemailSuffix = line.indexOf(".freemail");
+                               if (nextFreemailSuffix < 54) {
+                                       /* 52 chars for the id, 1 on @, at least 1 for the local part. */
+                                       return absent();
+                               }
+                               if (line.charAt(nextFreemailSuffix - 53) != '@') {
+                                       return absent();
+                               }
+                               if (!line.substring(nextFreemailSuffix - 52, nextFreemailSuffix).matches("^[a-z2-7]*$")) {
+                                       return absent();
+                               }
+                               int startOfLocalPart = nextFreemailSuffix - 54;
+                               if (!isAllowedInLocalPart(line.charAt(startOfLocalPart))) {
+                                       return absent();
+                               }
+                               while ((startOfLocalPart > 0) && isAllowedInLocalPart(line.charAt(startOfLocalPart - 1))) {
+                                       startOfLocalPart--;
+                               }
+                               return of(new NextLink(startOfLocalPart, line.substring(startOfLocalPart, nextFreemailSuffix + 9), line.substring(nextFreemailSuffix + 9), this));
+                       }
+
+                       private boolean isAllowedInLocalPart(char character) {
+                               return ((character >= 'A') && (character <= 'Z'))
+                                               || ((character >= 'a') && (character <= 'z'))
+                                               || ((character >= '0') && (character <= '9'))
+                                               || (character == '.') || (character == '-') || (character == '_');
+                       }
+               };
 
                private final String scheme;
                private final boolean freenetLink;
@@ -92,6 +162,38 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                        return freenetLink;
                }
 
+               public Optional<NextLink> findNext(String line) {
+                       int nextLinkPosition = line.indexOf(getScheme());
+                       if (nextLinkPosition == -1) {
+                               return absent();
+                       }
+                       int endOfLink = findEndOfLink(line.substring(nextLinkPosition));
+                       return of(new NextLink(nextLinkPosition, line.substring(nextLinkPosition, nextLinkPosition + endOfLink), line.substring(nextLinkPosition + endOfLink), this));
+               }
+
+               private static int findEndOfLink(String line) {
+                       Matcher matcher = whitespacePattern.matcher(line);
+                       int endOfLink = matcher.find() ? matcher.start() : line.length();
+                       while (isPunctuation(line.charAt(endOfLink - 1))) {
+                               endOfLink--;
+                       }
+                       int openParens = 0;
+                       for (int i = 0; i < endOfLink; i++) {
+                               switch (line.charAt(i)) {
+                                       case '(':
+                                               openParens++;
+                                               break;
+                                       case ')':
+                                               openParens--;
+                                               if (openParens < 0) {
+                                                       return i;
+                                               }
+                                       default:
+                               }
+                       }
+                       return endOfLink;
+               }
+
        }
 
        /** The Sone provider. */
@@ -123,7 +225,7 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
        @Nonnull
        @Override
        public Iterable<Part> parse(@Nonnull String source, @Nullable SoneTextParserContext context) {
-               PartContainer parts = new PartContainer();
+               List<Part> parts = new ArrayList<>();
                try (Reader sourceReader = new StringReader(source);
                                BufferedReader bufferedReader = new BufferedReader(sourceReader)) {
                        String line;
@@ -147,7 +249,7 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                                 */
                                boolean lineComplete = true;
                                while (line.length() > 0) {
-                                       Optional<NextLink> nextLink = NextLink.findNextLink(line);
+                                       Optional<NextLink> nextLink = findNextLink(line);
                                        if (!nextLink.isPresent()) {
                                                if (lineComplete && !lastLineEmpty) {
                                                        parts.add(new PlainTextPart("\n" + line));
@@ -175,8 +277,7 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                                        }
                                        lineComplete = false;
 
-                                       int endOfLink = findEndOfLink(line);
-                                       String link = line.substring(0, endOfLink);
+                                       String link = nextLink.get().getLink();
                                        logger.log(Level.FINER, String.format("Found link: %s", link));
 
                                        /* if there is no text after the scheme, it’s not a link! */
@@ -203,9 +304,11 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                                                case HTTPS:
                                                        renderHttpLink(parts, link, linkType);
                                                        break;
+                                               case FREEMAIL:
+                                                       renderFreemailLink(parts, link);
                                        }
 
-                                       line = line.substring(endOfLink);
+                                       line = nextLink.get().getRemainder();
                                }
                                lastLineEmpty = false;
                        }
@@ -214,16 +317,31 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                        throw new RuntimeException(ioe1);
                }
                for (int partIndex = parts.size() - 1; partIndex >= 0; --partIndex) {
-                       Part part = parts.getPart(partIndex);
+                       Part part = parts.get(partIndex);
                        if (!(part instanceof PlainTextPart) || !"\n".equals(part.getText())) {
                                break;
                        }
-                       parts.removePart(partIndex);
+                       parts.remove(partIndex);
                }
                return parts;
        }
 
-       private void renderSoneLink(PartContainer parts, String line) {
+       public static Optional<NextLink> findNextLink(String line) {
+               int earliestLinkPosition = Integer.MAX_VALUE;
+               NextLink earliestNextLink = null;
+               for (LinkType possibleLinkType : LinkType.values()) {
+                       Optional<NextLink> nextLink = possibleLinkType.findNext(line);
+                       if (nextLink.isPresent()) {
+                               if (nextLink.get().getPosition() < earliestLinkPosition) {
+                                       earliestNextLink = nextLink.get();
+                                       earliestLinkPosition = earliestNextLink.getPosition();
+                               }
+                       }
+               }
+               return Optional.fromNullable(earliestNextLink);
+       }
+
+       private void renderSoneLink(List<Part> parts, String line) {
                if (line.length() >= (7 + 43)) {
                        String soneId = line.substring(7, 50);
                        Optional<Sone> sone = soneProvider.getSone(soneId);
@@ -233,7 +351,7 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                }
        }
 
-       private void renderPostLink(PartContainer parts, String line) {
+       private void renderPostLink(List<Part> parts, String line) {
                if (line.length() >= (7 + 36)) {
                        String postId = line.substring(7, 43);
                        Optional<Post> post = postProvider.getPost(postId);
@@ -247,10 +365,11 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                }
        }
 
-       private void renderFreenetLink(PartContainer parts, String link, LinkType linkType, @Nullable SoneTextParserContext context) {
+       private void renderFreenetLink(List<Part> parts, String link, LinkType linkType, @Nullable SoneTextParserContext context) {
                String name = link;
+               String linkWithoutParameters = link;
                if (name.indexOf('?') > -1) {
-                       name = name.substring(0, name.indexOf('?'));
+                       linkWithoutParameters = name = name.substring(0, name.indexOf('?'));
                }
                if (name.endsWith("/")) {
                        name = name.substring(0, name.length() - 1);
@@ -265,7 +384,7 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                                name = link.substring(0, Math.min(9, link.length()));
                        }
                        boolean fromPostingSone = ((linkType == LinkType.SSK) || (linkType == LinkType.USK)) && (context != null) && (context.getPostingSone() != null) && link.substring(4, Math.min(link.length(), 47)).equals(context.getPostingSone().getId());
-                       parts.add(new FreenetLinkPart(link, name, fromPostingSone));
+                       parts.add(new FreenetLinkPart(link, name, linkWithoutParameters, fromPostingSone));
                } catch (MalformedURLException mue1) {
                        /* not a valid link, insert as plain text. */
                        parts.add(new PlainTextPart(link));
@@ -278,9 +397,8 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                }
        }
 
-       private void renderHttpLink(PartContainer parts, String link, LinkType linkType) {
-               String name;
-               name = link.substring(linkType == LinkType.HTTP ? 7 : 8);
+       private void renderHttpLink(List<Part> parts, String link, LinkType linkType) {
+               String name = link.substring(linkType == LinkType.HTTP ? 7 : 8);
                int firstSlash = name.indexOf('/');
                int lastSlash = name.lastIndexOf('/');
                if ((lastSlash - firstSlash) > 3) {
@@ -298,67 +416,16 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                parts.add(new LinkPart(link, name));
        }
 
-       private int findEndOfLink(String line) {
-               Matcher matcher = whitespacePattern.matcher(line);
-               int endOfLink = matcher.find() ? matcher.start() : line.length();
-               while ((endOfLink > 0) && isPunctuation(line.charAt(endOfLink - 1))) {
-                       endOfLink--;
-               }
-               int openParens = 0;
-               for (int i = 0; i < endOfLink; i++) {
-                       switch (line.charAt(i)) {
-                               case '(':
-                                       openParens++;
-                                       break;
-                               case ')':
-                                       openParens--;
-                                       if (openParens < 0) {
-                                               return i;
-                                       }
-                               default:
-                       }
-               }
-               return endOfLink;
+       private void renderFreemailLink(List<Part> parts, String line) {
+               int separator = line.indexOf('@');
+               String freemailId = line.substring(separator + 1, separator + 53);
+               String identityId = Base64.encode(Base32.decode(freemailId));
+               String emailLocalPart = line.substring(0, separator);
+               parts.add(new FreemailPart(emailLocalPart, freemailId, identityId));
        }
 
        private static boolean isPunctuation(char character) {
-               return (character == '.') || (character == ',');
-       }
-
-       private static class NextLink {
-
-               private final int position;
-               private final LinkType linkType;
-
-               private NextLink(int position, LinkType linkType) {
-                       this.position = position;
-                       this.linkType = linkType;
-               }
-
-               public int getPosition() {
-                       return position;
-               }
-
-               public LinkType getLinkType() {
-                       return linkType;
-               }
-
-               public static Optional<NextLink> findNextLink(String line) {
-                       int earliestLinkPosition = Integer.MAX_VALUE;
-                       LinkType linkType = null;
-                       for (LinkType possibleLinkType : LinkType.values()) {
-                               int nextLinkPosition = line.indexOf(possibleLinkType.getScheme());
-                               if (nextLinkPosition > -1) {
-                                       if (nextLinkPosition < earliestLinkPosition) {
-                                               earliestLinkPosition = nextLinkPosition;
-                                               linkType = possibleLinkType;
-                                       }
-                               }
-                       }
-                       return earliestLinkPosition < Integer.MAX_VALUE ?
-                                       Optional.of(new NextLink(earliestLinkPosition, linkType)) : Optional.<NextLink>absent();
-               }
-
+               return (character == '.') || (character == ',') || (character == '!') || (character == '?');
        }
 
 }