2 * Sone - MemoryDatabase.java - Copyright © 2013–2019 David Roden
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 package net.pterodactylus.sone.database.memory
20 import com.google.common.base.Preconditions.checkNotNull
21 import com.google.common.collect.HashMultimap
22 import com.google.common.collect.Multimap
23 import com.google.common.collect.TreeMultimap
24 import com.google.common.util.concurrent.AbstractService
25 import com.google.inject.Inject
26 import com.google.inject.Singleton
27 import net.pterodactylus.sone.data.Album
28 import net.pterodactylus.sone.data.Image
29 import net.pterodactylus.sone.data.Post
30 import net.pterodactylus.sone.data.PostReply
31 import net.pterodactylus.sone.data.Reply.TIME_COMPARATOR
32 import net.pterodactylus.sone.data.Sone
33 import net.pterodactylus.sone.data.Sone.toAllAlbums
34 import net.pterodactylus.sone.data.Sone.toAllImages
35 import net.pterodactylus.sone.data.impl.AlbumBuilderImpl
36 import net.pterodactylus.sone.data.impl.ImageBuilderImpl
37 import net.pterodactylus.sone.database.AlbumBuilder
38 import net.pterodactylus.sone.database.Database
39 import net.pterodactylus.sone.database.DatabaseException
40 import net.pterodactylus.sone.database.ImageBuilder
41 import net.pterodactylus.sone.database.PostBuilder
42 import net.pterodactylus.sone.database.PostDatabase
43 import net.pterodactylus.sone.database.PostReplyBuilder
44 import net.pterodactylus.sone.utils.unit
45 import net.pterodactylus.util.config.Configuration
46 import net.pterodactylus.util.config.ConfigurationException
47 import java.util.concurrent.locks.ReentrantReadWriteLock
48 import kotlin.concurrent.withLock
51 * Memory-based [PostDatabase] implementation.
54 class MemoryDatabase @Inject constructor(private val configuration: Configuration) : AbstractService(), Database {
56 private val lock = ReentrantReadWriteLock()
57 private val readLock by lazy { lock.readLock()!! }
58 private val writeLock by lazy { lock.writeLock()!! }
59 private val configurationLoader = ConfigurationLoader(configuration)
60 private val allSones = mutableMapOf<String, Sone>()
61 private val allPosts = mutableMapOf<String, Post>()
62 private val sonePosts: Multimap<String, Post> = HashMultimap.create<String, Post>()
63 private val knownPosts = mutableSetOf<String>()
64 private val allPostReplies = mutableMapOf<String, PostReply>()
65 private val sonePostReplies: Multimap<String, PostReply> = TreeMultimap.create<String, PostReply>(Comparator { leftString, rightString -> leftString.compareTo(rightString) }, TIME_COMPARATOR)
66 private val knownPostReplies = mutableSetOf<String>()
67 private val allAlbums = mutableMapOf<String, Album>()
68 private val soneAlbums: Multimap<String, Album> = HashMultimap.create<String, Album>()
69 private val allImages = mutableMapOf<String, Image>()
70 private val soneImages: Multimap<String, Image> = HashMultimap.create<String, Image>()
71 private val memoryBookmarkDatabase = MemoryBookmarkDatabase(this, configurationLoader)
72 private val memoryFriendDatabase = MemoryFriendDatabase(configurationLoader)
74 override val soneLoader get() = this::getSone
76 override val sones get() = readLock.withLock { allSones.values.toSet() }
78 override val localSones get() = readLock.withLock { allSones.values.filter(Sone::isLocal) }
80 override val remoteSones get() = readLock.withLock { allSones.values.filterNot(Sone::isLocal) }
82 override val bookmarkedPosts get() = memoryBookmarkDatabase.bookmarkedPosts
86 saveKnownPostReplies()
89 override fun doStart() {
90 memoryBookmarkDatabase.start()
92 loadKnownPostReplies()
96 override fun doStop() {
98 memoryBookmarkDatabase.stop()
101 } catch (de1: DatabaseException) {
106 override fun newSoneBuilder() = MemorySoneBuilder(this)
108 override fun storeSone(sone: Sone) {
112 allSones[sone.id] = sone
113 sonePosts.putAll(sone.id, sone.posts)
114 for (post in sone.posts) {
115 allPosts[post.id] = post
117 sonePostReplies.putAll(sone.id, sone.replies)
118 for (postReply in sone.replies) {
119 allPostReplies[postReply.id] = postReply
121 soneAlbums.putAll(sone.id, toAllAlbums.apply(sone)!!)
122 for (album in toAllAlbums.apply(sone)!!) {
123 allAlbums[album.id] = album
125 soneImages.putAll(sone.id, toAllImages.apply(sone)!!)
126 for (image in toAllImages.apply(sone)!!) {
127 allImages[image.id] = image
132 override fun removeSone(sone: Sone) {
134 allSones.remove(sone.id)
135 val removedPosts = sonePosts.removeAll(sone.id)
136 for (removedPost in removedPosts) {
137 allPosts.remove(removedPost.id)
139 val removedPostReplies = sonePostReplies.removeAll(sone.id)
140 for (removedPostReply in removedPostReplies) {
141 allPostReplies.remove(removedPostReply.id)
143 val removedAlbums = soneAlbums.removeAll(sone.id)
144 for (removedAlbum in removedAlbums) {
145 allAlbums.remove(removedAlbum.id)
147 val removedImages = soneImages.removeAll(sone.id)
148 for (removedImage in removedImages) {
149 allImages.remove(removedImage.id)
154 override fun getSone(soneId: String) = readLock.withLock { allSones[soneId] }
156 override fun getFriends(localSone: Sone): Collection<String> =
157 if (!localSone.isLocal) {
160 memoryFriendDatabase.getFriends(localSone.id)
163 override fun isFriend(localSone: Sone, friendSoneId: String) =
164 if (!localSone.isLocal) {
167 memoryFriendDatabase.isFriend(localSone.id, friendSoneId)
170 override fun addFriend(localSone: Sone, friendSoneId: String) {
171 if (!localSone.isLocal) {
174 memoryFriendDatabase.addFriend(localSone.id, friendSoneId)
177 override fun removeFriend(localSone: Sone, friendSoneId: String) {
178 if (!localSone.isLocal) {
181 memoryFriendDatabase.removeFriend(localSone.id, friendSoneId)
184 override fun getFollowingTime(friendSoneId: String) =
185 memoryFriendDatabase.getFollowingTime(friendSoneId)
187 override fun getPost(postId: String) =
188 readLock.withLock { allPosts[postId] }
190 override fun getPosts(soneId: String): Collection<Post> =
191 sonePosts[soneId].toSet()
193 override fun getDirectedPosts(recipientId: String) =
195 allPosts.values.filter {
196 it.recipientId.orNull() == recipientId
200 override fun newPostBuilder(): PostBuilder = MemoryPostBuilder(this, this)
202 override fun storePost(post: Post) {
203 checkNotNull(post, "post must not be null")
205 allPosts[post.id] = post
206 sonePosts[post.sone.id].add(post)
210 override fun removePost(post: Post) {
211 checkNotNull(post, "post must not be null")
213 allPosts.remove(post.id)
214 sonePosts[post.sone.id].remove(post)
215 post.sone.removePost(post)
219 override fun getPostReply(id: String) = readLock.withLock { allPostReplies[id] }
221 override fun getReplies(postId: String) =
223 allPostReplies.values
224 .filter { it.postId == postId }
225 .sortedWith(TIME_COMPARATOR)
228 override fun newPostReplyBuilder(): PostReplyBuilder =
229 MemoryPostReplyBuilder(this, this)
231 override fun storePostReply(postReply: PostReply) =
233 allPostReplies[postReply.id] = postReply
236 override fun removePostReply(postReply: PostReply) =
238 allPostReplies.remove(postReply.id)
241 override fun getAlbum(albumId: String) = readLock.withLock { allAlbums[albumId] }
243 override fun newAlbumBuilder(): AlbumBuilder = AlbumBuilderImpl()
245 override fun storeAlbum(album: Album) =
247 allAlbums[album.id] = album
248 soneAlbums.put(album.sone.id, album)
251 override fun removeAlbum(album: Album) =
253 allAlbums.remove(album.id)
254 soneAlbums.remove(album.sone.id, album)
257 override fun getImage(imageId: String) = readLock.withLock { allImages[imageId] }
259 override fun newImageBuilder(): ImageBuilder = ImageBuilderImpl()
261 override fun storeImage(image: Image): Unit =
263 allImages[image.id] = image
264 soneImages.put(image.sone.id, image)
267 override fun removeImage(image: Image): Unit =
269 allImages.remove(image.id)
270 soneImages.remove(image.sone.id, image)
273 override fun bookmarkPost(post: Post) =
274 memoryBookmarkDatabase.bookmarkPost(post)
276 override fun unbookmarkPost(post: Post) =
277 memoryBookmarkDatabase.unbookmarkPost(post)
279 override fun isPostBookmarked(post: Post) =
280 memoryBookmarkDatabase.isPostBookmarked(post)
282 protected fun isPostKnown(post: Post) = readLock.withLock { post.id in knownPosts }
284 protected fun setPostKnown(post: Post, known: Boolean): Unit =
287 knownPosts.add(post.id)
289 knownPosts.remove(post.id)
293 protected fun isPostReplyKnown(postReply: PostReply) = readLock.withLock { postReply.id in knownPostReplies }
295 protected fun setPostReplyKnown(postReply: PostReply, known: Boolean): Unit =
298 knownPostReplies.add(postReply.id)
300 knownPostReplies.remove(postReply.id)
301 saveKnownPostReplies()
304 private fun loadKnownPosts() =
305 configurationLoader.loadKnownPosts()
309 knownPosts.addAll(it)
313 private fun saveKnownPosts() =
316 knownPosts.forEachIndexed { index, knownPostId ->
317 configuration.getStringValue("KnownPosts/$index/ID").value = knownPostId
319 configuration.getStringValue("KnownPosts/${knownPosts.size}/ID").value = null
321 } catch (ce1: ConfigurationException) {
322 throw DatabaseException("Could not save database.", ce1)
325 private fun loadKnownPostReplies(): Unit =
326 configurationLoader.loadKnownPostReplies().let { knownPostReplies ->
328 this.knownPostReplies.clear()
329 this.knownPostReplies.addAll(knownPostReplies)
333 private fun saveKnownPostReplies() =
336 knownPostReplies.forEachIndexed { index, knownPostReply ->
337 configuration.getStringValue("KnownReplies/$index/ID").value = knownPostReply
339 configuration.getStringValue("KnownReplies/${knownPostReplies.size}/ID").value = null
341 } catch (ce1: ConfigurationException) {
342 throw DatabaseException("Could not save database.", ce1)