From 9fc45be4cce92e2d960395a35d046b2c5808a0bb Mon Sep 17 00:00:00 2001 From: =?utf8?q?David=20=E2=80=98Bombe=E2=80=99=20Roden?= Date: Fri, 2 Mar 2018 21:47:33 +0100 Subject: [PATCH] Convert MemoryDatabase to Kotlin --- .../sone/database/memory/MemoryDatabase.java | 760 --------------------- .../sone/database/memory/MemoryDatabase.kt | 345 ++++++++++ .../kotlin/net/pterodactylus/sone/utils/Objects.kt | 1 + .../sone/database/memory/MemoryDatabaseTest.java | 1 + 4 files changed, 347 insertions(+), 760 deletions(-) delete mode 100644 src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java create mode 100644 src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.kt diff --git a/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java b/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java deleted file mode 100644 index 4a8b6f0..0000000 --- a/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java +++ /dev/null @@ -1,760 +0,0 @@ -/* - * Sone - MemoryDatabase.java - Copyright © 2013–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 . - */ - -package net.pterodactylus.sone.database.memory; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Predicates.not; -import static com.google.common.collect.FluentIterable.from; -import static net.pterodactylus.sone.data.Reply.TIME_COMPARATOR; -import static net.pterodactylus.sone.data.Sone.LOCAL_SONE_FILTER; -import static net.pterodactylus.sone.data.Sone.toAllAlbums; -import static net.pterodactylus.sone.data.Sone.toAllImages; - -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -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.Sone; -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.database.SoneBuilder; -import net.pterodactylus.util.config.Configuration; -import net.pterodactylus.util.config.ConfigurationException; - -import com.google.common.base.Predicate; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; -import com.google.common.collect.SortedSetMultimap; -import com.google.common.collect.TreeMultimap; -import com.google.common.util.concurrent.AbstractService; -import com.google.inject.Inject; -import com.google.inject.Singleton; -import kotlin.jvm.functions.Function1; - -/** - * Memory-based {@link PostDatabase} implementation. - * - * @author David ‘Bombe’ Roden - */ -@Singleton -public class MemoryDatabase extends AbstractService implements Database { - - /** The lock. */ - private final ReadWriteLock lock = new ReentrantReadWriteLock(); - - /** The configuration. */ - private final Configuration configuration; - private final ConfigurationLoader configurationLoader; - - private final Map allSones = new HashMap(); - - /** All posts by their ID. */ - private final Map allPosts = new HashMap(); - - /** All posts by their Sones. */ - private final Multimap sonePosts = HashMultimap.create(); - - /** Whether posts are known. */ - private final Set knownPosts = new HashSet(); - - /** All post replies by their ID. */ - private final Map allPostReplies = new HashMap(); - - /** Replies sorted by Sone. */ - private final SortedSetMultimap sonePostReplies = TreeMultimap.create(new Comparator() { - - @Override - public int compare(String leftString, String rightString) { - return leftString.compareTo(rightString); - } - }, TIME_COMPARATOR); - - /** Whether post replies are known. */ - private final Set knownPostReplies = new HashSet(); - - private final Map allAlbums = new HashMap(); - private final Multimap soneAlbums = HashMultimap.create(); - - private final Map allImages = new HashMap(); - private final Multimap soneImages = HashMultimap.create(); - - private final MemoryBookmarkDatabase memoryBookmarkDatabase; - private final MemoryFriendDatabase memoryFriendDatabase; - - /** - * Creates a new memory database. - * - * @param configuration - * The configuration for loading and saving elements - */ - @Inject - public MemoryDatabase(Configuration configuration) { - this.configuration = configuration; - this.configurationLoader = new ConfigurationLoader(configuration); - memoryBookmarkDatabase = - new MemoryBookmarkDatabase(this, configurationLoader); - memoryFriendDatabase = new MemoryFriendDatabase(configurationLoader); - } - - // - // DATABASE METHODS - // - - /** - * Saves the database. - * - * @throws DatabaseException - * if an error occurs while saving - */ - @Override - public void save() throws DatabaseException { - saveKnownPosts(); - saveKnownPostReplies(); - } - - // - // SERVICE METHODS - // - - /** {@inheritDocs} */ - @Override - protected void doStart() { - memoryBookmarkDatabase.start(); - loadKnownPosts(); - loadKnownPostReplies(); - notifyStarted(); - } - - /** {@inheritDocs} */ - @Override - protected void doStop() { - try { - memoryBookmarkDatabase.stop(); - save(); - notifyStopped(); - } catch (DatabaseException de1) { - notifyFailed(de1); - } - } - - @Override - public SoneBuilder newSoneBuilder() { - return new MemorySoneBuilder(this); - } - - @Override - public void storeSone(Sone sone) { - lock.writeLock().lock(); - try { - removeSone(sone); - - allSones.put(sone.getId(), sone); - sonePosts.putAll(sone.getId(), sone.getPosts()); - for (Post post : sone.getPosts()) { - allPosts.put(post.getId(), post); - } - sonePostReplies.putAll(sone.getId(), sone.getReplies()); - for (PostReply postReply : sone.getReplies()) { - allPostReplies.put(postReply.getId(), postReply); - } - soneAlbums.putAll(sone.getId(), toAllAlbums.apply(sone)); - for (Album album : toAllAlbums.apply(sone)) { - allAlbums.put(album.getId(), album); - } - soneImages.putAll(sone.getId(), toAllImages.apply(sone)); - for (Image image : toAllImages.apply(sone)) { - allImages.put(image.getId(), image); - } - } finally { - lock.writeLock().unlock(); - } - } - - @Override - public void removeSone(Sone sone) { - lock.writeLock().lock(); - try { - allSones.remove(sone.getId()); - Collection removedPosts = sonePosts.removeAll(sone.getId()); - for (Post removedPost : removedPosts) { - allPosts.remove(removedPost.getId()); - } - Collection removedPostReplies = - sonePostReplies.removeAll(sone.getId()); - for (PostReply removedPostReply : removedPostReplies) { - allPostReplies.remove(removedPostReply.getId()); - } - Collection removedAlbums = - soneAlbums.removeAll(sone.getId()); - for (Album removedAlbum : removedAlbums) { - allAlbums.remove(removedAlbum.getId()); - } - Collection removedImages = - soneImages.removeAll(sone.getId()); - for (Image removedImage : removedImages) { - allImages.remove(removedImage.getId()); - } - } finally { - lock.writeLock().unlock(); - } - } - - @Nonnull - @Override - public Function1 getSoneLoader() { - return new Function1() { - @Override - public Sone invoke(String soneId) { - return getSone(soneId); - } - }; - } - - @Override - public Sone getSone(String soneId) { - lock.readLock().lock(); - try { - return allSones.get(soneId); - } finally { - lock.readLock().unlock(); - } - } - - @Override - public Collection getSones() { - lock.readLock().lock(); - try { - return new HashSet(allSones.values()); - } finally { - lock.readLock().unlock(); - } - } - - @Override - public Collection getLocalSones() { - lock.readLock().lock(); - try { - return from(allSones.values()).filter(LOCAL_SONE_FILTER).toSet(); - } finally { - lock.readLock().unlock(); - } - } - - @Override - public Collection getRemoteSones() { - lock.readLock().lock(); - try { - return from(allSones.values()) - .filter(not(LOCAL_SONE_FILTER)) .toSet(); - } finally { - lock.readLock().unlock(); - } - } - - @Override - public Collection getFriends(Sone localSone) { - if (!localSone.isLocal()) { - return Collections.emptySet(); - } - return memoryFriendDatabase.getFriends(localSone.getId()); - } - - @Override - public boolean isFriend(Sone localSone, String friendSoneId) { - if (!localSone.isLocal()) { - return false; - } - return memoryFriendDatabase.isFriend(localSone.getId(), friendSoneId); - } - - @Override - public void addFriend(Sone localSone, String friendSoneId) { - if (!localSone.isLocal()) { - return; - } - memoryFriendDatabase.addFriend(localSone.getId(), friendSoneId); - } - - @Override - public void removeFriend(Sone localSone, String friendSoneId) { - if (!localSone.isLocal()) { - return; - } - memoryFriendDatabase.removeFriend(localSone.getId(), friendSoneId); - } - - @Nullable - @Override - public Long getFollowingTime(@Nonnull String friendSoneId) { - return memoryFriendDatabase.getFollowingTime(friendSoneId); - } - - // - // POSTPROVIDER METHODS - // - - @Nullable - @Override - public Post getPost(@Nonnull String postId) { - lock.readLock().lock(); - try { - return allPosts.get(postId); - } finally { - lock.readLock().unlock(); - } - } - - /** {@inheritDocs} */ - @Override - public Collection getPosts(String soneId) { - return new HashSet(getPostsFrom(soneId)); - } - - /** {@inheritDocs} */ - @Override - public Collection getDirectedPosts(final String recipientId) { - lock.readLock().lock(); - try { - return from(sonePosts.values()).filter(new Predicate() { - @Override - public boolean apply(Post post) { - return post.getRecipientId().asSet().contains(recipientId); - } - }).toSet(); - } finally { - lock.readLock().unlock(); - } - } - - // - // POSTBUILDERFACTORY METHODS - // - - /** {@inheritDocs} */ - @Override - public PostBuilder newPostBuilder() { - return new MemoryPostBuilder(this, this); - } - - // - // POSTSTORE METHODS - // - - /** {@inheritDocs} */ - @Override - public void storePost(Post post) { - checkNotNull(post, "post must not be null"); - lock.writeLock().lock(); - try { - allPosts.put(post.getId(), post); - getPostsFrom(post.getSone().getId()).add(post); - } finally { - lock.writeLock().unlock(); - } - } - - /** {@inheritDocs} */ - @Override - public void removePost(Post post) { - checkNotNull(post, "post must not be null"); - lock.writeLock().lock(); - try { - allPosts.remove(post.getId()); - getPostsFrom(post.getSone().getId()).remove(post); - post.getSone().removePost(post); - } finally { - lock.writeLock().unlock(); - } - } - - // - // POSTREPLYPROVIDER METHODS - // - - @Nullable - @Override - public PostReply getPostReply(String id) { - lock.readLock().lock(); - try { - return allPostReplies.get(id); - } finally { - lock.readLock().unlock(); - } - } - - /** {@inheritDocs} */ - @Override - public List getReplies(final String postId) { - lock.readLock().lock(); - try { - return from(allPostReplies.values()) - .filter(new Predicate() { - @Override - public boolean apply(PostReply postReply) { - return postReply.getPostId().equals(postId); - } - }).toSortedList(TIME_COMPARATOR); - } finally { - lock.readLock().unlock(); - } - } - - // - // POSTREPLYBUILDERFACTORY METHODS - // - - /** {@inheritDocs} */ - @Override - public PostReplyBuilder newPostReplyBuilder() { - return new MemoryPostReplyBuilder(this, this); - } - - // - // POSTREPLYSTORE METHODS - // - - /** {@inheritDocs} */ - @Override - public void storePostReply(PostReply postReply) { - lock.writeLock().lock(); - try { - allPostReplies.put(postReply.getId(), postReply); - } finally { - lock.writeLock().unlock(); - } - } - - /** {@inheritDocs} */ - @Override - public void removePostReply(PostReply postReply) { - lock.writeLock().lock(); - try { - allPostReplies.remove(postReply.getId()); - } finally { - lock.writeLock().unlock(); - } - } - - // - // ALBUMPROVDER METHODS - // - - @Nullable - @Override - public Album getAlbum(@Nonnull String albumId) { - lock.readLock().lock(); - try { - return allAlbums.get(albumId); - } finally { - lock.readLock().unlock(); - } - } - - // - // ALBUMBUILDERFACTORY METHODS - // - - @Override - public AlbumBuilder newAlbumBuilder() { - return new AlbumBuilderImpl(); - } - - // - // ALBUMSTORE METHODS - // - - @Override - public void storeAlbum(Album album) { - lock.writeLock().lock(); - try { - allAlbums.put(album.getId(), album); - soneAlbums.put(album.getSone().getId(), album); - } finally { - lock.writeLock().unlock(); - } - } - - @Override - public void removeAlbum(Album album) { - lock.writeLock().lock(); - try { - allAlbums.remove(album.getId()); - soneAlbums.remove(album.getSone().getId(), album); - } finally { - lock.writeLock().unlock(); - } - } - - // - // IMAGEPROVIDER METHODS - // - - @Nullable - @Override - public Image getImage(@Nonnull String imageId) { - lock.readLock().lock(); - try { - return allImages.get(imageId); - } finally { - lock.readLock().unlock(); - } - } - - // - // IMAGEBUILDERFACTORY METHODS - // - - @Override - public ImageBuilder newImageBuilder() { - return new ImageBuilderImpl(); - } - - // - // IMAGESTORE METHODS - // - - @Override - public void storeImage(Image image) { - lock.writeLock().lock(); - try { - allImages.put(image.getId(), image); - soneImages.put(image.getSone().getId(), image); - } finally { - lock.writeLock().unlock(); - } - } - - @Override - public void removeImage(Image image) { - lock.writeLock().lock(); - try { - allImages.remove(image.getId()); - soneImages.remove(image.getSone().getId(), image); - } finally { - lock.writeLock().unlock(); - } - } - - @Override - public void bookmarkPost(Post post) { - memoryBookmarkDatabase.bookmarkPost(post); - } - - @Override - public void unbookmarkPost(Post post) { - memoryBookmarkDatabase.unbookmarkPost(post); - } - - @Override - public boolean isPostBookmarked(Post post) { - return memoryBookmarkDatabase.isPostBookmarked(post); - } - - @Override - public Set getBookmarkedPosts() { - return memoryBookmarkDatabase.getBookmarkedPosts(); - } - - // - // PACKAGE-PRIVATE METHODS - // - - /** - * Returns whether the given post is known. - * - * @param post - * The post - * @return {@code true} if the post is known, {@code false} otherwise - */ - boolean isPostKnown(Post post) { - lock.readLock().lock(); - try { - return knownPosts.contains(post.getId()); - } finally { - lock.readLock().unlock(); - } - } - - /** - * Sets whether the given post is known. - * - * @param post - * The post - * @param known - * {@code true} if the post is known, {@code false} otherwise - */ - void setPostKnown(Post post, boolean known) { - lock.writeLock().lock(); - try { - if (known) { - knownPosts.add(post.getId()); - } else { - knownPosts.remove(post.getId()); - } - } finally { - lock.writeLock().unlock(); - } - } - - /** - * Returns whether the given post reply is known. - * - * @param postReply - * The post reply - * @return {@code true} if the given post reply is known, {@code false} - * otherwise - */ - boolean isPostReplyKnown(PostReply postReply) { - lock.readLock().lock(); - try { - return knownPostReplies.contains(postReply.getId()); - } finally { - lock.readLock().unlock(); - } - } - - /** - * Sets whether the given post reply is known. - * - * @param postReply - * The post reply - * @param known - * {@code true} if the post reply is known, {@code false} otherwise - */ - void setPostReplyKnown(PostReply postReply, boolean known) { - lock.writeLock().lock(); - try { - if (known) { - knownPostReplies.add(postReply.getId()); - } else { - knownPostReplies.remove(postReply.getId()); - } - } finally { - lock.writeLock().unlock(); - } - } - - // - // PRIVATE METHODS - // - - /** - * Gets all posts for the given Sone, creating a new collection if there is - * none yet. - * - * @param soneId - * The ID of the Sone to get the posts for - * @return All posts - */ - private Collection getPostsFrom(String soneId) { - lock.readLock().lock(); - try { - return sonePosts.get(soneId); - } finally { - lock.readLock().unlock(); - } - } - - /** Loads the known posts. */ - private void loadKnownPosts() { - Set knownPosts = configurationLoader.loadKnownPosts(); - lock.writeLock().lock(); - try { - this.knownPosts.clear(); - this.knownPosts.addAll(knownPosts); - } finally { - lock.writeLock().unlock(); - } - } - - /** - * Saves the known posts to the configuration. - * - * @throws DatabaseException - * if a configuration error occurs - */ - private void saveKnownPosts() throws DatabaseException { - lock.readLock().lock(); - try { - int postCounter = 0; - for (String knownPostId : knownPosts) { - configuration.getStringValue("KnownPosts/" + postCounter++ + "/ID").setValue(knownPostId); - } - configuration.getStringValue("KnownPosts/" + postCounter + "/ID").setValue(null); - } catch (ConfigurationException ce1) { - throw new DatabaseException("Could not save database.", ce1); - } finally { - lock.readLock().unlock(); - } - } - - /** Loads the known post replies. */ - private void loadKnownPostReplies() { - Set knownPostReplies = configurationLoader.loadKnownPostReplies(); - lock.writeLock().lock(); - try { - this.knownPostReplies.clear(); - this.knownPostReplies.addAll(knownPostReplies); - } finally { - lock.writeLock().unlock(); - } - } - - /** - * Saves the known post replies to the configuration. - * - * @throws DatabaseException - * if a configuration error occurs - */ - private void saveKnownPostReplies() throws DatabaseException { - lock.readLock().lock(); - try { - int replyCounter = 0; - for (String knownReplyId : knownPostReplies) { - configuration.getStringValue("KnownReplies/" + replyCounter++ + "/ID").setValue(knownReplyId); - } - configuration.getStringValue("KnownReplies/" + replyCounter + "/ID").setValue(null); - } catch (ConfigurationException ce1) { - throw new DatabaseException("Could not save database.", ce1); - } finally { - lock.readLock().unlock(); - } - } - -} diff --git a/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.kt b/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.kt new file mode 100644 index 0000000..702d4e2 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.kt @@ -0,0 +1,345 @@ +/* + * Sone - MemoryDatabase.java - Copyright © 2013–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 . + */ + +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.AbstractService +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.unit +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. + * + * @author [David ‘Bombe’ Roden](mailto:bombe@pterodactylus.net) + */ +@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) + + 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() { + 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 } + + protected fun setPostKnown(post: Post, known: Boolean): Unit = + writeLock.withLock { + if (known) + knownPosts.add(post.id) + else + knownPosts.remove(post.id) + } + + protected fun isPostReplyKnown(postReply: PostReply) = readLock.withLock { postReply.id in knownPostReplies } + + protected fun setPostReplyKnown(postReply: PostReply, known: Boolean): Unit = + writeLock.withLock { + if (known) + knownPostReplies.add(postReply.id) + else + knownPostReplies.remove(postReply.id) + } + + private fun loadKnownPosts() = + configurationLoader.loadKnownPosts() + .let { + writeLock.withLock { + knownPosts.clear() + knownPosts.addAll(it) + } + } + + private fun saveKnownPosts() = + try { + readLock.withLock { + knownPosts.forEachIndexed { index, knownPostId -> + configuration.getStringValue("KnowsPosts/$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() = + 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/utils/Objects.kt b/src/main/kotlin/net/pterodactylus/sone/utils/Objects.kt index 36e530c..cd36ec9 100644 --- a/src/main/kotlin/net/pterodactylus/sone/utils/Objects.kt +++ b/src/main/kotlin/net/pterodactylus/sone/utils/Objects.kt @@ -1,3 +1,4 @@ package net.pterodactylus.sone.utils fun T?.asList() = this?.let(::listOf) ?: emptyList() +val Any?.unit get() = Unit diff --git a/src/test/java/net/pterodactylus/sone/database/memory/MemoryDatabaseTest.java b/src/test/java/net/pterodactylus/sone/database/memory/MemoryDatabaseTest.java index 9d704a6..d5c6ead 100644 --- a/src/test/java/net/pterodactylus/sone/database/memory/MemoryDatabaseTest.java +++ b/src/test/java/net/pterodactylus/sone/database/memory/MemoryDatabaseTest.java @@ -141,6 +141,7 @@ public class MemoryDatabaseTest { "album-description3").update(); firstAlbum.addAlbum(thirdAlbum); Album rootAlbum = mock(Album.class); + when(rootAlbum.getId()).thenReturn("root"); when(rootAlbum.getAlbums()).thenReturn( asList(firstAlbum, secondAlbum)); when(sone.getRootAlbum()).thenReturn(rootAlbum); -- 2.7.4