🎨 Store shells instead of full posts in in-memory database
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Thu, 6 Aug 2020 20:03:35 +0000 (22:03 +0200)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Thu, 6 Aug 2020 20:03:35 +0000 (22:03 +0200)
src/main/kotlin/net/pterodactylus/sone/database/memory/MemoryDatabase.kt
src/main/kotlin/net/pterodactylus/sone/database/memory/MemoryPost.kt
src/test/kotlin/net/pterodactylus/sone/database/memory/MemoryDatabaseTest.kt
src/test/kotlin/net/pterodactylus/sone/test/Matchers.kt
src/test/kotlin/net/pterodactylus/sone/test/Mocks.kt
src/test/kotlin/net/pterodactylus/sone/test/TestPostBuilder.kt

index 589d977..e2281ac 100644 (file)
@@ -62,8 +62,8 @@ class MemoryDatabase @Inject constructor(private val configuration: Configuratio
        private val writeLock: WriteLock by lazy { lock.writeLock() }
        private val configurationLoader = ConfigurationLoader(configuration)
        private val allSones = mutableMapOf<String, Sone>()
-       private val allPosts = mutableMapOf<String, Post>()
-       private val sonePosts: Multimap<String, Post> = HashMultimap.create<String, Post>()
+       private val allPosts = mutableMapOf<String, MemoryPost.Shell>()
+       private val sonePosts: Multimap<String, MemoryPost.Shell> = HashMultimap.create<String, MemoryPost.Shell>()
        private val knownPosts = mutableSetOf<String>()
        private val allPostReplies = mutableMapOf<String, MemoryPostReply.Shell>()
        private val sonePostReplies: Multimap<String, PostReply> = TreeMultimap.create<String, PostReply>(Comparator { leftString, rightString -> leftString.compareTo(rightString) }, newestReplyFirst)
@@ -119,8 +119,8 @@ class MemoryDatabase @Inject constructor(private val configuration: Configuratio
                        removeSone(sone)
 
                        allSones[sone.id] = sone
-                       sonePosts.putAll(sone.id, sone.posts)
-                       for (post in sone.posts) {
+                       sonePosts.putAll(sone.id, sone.posts.map(Post::toShell))
+                       for (post in sone.posts.map(Post::toShell)) {
                                allPosts[post.id] = post
                        }
                        sonePostReplies.putAll(sone.id, sone.replies)
@@ -193,17 +193,17 @@ class MemoryDatabase @Inject constructor(private val configuration: Configuratio
        override fun getFollowingTime(friendSoneId: String) =
                        memoryFriendDatabase.getFollowingTime(friendSoneId)
 
-       override fun getPost(postId: String) =
-                       readLock.withLock { allPosts[postId] }
+       override fun getPost(postId: String): Post? =
+                       readLock.withLock { allPosts[postId]?.build(newPostBuilder()) }
 
        override fun getPosts(soneId: String): Collection<Post> =
-                       sonePosts[soneId].toSet()
+                       sonePosts[soneId].map { it.build(newPostBuilder()) }.toSet()
 
        override fun getDirectedPosts(recipientId: String) =
                        readLock.withLock {
-                               allPosts.values.filter {
-                                       it.recipientId.orNull() == recipientId
-                               }
+                               allPosts.values
+                                               .filter { it.recipientId == recipientId }
+                                               .map { it.build(newPostBuilder()) }
                        }
 
        override fun newPostBuilder(): PostBuilder = MemoryPostBuilder(this, this)
@@ -211,8 +211,10 @@ class MemoryDatabase @Inject constructor(private val configuration: Configuratio
        override fun storePost(post: Post) {
                checkNotNull(post, "post must not be null")
                writeLock.withLock {
-                       allPosts[post.id] = post
-                       sonePosts[post.sone.id].add(post)
+                       post.toShell().also { shell ->
+                               allPosts[post.id] = shell
+                               sonePosts[post.sone.id].add(shell)
+                       }
                }
        }
 
@@ -220,7 +222,7 @@ class MemoryDatabase @Inject constructor(private val configuration: Configuratio
                checkNotNull(post, "post must not be null")
                writeLock.withLock {
                        allPosts.remove(post.id)
-                       sonePosts[post.sone.id].remove(post)
+                       sonePosts[post.sone.id].remove(post.toShell())
                        post.sone.removePost(post)
                }
        }
index c435084..b3664c2 100644 (file)
@@ -18,6 +18,7 @@ package net.pterodactylus.sone.database.memory
 
 import net.pterodactylus.sone.data.Post
 import net.pterodactylus.sone.data.Sone
+import net.pterodactylus.sone.database.PostBuilder
 import net.pterodactylus.sone.database.SoneProvider
 import net.pterodactylus.sone.utils.asOptional
 
@@ -25,7 +26,7 @@ import net.pterodactylus.sone.utils.asOptional
  * A post is a short message that a user writes in his Sone to let other users
  * know what is going on.
  */
-internal class MemoryPost(
+class MemoryPost(
                private val postDatabase: MemoryDatabase,
                private val soneProvider: SoneProvider,
                override val id: String,
@@ -59,4 +60,13 @@ internal class MemoryPost(
 
        override fun toString() = "${javaClass.name}[id=$id,sone=$soneId,recipient=$recipientId,time=$time,text=$text]"
 
+       data class Shell(val id: String, val soneId: String, val recipientId: String?, val time: Long, val text: String) {
+
+               fun build(postBuilder: PostBuilder) =
+                               postBuilder.withId(id).from(soneId).let { if (recipientId != null) it.to(recipientId) else it }.withTime(time).withText(text).build()
+
+       }
+
 }
+
+fun Post.toShell() = MemoryPost.Shell(id, sone!!.id, recipient.orNull()?.id, time, text)
index 6e9b31a..caacb68 100644 (file)
@@ -177,26 +177,18 @@ class MemoryDatabaseTest {
 
        @Test
        fun `post recipients are detected correctly`() {
-               val postWithRecipient = createPost(of(RECIPIENT_ID))
+               val postWithRecipient = createPost(id = "p1", recipient = createRemoteSone(RECIPIENT_ID))
                memoryDatabase.storePost(postWithRecipient)
-               val postWithoutRecipient = createPost(absent())
+               val postWithoutRecipient = createPost(id = "p2", recipient = null)
                memoryDatabase.storePost(postWithoutRecipient)
-               assertThat(memoryDatabase.getDirectedPosts(RECIPIENT_ID), contains(postWithRecipient))
-       }
-
-       private fun createPost(recipient: Optional<String>): Post {
-               val postWithRecipient = mock<Post>()
-               whenever(postWithRecipient.id).thenReturn(randomUUID().toString())
-               whenever(postWithRecipient.sone).thenReturn(sone)
-               whenever(postWithRecipient.recipientId).thenReturn(recipient)
-               return postWithRecipient
+               assertThat(memoryDatabase.getDirectedPosts(RECIPIENT_ID), contains(isPost(isRecipientId = equalTo(RECIPIENT_ID))))
        }
 
        @Test
        fun `post replies are managed correctly`() {
-               val firstPost = createPost(absent())
+               val firstPost = createPost()
                val firstPostFirstReply = createPostReply(id = "p1r1", post = firstPost, time = 1000L)
-               val secondPost = createPost(absent())
+               val secondPost = createPost()
                val secondPostFirstReply = createPostReply(id = "p2r1", post = secondPost, time = 1000L)
                val secondPostSecondReply = createPostReply(id = "p2r2", post = secondPost, time = 2000L)
                memoryDatabase.storePost(firstPost)
index 1f479f6..ecb2049 100644 (file)
@@ -1,6 +1,7 @@
 package net.pterodactylus.sone.test
 
 import freenet.support.*
+import net.pterodactylus.sone.data.Post
 import net.pterodactylus.sone.freenet.wot.*
 import net.pterodactylus.sone.utils.*
 import net.pterodactylus.util.web.*
@@ -106,6 +107,9 @@ fun hasField(name: String, valueMatcher: Matcher<String>) = object : TypeSafeDia
        }
 }
 
+fun isPost(isRecipientId: Matcher<String?> = any(String::class.java)) = AttributeMatcher<Post>("post")
+               .addAttribute("recipient ID", { it.recipientId.orNull() }, isRecipientId)
+
 /**
  * [TypeSafeDiagnosingMatcher] implementation that aims to cut down boilerplate on verifying the attributes
  * of typical container objects.
index e90c080..54307a8 100644 (file)
@@ -34,6 +34,7 @@ import net.pterodactylus.sone.freenet.wot.Identity
 import net.pterodactylus.sone.freenet.wot.OwnIdentity
 import net.pterodactylus.sone.utils.asFreenetBase64
 import net.pterodactylus.sone.utils.asOptional
+import java.util.UUID
 
 val remoteSone1 = createRemoteSone()
 val remoteSone2 = createRemoteSone()
@@ -71,8 +72,8 @@ fun createRemoteSone(id: String = createId(), identity: Identity = createIdentit
        override fun getIdentity(): Identity = identity
 }
 
-fun createPost(text: String = "", sone: Sone? = remoteSone1, known: Boolean = false, time: Long = 1, loaded: Boolean = true, recipient: Sone? = null): Post {
-       return object : Post.EmptyPost("post-id") {
+fun createPost(text: String = "text", sone: Sone? = remoteSone1, known: Boolean = false, time: Long = 1, loaded: Boolean = true, recipient: Sone? = null, id: String = UUID.randomUUID().toString()): Post {
+       return object : Post.EmptyPost(id) {
                override fun getRecipientId() = recipient?.id.asOptional()
                override fun getRecipient() = recipient.asOptional()
                override fun getSone() = sone
index 4c9efe7..6443cb2 100644 (file)
@@ -1,8 +1,6 @@
 package net.pterodactylus.sone.test
 
-import com.google.common.base.Optional
 import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.database.PostBuilder
 import java.util.UUID
 
@@ -11,44 +9,43 @@ import java.util.UUID
  */
 class TestPostBuilder : PostBuilder {
 
-       private val post = mock<Post>()
+       private var id: String? = null
+       private var soneId: String? = null
        private var recipientId: String? = null
+       private var time: Long? = null
+       private var text: String? = null
 
        override fun copyPost(post: Post): PostBuilder = this
 
        override fun from(senderId: String): PostBuilder = apply {
-               val sone = mock<Sone>()
-               whenever(sone.id).thenReturn(senderId)
-               whenever(post.sone).thenReturn(sone)
+               soneId = senderId
        }
 
        override fun randomId(): PostBuilder = apply {
-               whenever(post.id).thenReturn(UUID.randomUUID().toString())
+               id = UUID.randomUUID().toString()
        }
 
        override fun withId(id: String): PostBuilder = apply {
-               whenever(post.id).thenReturn(id)
+               this.id = id
        }
 
        override fun currentTime(): PostBuilder = apply {
-               whenever(post.time).thenReturn(System.currentTimeMillis())
+               time = System.currentTimeMillis()
        }
 
        override fun withTime(time: Long): PostBuilder = apply {
-               whenever(post.time).thenReturn(time)
+               this.time = time
        }
 
        override fun withText(text: String): PostBuilder = apply {
-               whenever(post.text).thenReturn(text)
+               this.text = text
        }
 
        override fun to(recipientId: String): PostBuilder = apply {
                this.recipientId = recipientId
        }
 
-       override fun build(): Post = post
-                       .also {
-                               whenever(post.recipientId).thenReturn(Optional.fromNullable(recipientId))
-                       }
+       override fun build(): Post =
+                       createPost(text!!, sone = createRemoteSone(soneId!!), time = time!!, recipient = recipientId?.let { createRemoteSone(it) }, id = id!!)
 
 }