From da82d77fe096ef89404a8323adced3b75fefaa87 Mon Sep 17 00:00:00 2001 From: =?utf8?q?David=20=E2=80=98Bombe=E2=80=99=20Roden?= Date: Wed, 26 Nov 2014 20:48:32 +0100 Subject: [PATCH] Move loading and saving local Sones into database. --- .../java/net/pterodactylus/sone/core/Core.java | 293 +---------------- .../net/pterodactylus/sone/database/Database.java | 2 +- .../sone/database/LocalSoneDatabase.java | 17 + .../sone/database/memory/ConfigurationLoader.java | 11 + .../sone/database/memory/MemoryDatabase.java | 355 +++++++++++++++++++-- 5 files changed, 377 insertions(+), 301 deletions(-) create mode 100644 src/main/java/net/pterodactylus/sone/database/LocalSoneDatabase.java diff --git a/src/main/java/net/pterodactylus/sone/core/Core.java b/src/main/java/net/pterodactylus/sone/core/Core.java index 17905f3..71dd349 100644 --- a/src/main/java/net/pterodactylus/sone/core/Core.java +++ b/src/main/java/net/pterodactylus/sone/core/Core.java @@ -21,8 +21,6 @@ import static com.google.common.base.Optional.fromNullable; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.primitives.Longs.tryParse; -import static java.lang.String.format; -import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; import java.util.ArrayList; @@ -40,11 +38,6 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; -import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidAlbumFound; -import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidImageFound; -import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidParentAlbumFound; -import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidPostFound; -import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidPostReplyFound; import net.pterodactylus.sone.core.SoneChangeDetector.PostProcessor; import net.pterodactylus.sone.core.SoneChangeDetector.PostReplyProcessor; import net.pterodactylus.sone.core.event.ImageInsertFinishedEvent; @@ -56,19 +49,16 @@ import net.pterodactylus.sone.core.event.NewPostReplyFoundEvent; import net.pterodactylus.sone.core.event.NewSoneFoundEvent; import net.pterodactylus.sone.core.event.PostRemovedEvent; import net.pterodactylus.sone.core.event.PostReplyRemovedEvent; +import net.pterodactylus.sone.core.event.SoneInsertedEvent; import net.pterodactylus.sone.core.event.SoneLockedEvent; import net.pterodactylus.sone.core.event.SoneRemovedEvent; import net.pterodactylus.sone.core.event.SoneUnlockedEvent; import net.pterodactylus.sone.data.Album; -import net.pterodactylus.sone.data.Client; import net.pterodactylus.sone.data.Image; import net.pterodactylus.sone.data.Post; import net.pterodactylus.sone.data.PostReply; -import net.pterodactylus.sone.data.Profile; -import net.pterodactylus.sone.data.Profile.Field; import net.pterodactylus.sone.data.Reply; import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.sone.data.Sone.ShowCustomAvatars; import net.pterodactylus.sone.data.Sone.SoneStatus; import net.pterodactylus.sone.data.TemporaryImage; import net.pterodactylus.sone.database.AlbumBuilder; @@ -89,7 +79,6 @@ import net.pterodactylus.sone.freenet.wot.event.IdentityRemovedEvent; import net.pterodactylus.sone.freenet.wot.event.IdentityUpdatedEvent; import net.pterodactylus.sone.freenet.wot.event.OwnIdentityAddedEvent; import net.pterodactylus.sone.freenet.wot.event.OwnIdentityRemovedEvent; -import net.pterodactylus.sone.main.SonePlugin; import net.pterodactylus.util.config.Configuration; import net.pterodactylus.util.config.ConfigurationException; import net.pterodactylus.util.service.AbstractService; @@ -98,7 +87,6 @@ import net.pterodactylus.util.thread.NamedThreadFactory; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Optional; -import com.google.common.collect.FluentIterable; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; @@ -640,16 +628,15 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, return null; } logger.info(String.format("Adding Sone from OwnIdentity: %s", ownIdentity)); - Sone sone = database.newSoneBuilder().local().from(ownIdentity).build(); - sone.setLatestEdition(fromNullable(tryParse(ownIdentity.getProperty("Sone.LatestEdition"))).or(0L)); - sone.setClient(new Client("Sone", SonePlugin.VERSION.toString())); - sone.setKnown(true); + Sone sone = database.registerLocalSone(ownIdentity); SoneInserter soneInserter = new SoneInserter(this, eventBus, freenetInterface, ownIdentity.getId()); eventBus.register(soneInserter); synchronized (soneInserters) { soneInserters.put(sone, soneInserter); } - loadSone(sone); + synchronized (soneInserters) { + soneInserters.get(sone).setLastInsertFingerprint(database.getLastInsertFingerprint(sone)); + } sone.setStatus(SoneStatus.idle); soneInserter.start(); return sone; @@ -975,131 +962,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, } /** - * Loads and updates the given Sone from the configuration. If any error is - * encountered, loading is aborted and the given Sone is not changed. - * - * @param sone - * The Sone to load and update - */ - public void loadSone(Sone sone) { - if (!sone.isLocal()) { - logger.log(Level.FINE, String.format("Tried to load non-local Sone: %s", sone)); - return; - } - logger.info(String.format("Loading local Sone: %s", sone)); - - /* load Sone. */ - String sonePrefix = "Sone/" + sone.getId(); - Long soneTime = configuration.getLongValue(sonePrefix + "/Time").getValue(null); - if (soneTime == null) { - logger.log(Level.INFO, "Could not load Sone because no Sone has been saved."); - return; - } - String lastInsertFingerprint = configuration.getStringValue(sonePrefix + "/LastInsertFingerprint").getValue(""); - - /* load profile. */ - ConfigurationSoneParser configurationSoneParser = new ConfigurationSoneParser(configuration, sone); - Profile profile = configurationSoneParser.parseProfile(); - - /* load posts. */ - Collection posts; - try { - posts = configurationSoneParser.parsePosts(database); - } catch (InvalidPostFound ipf) { - logger.log(Level.WARNING, "Invalid post found, aborting load!"); - return; - } - - /* load replies. */ - Collection replies; - try { - replies = configurationSoneParser.parsePostReplies(database); - } catch (InvalidPostReplyFound iprf) { - logger.log(Level.WARNING, "Invalid reply found, aborting load!"); - return; - } - - /* load post likes. */ - Set likedPostIds = - configurationSoneParser.parseLikedPostIds(); - - /* load reply likes. */ - Set likedReplyIds = - configurationSoneParser.parseLikedPostReplyIds(); - - /* load albums. */ - List topLevelAlbums; - try { - topLevelAlbums = - configurationSoneParser.parseTopLevelAlbums(database); - } catch (InvalidAlbumFound iaf) { - logger.log(Level.WARNING, "Invalid album found, aborting load!"); - return; - } catch (InvalidParentAlbumFound ipaf) { - logger.log(Level.WARNING, format("Invalid parent album ID: %s", - ipaf.getAlbumParentId())); - return; - } - - /* load images. */ - try { - configurationSoneParser.parseImages(database); - } catch (InvalidImageFound iif) { - logger.log(WARNING, "Invalid image found, aborting load!"); - return; - } catch (InvalidParentAlbumFound ipaf) { - logger.log(Level.WARNING, - format("Invalid album image (%s) encountered, aborting load!", - ipaf.getAlbumParentId())); - return; - } - - /* load avatar. */ - String avatarId = configuration.getStringValue(sonePrefix + "/Profile/Avatar").getValue(null); - if (avatarId != null) { - final Map images = - configurationSoneParser.getImages(); - profile.setAvatar(images.get(avatarId)); - } - - /* load options. */ - sone.getOptions().setAutoFollow(configuration.getBooleanValue(sonePrefix + "/Options/AutoFollow").getValue(null)); - sone.getOptions().setSoneInsertNotificationEnabled(configuration.getBooleanValue(sonePrefix + "/Options/EnableSoneInsertNotifications").getValue(null)); - sone.getOptions().setShowNewSoneNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewSones").getValue(null)); - sone.getOptions().setShowNewPostNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").getValue(null)); - sone.getOptions().setShowNewReplyNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").getValue(null)); - sone.getOptions().setShowCustomAvatars(ShowCustomAvatars.valueOf(configuration.getStringValue(sonePrefix + "/Options/ShowCustomAvatars").getValue(ShowCustomAvatars.NEVER.name()))); - - /* if we’re still here, Sone was loaded successfully. */ - synchronized (sone) { - sone.setTime(soneTime); - sone.setProfile(profile); - sone.setPosts(posts); - sone.setReplies(replies); - sone.setLikePostIds(likedPostIds); - sone.setLikeReplyIds(likedReplyIds); - for (Album album : sone.getRootAlbum().getAlbums()) { - sone.getRootAlbum().removeAlbum(album); - } - for (Album album : topLevelAlbums) { - sone.getRootAlbum().addAlbum(album); - } - database.storeSone(sone); - synchronized (soneInserters) { - soneInserters.get(sone).setLastInsertFingerprint(lastInsertFingerprint); - } - } - for (Post post : posts) { - post.setKnown(true); - } - for (PostReply reply : replies) { - reply.setKnown(true); - } - - logger.info(String.format("Sone loaded successfully: %s", sone)); - } - - /** * Creates a new post. * * @param sone @@ -1389,9 +1251,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, sleep(1000); long now = System.currentTimeMillis(); if (shouldStop() || ((lastConfigurationUpdate > lastSaved) && ((now - lastConfigurationUpdate) > 5000))) { - for (Sone localSone : getLocalSones()) { - saveSone(localSone); - } saveConfiguration(); lastSaved = now; } @@ -1407,11 +1266,10 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, synchronized (soneInserters) { for (Entry soneInserter : soneInserters.entrySet()) { soneInserter.getValue().stop(); - saveSone(soneInserter.getKey()); } } - saveConfiguration(); database.stop(); + saveConfiguration(); webOfTrustUpdater.stop(); updateChecker.stop(); soneDownloader.stop(); @@ -1424,137 +1282,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, // /** - * Saves the given Sone. This will persist all local settings for the given - * Sone, such as the friends list and similar, private options. - * - * @param sone - * The Sone to save - */ - private synchronized void saveSone(Sone sone) { - if (!sone.isLocal()) { - logger.log(Level.FINE, String.format("Tried to save non-local Sone: %s", sone)); - return; - } - if (!(sone.getIdentity() instanceof OwnIdentity)) { - logger.log(Level.WARNING, String.format("Local Sone without OwnIdentity found, refusing to save: %s", sone)); - return; - } - - logger.log(Level.INFO, String.format("Saving Sone: %s", sone)); - try { - /* save Sone into configuration. */ - String sonePrefix = "Sone/" + sone.getId(); - configuration.getLongValue(sonePrefix + "/Time").setValue(sone.getTime()); - configuration.getStringValue(sonePrefix + "/LastInsertFingerprint").setValue(soneInserters.get(sone).getLastInsertFingerprint()); - - /* save profile. */ - Profile profile = sone.getProfile(); - configuration.getStringValue(sonePrefix + "/Profile/FirstName").setValue(profile.getFirstName()); - configuration.getStringValue(sonePrefix + "/Profile/MiddleName").setValue(profile.getMiddleName()); - configuration.getStringValue(sonePrefix + "/Profile/LastName").setValue(profile.getLastName()); - configuration.getIntValue(sonePrefix + "/Profile/BirthDay").setValue(profile.getBirthDay()); - configuration.getIntValue(sonePrefix + "/Profile/BirthMonth").setValue(profile.getBirthMonth()); - configuration.getIntValue(sonePrefix + "/Profile/BirthYear").setValue(profile.getBirthYear()); - configuration.getStringValue(sonePrefix + "/Profile/Avatar").setValue(profile.getAvatar()); - - /* save profile fields. */ - int fieldCounter = 0; - for (Field profileField : profile.getFields()) { - String fieldPrefix = sonePrefix + "/Profile/Fields/" + fieldCounter++; - configuration.getStringValue(fieldPrefix + "/Name").setValue(profileField.getName()); - configuration.getStringValue(fieldPrefix + "/Value").setValue(profileField.getValue()); - } - configuration.getStringValue(sonePrefix + "/Profile/Fields/" + fieldCounter + "/Name").setValue(null); - - /* save posts. */ - int postCounter = 0; - for (Post post : sone.getPosts()) { - String postPrefix = sonePrefix + "/Posts/" + postCounter++; - configuration.getStringValue(postPrefix + "/ID").setValue(post.getId()); - configuration.getStringValue(postPrefix + "/Recipient").setValue(post.getRecipientId().orNull()); - configuration.getLongValue(postPrefix + "/Time").setValue(post.getTime()); - configuration.getStringValue(postPrefix + "/Text").setValue(post.getText()); - } - configuration.getStringValue(sonePrefix + "/Posts/" + postCounter + "/ID").setValue(null); - - /* save replies. */ - int replyCounter = 0; - for (PostReply reply : sone.getReplies()) { - String replyPrefix = sonePrefix + "/Replies/" + replyCounter++; - configuration.getStringValue(replyPrefix + "/ID").setValue(reply.getId()); - configuration.getStringValue(replyPrefix + "/Post/ID").setValue(reply.getPostId()); - configuration.getLongValue(replyPrefix + "/Time").setValue(reply.getTime()); - configuration.getStringValue(replyPrefix + "/Text").setValue(reply.getText()); - } - configuration.getStringValue(sonePrefix + "/Replies/" + replyCounter + "/ID").setValue(null); - - /* save post likes. */ - int postLikeCounter = 0; - for (String postId : sone.getLikedPostIds()) { - configuration.getStringValue(sonePrefix + "/Likes/Post/" + postLikeCounter++ + "/ID").setValue(postId); - } - configuration.getStringValue(sonePrefix + "/Likes/Post/" + postLikeCounter + "/ID").setValue(null); - - /* save reply likes. */ - int replyLikeCounter = 0; - for (String replyId : sone.getLikedReplyIds()) { - configuration.getStringValue(sonePrefix + "/Likes/Reply/" + replyLikeCounter++ + "/ID").setValue(replyId); - } - configuration.getStringValue(sonePrefix + "/Likes/Reply/" + replyLikeCounter + "/ID").setValue(null); - - /* save albums. first, collect in a flat structure, top-level first. */ - List albums = FluentIterable.from(sone.getRootAlbum().getAlbums()).transformAndConcat(Album.FLATTENER).toList(); - - int albumCounter = 0; - for (Album album : albums) { - String albumPrefix = sonePrefix + "/Albums/" + albumCounter++; - configuration.getStringValue(albumPrefix + "/ID").setValue(album.getId()); - configuration.getStringValue(albumPrefix + "/Title").setValue(album.getTitle()); - configuration.getStringValue(albumPrefix + "/Description").setValue(album.getDescription()); - configuration.getStringValue(albumPrefix + "/Parent").setValue(album.getParent().equals(sone.getRootAlbum()) ? null : album.getParent().getId()); - configuration.getStringValue(albumPrefix + "/AlbumImage").setValue(album.getAlbumImage() == null ? null : album.getAlbumImage().getId()); - } - configuration.getStringValue(sonePrefix + "/Albums/" + albumCounter + "/ID").setValue(null); - - /* save images. */ - int imageCounter = 0; - for (Album album : albums) { - for (Image image : album.getImages()) { - if (!image.isInserted()) { - continue; - } - String imagePrefix = sonePrefix + "/Images/" + imageCounter++; - configuration.getStringValue(imagePrefix + "/ID").setValue(image.getId()); - configuration.getStringValue(imagePrefix + "/Album").setValue(album.getId()); - configuration.getStringValue(imagePrefix + "/Key").setValue(image.getKey()); - configuration.getStringValue(imagePrefix + "/Title").setValue(image.getTitle()); - configuration.getStringValue(imagePrefix + "/Description").setValue(image.getDescription()); - configuration.getLongValue(imagePrefix + "/CreationTime").setValue(image.getCreationTime()); - configuration.getIntValue(imagePrefix + "/Width").setValue(image.getWidth()); - configuration.getIntValue(imagePrefix + "/Height").setValue(image.getHeight()); - } - } - configuration.getStringValue(sonePrefix + "/Images/" + imageCounter + "/ID").setValue(null); - - /* save options. */ - configuration.getBooleanValue(sonePrefix + "/Options/AutoFollow").setValue(sone.getOptions().isAutoFollow()); - configuration.getBooleanValue(sonePrefix + "/Options/EnableSoneInsertNotifications").setValue(sone.getOptions().isSoneInsertNotificationEnabled()); - configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewSones").setValue(sone.getOptions().isShowNewSoneNotifications()); - configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").setValue(sone.getOptions().isShowNewPostNotifications()); - configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").setValue(sone.getOptions().isShowNewReplyNotifications()); - configuration.getStringValue(sonePrefix + "/Options/ShowCustomAvatars").setValue(sone.getOptions().getShowCustomAvatars().name()); - - configuration.save(); - - webOfTrustUpdater.setProperty((OwnIdentity) sone.getIdentity(), "Sone.LatestEdition", String.valueOf(sone.getLatestEdition())); - - logger.log(Level.INFO, String.format("Sone %s saved.", sone)); - } catch (ConfigurationException ce1) { - logger.log(Level.WARNING, String.format("Could not save Sone: %s", sone), ce1); - } - } - - /** * Saves the current options. */ private void saveConfiguration() { @@ -1728,6 +1455,14 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, eventBus.post(new SoneRemovedEvent(sone.get())); } + @Subscribe + public void soneInserted(SoneInsertedEvent soneInsertedEvent) { + Sone sone = soneInsertedEvent.sone(); + database.setLastInsertFingerprint(sone, soneInsertedEvent.insertFingerprint()); + webOfTrustUpdater.setProperty((OwnIdentity) sone.getIdentity(), "Sone.LatestEdition", String.valueOf( + sone.getLatestEdition())); + } + /** * Deletes the temporary image. * diff --git a/src/main/java/net/pterodactylus/sone/database/Database.java b/src/main/java/net/pterodactylus/sone/database/Database.java index 971a427..6c36418 100644 --- a/src/main/java/net/pterodactylus/sone/database/Database.java +++ b/src/main/java/net/pterodactylus/sone/database/Database.java @@ -29,7 +29,7 @@ import com.google.inject.ImplementedBy; * @author David ‘Bombe’ Roden */ @ImplementedBy(MemoryDatabase.class) -public interface Database extends Service, SoneDatabase, FriendDatabase, PostDatabase, PostReplyDatabase, AlbumDatabase, ImageDatabase, BookmarkDatabase { +public interface Database extends Service, SoneDatabase, LocalSoneDatabase, FriendDatabase, PostDatabase, PostReplyDatabase, AlbumDatabase, ImageDatabase, BookmarkDatabase { /** * Saves the database. diff --git a/src/main/java/net/pterodactylus/sone/database/LocalSoneDatabase.java b/src/main/java/net/pterodactylus/sone/database/LocalSoneDatabase.java new file mode 100644 index 0000000..b53d890 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/database/LocalSoneDatabase.java @@ -0,0 +1,17 @@ +package net.pterodactylus.sone.database; + +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.freenet.wot.OwnIdentity; + +/** + * Database functions for local Sones. + * + * @author David ‘Bombe’ Roden + */ +public interface LocalSoneDatabase { + + Sone registerLocalSone(OwnIdentity ownIdentity); + String getLastInsertFingerprint(Sone sone); + void setLastInsertFingerprint(Sone sone, String lastInsertFingerprint); + +} diff --git a/src/main/java/net/pterodactylus/sone/database/memory/ConfigurationLoader.java b/src/main/java/net/pterodactylus/sone/database/memory/ConfigurationLoader.java index 1691ddb..8974d10 100644 --- a/src/main/java/net/pterodactylus/sone/database/memory/ConfigurationLoader.java +++ b/src/main/java/net/pterodactylus/sone/database/memory/ConfigurationLoader.java @@ -10,6 +10,8 @@ import java.util.logging.Logger; import net.pterodactylus.util.config.Configuration; import net.pterodactylus.util.config.ConfigurationException; +import com.google.common.base.Optional; + /** * Helper class for interacting with a {@link Configuration}. * @@ -81,4 +83,13 @@ public class ConfigurationLoader { } } + public long getLocalSoneTime(String localSoneId) { + Long time = configuration.getLongValue("Sone/" + localSoneId + "/Time").getValue(null); + return Optional.fromNullable(time).or(-1L); + } + + public String getLastInsertFingerprint(String localSoneId) { + return configuration.getStringValue("Sone/" + localSoneId + "/LastInsertFingerprint").getValue(""); + } + } diff --git a/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java b/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java index b830ba5..83a886f 100644 --- a/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java +++ b/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java @@ -21,6 +21,8 @@ import static com.google.common.base.Optional.fromNullable; 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 java.lang.String.format; +import static java.util.logging.Level.WARNING; 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; @@ -36,12 +38,24 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; - +import java.util.logging.Level; +import java.util.logging.Logger; + +import net.pterodactylus.sone.core.ConfigurationSoneParser; +import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidAlbumFound; +import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidImageFound; +import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidParentAlbumFound; +import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidPostFound; +import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidPostReplyFound; import net.pterodactylus.sone.data.Album; +import net.pterodactylus.sone.data.Client; import net.pterodactylus.sone.data.Image; import net.pterodactylus.sone.data.Post; import net.pterodactylus.sone.data.PostReply; +import net.pterodactylus.sone.data.Profile; +import net.pterodactylus.sone.data.Profile.Field; import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.data.Sone.ShowCustomAvatars; import net.pterodactylus.sone.data.impl.AlbumBuilderImpl; import net.pterodactylus.sone.data.impl.ImageBuilderImpl; import net.pterodactylus.sone.database.AlbumBuilder; @@ -53,16 +67,21 @@ import net.pterodactylus.sone.database.PostDatabase; import net.pterodactylus.sone.database.PostReplyBuilder; import net.pterodactylus.sone.database.SoneBuilder; import net.pterodactylus.sone.database.SoneProvider; +import net.pterodactylus.sone.freenet.wot.OwnIdentity; +import net.pterodactylus.sone.main.SonePlugin; +import net.pterodactylus.sone.utils.Optionals; import net.pterodactylus.util.config.Configuration; import net.pterodactylus.util.config.ConfigurationException; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Predicate; +import com.google.common.collect.FluentIterable; 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.primitives.Longs; import com.google.common.util.concurrent.AbstractService; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -75,6 +94,8 @@ import com.google.inject.Singleton; @Singleton public class MemoryDatabase extends AbstractService implements Database { + private static final Logger logger = Logger.getLogger("Sone.Database.Memory"); + private static final String LATEST_EDITION_PROPERTY = "Sone.LatestEdition"; /** The lock. */ private final ReadWriteLock lock = new ReentrantReadWriteLock(); @@ -85,7 +106,9 @@ public class MemoryDatabase extends AbstractService implements Database { private final Configuration configuration; private final ConfigurationLoader configurationLoader; + private final Set localSones = new HashSet(); private final Map allSones = new HashMap(); + private final Map lastInsertFingerprints = new HashMap(); /** All posts by their ID. */ private final Map allPosts = new HashMap(); @@ -142,6 +165,157 @@ public class MemoryDatabase extends AbstractService implements Database { // DATABASE METHODS // + + @Override + public Sone registerLocalSone(OwnIdentity ownIdentity) { + final Sone localSone = loadLocalSone(ownIdentity); + localSones.add(ownIdentity.getId()); + return localSone; + } + + private Sone loadLocalSone(OwnIdentity ownIdentity) { + Sone localSone = newSoneBuilder().local().from(ownIdentity).build(); + localSone.setLatestEdition( + Optional.fromNullable( + Longs.tryParse(ownIdentity.getProperty(LATEST_EDITION_PROPERTY))) + .or(0L)); + localSone.setClient(new Client("Sone", SonePlugin.VERSION.toString())); + localSone.setKnown(true); + + loadSone(localSone); + return localSone; + } + + public void loadSone(Sone sone) { + long soneTime = configurationLoader.getLocalSoneTime(sone.getId()); + if (soneTime == -1) { + return; + } + + /* load profile. */ + ConfigurationSoneParser configurationSoneParser = new ConfigurationSoneParser(configuration, sone); + Profile profile = configurationSoneParser.parseProfile(); + + /* load posts. */ + Collection posts; + try { + posts = configurationSoneParser.parsePosts(this); + } catch (InvalidPostFound ipf) { + logger.log(Level.WARNING, "Invalid post found, aborting load!"); + return; + } + + /* load replies. */ + Collection postReplies; + try { + postReplies = configurationSoneParser.parsePostReplies(this); + } catch (InvalidPostReplyFound iprf) { + logger.log(Level.WARNING, "Invalid reply found, aborting load!"); + return; + } + + /* load post likes. */ + Set likedPostIds = configurationSoneParser.parseLikedPostIds(); + + /* load reply likes. */ + Set likedReplyIds = configurationSoneParser.parseLikedPostReplyIds(); + + /* load albums. */ + List topLevelAlbums; + try { + topLevelAlbums = configurationSoneParser.parseTopLevelAlbums(this); + } catch (InvalidAlbumFound iaf) { + logger.log(Level.WARNING, "Invalid album found, aborting load!"); + return; + } catch (InvalidParentAlbumFound ipaf) { + logger.log(Level.WARNING, + format("Invalid parent album ID: %s", ipaf.getAlbumParentId())); + return; + } + + /* load images. */ + try { + configurationSoneParser.parseImages(this); + } catch (InvalidImageFound iif) { + logger.log(WARNING, "Invalid image found, aborting load!"); + return; + } catch (InvalidParentAlbumFound ipaf) { + logger.log(Level.WARNING, + format("Invalid album image (%s) encountered, aborting load!", + ipaf.getAlbumParentId())); + return; + } + + /* load avatar. */ + String sonePrefix = "Sone/" + sone.getId(); + String avatarId = configuration.getStringValue(sonePrefix + "/Profile/Avatar").getValue(null); + if (avatarId != null) { + final Map images = configurationSoneParser.getImages(); + profile.setAvatar(images.get(avatarId)); + } + + /* load options. */ + sone.getOptions().setAutoFollow(configuration.getBooleanValue(sonePrefix + "/Options/AutoFollow").getValue(null)); + sone.getOptions().setSoneInsertNotificationEnabled(configuration.getBooleanValue(sonePrefix + "/Options/EnableSoneInsertNotifications").getValue(null)); + sone.getOptions().setShowNewSoneNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewSones").getValue(null)); + sone.getOptions().setShowNewPostNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").getValue(null)); + sone.getOptions().setShowNewReplyNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").getValue(null)); + sone.getOptions().setShowCustomAvatars(ShowCustomAvatars.valueOf( + configuration.getStringValue(sonePrefix + "/Options/ShowCustomAvatars") + .getValue(ShowCustomAvatars.NEVER.name()))); + + /* if we’re still here, Sone was loaded successfully. */ + lock.writeLock().lock(); + try { + sone.setTime(soneTime); + sone.setProfile(profile); + sone.setLikePostIds(likedPostIds); + sone.setLikeReplyIds(likedReplyIds); + + String lastInsertFingerprint = configurationLoader.getLastInsertFingerprint(sone.getId()); + lastInsertFingerprints.put(sone.getId(), lastInsertFingerprint); + + allSones.put(sone.getId(), sone); + storePosts(sone.getId(), posts); + storePostReplies(sone.getId(), postReplies); + storeAlbums(sone.getId(), topLevelAlbums); + storeImages(sone.getId(), from(topLevelAlbums).transformAndConcat(Album.FLATTENER).transformAndConcat(Album.IMAGES).toList()); + } finally { + lock.writeLock().unlock(); + } + for (Post post : posts) { + post.setKnown(true); + } + for (PostReply reply : postReplies) { + reply.setKnown(true); + } + + logger.info(String.format("Sone loaded successfully: %s", sone)); + } + + @Override + public String getLastInsertFingerprint(Sone sone) { + lock.readLock().lock(); + try { + if (!lastInsertFingerprints.containsKey(sone.getId())) { + return ""; + } + return lastInsertFingerprints.get(sone.getId()); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public void setLastInsertFingerprint(Sone sone, String lastInsertFingerprint) { + lock.writeLock().lock(); + try { + lastInsertFingerprints.put(sone.getId(), lastInsertFingerprint); + } finally { + lock.writeLock().unlock(); + } + } + /** * Saves the database. * @@ -150,8 +324,129 @@ public class MemoryDatabase extends AbstractService implements Database { */ @Override public void save() throws DatabaseException { - saveKnownPosts(); - saveKnownPostReplies(); + lock.writeLock().lock(); + try { + saveKnownPosts(); + saveKnownPostReplies(); + for (Sone localSone : from(localSones).transform(soneLoader()).transform(Optionals.get())) { + saveSone(localSone); + } + } finally { + lock.writeLock().unlock(); + } + } + + private synchronized void saveSone(Sone sone) { + logger.log(Level.INFO, String.format("Saving Sone: %s", sone)); + try { + /* save Sone into configuration. */ + String sonePrefix = "Sone/" + sone.getId(); + configuration.getLongValue(sonePrefix + "/Time").setValue(sone.getTime()); + configuration.getStringValue(sonePrefix + "/LastInsertFingerprint").setValue(lastInsertFingerprints.get(sone.getId())); + + /* save profile. */ + Profile profile = sone.getProfile(); + configuration.getStringValue(sonePrefix + "/Profile/FirstName").setValue(profile.getFirstName()); + configuration.getStringValue(sonePrefix + "/Profile/MiddleName").setValue(profile.getMiddleName()); + configuration.getStringValue(sonePrefix + "/Profile/LastName").setValue(profile.getLastName()); + configuration.getIntValue(sonePrefix + "/Profile/BirthDay").setValue(profile.getBirthDay()); + configuration.getIntValue(sonePrefix + "/Profile/BirthMonth").setValue(profile.getBirthMonth()); + configuration.getIntValue(sonePrefix + "/Profile/BirthYear").setValue(profile.getBirthYear()); + configuration.getStringValue(sonePrefix + "/Profile/Avatar").setValue(profile.getAvatar()); + + /* save profile fields. */ + int fieldCounter = 0; + for (Field profileField : profile.getFields()) { + String fieldPrefix = sonePrefix + "/Profile/Fields/" + fieldCounter++; + configuration.getStringValue(fieldPrefix + "/Name").setValue(profileField.getName()); + configuration.getStringValue(fieldPrefix + "/Value").setValue(profileField.getValue()); + } + configuration.getStringValue(sonePrefix + "/Profile/Fields/" + fieldCounter + "/Name").setValue(null); + + /* save posts. */ + int postCounter = 0; + for (Post post : sone.getPosts()) { + String postPrefix = sonePrefix + "/Posts/" + postCounter++; + configuration.getStringValue(postPrefix + "/ID").setValue(post.getId()); + configuration.getStringValue(postPrefix + "/Recipient").setValue(post.getRecipientId().orNull()); + configuration.getLongValue(postPrefix + "/Time").setValue(post.getTime()); + configuration.getStringValue(postPrefix + "/Text").setValue(post.getText()); + } + configuration.getStringValue(sonePrefix + "/Posts/" + postCounter + "/ID").setValue(null); + + /* save replies. */ + int replyCounter = 0; + for (PostReply reply : sone.getReplies()) { + String replyPrefix = sonePrefix + "/Replies/" + replyCounter++; + configuration.getStringValue(replyPrefix + "/ID").setValue(reply.getId()); + configuration.getStringValue(replyPrefix + "/Post/ID").setValue(reply.getPostId()); + configuration.getLongValue(replyPrefix + "/Time").setValue(reply.getTime()); + configuration.getStringValue(replyPrefix + "/Text").setValue(reply.getText()); + } + configuration.getStringValue(sonePrefix + "/Replies/" + replyCounter + "/ID").setValue(null); + + /* save post likes. */ + int postLikeCounter = 0; + for (String postId : sone.getLikedPostIds()) { + configuration.getStringValue(sonePrefix + "/Likes/Post/" + postLikeCounter++ + "/ID").setValue(postId); + } + configuration.getStringValue(sonePrefix + "/Likes/Post/" + postLikeCounter + "/ID").setValue(null); + + /* save reply likes. */ + int replyLikeCounter = 0; + for (String replyId : sone.getLikedReplyIds()) { + configuration.getStringValue(sonePrefix + "/Likes/Reply/" + replyLikeCounter++ + "/ID").setValue(replyId); + } + configuration.getStringValue(sonePrefix + "/Likes/Reply/" + replyLikeCounter + "/ID").setValue(null); + + /* save albums. first, collect in a flat structure, top-level first. */ + List albums = FluentIterable.from(sone.getRootAlbum().getAlbums()).transformAndConcat(Album.FLATTENER).toList(); + + int albumCounter = 0; + for (Album album : albums) { + String albumPrefix = sonePrefix + "/Albums/" + albumCounter++; + configuration.getStringValue(albumPrefix + "/ID").setValue(album.getId()); + configuration.getStringValue(albumPrefix + "/Title").setValue(album.getTitle()); + configuration.getStringValue(albumPrefix + "/Description").setValue(album.getDescription()); + configuration.getStringValue(albumPrefix + "/Parent").setValue(album.getParent().equals(sone.getRootAlbum()) ? null : album.getParent().getId()); + configuration.getStringValue(albumPrefix + "/AlbumImage").setValue(album.getAlbumImage() == null ? null : album.getAlbumImage().getId()); + } + configuration.getStringValue(sonePrefix + "/Albums/" + albumCounter + "/ID").setValue(null); + + /* save images. */ + int imageCounter = 0; + for (Album album : albums) { + for (Image image : album.getImages()) { + if (!image.isInserted()) { + continue; + } + String imagePrefix = sonePrefix + "/Images/" + imageCounter++; + configuration.getStringValue(imagePrefix + "/ID").setValue(image.getId()); + configuration.getStringValue(imagePrefix + "/Album").setValue(album.getId()); + configuration.getStringValue(imagePrefix + "/Key").setValue(image.getKey()); + configuration.getStringValue(imagePrefix + "/Title").setValue(image.getTitle()); + configuration.getStringValue(imagePrefix + "/Description").setValue(image.getDescription()); + configuration.getLongValue(imagePrefix + "/CreationTime").setValue(image.getCreationTime()); + configuration.getIntValue(imagePrefix + "/Width").setValue(image.getWidth()); + configuration.getIntValue(imagePrefix + "/Height").setValue(image.getHeight()); + } + } + configuration.getStringValue(sonePrefix + "/Images/" + imageCounter + "/ID").setValue(null); + + /* save options. */ + configuration.getBooleanValue(sonePrefix + "/Options/AutoFollow").setValue(sone.getOptions().isAutoFollow()); + configuration.getBooleanValue(sonePrefix + "/Options/EnableSoneInsertNotifications").setValue(sone.getOptions().isSoneInsertNotificationEnabled()); + configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewSones").setValue(sone.getOptions().isShowNewSoneNotifications()); + configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").setValue(sone.getOptions().isShowNewPostNotifications()); + configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").setValue(sone.getOptions().isShowNewReplyNotifications()); + configuration.getStringValue(sonePrefix + "/Options/ShowCustomAvatars").setValue(sone.getOptions().getShowCustomAvatars().name()); + + configuration.save(); + + logger.log(Level.INFO, String.format("Sone %s saved.", sone)); + } catch (ConfigurationException ce1) { + logger.log(Level.WARNING, String.format("Could not save Sone: %s", sone), ce1); + } } // @@ -191,27 +486,43 @@ public class MemoryDatabase extends AbstractService implements Database { 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); - } + storePosts(sone.getId(), sone.getPosts()); + storePostReplies(sone.getId(), sone.getReplies()); + storeAlbums(sone.getId(), toAllAlbums.apply(sone)); + storeImages(sone.getId(), toAllImages.apply(sone)); } finally { lock.writeLock().unlock(); } } + private void storePosts(String soneId, Collection posts) { + sonePosts.putAll(soneId, posts); + for (Post post : posts) { + allPosts.put(post.getId(), post); + } + } + + private void storePostReplies(String soneId, Collection postReplies) { + sonePostReplies.putAll(soneId, postReplies); + for (PostReply postReply : postReplies) { + allPostReplies.put(postReply.getId(), postReply); + } + } + + private void storeAlbums(String soneId, Collection albums) { + soneAlbums.putAll(soneId, albums); + for (Album album : albums) { + allAlbums.put(album.getId(), album); + } + } + + private void storeImages(String soneId, Collection images) { + soneImages.putAll(soneId, images); + for (Image image : images) { + allImages.put(image.getId(), image); + } + } + @Override public void removeSone(Sone sone) { lock.writeLock().lock(); @@ -711,7 +1022,8 @@ public class MemoryDatabase extends AbstractService implements Database { try { int postCounter = 0; for (String knownPostId : knownPosts) { - configuration.getStringValue("KnownPosts/" + postCounter++ + "/ID").setValue(knownPostId); + configuration.getStringValue("KnownPosts/" + postCounter++ + "/ID").setValue( + knownPostId); } configuration.getStringValue("KnownPosts/" + postCounter + "/ID").setValue(null); } catch (ConfigurationException ce1) { @@ -744,7 +1056,8 @@ public class MemoryDatabase extends AbstractService implements Database { try { int replyCounter = 0; for (String knownReplyId : knownPostReplies) { - configuration.getStringValue("KnownReplies/" + replyCounter++ + "/ID").setValue(knownReplyId); + configuration.getStringValue("KnownReplies/" + replyCounter++ + "/ID").setValue( + knownReplyId); } configuration.getStringValue("KnownReplies/" + replyCounter + "/ID").setValue(null); } catch (ConfigurationException ce1) { -- 2.7.4