From 90993f1fc13288b83996066e726653b4dd011479 Mon Sep 17 00:00:00 2001 From: =?utf8?q?David=20=E2=80=98Bombe=E2=80=99=20Roden?= Date: Sun, 9 Feb 2020 14:03:41 +0100 Subject: [PATCH] =?utf8?q?=F0=9F=9A=9A=20Move=20kotlin=20files=20to=20corr?= =?utf8?q?ect=20source=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../sone/core/PreferenceChangedEvent.kt | 3 - .../pterodactylus/sone/core/PreferencesLoader.kt | 58 ---- .../sone/database/memory/MemoryDatabase.kt | 354 --------------------- .../sone/freenet/wot/IdentityChangeDetector.kt | 68 ---- .../java/net/pterodactylus/sone/main/SonePlugin.kt | 7 - .../java/net/pterodactylus/sone/web/AllPages.kt | 52 --- .../sone/core/PreferenceChangedEvent.kt | 3 + .../pterodactylus/sone/core/PreferencesLoader.kt | 58 ++++ .../sone/database/memory/MemoryDatabase.kt | 354 +++++++++++++++++++++ .../sone/freenet/wot/IdentityChangeDetector.kt | 68 ++++ .../net/pterodactylus/sone/main/SonePlugin.kt | 7 + .../kotlin/net/pterodactylus/sone/web/AllPages.kt | 52 +++ .../sone/freenet/wot/IdentityManagerTest.kt | 33 -- .../sone/freenet/wot/IdentityManagerTest.kt | 33 ++ 14 files changed, 575 insertions(+), 575 deletions(-) delete mode 100644 src/main/java/net/pterodactylus/sone/core/PreferenceChangedEvent.kt delete mode 100644 src/main/java/net/pterodactylus/sone/core/PreferencesLoader.kt delete mode 100644 src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.kt delete mode 100644 src/main/java/net/pterodactylus/sone/freenet/wot/IdentityChangeDetector.kt delete mode 100644 src/main/java/net/pterodactylus/sone/main/SonePlugin.kt delete mode 100644 src/main/java/net/pterodactylus/sone/web/AllPages.kt create mode 100644 src/main/kotlin/net/pterodactylus/sone/core/PreferenceChangedEvent.kt create mode 100644 src/main/kotlin/net/pterodactylus/sone/core/PreferencesLoader.kt create mode 100644 src/main/kotlin/net/pterodactylus/sone/database/memory/MemoryDatabase.kt create mode 100644 src/main/kotlin/net/pterodactylus/sone/freenet/wot/IdentityChangeDetector.kt create mode 100644 src/main/kotlin/net/pterodactylus/sone/main/SonePlugin.kt create mode 100644 src/main/kotlin/net/pterodactylus/sone/web/AllPages.kt delete mode 100644 src/test/java/net/pterodactylus/sone/freenet/wot/IdentityManagerTest.kt create mode 100644 src/test/kotlin/net/pterodactylus/sone/freenet/wot/IdentityManagerTest.kt diff --git a/src/main/java/net/pterodactylus/sone/core/PreferenceChangedEvent.kt b/src/main/java/net/pterodactylus/sone/core/PreferenceChangedEvent.kt deleted file mode 100644 index 2ebb62d..0000000 --- a/src/main/java/net/pterodactylus/sone/core/PreferenceChangedEvent.kt +++ /dev/null @@ -1,3 +0,0 @@ -package net.pterodactylus.sone.core - -data class PreferenceChangedEvent(val preferenceName: String, val newValue: Any) diff --git a/src/main/java/net/pterodactylus/sone/core/PreferencesLoader.kt b/src/main/java/net/pterodactylus/sone/core/PreferencesLoader.kt deleted file mode 100644 index 32c35cb..0000000 --- a/src/main/java/net/pterodactylus/sone/core/PreferencesLoader.kt +++ /dev/null @@ -1,58 +0,0 @@ -package net.pterodactylus.sone.core - -import net.pterodactylus.sone.fcp.FcpInterface.* -import net.pterodactylus.util.config.* - -/** - * Loads preferences stored in a [Configuration] into a [Preferences] object. - */ -class PreferencesLoader(private val preferences: Preferences) { - - fun loadFrom(configuration: Configuration) { - loadInsertionDelay(configuration) - loadPostsPerPage(configuration) - loadImagesPerPage(configuration) - loadCharactersPerPost(configuration) - loadPostCutOffLength(configuration) - loadRequireFullAccess(configuration) - loadFcpInterfaceActive(configuration) - loadFcpFullAccessRequired(configuration) - } - - private fun loadInsertionDelay(configuration: Configuration) { - preferences.newInsertionDelay = configuration.getIntValue("Option/InsertionDelay").getValue(null) - } - - private fun loadPostsPerPage(configuration: Configuration) { - preferences.newPostsPerPage = configuration.getIntValue("Option/PostsPerPage").getValue(null) - } - - private fun loadImagesPerPage(configuration: Configuration) { - preferences.newImagesPerPage = configuration.getIntValue("Option/ImagesPerPage").getValue(null) - } - - private fun loadCharactersPerPost(configuration: Configuration) { - preferences.newCharactersPerPost = configuration.getIntValue("Option/CharactersPerPost").getValue(null) - } - - private fun loadPostCutOffLength(configuration: Configuration) { - try { - preferences.newPostCutOffLength = configuration.getIntValue("Option/PostCutOffLength").getValue(null) - } catch (iae1: IllegalArgumentException) { /* previous versions allowed -1, ignore and use default. */ - } - } - - private fun loadRequireFullAccess(configuration: Configuration) { - preferences.newRequireFullAccess = configuration.getBooleanValue("Option/RequireFullAccess").getValue(null) - } - - private fun loadFcpInterfaceActive(configuration: Configuration) { - preferences.newFcpInterfaceActive = configuration.getBooleanValue("Option/ActivateFcpInterface").getValue(null) - } - - private fun loadFcpFullAccessRequired(configuration: Configuration) { - val fullAccessRequiredInteger = configuration.getIntValue("Option/FcpFullAccessRequired").getValue(null) - preferences.newFcpFullAccessRequired = fullAccessRequiredInteger?.let { FullAccessRequired.values()[it] } - } - -} diff --git a/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.kt b/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.kt deleted file mode 100644 index 8722873..0000000 --- a/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.kt +++ /dev/null @@ -1,354 +0,0 @@ -/* - * Sone - MemoryDatabase.kt - Copyright © 2013–2020 David Roden - * - * This 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 . - */ - -package net.pterodactylus.sone.database.memory - -import com.google.common.base.Preconditions.checkNotNull -import com.google.common.collect.HashMultimap -import com.google.common.collect.Multimap -import com.google.common.collect.TreeMultimap -import com.google.common.util.concurrent.* -import com.google.inject.Inject -import com.google.inject.Singleton -import net.pterodactylus.sone.data.Album -import net.pterodactylus.sone.data.Image -import net.pterodactylus.sone.data.Post -import net.pterodactylus.sone.data.PostReply -import net.pterodactylus.sone.data.Reply.TIME_COMPARATOR -import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.data.Sone.toAllAlbums -import net.pterodactylus.sone.data.Sone.toAllImages -import net.pterodactylus.sone.data.impl.AlbumBuilderImpl -import net.pterodactylus.sone.data.impl.ImageBuilderImpl -import net.pterodactylus.sone.database.AlbumBuilder -import net.pterodactylus.sone.database.Database -import net.pterodactylus.sone.database.DatabaseException -import net.pterodactylus.sone.database.ImageBuilder -import net.pterodactylus.sone.database.PostBuilder -import net.pterodactylus.sone.database.PostDatabase -import net.pterodactylus.sone.database.PostReplyBuilder -import net.pterodactylus.sone.utils.* -import net.pterodactylus.util.config.Configuration -import net.pterodactylus.util.config.ConfigurationException -import java.util.concurrent.locks.ReentrantReadWriteLock -import kotlin.concurrent.withLock - -/** - * Memory-based [PostDatabase] implementation. - */ -@Singleton -class MemoryDatabase @Inject constructor(private val configuration: Configuration) : AbstractService(), Database { - - private val lock = ReentrantReadWriteLock() - private val readLock by lazy { lock.readLock()!! } - private val writeLock by lazy { lock.writeLock()!! } - private val configurationLoader = ConfigurationLoader(configuration) - private val allSones = mutableMapOf() - private val allPosts = mutableMapOf() - private val sonePosts: Multimap = HashMultimap.create() - private val knownPosts = mutableSetOf() - private val allPostReplies = mutableMapOf() - private val sonePostReplies: Multimap = TreeMultimap.create(Comparator { leftString, rightString -> leftString.compareTo(rightString) }, TIME_COMPARATOR) - private val knownPostReplies = mutableSetOf() - private val allAlbums = mutableMapOf() - private val soneAlbums: Multimap = HashMultimap.create() - private val allImages = mutableMapOf() - private val soneImages: Multimap = HashMultimap.create() - private val memoryBookmarkDatabase = MemoryBookmarkDatabase(this, configurationLoader) - private val memoryFriendDatabase = MemoryFriendDatabase(configurationLoader) - private val saveRateLimiter: RateLimiter = RateLimiter.create(1.0) - private val saveKnownPostsRateLimiter: RateLimiter = RateLimiter.create(1.0) - private val saveKnownPostRepliesRateLimiter: RateLimiter = RateLimiter.create(1.0) - - override val soneLoader get() = this::getSone - - override val sones get() = readLock.withLock { allSones.values.toSet() } - - override val localSones get() = readLock.withLock { allSones.values.filter(Sone::isLocal) } - - override val remoteSones get() = readLock.withLock { allSones.values.filterNot(Sone::isLocal) } - - override val bookmarkedPosts get() = memoryBookmarkDatabase.bookmarkedPosts - - override fun save() { - if (saveRateLimiter.tryAcquire()) { - saveKnownPosts() - saveKnownPostReplies() - } - } - - override fun doStart() { - memoryBookmarkDatabase.start() - loadKnownPosts() - loadKnownPostReplies() - notifyStarted() - } - - override fun doStop() { - try { - memoryBookmarkDatabase.stop() - save() - notifyStopped() - } catch (de1: DatabaseException) { - notifyFailed(de1) - } - } - - override fun newSoneBuilder() = MemorySoneBuilder(this) - - override fun storeSone(sone: Sone) { - writeLock.withLock { - removeSone(sone) - - allSones[sone.id] = sone - sonePosts.putAll(sone.id, sone.posts) - for (post in sone.posts) { - allPosts[post.id] = post - } - sonePostReplies.putAll(sone.id, sone.replies) - for (postReply in sone.replies) { - allPostReplies[postReply.id] = postReply - } - soneAlbums.putAll(sone.id, toAllAlbums.apply(sone)!!) - for (album in toAllAlbums.apply(sone)!!) { - allAlbums[album.id] = album - } - soneImages.putAll(sone.id, toAllImages.apply(sone)!!) - for (image in toAllImages.apply(sone)!!) { - allImages[image.id] = image - } - } - } - - override fun removeSone(sone: Sone) { - writeLock.withLock { - allSones.remove(sone.id) - val removedPosts = sonePosts.removeAll(sone.id) - for (removedPost in removedPosts) { - allPosts.remove(removedPost.id) - } - val removedPostReplies = sonePostReplies.removeAll(sone.id) - for (removedPostReply in removedPostReplies) { - allPostReplies.remove(removedPostReply.id) - } - val removedAlbums = soneAlbums.removeAll(sone.id) - for (removedAlbum in removedAlbums) { - allAlbums.remove(removedAlbum.id) - } - val removedImages = soneImages.removeAll(sone.id) - for (removedImage in removedImages) { - allImages.remove(removedImage.id) - } - } - } - - override fun getSone(soneId: String) = readLock.withLock { allSones[soneId] } - - override fun getFriends(localSone: Sone): Collection = - if (!localSone.isLocal) { - emptySet() - } else { - memoryFriendDatabase.getFriends(localSone.id) - } - - override fun isFriend(localSone: Sone, friendSoneId: String) = - if (!localSone.isLocal) { - false - } else { - memoryFriendDatabase.isFriend(localSone.id, friendSoneId) - } - - override fun addFriend(localSone: Sone, friendSoneId: String) { - if (!localSone.isLocal) { - return - } - memoryFriendDatabase.addFriend(localSone.id, friendSoneId) - } - - override fun removeFriend(localSone: Sone, friendSoneId: String) { - if (!localSone.isLocal) { - return - } - memoryFriendDatabase.removeFriend(localSone.id, friendSoneId) - } - - override fun getFollowingTime(friendSoneId: String) = - memoryFriendDatabase.getFollowingTime(friendSoneId) - - override fun getPost(postId: String) = - readLock.withLock { allPosts[postId] } - - override fun getPosts(soneId: String): Collection = - sonePosts[soneId].toSet() - - override fun getDirectedPosts(recipientId: String) = - readLock.withLock { - allPosts.values.filter { - it.recipientId.orNull() == recipientId - } - } - - override fun newPostBuilder(): PostBuilder = MemoryPostBuilder(this, this) - - override fun storePost(post: Post) { - checkNotNull(post, "post must not be null") - writeLock.withLock { - allPosts[post.id] = post - sonePosts[post.sone.id].add(post) - } - } - - override fun removePost(post: Post) { - checkNotNull(post, "post must not be null") - writeLock.withLock { - allPosts.remove(post.id) - sonePosts[post.sone.id].remove(post) - post.sone.removePost(post) - } - } - - override fun getPostReply(id: String) = readLock.withLock { allPostReplies[id] } - - override fun getReplies(postId: String) = - readLock.withLock { - allPostReplies.values - .filter { it.postId == postId } - .sortedWith(TIME_COMPARATOR) - } - - override fun newPostReplyBuilder(): PostReplyBuilder = - MemoryPostReplyBuilder(this, this) - - override fun storePostReply(postReply: PostReply) = - writeLock.withLock { - allPostReplies[postReply.id] = postReply - } - - override fun removePostReply(postReply: PostReply) = - writeLock.withLock { - allPostReplies.remove(postReply.id) - }.unit - - override fun getAlbum(albumId: String) = readLock.withLock { allAlbums[albumId] } - - override fun newAlbumBuilder(): AlbumBuilder = AlbumBuilderImpl() - - override fun storeAlbum(album: Album) = - writeLock.withLock { - allAlbums[album.id] = album - soneAlbums.put(album.sone.id, album) - }.unit - - override fun removeAlbum(album: Album) = - writeLock.withLock { - allAlbums.remove(album.id) - soneAlbums.remove(album.sone.id, album) - }.unit - - override fun getImage(imageId: String) = readLock.withLock { allImages[imageId] } - - override fun newImageBuilder(): ImageBuilder = ImageBuilderImpl() - - override fun storeImage(image: Image): Unit = - writeLock.withLock { - allImages[image.id] = image - soneImages.put(image.sone.id, image) - } - - override fun removeImage(image: Image): Unit = - writeLock.withLock { - allImages.remove(image.id) - soneImages.remove(image.sone.id, image) - } - - override fun bookmarkPost(post: Post) = - memoryBookmarkDatabase.bookmarkPost(post) - - override fun unbookmarkPost(post: Post) = - memoryBookmarkDatabase.unbookmarkPost(post) - - override fun isPostBookmarked(post: Post) = - memoryBookmarkDatabase.isPostBookmarked(post) - - protected fun isPostKnown(post: Post) = readLock.withLock { post.id in knownPosts } - - fun setPostKnown(post: Post, known: Boolean): Unit = - writeLock.withLock { - if (known) - knownPosts.add(post.id) - else - knownPosts.remove(post.id) - saveKnownPosts() - } - - protected fun isPostReplyKnown(postReply: PostReply) = readLock.withLock { postReply.id in knownPostReplies } - - fun setPostReplyKnown(postReply: PostReply, known: Boolean): Unit = - writeLock.withLock { - if (known) - knownPostReplies.add(postReply.id) - else - knownPostReplies.remove(postReply.id) - saveKnownPostReplies() - } - - private fun loadKnownPosts() = - configurationLoader.loadKnownPosts() - .let { - writeLock.withLock { - knownPosts.clear() - knownPosts.addAll(it) - } - } - - private fun saveKnownPosts() = - saveKnownPostsRateLimiter.tryAcquire().ifTrue { - try { - readLock.withLock { - knownPosts.forEachIndexed { index, knownPostId -> - configuration.getStringValue("KnownPosts/$index/ID").value = knownPostId - } - configuration.getStringValue("KnownPosts/${knownPosts.size}/ID").value = null - } - } catch (ce1: ConfigurationException) { - throw DatabaseException("Could not save database.", ce1) - } - } - - private fun loadKnownPostReplies(): Unit = - configurationLoader.loadKnownPostReplies().let { knownPostReplies -> - writeLock.withLock { - this.knownPostReplies.clear() - this.knownPostReplies.addAll(knownPostReplies) - } - } - - private fun saveKnownPostReplies() = - saveKnownPostRepliesRateLimiter.tryAcquire().ifTrue { - try { - readLock.withLock { - knownPostReplies.forEachIndexed { index, knownPostReply -> - configuration.getStringValue("KnownReplies/$index/ID").value = knownPostReply - } - configuration.getStringValue("KnownReplies/${knownPostReplies.size}/ID").value = null - } - } catch (ce1: ConfigurationException) { - throw DatabaseException("Could not save database.", ce1) - } - } - -} diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityChangeDetector.kt b/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityChangeDetector.kt deleted file mode 100644 index ffcafb3..0000000 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityChangeDetector.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Sone - IdentityChangeDetector.kt - Copyright © 2013–2020 David Roden - * - * This 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 . - */ - -package net.pterodactylus.sone.freenet.wot - -/** - * Detects changes between two lists of [Identity]s. The detector can find - * added and removed identities, and for identities that exist in both list - * their contexts and properties are checked for added, removed, or (in case of - * properties) changed values. - */ -class IdentityChangeDetector(oldIdentities: Collection) { - - private val oldIdentities: Map = oldIdentities.associateBy { it.id } - var onNewIdentity: IdentityProcessor? = null - var onRemovedIdentity: IdentityProcessor? = null - var onChangedIdentity: IdentityProcessor? = null - var onUnchangedIdentity: IdentityProcessor? = null - - fun detectChanges(newIdentities: Collection) { - onRemovedIdentity.notify(oldIdentities.values.filter { it !in newIdentities }) - onNewIdentity.notify(newIdentities.filter { it !in oldIdentities.values }) - onChangedIdentity.notify(newIdentities.filter { it.id in oldIdentities }.filter { identityHasChanged(oldIdentities[it.id]!!, it) }) - onUnchangedIdentity.notify(newIdentities.filter { it.id in oldIdentities }.filterNot { identityHasChanged(oldIdentities[it.id]!!, it) }) - } - - private fun identityHasChanged(oldIdentity: Identity, newIdentity: Identity?) = - identityHasNewContexts(oldIdentity, newIdentity!!) - || identityHasRemovedContexts(oldIdentity, newIdentity) - || identityHasNewProperties(oldIdentity, newIdentity) - || identityHasRemovedProperties(oldIdentity, newIdentity) - || identityHasChangedProperties(oldIdentity, newIdentity) - - private fun identityHasNewContexts(oldIdentity: Identity, newIdentity: Identity) = - newIdentity.contexts.any { it !in oldIdentity.contexts } - - private fun identityHasRemovedContexts(oldIdentity: Identity, newIdentity: Identity) = - oldIdentity.contexts.any { it !in newIdentity.contexts } - - private fun identityHasNewProperties(oldIdentity: Identity, newIdentity: Identity) = - newIdentity.properties.keys.any { it !in oldIdentity.properties } - - private fun identityHasRemovedProperties(oldIdentity: Identity, newIdentity: Identity) = - oldIdentity.properties.keys.any { it !in newIdentity.properties } - - private fun identityHasChangedProperties(oldIdentity: Identity, newIdentity: Identity) = - oldIdentity.properties.entries.any { newIdentity.properties[it.key] != it.value } - -} - -typealias IdentityProcessor = (Identity) -> Unit - -private fun IdentityProcessor?.notify(identities: Iterable) = - this?.let { identities.forEach(this::invoke) } diff --git a/src/main/java/net/pterodactylus/sone/main/SonePlugin.kt b/src/main/java/net/pterodactylus/sone/main/SonePlugin.kt deleted file mode 100644 index 5e0b2c1..0000000 --- a/src/main/java/net/pterodactylus/sone/main/SonePlugin.kt +++ /dev/null @@ -1,7 +0,0 @@ -package net.pterodactylus.sone.main - -data class PluginVersion(val version: String) - -data class PluginYear(val year: Int) - -data class PluginHomepage(val homepage: String) diff --git a/src/main/java/net/pterodactylus/sone/web/AllPages.kt b/src/main/java/net/pterodactylus/sone/web/AllPages.kt deleted file mode 100644 index c538f0e..0000000 --- a/src/main/java/net/pterodactylus/sone/web/AllPages.kt +++ /dev/null @@ -1,52 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.web.pages.* -import javax.inject.Inject - -/** - * Container for all web pages. This uses field injection because there are way too many pages - * to sensibly use constructor injection. - */ -class AllPages { - - @Inject lateinit var aboutPage: AboutPage - @Inject lateinit var bookmarkPage: BookmarkPage - @Inject lateinit var bookmarksPage: BookmarksPage - @Inject lateinit var createAlbumPage: CreateAlbumPage - @Inject lateinit var createPostPage: CreatePostPage - @Inject lateinit var createReplyPage: CreateReplyPage - @Inject lateinit var createSonePage: CreateSonePage - @Inject lateinit var deleteAlbumPage: DeleteAlbumPage - @Inject lateinit var deleteImagePage: DeleteImagePage - @Inject lateinit var deletePostPage: DeletePostPage - @Inject lateinit var deleteProfileFieldPage: DeleteProfileFieldPage - @Inject lateinit var deleteReplyPage: DeleteReplyPage - @Inject lateinit var deleteSonePage: DeleteSonePage - @Inject lateinit var dismissNotificationPage: DismissNotificationPage - @Inject lateinit var editAlbumPage: EditAlbumPage - @Inject lateinit var editImagePage: EditImagePage - @Inject lateinit var editProfileFieldPage: EditProfileFieldPage - @Inject lateinit var editProfilePage: EditProfilePage - @Inject lateinit var followSonePage: FollowSonePage - @Inject lateinit var getImagePage: GetImagePage - @Inject lateinit var imageBrowserPage: ImageBrowserPage - @Inject lateinit var indexPage: IndexPage - @Inject lateinit var knownSonesPage: KnownSonesPage - @Inject lateinit var likePage: LikePage - @Inject lateinit var lockSonePage: LockSonePage - @Inject lateinit var loginPage: LoginPage - @Inject lateinit var logoutPage: LogoutPage - @Inject lateinit var markAsKnownPage: MarkAsKnownPage - @Inject lateinit var newPage: NewPage - @Inject lateinit var optionsPage: OptionsPage - @Inject lateinit var rescuePage: RescuePage - @Inject lateinit var searchPage: SearchPage - @Inject lateinit var unbookmarkPage: UnbookmarkPage - @Inject lateinit var unfollowSonePage: UnfollowSonePage - @Inject lateinit var unlikePage: UnlikePage - @Inject lateinit var unlockSonePage: UnlockSonePage - @Inject lateinit var uploadImagePage: UploadImagePage - @Inject lateinit var viewPostPage: ViewPostPage - @Inject lateinit var viewSonePage: ViewSonePage - -} diff --git a/src/main/kotlin/net/pterodactylus/sone/core/PreferenceChangedEvent.kt b/src/main/kotlin/net/pterodactylus/sone/core/PreferenceChangedEvent.kt new file mode 100644 index 0000000..2ebb62d --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/core/PreferenceChangedEvent.kt @@ -0,0 +1,3 @@ +package net.pterodactylus.sone.core + +data class PreferenceChangedEvent(val preferenceName: String, val newValue: Any) diff --git a/src/main/kotlin/net/pterodactylus/sone/core/PreferencesLoader.kt b/src/main/kotlin/net/pterodactylus/sone/core/PreferencesLoader.kt new file mode 100644 index 0000000..32c35cb --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/core/PreferencesLoader.kt @@ -0,0 +1,58 @@ +package net.pterodactylus.sone.core + +import net.pterodactylus.sone.fcp.FcpInterface.* +import net.pterodactylus.util.config.* + +/** + * Loads preferences stored in a [Configuration] into a [Preferences] object. + */ +class PreferencesLoader(private val preferences: Preferences) { + + fun loadFrom(configuration: Configuration) { + loadInsertionDelay(configuration) + loadPostsPerPage(configuration) + loadImagesPerPage(configuration) + loadCharactersPerPost(configuration) + loadPostCutOffLength(configuration) + loadRequireFullAccess(configuration) + loadFcpInterfaceActive(configuration) + loadFcpFullAccessRequired(configuration) + } + + private fun loadInsertionDelay(configuration: Configuration) { + preferences.newInsertionDelay = configuration.getIntValue("Option/InsertionDelay").getValue(null) + } + + private fun loadPostsPerPage(configuration: Configuration) { + preferences.newPostsPerPage = configuration.getIntValue("Option/PostsPerPage").getValue(null) + } + + private fun loadImagesPerPage(configuration: Configuration) { + preferences.newImagesPerPage = configuration.getIntValue("Option/ImagesPerPage").getValue(null) + } + + private fun loadCharactersPerPost(configuration: Configuration) { + preferences.newCharactersPerPost = configuration.getIntValue("Option/CharactersPerPost").getValue(null) + } + + private fun loadPostCutOffLength(configuration: Configuration) { + try { + preferences.newPostCutOffLength = configuration.getIntValue("Option/PostCutOffLength").getValue(null) + } catch (iae1: IllegalArgumentException) { /* previous versions allowed -1, ignore and use default. */ + } + } + + private fun loadRequireFullAccess(configuration: Configuration) { + preferences.newRequireFullAccess = configuration.getBooleanValue("Option/RequireFullAccess").getValue(null) + } + + private fun loadFcpInterfaceActive(configuration: Configuration) { + preferences.newFcpInterfaceActive = configuration.getBooleanValue("Option/ActivateFcpInterface").getValue(null) + } + + private fun loadFcpFullAccessRequired(configuration: Configuration) { + val fullAccessRequiredInteger = configuration.getIntValue("Option/FcpFullAccessRequired").getValue(null) + preferences.newFcpFullAccessRequired = fullAccessRequiredInteger?.let { FullAccessRequired.values()[it] } + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/database/memory/MemoryDatabase.kt b/src/main/kotlin/net/pterodactylus/sone/database/memory/MemoryDatabase.kt new file mode 100644 index 0000000..8722873 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/database/memory/MemoryDatabase.kt @@ -0,0 +1,354 @@ +/* + * Sone - MemoryDatabase.kt - Copyright © 2013–2020 David Roden + * + * This 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 . + */ + +package net.pterodactylus.sone.database.memory + +import com.google.common.base.Preconditions.checkNotNull +import com.google.common.collect.HashMultimap +import com.google.common.collect.Multimap +import com.google.common.collect.TreeMultimap +import com.google.common.util.concurrent.* +import com.google.inject.Inject +import com.google.inject.Singleton +import net.pterodactylus.sone.data.Album +import net.pterodactylus.sone.data.Image +import net.pterodactylus.sone.data.Post +import net.pterodactylus.sone.data.PostReply +import net.pterodactylus.sone.data.Reply.TIME_COMPARATOR +import net.pterodactylus.sone.data.Sone +import net.pterodactylus.sone.data.Sone.toAllAlbums +import net.pterodactylus.sone.data.Sone.toAllImages +import net.pterodactylus.sone.data.impl.AlbumBuilderImpl +import net.pterodactylus.sone.data.impl.ImageBuilderImpl +import net.pterodactylus.sone.database.AlbumBuilder +import net.pterodactylus.sone.database.Database +import net.pterodactylus.sone.database.DatabaseException +import net.pterodactylus.sone.database.ImageBuilder +import net.pterodactylus.sone.database.PostBuilder +import net.pterodactylus.sone.database.PostDatabase +import net.pterodactylus.sone.database.PostReplyBuilder +import net.pterodactylus.sone.utils.* +import net.pterodactylus.util.config.Configuration +import net.pterodactylus.util.config.ConfigurationException +import java.util.concurrent.locks.ReentrantReadWriteLock +import kotlin.concurrent.withLock + +/** + * Memory-based [PostDatabase] implementation. + */ +@Singleton +class MemoryDatabase @Inject constructor(private val configuration: Configuration) : AbstractService(), Database { + + private val lock = ReentrantReadWriteLock() + private val readLock by lazy { lock.readLock()!! } + private val writeLock by lazy { lock.writeLock()!! } + private val configurationLoader = ConfigurationLoader(configuration) + private val allSones = mutableMapOf() + private val allPosts = mutableMapOf() + private val sonePosts: Multimap = HashMultimap.create() + private val knownPosts = mutableSetOf() + private val allPostReplies = mutableMapOf() + private val sonePostReplies: Multimap = TreeMultimap.create(Comparator { leftString, rightString -> leftString.compareTo(rightString) }, TIME_COMPARATOR) + private val knownPostReplies = mutableSetOf() + private val allAlbums = mutableMapOf() + private val soneAlbums: Multimap = HashMultimap.create() + private val allImages = mutableMapOf() + private val soneImages: Multimap = HashMultimap.create() + private val memoryBookmarkDatabase = MemoryBookmarkDatabase(this, configurationLoader) + private val memoryFriendDatabase = MemoryFriendDatabase(configurationLoader) + private val saveRateLimiter: RateLimiter = RateLimiter.create(1.0) + private val saveKnownPostsRateLimiter: RateLimiter = RateLimiter.create(1.0) + private val saveKnownPostRepliesRateLimiter: RateLimiter = RateLimiter.create(1.0) + + override val soneLoader get() = this::getSone + + override val sones get() = readLock.withLock { allSones.values.toSet() } + + override val localSones get() = readLock.withLock { allSones.values.filter(Sone::isLocal) } + + override val remoteSones get() = readLock.withLock { allSones.values.filterNot(Sone::isLocal) } + + override val bookmarkedPosts get() = memoryBookmarkDatabase.bookmarkedPosts + + override fun save() { + if (saveRateLimiter.tryAcquire()) { + saveKnownPosts() + saveKnownPostReplies() + } + } + + override fun doStart() { + memoryBookmarkDatabase.start() + loadKnownPosts() + loadKnownPostReplies() + notifyStarted() + } + + override fun doStop() { + try { + memoryBookmarkDatabase.stop() + save() + notifyStopped() + } catch (de1: DatabaseException) { + notifyFailed(de1) + } + } + + override fun newSoneBuilder() = MemorySoneBuilder(this) + + override fun storeSone(sone: Sone) { + writeLock.withLock { + removeSone(sone) + + allSones[sone.id] = sone + sonePosts.putAll(sone.id, sone.posts) + for (post in sone.posts) { + allPosts[post.id] = post + } + sonePostReplies.putAll(sone.id, sone.replies) + for (postReply in sone.replies) { + allPostReplies[postReply.id] = postReply + } + soneAlbums.putAll(sone.id, toAllAlbums.apply(sone)!!) + for (album in toAllAlbums.apply(sone)!!) { + allAlbums[album.id] = album + } + soneImages.putAll(sone.id, toAllImages.apply(sone)!!) + for (image in toAllImages.apply(sone)!!) { + allImages[image.id] = image + } + } + } + + override fun removeSone(sone: Sone) { + writeLock.withLock { + allSones.remove(sone.id) + val removedPosts = sonePosts.removeAll(sone.id) + for (removedPost in removedPosts) { + allPosts.remove(removedPost.id) + } + val removedPostReplies = sonePostReplies.removeAll(sone.id) + for (removedPostReply in removedPostReplies) { + allPostReplies.remove(removedPostReply.id) + } + val removedAlbums = soneAlbums.removeAll(sone.id) + for (removedAlbum in removedAlbums) { + allAlbums.remove(removedAlbum.id) + } + val removedImages = soneImages.removeAll(sone.id) + for (removedImage in removedImages) { + allImages.remove(removedImage.id) + } + } + } + + override fun getSone(soneId: String) = readLock.withLock { allSones[soneId] } + + override fun getFriends(localSone: Sone): Collection = + if (!localSone.isLocal) { + emptySet() + } else { + memoryFriendDatabase.getFriends(localSone.id) + } + + override fun isFriend(localSone: Sone, friendSoneId: String) = + if (!localSone.isLocal) { + false + } else { + memoryFriendDatabase.isFriend(localSone.id, friendSoneId) + } + + override fun addFriend(localSone: Sone, friendSoneId: String) { + if (!localSone.isLocal) { + return + } + memoryFriendDatabase.addFriend(localSone.id, friendSoneId) + } + + override fun removeFriend(localSone: Sone, friendSoneId: String) { + if (!localSone.isLocal) { + return + } + memoryFriendDatabase.removeFriend(localSone.id, friendSoneId) + } + + override fun getFollowingTime(friendSoneId: String) = + memoryFriendDatabase.getFollowingTime(friendSoneId) + + override fun getPost(postId: String) = + readLock.withLock { allPosts[postId] } + + override fun getPosts(soneId: String): Collection = + sonePosts[soneId].toSet() + + override fun getDirectedPosts(recipientId: String) = + readLock.withLock { + allPosts.values.filter { + it.recipientId.orNull() == recipientId + } + } + + override fun newPostBuilder(): PostBuilder = MemoryPostBuilder(this, this) + + override fun storePost(post: Post) { + checkNotNull(post, "post must not be null") + writeLock.withLock { + allPosts[post.id] = post + sonePosts[post.sone.id].add(post) + } + } + + override fun removePost(post: Post) { + checkNotNull(post, "post must not be null") + writeLock.withLock { + allPosts.remove(post.id) + sonePosts[post.sone.id].remove(post) + post.sone.removePost(post) + } + } + + override fun getPostReply(id: String) = readLock.withLock { allPostReplies[id] } + + override fun getReplies(postId: String) = + readLock.withLock { + allPostReplies.values + .filter { it.postId == postId } + .sortedWith(TIME_COMPARATOR) + } + + override fun newPostReplyBuilder(): PostReplyBuilder = + MemoryPostReplyBuilder(this, this) + + override fun storePostReply(postReply: PostReply) = + writeLock.withLock { + allPostReplies[postReply.id] = postReply + } + + override fun removePostReply(postReply: PostReply) = + writeLock.withLock { + allPostReplies.remove(postReply.id) + }.unit + + override fun getAlbum(albumId: String) = readLock.withLock { allAlbums[albumId] } + + override fun newAlbumBuilder(): AlbumBuilder = AlbumBuilderImpl() + + override fun storeAlbum(album: Album) = + writeLock.withLock { + allAlbums[album.id] = album + soneAlbums.put(album.sone.id, album) + }.unit + + override fun removeAlbum(album: Album) = + writeLock.withLock { + allAlbums.remove(album.id) + soneAlbums.remove(album.sone.id, album) + }.unit + + override fun getImage(imageId: String) = readLock.withLock { allImages[imageId] } + + override fun newImageBuilder(): ImageBuilder = ImageBuilderImpl() + + override fun storeImage(image: Image): Unit = + writeLock.withLock { + allImages[image.id] = image + soneImages.put(image.sone.id, image) + } + + override fun removeImage(image: Image): Unit = + writeLock.withLock { + allImages.remove(image.id) + soneImages.remove(image.sone.id, image) + } + + override fun bookmarkPost(post: Post) = + memoryBookmarkDatabase.bookmarkPost(post) + + override fun unbookmarkPost(post: Post) = + memoryBookmarkDatabase.unbookmarkPost(post) + + override fun isPostBookmarked(post: Post) = + memoryBookmarkDatabase.isPostBookmarked(post) + + protected fun isPostKnown(post: Post) = readLock.withLock { post.id in knownPosts } + + fun setPostKnown(post: Post, known: Boolean): Unit = + writeLock.withLock { + if (known) + knownPosts.add(post.id) + else + knownPosts.remove(post.id) + saveKnownPosts() + } + + protected fun isPostReplyKnown(postReply: PostReply) = readLock.withLock { postReply.id in knownPostReplies } + + fun setPostReplyKnown(postReply: PostReply, known: Boolean): Unit = + writeLock.withLock { + if (known) + knownPostReplies.add(postReply.id) + else + knownPostReplies.remove(postReply.id) + saveKnownPostReplies() + } + + private fun loadKnownPosts() = + configurationLoader.loadKnownPosts() + .let { + writeLock.withLock { + knownPosts.clear() + knownPosts.addAll(it) + } + } + + private fun saveKnownPosts() = + saveKnownPostsRateLimiter.tryAcquire().ifTrue { + try { + readLock.withLock { + knownPosts.forEachIndexed { index, knownPostId -> + configuration.getStringValue("KnownPosts/$index/ID").value = knownPostId + } + configuration.getStringValue("KnownPosts/${knownPosts.size}/ID").value = null + } + } catch (ce1: ConfigurationException) { + throw DatabaseException("Could not save database.", ce1) + } + } + + private fun loadKnownPostReplies(): Unit = + configurationLoader.loadKnownPostReplies().let { knownPostReplies -> + writeLock.withLock { + this.knownPostReplies.clear() + this.knownPostReplies.addAll(knownPostReplies) + } + } + + private fun saveKnownPostReplies() = + saveKnownPostRepliesRateLimiter.tryAcquire().ifTrue { + try { + readLock.withLock { + knownPostReplies.forEachIndexed { index, knownPostReply -> + configuration.getStringValue("KnownReplies/$index/ID").value = knownPostReply + } + configuration.getStringValue("KnownReplies/${knownPostReplies.size}/ID").value = null + } + } catch (ce1: ConfigurationException) { + throw DatabaseException("Could not save database.", ce1) + } + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/IdentityChangeDetector.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/IdentityChangeDetector.kt new file mode 100644 index 0000000..ffcafb3 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/IdentityChangeDetector.kt @@ -0,0 +1,68 @@ +/* + * Sone - IdentityChangeDetector.kt - Copyright © 2013–2020 David Roden + * + * This 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 . + */ + +package net.pterodactylus.sone.freenet.wot + +/** + * Detects changes between two lists of [Identity]s. The detector can find + * added and removed identities, and for identities that exist in both list + * their contexts and properties are checked for added, removed, or (in case of + * properties) changed values. + */ +class IdentityChangeDetector(oldIdentities: Collection) { + + private val oldIdentities: Map = oldIdentities.associateBy { it.id } + var onNewIdentity: IdentityProcessor? = null + var onRemovedIdentity: IdentityProcessor? = null + var onChangedIdentity: IdentityProcessor? = null + var onUnchangedIdentity: IdentityProcessor? = null + + fun detectChanges(newIdentities: Collection) { + onRemovedIdentity.notify(oldIdentities.values.filter { it !in newIdentities }) + onNewIdentity.notify(newIdentities.filter { it !in oldIdentities.values }) + onChangedIdentity.notify(newIdentities.filter { it.id in oldIdentities }.filter { identityHasChanged(oldIdentities[it.id]!!, it) }) + onUnchangedIdentity.notify(newIdentities.filter { it.id in oldIdentities }.filterNot { identityHasChanged(oldIdentities[it.id]!!, it) }) + } + + private fun identityHasChanged(oldIdentity: Identity, newIdentity: Identity?) = + identityHasNewContexts(oldIdentity, newIdentity!!) + || identityHasRemovedContexts(oldIdentity, newIdentity) + || identityHasNewProperties(oldIdentity, newIdentity) + || identityHasRemovedProperties(oldIdentity, newIdentity) + || identityHasChangedProperties(oldIdentity, newIdentity) + + private fun identityHasNewContexts(oldIdentity: Identity, newIdentity: Identity) = + newIdentity.contexts.any { it !in oldIdentity.contexts } + + private fun identityHasRemovedContexts(oldIdentity: Identity, newIdentity: Identity) = + oldIdentity.contexts.any { it !in newIdentity.contexts } + + private fun identityHasNewProperties(oldIdentity: Identity, newIdentity: Identity) = + newIdentity.properties.keys.any { it !in oldIdentity.properties } + + private fun identityHasRemovedProperties(oldIdentity: Identity, newIdentity: Identity) = + oldIdentity.properties.keys.any { it !in newIdentity.properties } + + private fun identityHasChangedProperties(oldIdentity: Identity, newIdentity: Identity) = + oldIdentity.properties.entries.any { newIdentity.properties[it.key] != it.value } + +} + +typealias IdentityProcessor = (Identity) -> Unit + +private fun IdentityProcessor?.notify(identities: Iterable) = + this?.let { identities.forEach(this::invoke) } diff --git a/src/main/kotlin/net/pterodactylus/sone/main/SonePlugin.kt b/src/main/kotlin/net/pterodactylus/sone/main/SonePlugin.kt new file mode 100644 index 0000000..5e0b2c1 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/main/SonePlugin.kt @@ -0,0 +1,7 @@ +package net.pterodactylus.sone.main + +data class PluginVersion(val version: String) + +data class PluginYear(val year: Int) + +data class PluginHomepage(val homepage: String) diff --git a/src/main/kotlin/net/pterodactylus/sone/web/AllPages.kt b/src/main/kotlin/net/pterodactylus/sone/web/AllPages.kt new file mode 100644 index 0000000..c538f0e --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/AllPages.kt @@ -0,0 +1,52 @@ +package net.pterodactylus.sone.web + +import net.pterodactylus.sone.web.pages.* +import javax.inject.Inject + +/** + * Container for all web pages. This uses field injection because there are way too many pages + * to sensibly use constructor injection. + */ +class AllPages { + + @Inject lateinit var aboutPage: AboutPage + @Inject lateinit var bookmarkPage: BookmarkPage + @Inject lateinit var bookmarksPage: BookmarksPage + @Inject lateinit var createAlbumPage: CreateAlbumPage + @Inject lateinit var createPostPage: CreatePostPage + @Inject lateinit var createReplyPage: CreateReplyPage + @Inject lateinit var createSonePage: CreateSonePage + @Inject lateinit var deleteAlbumPage: DeleteAlbumPage + @Inject lateinit var deleteImagePage: DeleteImagePage + @Inject lateinit var deletePostPage: DeletePostPage + @Inject lateinit var deleteProfileFieldPage: DeleteProfileFieldPage + @Inject lateinit var deleteReplyPage: DeleteReplyPage + @Inject lateinit var deleteSonePage: DeleteSonePage + @Inject lateinit var dismissNotificationPage: DismissNotificationPage + @Inject lateinit var editAlbumPage: EditAlbumPage + @Inject lateinit var editImagePage: EditImagePage + @Inject lateinit var editProfileFieldPage: EditProfileFieldPage + @Inject lateinit var editProfilePage: EditProfilePage + @Inject lateinit var followSonePage: FollowSonePage + @Inject lateinit var getImagePage: GetImagePage + @Inject lateinit var imageBrowserPage: ImageBrowserPage + @Inject lateinit var indexPage: IndexPage + @Inject lateinit var knownSonesPage: KnownSonesPage + @Inject lateinit var likePage: LikePage + @Inject lateinit var lockSonePage: LockSonePage + @Inject lateinit var loginPage: LoginPage + @Inject lateinit var logoutPage: LogoutPage + @Inject lateinit var markAsKnownPage: MarkAsKnownPage + @Inject lateinit var newPage: NewPage + @Inject lateinit var optionsPage: OptionsPage + @Inject lateinit var rescuePage: RescuePage + @Inject lateinit var searchPage: SearchPage + @Inject lateinit var unbookmarkPage: UnbookmarkPage + @Inject lateinit var unfollowSonePage: UnfollowSonePage + @Inject lateinit var unlikePage: UnlikePage + @Inject lateinit var unlockSonePage: UnlockSonePage + @Inject lateinit var uploadImagePage: UploadImagePage + @Inject lateinit var viewPostPage: ViewPostPage + @Inject lateinit var viewSonePage: ViewSonePage + +} diff --git a/src/test/java/net/pterodactylus/sone/freenet/wot/IdentityManagerTest.kt b/src/test/java/net/pterodactylus/sone/freenet/wot/IdentityManagerTest.kt deleted file mode 100644 index 3768efd..0000000 --- a/src/test/java/net/pterodactylus/sone/freenet/wot/IdentityManagerTest.kt +++ /dev/null @@ -1,33 +0,0 @@ -package net.pterodactylus.sone.freenet.wot - -import com.google.common.eventbus.* -import net.pterodactylus.sone.freenet.plugin.* -import net.pterodactylus.sone.test.* -import org.hamcrest.MatcherAssert.* -import org.hamcrest.Matchers.* -import org.junit.* -import org.mockito.Mockito.* - -/** - * Unit test for [IdentityManagerImpl]. - */ -class IdentityManagerTest { - - private val eventBus = mock() - private val webOfTrustConnector = mock() - private val identityManager = IdentityManagerImpl(eventBus, webOfTrustConnector, IdentityLoader(webOfTrustConnector, Context("Test"))) - - @Test - fun identityManagerPingsWotConnector() { - assertThat(identityManager.isConnected, equalTo(true)) - verify(webOfTrustConnector).ping() - } - - @Test - fun disconnectedWotConnectorIsRecognized() { - doThrow(PluginException::class.java).whenever(webOfTrustConnector).ping() - assertThat(identityManager.isConnected, equalTo(false)) - verify(webOfTrustConnector).ping() - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/freenet/wot/IdentityManagerTest.kt b/src/test/kotlin/net/pterodactylus/sone/freenet/wot/IdentityManagerTest.kt new file mode 100644 index 0000000..3768efd --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/freenet/wot/IdentityManagerTest.kt @@ -0,0 +1,33 @@ +package net.pterodactylus.sone.freenet.wot + +import com.google.common.eventbus.* +import net.pterodactylus.sone.freenet.plugin.* +import net.pterodactylus.sone.test.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import org.junit.* +import org.mockito.Mockito.* + +/** + * Unit test for [IdentityManagerImpl]. + */ +class IdentityManagerTest { + + private val eventBus = mock() + private val webOfTrustConnector = mock() + private val identityManager = IdentityManagerImpl(eventBus, webOfTrustConnector, IdentityLoader(webOfTrustConnector, Context("Test"))) + + @Test + fun identityManagerPingsWotConnector() { + assertThat(identityManager.isConnected, equalTo(true)) + verify(webOfTrustConnector).ping() + } + + @Test + fun disconnectedWotConnectorIsRecognized() { + doThrow(PluginException::class.java).whenever(webOfTrustConnector).ping() + assertThat(identityManager.isConnected, equalTo(false)) + verify(webOfTrustConnector).ping() + } + +} -- 2.7.4