2 * Sone - MemoryDatabase.java - Copyright © 2013–2016 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.
53 * @author [David ‘Bombe’ Roden](mailto:bombe@pterodactylus.net)
56 class MemoryDatabase @Inject constructor(private val configuration: Configuration) : AbstractService(), Database {
58 private val lock = ReentrantReadWriteLock()
59 private val readLock by lazy { lock.readLock()!! }
60 private val writeLock by lazy { lock.writeLock()!! }
61 private val configurationLoader = ConfigurationLoader(configuration)
62 private val allSones = mutableMapOf<String, Sone>()
63 private val allPosts = mutableMapOf<String, Post>()
64 private val sonePosts: Multimap<String, Post> = HashMultimap.create<String, Post>()
65 private val knownPosts = mutableSetOf<String>()
66 private val allPostReplies = mutableMapOf<String, PostReply>()
67 private val sonePostReplies: Multimap<String, PostReply> = TreeMultimap.create<String, PostReply>(Comparator { leftString, rightString -> leftString.compareTo(rightString) }, TIME_COMPARATOR)
68 private val knownPostReplies = mutableSetOf<String>()
69 private val allAlbums = mutableMapOf<String, Album>()
70 private val soneAlbums: Multimap<String, Album> = HashMultimap.create<String, Album>()
71 private val allImages = mutableMapOf<String, Image>()
72 private val soneImages: Multimap<String, Image> = HashMultimap.create<String, Image>()
73 private val memoryBookmarkDatabase = MemoryBookmarkDatabase(this, configurationLoader)
74 private val memoryFriendDatabase = MemoryFriendDatabase(configurationLoader)
76 override val soneLoader get() = this::getSone
78 override val sones get() = readLock.withLock { allSones.values.toSet() }
80 override val localSones get() = readLock.withLock { allSones.values.filter(Sone::isLocal) }
82 override val remoteSones get() = readLock.withLock { allSones.values.filterNot(Sone::isLocal) }
84 override val bookmarkedPosts get() = memoryBookmarkDatabase.bookmarkedPosts
88 saveKnownPostReplies()
91 override fun doStart() {
92 memoryBookmarkDatabase.start()
94 loadKnownPostReplies()
98 override fun doStop() {
100 memoryBookmarkDatabase.stop()
103 } catch (de1: DatabaseException) {
108 override fun newSoneBuilder() = MemorySoneBuilder(this)
110 override fun storeSone(sone: Sone) {
114 allSones[sone.id] = sone
115 sonePosts.putAll(sone.id, sone.posts)
116 for (post in sone.posts) {
117 allPosts[post.id] = post
119 sonePostReplies.putAll(sone.id, sone.replies)
120 for (postReply in sone.replies) {
121 allPostReplies[postReply.id] = postReply
123 soneAlbums.putAll(sone.id, toAllAlbums.apply(sone)!!)
124 for (album in toAllAlbums.apply(sone)!!) {
125 allAlbums[album.id] = album
127 soneImages.putAll(sone.id, toAllImages.apply(sone)!!)
128 for (image in toAllImages.apply(sone)!!) {
129 allImages[image.id] = image
134 override fun removeSone(sone: Sone) {
136 allSones.remove(sone.id)
137 val removedPosts = sonePosts.removeAll(sone.id)
138 for (removedPost in removedPosts) {
139 allPosts.remove(removedPost.id)
141 val removedPostReplies = sonePostReplies.removeAll(sone.id)
142 for (removedPostReply in removedPostReplies) {
143 allPostReplies.remove(removedPostReply.id)
145 val removedAlbums = soneAlbums.removeAll(sone.id)
146 for (removedAlbum in removedAlbums) {
147 allAlbums.remove(removedAlbum.id)
149 val removedImages = soneImages.removeAll(sone.id)
150 for (removedImage in removedImages) {
151 allImages.remove(removedImage.id)
156 override fun getSone(soneId: String) = readLock.withLock { allSones[soneId] }
158 override fun getFriends(localSone: Sone): Collection<String> =
159 if (!localSone.isLocal) {
162 memoryFriendDatabase.getFriends(localSone.id)
165 override fun isFriend(localSone: Sone, friendSoneId: String) =
166 if (!localSone.isLocal) {
169 memoryFriendDatabase.isFriend(localSone.id, friendSoneId)
172 override fun addFriend(localSone: Sone, friendSoneId: String) {
173 if (!localSone.isLocal) {
176 memoryFriendDatabase.addFriend(localSone.id, friendSoneId)
179 override fun removeFriend(localSone: Sone, friendSoneId: String) {
180 if (!localSone.isLocal) {
183 memoryFriendDatabase.removeFriend(localSone.id, friendSoneId)
186 override fun getFollowingTime(friendSoneId: String) =
187 memoryFriendDatabase.getFollowingTime(friendSoneId)
189 override fun getPost(postId: String) =
190 readLock.withLock { allPosts[postId] }
192 override fun getPosts(soneId: String): Collection<Post> =
193 sonePosts[soneId].toSet()
195 override fun getDirectedPosts(recipientId: String) =
197 allPosts.values.filter {
198 it.recipientId.orNull() == recipientId
202 override fun newPostBuilder(): PostBuilder = MemoryPostBuilder(this, this)
204 override fun storePost(post: Post) {
205 checkNotNull(post, "post must not be null")
207 allPosts[post.id] = post
208 sonePosts[post.sone.id].add(post)
212 override fun removePost(post: Post) {
213 checkNotNull(post, "post must not be null")
215 allPosts.remove(post.id)
216 sonePosts[post.sone.id].remove(post)
217 post.sone.removePost(post)
221 override fun getPostReply(id: String) = readLock.withLock { allPostReplies[id] }
223 override fun getReplies(postId: String) =
225 allPostReplies.values
226 .filter { it.postId == postId }
227 .sortedWith(TIME_COMPARATOR)
230 override fun newPostReplyBuilder(): PostReplyBuilder =
231 MemoryPostReplyBuilder(this, this)
233 override fun storePostReply(postReply: PostReply) =
235 allPostReplies[postReply.id] = postReply
238 override fun removePostReply(postReply: PostReply) =
240 allPostReplies.remove(postReply.id)
243 override fun getAlbum(albumId: String) = readLock.withLock { allAlbums[albumId] }
245 override fun newAlbumBuilder(): AlbumBuilder = AlbumBuilderImpl()
247 override fun storeAlbum(album: Album) =
249 allAlbums[album.id] = album
250 soneAlbums.put(album.sone.id, album)
253 override fun removeAlbum(album: Album) =
255 allAlbums.remove(album.id)
256 soneAlbums.remove(album.sone.id, album)
259 override fun getImage(imageId: String) = readLock.withLock { allImages[imageId] }
261 override fun newImageBuilder(): ImageBuilder = ImageBuilderImpl()
263 override fun storeImage(image: Image): Unit =
265 allImages[image.id] = image
266 soneImages.put(image.sone.id, image)
269 override fun removeImage(image: Image): Unit =
271 allImages.remove(image.id)
272 soneImages.remove(image.sone.id, image)
275 override fun bookmarkPost(post: Post) =
276 memoryBookmarkDatabase.bookmarkPost(post)
278 override fun unbookmarkPost(post: Post) =
279 memoryBookmarkDatabase.unbookmarkPost(post)
281 override fun isPostBookmarked(post: Post) =
282 memoryBookmarkDatabase.isPostBookmarked(post)
284 protected fun isPostKnown(post: Post) = readLock.withLock { post.id in knownPosts }
286 protected fun setPostKnown(post: Post, known: Boolean): Unit =
289 knownPosts.add(post.id)
291 knownPosts.remove(post.id)
294 protected fun isPostReplyKnown(postReply: PostReply) = readLock.withLock { postReply.id in knownPostReplies }
296 protected fun setPostReplyKnown(postReply: PostReply, known: Boolean): Unit =
299 knownPostReplies.add(postReply.id)
301 knownPostReplies.remove(postReply.id)
304 private fun loadKnownPosts() =
305 configurationLoader.loadKnownPosts()
309 knownPosts.addAll(it)
313 private fun saveKnownPosts() =
316 knownPosts.forEachIndexed { index, knownPostId ->
317 configuration.getStringValue("KnowsPosts/$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)