From: David ‘Bombe’ Roden Date: Sat, 12 Jan 2013 16:41:27 +0000 (+0100) Subject: Merge branch 'next' into new-database-38 X-Git-Url: https://git.pterodactylus.net/?p=Sone.git;a=commitdiff_plain;h=771e8efcd92c7325423441d57c5a6ed90a835e6f;hp=66b03d16a393c9f02090245ea963d45ce0b4cf79 Merge branch 'next' into new-database-38 Conflicts: src/main/java/net/pterodactylus/sone/core/Core.java src/main/java/net/pterodactylus/sone/web/SearchPage.java --- diff --git a/src/main/java/net/pterodactylus/sone/core/Core.java b/src/main/java/net/pterodactylus/sone/core/Core.java index 5843b30..1bb638a 100644 --- a/src/main/java/net/pterodactylus/sone/core/Core.java +++ b/src/main/java/net/pterodactylus/sone/core/Core.java @@ -19,6 +19,7 @@ package net.pterodactylus.sone.core; import java.net.MalformedURLException; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -47,6 +48,7 @@ import net.pterodactylus.sone.data.Sone.ShowCustomAvatars; import net.pterodactylus.sone.data.Sone.SoneStatus; import net.pterodactylus.sone.data.TemporaryImage; import net.pterodactylus.sone.data.impl.PostImpl; +import net.pterodactylus.sone.database.Database; import net.pterodactylus.sone.fcp.FcpInterface; import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired; import net.pterodactylus.sone.freenet.wot.Identity; @@ -96,6 +98,9 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis /** Whether we’re currently saving the configuration. */ private boolean storingConfiguration = false; + /** The database. */ + private Database database; + /** The identity manager. */ private final IdentityManager identityManager; @@ -135,14 +140,6 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis /* synchronize access on this on localSones. */ private final Map soneRescuers = new HashMap(); - /** All local Sones. */ - /* synchronize access on this on itself. */ - private final Map localSones = new HashMap(); - - /** All remote Sones. */ - /* synchronize access on this on itself. */ - private final Map remoteSones = new HashMap(); - /** All known Sones. */ private final Set knownSones = new HashSet(); @@ -185,6 +182,8 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis * * @param configuration * The configuration of the core + * @param database + * The database to use * @param freenetInterface * The freenet interface * @param identityManager @@ -192,9 +191,10 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis * @param webOfTrustUpdater * The WebOfTrust updater */ - public Core(Configuration configuration, FreenetInterface freenetInterface, IdentityManager identityManager, WebOfTrustUpdater webOfTrustUpdater) { + public Core(Configuration configuration, Database database, FreenetInterface freenetInterface, IdentityManager identityManager, WebOfTrustUpdater webOfTrustUpdater) { super("Sone Core"); this.configuration = configuration; + this.database = database; this.freenetInterface = freenetInterface; this.identityManager = identityManager; this.soneDownloader = new SoneDownloader(this, freenetInterface); @@ -297,8 +297,8 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis * @return The Sone rescuer for the given Sone */ public SoneRescuer getSoneRescuer(Sone sone) { - Validation.begin().isNotNull("Sone", sone).check().is("Local Sone", isLocalSone(sone)).check(); - synchronized (localSones) { + Validation.begin().isNotNull("Sone", sone).check().is("Local Sone", sone.isLocal()).check(); + synchronized (soneRescuers) { SoneRescuer soneRescuer = soneRescuers.get(sone); if (soneRescuer == null) { soneRescuer = new SoneRescuer(this, soneDownloader, sone); @@ -327,11 +327,8 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis * * @return All Sones */ - public Set getSones() { - Set allSones = new HashSet(); - allSones.addAll(getLocalSones()); - allSones.addAll(getRemoteSones()); - return allSones; + public Collection getSones() { + return database.getSones(); } /** @@ -362,10 +359,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis */ @Override public Sone getSone(String id, boolean create) { - if (isLocalSone(id)) { - return getLocalSone(id); - } - return getRemoteSone(id, create); + return database.getSone(id, create); } /** @@ -377,34 +371,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis * otherwise */ public boolean hasSone(String id) { - return isLocalSone(id) || isRemoteSone(id); - } - - /** - * Returns whether the given Sone is a local Sone. - * - * @param sone - * The Sone to check for its locality - * @return {@code true} if the given Sone is local, {@code false} otherwise - */ - public boolean isLocalSone(Sone sone) { - synchronized (localSones) { - return localSones.containsKey(sone.getId()); - } - } - - /** - * Returns whether the given ID is the ID of a local Sone. - * - * @param id - * The Sone ID to check for its locality - * @return {@code true} if the given ID is a local Sone, {@code false} - * otherwise - */ - public boolean isLocalSone(String id) { - synchronized (localSones) { - return localSones.containsKey(id); - } + return database.getSone(id, false) != null; } /** @@ -412,10 +379,8 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis * * @return All local Sones */ - public Set getLocalSones() { - synchronized (localSones) { - return new HashSet(localSones.values()); - } + public Collection getLocalSones() { + return database.getLocalSones(); } /** @@ -440,14 +405,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis * @return The Sone with the given ID, or {@code null} */ public Sone getLocalSone(String id, boolean create) { - synchronized (localSones) { - Sone sone = localSones.get(id); - if ((sone == null) && create) { - sone = new Sone(id); - localSones.put(id, sone); - } - return sone; - } + return database.getLocalSone(id, create); } /** @@ -455,10 +413,8 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis * * @return All remote Sones */ - public Set getRemoteSones() { - synchronized (remoteSones) { - return new HashSet(remoteSones.values()); - } + public Collection getRemoteSones() { + return database.getRemoteSones(); } /** @@ -472,42 +428,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis * @return The Sone with the given ID */ public Sone getRemoteSone(String id, boolean create) { - synchronized (remoteSones) { - Sone sone = remoteSones.get(id); - if ((sone == null) && create && (id != null) && (id.length() == 43)) { - sone = new Sone(id); - remoteSones.put(id, sone); - } - return sone; - } - } - - /** - * Returns whether the given Sone is a remote Sone. - * - * @param sone - * The Sone to check - * @return {@code true} if the given Sone is a remote Sone, {@code false} - * otherwise - */ - public boolean isRemoteSone(Sone sone) { - synchronized (remoteSones) { - return remoteSones.containsKey(sone.getId()); - } - } - - /** - * Returns whether the Sone with the given ID is a remote Sone. - * - * @param id - * The ID of the Sone to check - * @return {@code true} if the Sone with the given ID is a remote Sone, - * {@code false} otherwise - */ - public boolean isRemoteSone(String id) { - synchronized (remoteSones) { - return remoteSones.containsKey(id); - } + return database.getRemoteSone(id, create); } /** @@ -554,17 +475,6 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis } /** - * Returns the post with the given ID. - * - * @param postId - * The ID of the post to get - * @return The post with the given ID, or a new post with the given ID - */ - public Post getPost(String postId) { - return getPost(postId, true); - } - - /** * Returns the post with the given ID, optionally creating a new post. * * @param postId @@ -650,7 +560,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis * @return All replies for the given post */ public List getReplies(Post post) { - Set sones = getSones(); + Collection sones = getSones(); List replies = new ArrayList(); for (Sone sone : sones) { for (PostReply reply : sone.getReplies()) { @@ -871,27 +781,25 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis logger.log(Level.WARNING, "Given OwnIdentity is null!"); return null; } - synchronized (localSones) { - final Sone sone; - try { - sone = getLocalSone(ownIdentity.getId()).setIdentity(ownIdentity).setInsertUri(new FreenetURI(ownIdentity.getInsertUri())).setRequestUri(new FreenetURI(ownIdentity.getRequestUri())); - } catch (MalformedURLException mue1) { - logger.log(Level.SEVERE, String.format("Could not convert the Identity’s URIs to Freenet URIs: %s, %s", ownIdentity.getInsertUri(), ownIdentity.getRequestUri()), mue1); - return null; - } - sone.setLatestEdition(Numbers.safeParseLong(ownIdentity.getProperty("Sone.LatestEdition"), (long) 0)); - sone.setClient(new Client("Sone", SonePlugin.VERSION.toString())); - sone.setKnown(true); - /* TODO - load posts ’n stuff */ - localSones.put(ownIdentity.getId(), sone); - final SoneInserter soneInserter = new SoneInserter(this, freenetInterface, sone); - soneInserter.addSoneInsertListener(this); - soneInserters.put(sone, soneInserter); - sone.setStatus(SoneStatus.idle); - loadSone(sone); - soneInserter.start(); - return sone; + Sone sone; + try { + sone = getLocalSone(ownIdentity.getId()).setIdentity(ownIdentity).setInsertUri(new FreenetURI(ownIdentity.getInsertUri())).setRequestUri(new FreenetURI(ownIdentity.getRequestUri())); + } catch (MalformedURLException mue1) { + logger.log(Level.SEVERE, String.format("Could not convert the Identity’s URIs to Freenet URIs: %s, %s", ownIdentity.getInsertUri(), ownIdentity.getRequestUri()), mue1); + return null; } + sone.setLatestEdition(Numbers.safeParseLong(ownIdentity.getProperty("Sone.LatestEdition"), (long) 0)); + sone.setClient(new Client("Sone", SonePlugin.VERSION.toString())); + sone.setKnown(true); + /* TODO - load posts ’n stuff */ + final SoneInserter soneInserter = new SoneInserter(this, freenetInterface, sone); + soneInserter.addSoneInsertListener(this); + soneInserters.put(sone, soneInserter); + sone.setStatus(SoneStatus.idle); + loadSone(sone); + soneInserter.start(); + database.saveSone(sone); + return sone; } /** @@ -931,37 +839,36 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis logger.log(Level.WARNING, "Given Identity is null!"); return null; } - synchronized (remoteSones) { - final Sone sone = getRemoteSone(identity.getId(), true).setIdentity(identity); - boolean newSone = sone.getRequestUri() == null; - sone.setRequestUri(getSoneUri(identity.getRequestUri())); - sone.setLatestEdition(Numbers.safeParseLong(identity.getProperty("Sone.LatestEdition"), (long) 0)); + final Sone sone = getRemoteSone(identity.getId(), true).setIdentity(identity); + boolean newSone = sone.getRequestUri() == null; + sone.setRequestUri(getSoneUri(identity.getRequestUri())); + sone.setLatestEdition(Numbers.safeParseLong(identity.getProperty("Sone.LatestEdition"), (long) 0)); + if (newSone) { + synchronized (knownSones) { + newSone = !knownSones.contains(sone.getId()); + } + sone.setKnown(!newSone); if (newSone) { - synchronized (knownSones) { - newSone = !knownSones.contains(sone.getId()); - } - sone.setKnown(!newSone); - if (newSone) { - coreListenerManager.fireNewSoneFound(sone); - for (Sone localSone : getLocalSones()) { - if (localSone.getOptions().getBooleanOption("AutoFollow").get()) { - followSone(localSone, sone); - } + coreListenerManager.fireNewSoneFound(sone); + for (Sone localSone : getLocalSones()) { + if (localSone.getOptions().getBooleanOption("AutoFollow").get()) { + followSone(localSone, sone); } } } - soneDownloader.addSone(sone); - soneDownloaders.execute(new Runnable() { + } + soneDownloader.addSone(sone); + soneDownloaders.execute(new Runnable() { - @Override - @SuppressWarnings("synthetic-access") - public void run() { - soneDownloader.fetchSone(sone, sone.getRequestUri()); - } + @Override + @SuppressWarnings("synthetic-access") + public void run() { + soneDownloader.fetchSone(sone, sone.getRequestUri()); + } - }); - return sone; - } + }); + database.saveSone(sone); + return sone; } /** @@ -1054,7 +961,8 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis } /** - * Sets the trust value of the given origin Sone for the target Sone. + * Sets the trust value of the given origin Sone for + * the target Sone. * * @param origin * The origin Sone @@ -1258,16 +1166,14 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis logger.log(Level.WARNING, String.format("Tried to delete Sone of non-own identity: %s", sone)); return; } - synchronized (localSones) { - if (!localSones.containsKey(sone.getId())) { - logger.log(Level.WARNING, String.format("Tried to delete non-local Sone: %s", sone)); - return; - } - localSones.remove(sone.getId()); - SoneInserter soneInserter = soneInserters.remove(sone); - soneInserter.removeSoneInsertListener(this); - soneInserter.stop(); + if (!sone.isLocal()) { + logger.log(Level.WARNING, String.format("Tried to delete non-local Sone: %s", sone)); + return; } + database.removeSone(sone.getId()); + SoneInserter soneInserter = soneInserters.remove(sone); + soneInserter.removeSoneInsertListener(this); + soneInserter.stop(); webOfTrustUpdater.removeContext((OwnIdentity) sone.getIdentity(), "Sone"); webOfTrustUpdater.removeProperty((OwnIdentity) sone.getIdentity(), "Sone.LatestEdition"); try { @@ -1303,7 +1209,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis * The Sone to load and update */ public void loadSone(Sone sone) { - if (!isLocalSone(sone)) { + if (!sone.isLocal()) { logger.log(Level.FINE, String.format("Tried to load non-local Sone: %s", sone)); return; } @@ -1360,7 +1266,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis logger.log(Level.WARNING, "Invalid post found, aborting load!"); return; } - Post post = getPost(postId).setSone(sone).setTime(postTime).setText(postText); + Post post = getPost(postId, true).setSone(sone).setTime(postTime).setText(postText); if ((postRecipientId != null) && (postRecipientId.length() == 43)) { post.setRecipient(getSone(postRecipientId)); } @@ -1382,7 +1288,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis logger.log(Level.WARNING, "Invalid reply found, aborting load!"); return; } - replies.add(getReply(replyId).setSone(sone).setPost(getPost(postId)).setTime(replyTime).setText(replyText)); + replies.add(getReply(replyId).setSone(sone).setPost(getPost(postId, true)).setTime(replyTime).setText(replyText)); } /* load post likes. */ @@ -1526,50 +1432,6 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis * * @param sone * The Sone that creates the post - * @param text - * The text of the post - * @return The created post - */ - public Post createPost(Sone sone, String text) { - return createPost(sone, System.currentTimeMillis(), text); - } - - /** - * Creates a new post. - * - * @param sone - * The Sone that creates the post - * @param time - * The time of the post - * @param text - * The text of the post - * @return The created post - */ - public Post createPost(Sone sone, long time, String text) { - return createPost(sone, null, time, text); - } - - /** - * Creates a new post. - * - * @param sone - * The Sone that creates the post - * @param recipient - * The recipient Sone, or {@code null} if this post does not have - * a recipient - * @param text - * The text of the post - * @return The created post - */ - public Post createPost(Sone sone, Sone recipient, String text) { - return createPost(sone, recipient, System.currentTimeMillis(), text); - } - - /** - * Creates a new post. - * - * @param sone - * The Sone that creates the post * @param recipient * The recipient Sone, or {@code null} if this post does not have * a recipient @@ -1581,7 +1443,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis */ public Post createPost(Sone sone, Sone recipient, long time, String text) { Validation.begin().isNotNull("Text", text).check().isGreater("Text Length", text.length(), 0).check(); - if (!isLocalSone(sone)) { + if (!sone.isLocal()) { logger.log(Level.FINE, String.format("Tried to create post for non-local Sone: %s", sone)); return null; } @@ -1615,7 +1477,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis * The post to delete */ public void deletePost(Post post) { - if (!isLocalSone(post.getSone())) { + if (!post.getSone().isLocal()) { logger.log(Level.WARNING, String.format("Tried to delete post of non-local Sone: %s", post.getSone())); return; } @@ -1722,7 +1584,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis */ public PostReply createReply(Sone sone, Post post, long time, String text) { Validation.begin().isNotNull("Text", text).check().isGreater("Text Length", text.trim().length(), 0).check(); - if (!isLocalSone(sone)) { + if (!sone.isLocal()) { logger.log(Level.FINE, String.format("Tried to create reply for non-local Sone: %s", sone)); return null; } @@ -1756,7 +1618,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis */ public void deleteReply(PostReply reply) { Sone sone = reply.getSone(); - if (!isLocalSone(sone)) { + if (!sone.isLocal()) { logger.log(Level.FINE, String.format("Tried to delete non-local reply: %s", reply)); return; } @@ -1831,7 +1693,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis * The album to remove */ public void deleteAlbum(Album album) { - Validation.begin().isNotNull("Album", album).check().is("Local Sone", isLocalSone(album.getSone())).check(); + Validation.begin().isNotNull("Album", album).check().is("Local Sone", album.getSone().isLocal()).check(); if (!album.isEmpty()) { return; } @@ -1858,7 +1720,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis * @return The newly created image */ public Image createImage(Sone sone, Album album, TemporaryImage temporaryImage) { - Validation.begin().isNotNull("Sone", sone).isNotNull("Album", album).isNotNull("Temporary Image", temporaryImage).check().is("Local Sone", isLocalSone(sone)).check().isEqual("Owner and Album Owner", sone, album.getSone()).check(); + Validation.begin().isNotNull("Sone", sone).isNotNull("Album", album).isNotNull("Temporary Image", temporaryImage).check().is("Local Sone", sone.isLocal()).check().isEqual("Owner and Album Owner", sone, album.getSone()).check(); Image image = new Image(temporaryImage.getId()).setSone(sone).setCreationTime(System.currentTimeMillis()); album.addImage(image); synchronized (images) { @@ -1877,7 +1739,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis * The image to delete */ public void deleteImage(Image image) { - Validation.begin().isNotNull("Image", image).check().is("Local Sone", isLocalSone(image.getSone())).check(); + Validation.begin().isNotNull("Image", image).check().is("Local Sone", image.getSone().isLocal()).check(); deleteTemporaryImage(image.getId()); image.getAlbum().removeImage(image); synchronized (images) { @@ -1980,12 +1842,10 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis */ @Override public void serviceStop() { - synchronized (localSones) { - for (Entry soneInserter : soneInserters.entrySet()) { - soneInserter.getValue().removeSoneInsertListener(this); - soneInserter.getValue().stop(); - saveSone(soneInserter.getKey()); - } + for (Entry soneInserter : soneInserters.entrySet()) { + soneInserter.getValue().removeSoneInsertListener(this); + soneInserter.getValue().stop(); + saveSone(soneInserter.getKey()); } saveConfiguration(); webOfTrustUpdater.stop(); @@ -2006,7 +1866,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis * The Sone to save */ private synchronized void saveSone(Sone sone) { - if (!isLocalSone(sone)) { + if (!sone.isLocal()) { logger.log(Level.FINE, String.format("Tried to save non-local Sone: %s", sone)); return; } @@ -2469,9 +2329,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis } } } - synchronized (remoteSones) { - remoteSones.remove(identity.getId()); - } + database.removeSone(identity.getId()); coreListenerManager.fireSoneRemoved(sone); } diff --git a/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java b/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java index 72ff008..f2d05ae 100644 --- a/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java +++ b/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java @@ -34,6 +34,7 @@ import net.pterodactylus.sone.data.PostReply; import net.pterodactylus.sone.data.Profile; import net.pterodactylus.sone.data.Sone; import net.pterodactylus.sone.data.Sone.SoneStatus; +import net.pterodactylus.sone.database.memory.MemorySone; import net.pterodactylus.util.collection.Pair; import net.pterodactylus.util.io.Closer; import net.pterodactylus.util.logging.Logging; @@ -236,7 +237,7 @@ public class SoneDownloader extends AbstractService { return null; } - Sone sone = new Sone(originalSone.getId()).setIdentity(originalSone.getIdentity()); + Sone sone = new MemorySone(originalSone.getId(), originalSone.isLocal()).setIdentity(originalSone.getIdentity()); SimpleXML soneXml; try { @@ -372,7 +373,7 @@ public class SoneDownloader extends AbstractService { return null; } try { - Post post = core.getPost(postId).setSone(sone).setTime(Long.parseLong(postTime)).setText(postText); + Post post = core.getPost(postId, true).setSone(sone).setTime(Long.parseLong(postTime)).setText(postText); if ((postRecipientId != null) && (postRecipientId.length() == 43)) { post.setRecipient(core.getSone(postRecipientId)); } @@ -403,7 +404,7 @@ public class SoneDownloader extends AbstractService { return null; } try { - replies.add(core.getReply(replyId).setSone(sone).setPost(core.getPost(replyPostId)).setTime(Long.parseLong(replyTime)).setText(replyText)); + replies.add(core.getReply(replyId).setSone(sone).setPost(core.getPost(replyPostId, true)).setTime(Long.parseLong(replyTime)).setText(replyText)); } catch (NumberFormatException nfe1) { /* TODO - mark Sone as bad. */ logger.log(Level.WARNING, String.format("Downloaded reply for Sone %s with invalid time: %s", sone, replyTime)); diff --git a/src/main/java/net/pterodactylus/sone/data/Sone.java b/src/main/java/net/pterodactylus/sone/data/Sone.java index 99d65ec..28a5afa 100644 --- a/src/main/java/net/pterodactylus/sone/data/Sone.java +++ b/src/main/java/net/pterodactylus/sone/data/Sone.java @@ -17,16 +17,10 @@ package net.pterodactylus.sone.data; -import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Set; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.CopyOnWriteArraySet; -import java.util.logging.Level; -import java.util.logging.Logger; import net.pterodactylus.sone.core.Core; import net.pterodactylus.sone.core.Options; @@ -34,8 +28,6 @@ import net.pterodactylus.sone.freenet.wot.Identity; import net.pterodactylus.sone.freenet.wot.OwnIdentity; import net.pterodactylus.sone.template.SoneAccessor; import net.pterodactylus.util.collection.filter.Filter; -import net.pterodactylus.util.logging.Logging; -import net.pterodactylus.util.validation.Validation; import freenet.keys.FreenetURI; /** @@ -46,7 +38,7 @@ import freenet.keys.FreenetURI; * * @author David ‘Bombe’ Roden */ -public class Sone implements Fingerprintable, Comparable { +public interface Sone extends Fingerprintable, Comparable { /** * Enumeration for the possible states of a {@link Sone}. @@ -150,7 +142,7 @@ public class Sone implements Fingerprintable, Comparable { } }; - /** Filter that matches all {@link Core#isLocalSone(Sone) local Sones}. */ + /** Filter that matches all {@link Core#getLocalSones() local Sones}. */ public static final Filter LOCAL_SONE_FILTER = new Filter() { @Override @@ -169,71 +161,6 @@ public class Sone implements Fingerprintable, Comparable { } }; - /** The logger. */ - private static final Logger logger = Logging.getLogger(Sone.class); - - /** The ID of this Sone. */ - private final String id; - - /** The identity of this Sone. */ - private Identity identity; - - /** The URI under which the Sone is stored in Freenet. */ - private volatile FreenetURI requestUri; - - /** The URI used to insert a new version of this Sone. */ - /* This will be null for remote Sones! */ - private volatile FreenetURI insertUri; - - /** The latest edition of the Sone. */ - private volatile long latestEdition; - - /** The time of the last inserted update. */ - private volatile long time; - - /** The status of this Sone. */ - private volatile SoneStatus status = SoneStatus.unknown; - - /** The profile of this Sone. */ - private volatile Profile profile = new Profile(this); - - /** The client used by the Sone. */ - private volatile Client client; - - /** Whether this Sone is known. */ - private volatile boolean known; - - /** All friend Sones. */ - private final Set friendSones = new CopyOnWriteArraySet(); - - /** All posts. */ - private final Set posts = new CopyOnWriteArraySet(); - - /** All replies. */ - private final Set replies = new CopyOnWriteArraySet(); - - /** The IDs of all liked posts. */ - private final Set likedPostIds = new CopyOnWriteArraySet(); - - /** The IDs of all liked replies. */ - private final Set likedReplyIds = new CopyOnWriteArraySet(); - - /** The albums of this Sone. */ - private final List albums = new CopyOnWriteArrayList(); - - /** Sone-specific options. */ - private final Options options = new Options(); - - /** - * Creates a new Sone. - * - * @param id - * The ID of the Sone - */ - public Sone(String id) { - this.id = id; - } - // // ACCESSORS // @@ -243,18 +170,14 @@ public class Sone implements Fingerprintable, Comparable { * * @return The identity of this Sone */ - public String getId() { - return id; - } + public String getId(); /** * Returns the identity of this Sone. * * @return The identity of this Sone */ - public Identity getIdentity() { - return identity; - } + public Identity getIdentity(); /** * Sets the identity of this Sone. The {@link Identity#getId() ID} of the @@ -266,31 +189,28 @@ public class Sone implements Fingerprintable, Comparable { * @throws IllegalArgumentException * if the ID of the identity does not match this Sone’s ID */ - public Sone setIdentity(Identity identity) throws IllegalArgumentException { - if (!identity.getId().equals(id)) { - throw new IllegalArgumentException("Identity’s ID does not match Sone’s ID!"); - } - this.identity = identity; - return this; - } + public Sone setIdentity(Identity identity) throws IllegalArgumentException; + + /** + * Returns whether this Sone is local. + * + * @return {@code true} if this Sone is local, {@code false} otherwise + */ + public boolean isLocal(); /** * Returns the name of this Sone. * * @return The name of this Sone */ - public String getName() { - return (identity != null) ? identity.getNickname() : null; - } + public String getName(); /** * Returns the request URI of this Sone. * * @return The request URI of this Sone */ - public FreenetURI getRequestUri() { - return (requestUri != null) ? requestUri.setSuggestedEdition(latestEdition) : null; - } + public FreenetURI getRequestUri(); /** * Sets the request URI of this Sone. @@ -299,26 +219,14 @@ public class Sone implements Fingerprintable, Comparable { * The request URI of this Sone * @return This Sone (for method chaining) */ - public Sone setRequestUri(FreenetURI requestUri) { - if (this.requestUri == null) { - this.requestUri = requestUri.setKeyType("USK").setDocName("Sone").setMetaString(new String[0]); - return this; - } - if (!this.requestUri.equalsKeypair(requestUri)) { - logger.log(Level.WARNING, String.format("Request URI %s tried to overwrite %s!", requestUri, this.requestUri)); - return this; - } - return this; - } + public Sone setRequestUri(FreenetURI requestUri); /** * Returns the insert URI of this Sone. * * @return The insert URI of this Sone */ - public FreenetURI getInsertUri() { - return (insertUri != null) ? insertUri.setSuggestedEdition(latestEdition) : null; - } + public FreenetURI getInsertUri(); /** * Sets the insert URI of this Sone. @@ -327,26 +235,14 @@ public class Sone implements Fingerprintable, Comparable { * The insert URI of this Sone * @return This Sone (for method chaining) */ - public Sone setInsertUri(FreenetURI insertUri) { - if (this.insertUri == null) { - this.insertUri = insertUri.setKeyType("USK").setDocName("Sone").setMetaString(new String[0]); - return this; - } - if (!this.insertUri.equalsKeypair(insertUri)) { - logger.log(Level.WARNING, String.format("Request URI %s tried to overwrite %s!", insertUri, this.insertUri)); - return this; - } - return this; - } + public Sone setInsertUri(FreenetURI insertUri); /** * Returns the latest edition of this Sone. * * @return The latest edition of this Sone */ - public long getLatestEdition() { - return latestEdition; - } + public long getLatestEdition(); /** * Sets the latest edition of this Sone. If the given latest edition is not @@ -356,22 +252,14 @@ public class Sone implements Fingerprintable, Comparable { * @param latestEdition * The latest edition of this Sone */ - public void setLatestEdition(long latestEdition) { - if (!(latestEdition > this.latestEdition)) { - logger.log(Level.FINE, String.format("New latest edition %d is not greater than current latest edition %d!", latestEdition, this.latestEdition)); - return; - } - this.latestEdition = latestEdition; - } + public void setLatestEdition(long latestEdition); /** * Return the time of the last inserted update of this Sone. * * @return The time of the update (in milliseconds since Jan 1, 1970 UTC) */ - public long getTime() { - return time; - } + public long getTime(); /** * Sets the time of the last inserted update of this Sone. @@ -380,19 +268,14 @@ public class Sone implements Fingerprintable, Comparable { * The time of the update (in milliseconds since Jan 1, 1970 UTC) * @return This Sone (for method chaining) */ - public Sone setTime(long time) { - this.time = time; - return this; - } + public Sone setTime(long time); /** * Returns the status of this Sone. * * @return The status of this Sone */ - public SoneStatus getStatus() { - return status; - } + public SoneStatus getStatus(); /** * Sets the new status of this Sone. @@ -403,11 +286,7 @@ public class Sone implements Fingerprintable, Comparable { * @throws IllegalArgumentException * if {@code status} is {@code null} */ - public Sone setStatus(SoneStatus status) { - Validation.begin().isNotNull("Sone Status", status).check(); - this.status = status; - return this; - } + public Sone setStatus(SoneStatus status); /** * Returns a copy of the profile. If you want to update values in the @@ -416,9 +295,7 @@ public class Sone implements Fingerprintable, Comparable { * * @return A copy of the profile */ - public Profile getProfile() { - return new Profile(profile); - } + public Profile getProfile(); /** * Sets the profile of this Sone. A copy of the given profile is stored so @@ -428,18 +305,14 @@ public class Sone implements Fingerprintable, Comparable { * @param profile * The profile to set */ - public void setProfile(Profile profile) { - this.profile = new Profile(profile); - } + public void setProfile(Profile profile); /** * Returns the client used by this Sone. * * @return The client used by this Sone, or {@code null} */ - public Client getClient() { - return client; - } + public Client getClient(); /** * Sets the client used by this Sone. @@ -448,19 +321,14 @@ public class Sone implements Fingerprintable, Comparable { * The client used by this Sone, or {@code null} * @return This Sone (for method chaining) */ - public Sone setClient(Client client) { - this.client = client; - return this; - } + public Sone setClient(Client client); /** * Returns whether this Sone is known. * * @return {@code true} if this Sone is known, {@code false} otherwise */ - public boolean isKnown() { - return known; - } + public boolean isKnown(); /** * Sets whether this Sone is known. @@ -469,20 +337,14 @@ public class Sone implements Fingerprintable, Comparable { * {@code true} if this Sone is known, {@code false} otherwise * @return This Sone */ - public Sone setKnown(boolean known) { - this.known = known; - return this; - } + public Sone setKnown(boolean known); /** * Returns all friend Sones of this Sone. * * @return The friend Sones of this Sone */ - public List getFriends() { - List friends = new ArrayList(friendSones); - return friends; - } + public Collection getFriends(); /** * Returns whether this Sone has the given Sone as a friend Sone. @@ -492,9 +354,7 @@ public class Sone implements Fingerprintable, Comparable { * @return {@code true} if this Sone has the given Sone as a friend, * {@code false} otherwise */ - public boolean hasFriend(String friendSoneId) { - return friendSones.contains(friendSoneId); - } + public boolean hasFriend(String friendSoneId); /** * Adds the given Sone as a friend Sone. @@ -503,12 +363,7 @@ public class Sone implements Fingerprintable, Comparable { * The friend Sone to add * @return This Sone (for method chaining) */ - public Sone addFriend(String friendSone) { - if (!friendSone.equals(id)) { - friendSones.add(friendSone); - } - return this; - } + public Sone addFriend(String friendSone); /** * Removes the given Sone as a friend Sone. @@ -517,24 +372,14 @@ public class Sone implements Fingerprintable, Comparable { * The ID of the friend Sone to remove * @return This Sone (for method chaining) */ - public Sone removeFriend(String friendSoneId) { - friendSones.remove(friendSoneId); - return this; - } + public Sone removeFriend(String friendSoneId); /** * Returns the list of posts of this Sone, sorted by time, newest first. * * @return All posts of this Sone */ - public List getPosts() { - List sortedPosts; - synchronized (this) { - sortedPosts = new ArrayList(posts); - } - Collections.sort(sortedPosts, Post.TIME_COMPARATOR); - return sortedPosts; - } + public List getPosts(); /** * Sets all posts of this Sone at once. @@ -543,13 +388,7 @@ public class Sone implements Fingerprintable, Comparable { * The new (and only) posts of this Sone * @return This Sone (for method chaining) */ - public Sone setPosts(Collection posts) { - synchronized (this) { - this.posts.clear(); - this.posts.addAll(posts); - } - return this; - } + public Sone setPosts(Collection posts); /** * Adds the given post to this Sone. The post will not be added if its @@ -558,11 +397,7 @@ public class Sone implements Fingerprintable, Comparable { * @param post * The post to add */ - public void addPost(Post post) { - if (post.getSone().equals(this) && posts.add(post)) { - logger.log(Level.FINEST, String.format("Adding %s to “%s”.", post, getName())); - } - } + public void addPost(Post post); /** * Removes the given post from this Sone. @@ -570,20 +405,14 @@ public class Sone implements Fingerprintable, Comparable { * @param post * The post to remove */ - public void removePost(Post post) { - if (post.getSone().equals(this)) { - posts.remove(post); - } - } + public void removePost(Post post); /** * Returns all replies this Sone made. * * @return All replies this Sone made */ - public Set getReplies() { - return Collections.unmodifiableSet(replies); - } + public Set getReplies(); /** * Sets all replies of this Sone at once. @@ -592,11 +421,7 @@ public class Sone implements Fingerprintable, Comparable { * The new (and only) replies of this Sone * @return This Sone (for method chaining) */ - public Sone setReplies(Collection replies) { - this.replies.clear(); - this.replies.addAll(replies); - return this; - } + public Sone setReplies(Collection replies); /** * Adds a reply to this Sone. If the given reply was not made by this Sone, @@ -605,11 +430,7 @@ public class Sone implements Fingerprintable, Comparable { * @param reply * The reply to add */ - public void addReply(PostReply reply) { - if (reply.getSone().equals(this)) { - replies.add(reply); - } - } + public void addReply(PostReply reply); /** * Removes a reply from this Sone. @@ -617,20 +438,14 @@ public class Sone implements Fingerprintable, Comparable { * @param reply * The reply to remove */ - public void removeReply(PostReply reply) { - if (reply.getSone().equals(this)) { - replies.remove(reply); - } - } + public void removeReply(PostReply reply); /** * Returns the IDs of all liked posts. * * @return All liked posts’ IDs */ - public Set getLikedPostIds() { - return Collections.unmodifiableSet(likedPostIds); - } + public Set getLikedPostIds(); /** * Sets the IDs of all liked posts. @@ -639,11 +454,7 @@ public class Sone implements Fingerprintable, Comparable { * All liked posts’ IDs * @return This Sone (for method chaining) */ - public Sone setLikePostIds(Set likedPostIds) { - this.likedPostIds.clear(); - this.likedPostIds.addAll(likedPostIds); - return this; - } + public Sone setLikePostIds(Set likedPostIds); /** * Checks whether the given post ID is liked by this Sone. @@ -653,9 +464,7 @@ public class Sone implements Fingerprintable, Comparable { * @return {@code true} if this Sone likes the given post, {@code false} * otherwise */ - public boolean isLikedPostId(String postId) { - return likedPostIds.contains(postId); - } + public boolean isLikedPostId(String postId); /** * Adds the given post ID to the list of posts this Sone likes. @@ -664,10 +473,7 @@ public class Sone implements Fingerprintable, Comparable { * The ID of the post * @return This Sone (for method chaining) */ - public Sone addLikedPostId(String postId) { - likedPostIds.add(postId); - return this; - } + public Sone addLikedPostId(String postId); /** * Removes the given post ID from the list of posts this Sone likes. @@ -676,19 +482,14 @@ public class Sone implements Fingerprintable, Comparable { * The ID of the post * @return This Sone (for method chaining) */ - public Sone removeLikedPostId(String postId) { - likedPostIds.remove(postId); - return this; - } + public Sone removeLikedPostId(String postId); /** * Returns the IDs of all liked replies. * * @return All liked replies’ IDs */ - public Set getLikedReplyIds() { - return Collections.unmodifiableSet(likedReplyIds); - } + public Set getLikedReplyIds(); /** * Sets the IDs of all liked replies. @@ -697,11 +498,7 @@ public class Sone implements Fingerprintable, Comparable { * All liked replies’ IDs * @return This Sone (for method chaining) */ - public Sone setLikeReplyIds(Set likedReplyIds) { - this.likedReplyIds.clear(); - this.likedReplyIds.addAll(likedReplyIds); - return this; - } + public Sone setLikeReplyIds(Set likedReplyIds); /** * Checks whether the given reply ID is liked by this Sone. @@ -711,9 +508,7 @@ public class Sone implements Fingerprintable, Comparable { * @return {@code true} if this Sone likes the given reply, {@code false} * otherwise */ - public boolean isLikedReplyId(String replyId) { - return likedReplyIds.contains(replyId); - } + public boolean isLikedReplyId(String replyId); /** * Adds the given reply ID to the list of replies this Sone likes. @@ -722,10 +517,7 @@ public class Sone implements Fingerprintable, Comparable { * The ID of the reply * @return This Sone (for method chaining) */ - public Sone addLikedReplyId(String replyId) { - likedReplyIds.add(replyId); - return this; - } + public Sone addLikedReplyId(String replyId); /** * Removes the given post ID from the list of replies this Sone likes. @@ -734,19 +526,14 @@ public class Sone implements Fingerprintable, Comparable { * The ID of the reply * @return This Sone (for method chaining) */ - public Sone removeLikedReplyId(String replyId) { - likedReplyIds.remove(replyId); - return this; - } + public Sone removeLikedReplyId(String replyId); /** * Returns the albums of this Sone. * * @return The albums of this Sone */ - public List getAlbums() { - return Collections.unmodifiableList(albums); - } + public List getAlbums(); /** * Returns a flattened list of all albums of this Sone. The resulting list @@ -755,19 +542,7 @@ public class Sone implements Fingerprintable, Comparable { * * @return The flattened albums */ - public List getAllAlbums() { - List flatAlbums = new ArrayList(); - flatAlbums.addAll(albums); - int lastAlbumIndex = 0; - while (lastAlbumIndex < flatAlbums.size()) { - int previousAlbumCount = flatAlbums.size(); - for (Album album : new ArrayList(flatAlbums.subList(lastAlbumIndex, flatAlbums.size()))) { - flatAlbums.addAll(album.getAlbums()); - } - lastAlbumIndex = previousAlbumCount; - } - return flatAlbums; - } + public List getAllAlbums(); /** * Returns all images of a Sone. Images of a album are inserted into this @@ -775,13 +550,7 @@ public class Sone implements Fingerprintable, Comparable { * * @return The list of all images */ - public List getAllImages() { - List allImages = new ArrayList(); - for (Album album : getAllAlbums()) { - allImages.addAll(album.getImages()); - } - return allImages; - } + public List getAllImages(); /** * Adds an album to this Sone. @@ -789,12 +558,7 @@ public class Sone implements Fingerprintable, Comparable { * @param album * The album to add */ - public void addAlbum(Album album) { - Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).check(); - if (!albums.contains(album)) { - albums.add(album); - } - } + public void addAlbum(Album album); /** * Sets the albums of this Sone. @@ -802,13 +566,7 @@ public class Sone implements Fingerprintable, Comparable { * @param albums * The albums of this Sone */ - public void setAlbums(Collection albums) { - Validation.begin().isNotNull("Albums", albums).check(); - this.albums.clear(); - for (Album album : albums) { - addAlbum(album); - } - } + public void setAlbums(Collection albums); /** * Removes an album from this Sone. @@ -816,10 +574,7 @@ public class Sone implements Fingerprintable, Comparable { * @param album * The album to remove */ - public void removeAlbum(Album album) { - Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).check(); - albums.remove(album); - } + public void removeAlbum(Album album); /** * Moves the given album up in this album’s albums. If the album is already @@ -830,16 +585,7 @@ public class Sone implements Fingerprintable, Comparable { * @return The album that the given album swapped the place with, or * null if the album did not change its place */ - public Album moveAlbumUp(Album album) { - Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).isNull("Album Parent", album.getParent()).check(); - int oldIndex = albums.indexOf(album); - if (oldIndex <= 0) { - return null; - } - albums.remove(oldIndex); - albums.add(oldIndex - 1, album); - return albums.get(oldIndex); - } + public Album moveAlbumUp(Album album); /** * Moves the given album down in this album’s albums. If the album is @@ -850,118 +596,13 @@ public class Sone implements Fingerprintable, Comparable { * @return The album that the given album swapped the place with, or * null if the album did not change its place */ - public Album moveAlbumDown(Album album) { - Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).isNull("Album Parent", album.getParent()).check(); - int oldIndex = albums.indexOf(album); - if ((oldIndex < 0) || (oldIndex >= (albums.size() - 1))) { - return null; - } - albums.remove(oldIndex); - albums.add(oldIndex + 1, album); - return albums.get(oldIndex); - } + public Album moveAlbumDown(Album album); /** * Returns Sone-specific options. * * @return The options of this Sone */ - public Options getOptions() { - return options; - } - - // - // FINGERPRINTABLE METHODS - // - - /** - * {@inheritDoc} - */ - @Override - public synchronized String getFingerprint() { - StringBuilder fingerprint = new StringBuilder(); - fingerprint.append(profile.getFingerprint()); - - fingerprint.append("Posts("); - for (Post post : getPosts()) { - fingerprint.append("Post(").append(post.getId()).append(')'); - } - fingerprint.append(")"); - - List replies = new ArrayList(getReplies()); - Collections.sort(replies, Reply.TIME_COMPARATOR); - fingerprint.append("Replies("); - for (PostReply reply : replies) { - fingerprint.append("Reply(").append(reply.getId()).append(')'); - } - fingerprint.append(')'); - - List likedPostIds = new ArrayList(getLikedPostIds()); - Collections.sort(likedPostIds); - fingerprint.append("LikedPosts("); - for (String likedPostId : likedPostIds) { - fingerprint.append("Post(").append(likedPostId).append(')'); - } - fingerprint.append(')'); - - List likedReplyIds = new ArrayList(getLikedReplyIds()); - Collections.sort(likedReplyIds); - fingerprint.append("LikedReplies("); - for (String likedReplyId : likedReplyIds) { - fingerprint.append("Reply(").append(likedReplyId).append(')'); - } - fingerprint.append(')'); - - fingerprint.append("Albums("); - for (Album album : albums) { - fingerprint.append(album.getFingerprint()); - } - fingerprint.append(')'); - - return fingerprint.toString(); - } - - // - // INTERFACE Comparable - // - - /** - * {@inheritDoc} - */ - @Override - public int compareTo(Sone sone) { - return NICE_NAME_COMPARATOR.compare(this, sone); - } - - // - // OBJECT METHODS - // - - /** - * {@inheritDoc} - */ - @Override - public int hashCode() { - return id.hashCode(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean equals(Object object) { - if (!(object instanceof Sone)) { - return false; - } - return ((Sone) object).id.equals(id); - } - - /** - * {@inheritDoc} - */ - @Override - public String toString() { - return getClass().getName() + "[identity=" + identity + ",requestUri=" + requestUri + ",insertUri(" + String.valueOf(insertUri).length() + "),friends(" + friendSones.size() + "),posts(" + posts.size() + "),replies(" + replies.size() + ")]"; - } + public Options getOptions(); } diff --git a/src/main/java/net/pterodactylus/sone/database/Database.java b/src/main/java/net/pterodactylus/sone/database/Database.java new file mode 100644 index 0000000..0ff7dfb --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/database/Database.java @@ -0,0 +1,141 @@ +/* + * Sone - Database.java - Copyright © 2011 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; + +import java.util.Collection; + +import net.pterodactylus.sone.data.Sone; + +/** + * Interface for Sone’s database. + * + * @author David ‘Bombe’ Roden + */ +public interface Database { + + /** + * Returns the Sone with the given ID, creating a new Sone if a Sone with + * the given ID does not exist and {@code create} is {@code true}. When + * searching for a Sone with the given IDs, local Sones are preferred. + * + * @param id + * The ID of the new Sone + * @param create + * {@code true} to create a new Sone if a Sone with the given ID + * does not exist, {@code false} to return {@code null} if a Sone + * with the given ID does not exist + * @return The created Sone + * @throws IllegalArgumentException + * if {@code id} is {@code null} + * @throws DatabaseException + * if a database error occurs + */ + public Sone getSone(String id, boolean create) throws DatabaseException; + + /** + * Returns all known Sones. + * + * @return All known Sones + * @throws DatabaseException + * if a database error occurs + */ + public Collection getSones() throws DatabaseException; + + /** + * Returns all known local Sones. + * + * @return All known local Sones + * @throws DatabaseException + * if a database error occurs + */ + public Collection getLocalSones() throws DatabaseException; + + /** + * Returns all known remote Sones. + * + * @return All known remote Sones + * @throws DatabaseException + * if a database error occurs + */ + public Collection getRemoteSones() throws DatabaseException; + + /** + * Returns the local Sone with the given ID, creating it if it doesn’t exist + * and {@code create} is {@code true}. + * + * @param id + * The ID of the Sone + * @param create + * {@code true} to create a Sone if no Sone with the given ID + * exists yet, {@code false} to return {@code null} + * @return The existing Sone, the created Sone, or {@code null} + * @throws DatabaseException + * if adatabase error occurs + */ + public Sone getLocalSone(String id, boolean create) throws DatabaseException; + + /** + * Returns the remote Sone with the given ID, creating it if it doesn’t + * exist and {@code create} is {@code true}. + * + * @param id + * The ID of the Sone + * @param create + * {@code true} to create a Sone if no Sone with the given ID + * exists yet, {@code false} to return {@code null} + * @return The existing Sone, the created Sone, or {@code null} + * @throws DatabaseException + * if adatabase error occurs + */ + public Sone getRemoteSone(String id, boolean create) throws DatabaseException; + + /** + * Stores the given Sone. The given Sone has to be an object that was + * returned by a previous call to {@link #getSone(String, boolean)}. + * + * @param sone + * The Sone to store + * @throws IllegalArgumentException + * if {@code sone} is {@code null}, or the Sone was not + * retrieved by a call to {@link #getSone(String, boolean)} + * @throws DatabaseException + * if a database error occurs + */ + public void saveSone(Sone sone) throws DatabaseException; + + /** + * Removes the given Sone from the database. + * + * @param sone + * The Sone to remove + * @throws DatabaseException + * if a database error occurs + */ + public void removeSone(Sone sone) throws DatabaseException; + + /** + * Removes the Sone with the given ID from the database. + * + * @param id + * The ID of the Sone to remove + * @throws DatabaseException + * if a database error occurs + */ + public void removeSone(String id) throws DatabaseException; + +} diff --git a/src/main/java/net/pterodactylus/sone/database/DatabaseException.java b/src/main/java/net/pterodactylus/sone/database/DatabaseException.java new file mode 100644 index 0000000..0f4a46f --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/database/DatabaseException.java @@ -0,0 +1,67 @@ +/* + * Sone - DatabaseException.java - Copyright © 2011 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; + + +/** + * Exception that signals an error working with the database. + * + * @author David ‘Bombe’ Roden + */ +public class DatabaseException extends RuntimeException { + + /** + * Creates a new database exception. + */ + public DatabaseException() { + super(); + } + + /** + * Creates a new database exception. + * + * @param message + * The message of the exception + */ + public DatabaseException(String message) { + super(message); + } + + /** + * Creates a new database exception. + * + * @param cause + * The cause of the exception + */ + public DatabaseException(Throwable cause) { + super(cause); + } + + /** + * Creates a new database exception. + * + * @param message + * The message of the exception + * @param cause + * The cause of the exception + */ + public DatabaseException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java b/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java new file mode 100644 index 0000000..db295e8 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java @@ -0,0 +1,153 @@ +/* + * Sone - MemoryDatabase.java - Copyright © 2011 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 java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import net.pterodactylus.sone.core.Core; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.database.Database; +import net.pterodactylus.sone.database.DatabaseException; +import net.pterodactylus.util.collection.SetBuilder; +import net.pterodactylus.util.validation.Validation; + +/** + * In-memory implementation of {@link Database} that stores all its data in a + * {@link Map}. This is roughly equivalent to what {@link Core} does today and + * is mainly used while Sone is transitioning to using a real database. + * + * @author David ‘Bombe’ Roden + */ +public class MemoryDatabase implements Database { + + /** The local Sones. */ + private final Map localSones = new HashMap(); + + /** The remote Sones. */ + private final Map remoteSones = new HashMap(); + + /** + * {@inheritDoc} + */ + @Override + public Sone getSone(String id, boolean create) throws DatabaseException { + if (getLocalSone(id, false) != null) { + return getLocalSone(id, create); + } + return getRemoteSone(id, create); + } + + /** + * {@inheritDoc} + */ + @Override + public Collection getSones() throws DatabaseException { + return new SetBuilder().addAll(getLocalSones()).addAll(getRemoteSones()).get(); + } + + /** + * {@inheritDoc} + */ + @Override + public Collection getLocalSones() throws DatabaseException { + synchronized (localSones) { + return Collections.unmodifiableCollection(localSones.values()); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Collection getRemoteSones() throws DatabaseException { + synchronized (remoteSones) { + return Collections.unmodifiableCollection(remoteSones.values()); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Sone getLocalSone(String id, boolean create) throws DatabaseException { + Validation.begin().isNotNull("Sone ID", id).check(); + synchronized (localSones) { + if (!localSones.containsKey(id) && create) { + localSones.put(id, new MemorySone(id, true)); + } + return localSones.get(id); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Sone getRemoteSone(String id, boolean create) throws DatabaseException { + Validation.begin().isNotNull("Sone ID", id).check(); + synchronized (remoteSones) { + if (!remoteSones.containsKey(id) && create) { + remoteSones.put(id, new MemorySone(id, false)); + } + return remoteSones.get(id); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void saveSone(Sone sone) throws DatabaseException { + if (sone.isLocal()) { + synchronized (localSones) { + localSones.put(sone.getId(), sone); + } + } else { + synchronized (remoteSones) { + Validation.begin().is("Sone is from this database", remoteSones.containsKey(sone.getId())).check(); + remoteSones.put(sone.getId(), sone); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void removeSone(Sone sone) throws DatabaseException { + Map sones = sone.isLocal() ? localSones : remoteSones; + synchronized (sones) { + sones.remove(sone.getId()); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void removeSone(String id) throws DatabaseException { + Map sones = ((getSone(id, false) != null) && getSone(id, false).isLocal()) ? localSones : remoteSones; + synchronized (sones) { + sones.remove(id); + } + } + +} diff --git a/src/main/java/net/pterodactylus/sone/database/memory/MemorySone.java b/src/main/java/net/pterodactylus/sone/database/memory/MemorySone.java new file mode 100644 index 0000000..da487b3 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/database/memory/MemorySone.java @@ -0,0 +1,737 @@ +/* + * Sone - MemorySone.java - Copyright © 2010–2012 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 java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.logging.Level; +import java.util.logging.Logger; + +import net.pterodactylus.sone.core.Options; +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.Reply; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.database.Database; +import net.pterodactylus.sone.freenet.wot.Identity; +import net.pterodactylus.util.logging.Logging; +import net.pterodactylus.util.validation.Validation; +import freenet.keys.FreenetURI; + +/** + * Implementation of a {@link Sone} that keeps all added data in memory. A + * self-created instance of this class should be converted to a {@link Database} + * -based instance of {@link Sone} as soon as possible (unless it was returned + * by a {@link MemoryDatabase}). + * + * @author David ‘Bombe’ Roden + */ +public class MemorySone implements Sone { + + /** The logger. */ + private static final Logger logger = Logging.getLogger(Sone.class); + + /** The ID of this Sone. */ + private final String id; + + /** Whether this is a local Sone. */ + private final boolean local; + + /** The identity of this Sone. */ + private Identity identity; + + /** The URI under which the Sone is stored in Freenet. */ + private volatile FreenetURI requestUri; + + /** The URI used to insert a new version of this Sone. */ + /* This will be null for remote Sones! */ + private volatile FreenetURI insertUri; + + /** The latest edition of the Sone. */ + private volatile long latestEdition; + + /** The time of the last inserted update. */ + private volatile long time; + + /** The status of this Sone. */ + private volatile SoneStatus status = SoneStatus.unknown; + + /** The profile of this Sone. */ + private volatile Profile profile = new Profile(this); + + /** The client used by the Sone. */ + private volatile Client client; + + /** Whether this Sone is known. */ + private volatile boolean known; + + /** All friend Sones. */ + private final Set friendSones = new CopyOnWriteArraySet(); + + /** All posts. */ + private final Set posts = new CopyOnWriteArraySet(); + + /** All replies. */ + private final Set replies = new CopyOnWriteArraySet(); + + /** The IDs of all liked posts. */ + private final Set likedPostIds = new CopyOnWriteArraySet(); + + /** The IDs of all liked replies. */ + private final Set likedReplyIds = new CopyOnWriteArraySet(); + + /** The albums of this Sone. */ + private final List albums = new CopyOnWriteArrayList(); + + /** Sone-specific options. */ + private final Options options = new Options(); + + /** + * Creates a new Sone. + * + * @param id + * The ID of the Sone + * @param local + * {@code true} if this Sone is local, {@code false} otherwise + */ + public MemorySone(String id, boolean local) { + this.id = id; + this.local = local; + } + + // + // ACCESSORS + // + + /** + * {@inheritDoc} + */ + @Override + public String getId() { + return id; + } + + /** + * {@inheritDoc} + */ + @Override + public Identity getIdentity() { + return identity; + } + + /** + * {@inheritDoc} + */ + @Override + public Sone setIdentity(Identity identity) throws IllegalArgumentException { + if (!identity.getId().equals(id)) { + throw new IllegalArgumentException("Identity’s ID does not match Sone’s ID!"); + } + this.identity = identity; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isLocal() { + return local; + } + + /** + * {@inheritDoc} + */ + @Override + public String getName() { + return (identity != null) ? identity.getNickname() : null; + } + + /** + * {@inheritDoc} + */ + @Override + public FreenetURI getRequestUri() { + return (requestUri != null) ? requestUri.setSuggestedEdition(latestEdition) : null; + } + + /** + * {@inheritDoc} + */ + @Override + public Sone setRequestUri(FreenetURI requestUri) { + if (this.requestUri == null) { + this.requestUri = requestUri.setKeyType("USK").setDocName("Sone").setMetaString(new String[0]); + return this; + } + if (!this.requestUri.equalsKeypair(requestUri)) { + logger.log(Level.WARNING, String.format("Request URI %s tried to overwrite %s!", requestUri, this.requestUri)); + return this; + } + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public FreenetURI getInsertUri() { + return (insertUri != null) ? insertUri.setSuggestedEdition(latestEdition) : null; + } + + /** + * {@inheritDoc} + */ + @Override + public Sone setInsertUri(FreenetURI insertUri) { + if (this.insertUri == null) { + this.insertUri = insertUri.setKeyType("USK").setDocName("Sone").setMetaString(new String[0]); + return this; + } + if (!this.insertUri.equalsKeypair(insertUri)) { + logger.log(Level.WARNING, String.format("Request URI %s tried to overwrite %s!", insertUri, this.insertUri)); + return this; + } + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public long getLatestEdition() { + return latestEdition; + } + + /** + * {@inheritDoc} + */ + @Override + public void setLatestEdition(long latestEdition) { + if (!(latestEdition > this.latestEdition)) { + logger.log(Level.FINE, String.format("New latest edition %d is not greater than current latest edition %d!", latestEdition, this.latestEdition)); + return; + } + this.latestEdition = latestEdition; + } + + /** + * {@inheritDoc} + */ + @Override + public long getTime() { + return time; + } + + /** + * {@inheritDoc} + */ + @Override + public Sone setTime(long time) { + this.time = time; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public SoneStatus getStatus() { + return status; + } + + /** + * {@inheritDoc} + */ + @Override + public Sone setStatus(SoneStatus status) { + Validation.begin().isNotNull("Sone Status", status).check(); + this.status = status; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public Profile getProfile() { + return new Profile(profile); + } + + /** + * {@inheritDoc} + */ + @Override + public void setProfile(Profile profile) { + this.profile = new Profile(profile); + } + + /** + * {@inheritDoc} + */ + @Override + public Client getClient() { + return client; + } + + /** + * {@inheritDoc} + */ + @Override + public Sone setClient(Client client) { + this.client = client; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isKnown() { + return known; + } + + /** + * {@inheritDoc} + */ + @Override + public Sone setKnown(boolean known) { + this.known = known; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public List getFriends() { + List friends = new ArrayList(friendSones); + return friends; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean hasFriend(String friendSoneId) { + return friendSones.contains(friendSoneId); + } + + /** + * {@inheritDoc} + */ + @Override + public Sone addFriend(String friendSone) { + if (!friendSone.equals(id)) { + friendSones.add(friendSone); + } + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public Sone removeFriend(String friendSoneId) { + friendSones.remove(friendSoneId); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public List getPosts() { + List sortedPosts; + synchronized (this) { + sortedPosts = new ArrayList(posts); + } + Collections.sort(sortedPosts, Post.TIME_COMPARATOR); + return sortedPosts; + } + + /** + * {@inheritDoc} + */ + @Override + public Sone setPosts(Collection posts) { + synchronized (this) { + this.posts.clear(); + this.posts.addAll(posts); + } + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public void addPost(Post post) { + if (post.getSone().equals(this) && posts.add(post)) { + logger.log(Level.FINEST, String.format("Adding %s to “%s”.", post, getName())); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void removePost(Post post) { + if (post.getSone().equals(this)) { + posts.remove(post); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Set getReplies() { + return Collections.unmodifiableSet(replies); + } + + /** + * {@inheritDoc} + */ + @Override + public Sone setReplies(Collection replies) { + this.replies.clear(); + this.replies.addAll(replies); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public void addReply(PostReply reply) { + if (reply.getSone().equals(this)) { + replies.add(reply); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void removeReply(PostReply reply) { + if (reply.getSone().equals(this)) { + replies.remove(reply); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Set getLikedPostIds() { + return Collections.unmodifiableSet(likedPostIds); + } + + /** + * {@inheritDoc} + */ + @Override + public Sone setLikePostIds(Set likedPostIds) { + this.likedPostIds.clear(); + this.likedPostIds.addAll(likedPostIds); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isLikedPostId(String postId) { + return likedPostIds.contains(postId); + } + + /** + * {@inheritDoc} + */ + @Override + public Sone addLikedPostId(String postId) { + likedPostIds.add(postId); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public Sone removeLikedPostId(String postId) { + likedPostIds.remove(postId); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public Set getLikedReplyIds() { + return Collections.unmodifiableSet(likedReplyIds); + } + + /** + * {@inheritDoc} + */ + @Override + public Sone setLikeReplyIds(Set likedReplyIds) { + this.likedReplyIds.clear(); + this.likedReplyIds.addAll(likedReplyIds); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isLikedReplyId(String replyId) { + return likedReplyIds.contains(replyId); + } + + /** + * {@inheritDoc} + */ + @Override + public Sone addLikedReplyId(String replyId) { + likedReplyIds.add(replyId); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public Sone removeLikedReplyId(String replyId) { + likedReplyIds.remove(replyId); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public List getAlbums() { + return Collections.unmodifiableList(albums); + } + + /** + * {@inheritDoc} + */ + @Override + public List getAllAlbums() { + List flatAlbums = new ArrayList(); + flatAlbums.addAll(albums); + int lastAlbumIndex = 0; + while (lastAlbumIndex < flatAlbums.size()) { + int previousAlbumCount = flatAlbums.size(); + for (Album album : new ArrayList(flatAlbums.subList(lastAlbumIndex, flatAlbums.size()))) { + flatAlbums.addAll(album.getAlbums()); + } + lastAlbumIndex = previousAlbumCount; + } + return flatAlbums; + } + + /** + * {@inheritDoc} + */ + @Override + public List getAllImages() { + List allImages = new ArrayList(); + for (Album album : getAllAlbums()) { + allImages.addAll(album.getImages()); + } + return allImages; + } + + /** + * {@inheritDoc} + */ + @Override + public void addAlbum(Album album) { + Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).check(); + if (!albums.contains(album)) { + albums.add(album); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void setAlbums(Collection albums) { + Validation.begin().isNotNull("Albums", albums).check(); + this.albums.clear(); + for (Album album : albums) { + addAlbum(album); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void removeAlbum(Album album) { + Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).check(); + albums.remove(album); + } + + /** + * {@inheritDoc} + */ + @Override + public Album moveAlbumUp(Album album) { + Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).isNull("Album Parent", album.getParent()).check(); + int oldIndex = albums.indexOf(album); + if (oldIndex <= 0) { + return null; + } + albums.remove(oldIndex); + albums.add(oldIndex - 1, album); + return albums.get(oldIndex); + } + + /** + * {@inheritDoc} + */ + @Override + public Album moveAlbumDown(Album album) { + Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).isNull("Album Parent", album.getParent()).check(); + int oldIndex = albums.indexOf(album); + if ((oldIndex < 0) || (oldIndex >= (albums.size() - 1))) { + return null; + } + albums.remove(oldIndex); + albums.add(oldIndex + 1, album); + return albums.get(oldIndex); + } + + /** + * {@inheritDoc} + */ + @Override + public Options getOptions() { + return options; + } + + // + // FINGERPRINTABLE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + public synchronized String getFingerprint() { + StringBuilder fingerprint = new StringBuilder(); + fingerprint.append(profile.getFingerprint()); + + fingerprint.append("Posts("); + for (Post post : getPosts()) { + fingerprint.append("Post(").append(post.getId()).append(')'); + } + fingerprint.append(")"); + + List replies = new ArrayList(getReplies()); + Collections.sort(replies, Reply.TIME_COMPARATOR); + fingerprint.append("Replies("); + for (PostReply reply : replies) { + fingerprint.append("Reply(").append(reply.getId()).append(')'); + } + fingerprint.append(')'); + + List likedPostIds = new ArrayList(getLikedPostIds()); + Collections.sort(likedPostIds); + fingerprint.append("LikedPosts("); + for (String likedPostId : likedPostIds) { + fingerprint.append("Post(").append(likedPostId).append(')'); + } + fingerprint.append(')'); + + List likedReplyIds = new ArrayList(getLikedReplyIds()); + Collections.sort(likedReplyIds); + fingerprint.append("LikedReplies("); + for (String likedReplyId : likedReplyIds) { + fingerprint.append("Reply(").append(likedReplyId).append(')'); + } + fingerprint.append(')'); + + fingerprint.append("Albums("); + for (Album album : albums) { + fingerprint.append(album.getFingerprint()); + } + fingerprint.append(')'); + + return fingerprint.toString(); + } + + // + // INTERFACE Comparable + // + + /** + * {@inheritDoc} + */ + @Override + public int compareTo(Sone sone) { + return NICE_NAME_COMPARATOR.compare(this, sone); + } + + // + // OBJECT METHODS + // + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return id.hashCode(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (!(object instanceof Sone)) { + return false; + } + return ((Sone) object).getId().equals(id); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return getClass().getName() + "[identity=" + identity + ",requestUri=" + requestUri + ",insertUri(" + String.valueOf(insertUri).length() + "),friends(" + friendSones.size() + "),posts(" + posts.size() + "),replies(" + replies.size() + ")]"; + } + +} diff --git a/src/main/java/net/pterodactylus/sone/fcp/CreatePostCommand.java b/src/main/java/net/pterodactylus/sone/fcp/CreatePostCommand.java index a37dc46..bc90594 100644 --- a/src/main/java/net/pterodactylus/sone/fcp/CreatePostCommand.java +++ b/src/main/java/net/pterodactylus/sone/fcp/CreatePostCommand.java @@ -28,7 +28,7 @@ import freenet.support.api.Bucket; /** * FCP command that creates a new {@link Post}. * - * @see Core#createPost(Sone, Sone, String) + * @see Core#createPost(Sone, Sone, long, String) * @author David ‘Bombe’ Roden */ public class CreatePostCommand extends AbstractSoneCommand { @@ -57,7 +57,7 @@ public class CreatePostCommand extends AbstractSoneCommand { if (sone.equals(recipient)) { return new ErrorResponse("Sone and Recipient must not be the same."); } - Post post = getCore().createPost(sone, recipient, text); + Post post = getCore().createPost(sone, recipient, System.currentTimeMillis(), text); return new Response("PostCreated", new SimpleFieldSetBuilder().put("Post", post.getId()).get()); } diff --git a/src/main/java/net/pterodactylus/sone/fcp/DeletePostCommand.java b/src/main/java/net/pterodactylus/sone/fcp/DeletePostCommand.java index 54f01c8..8d929f3 100644 --- a/src/main/java/net/pterodactylus/sone/fcp/DeletePostCommand.java +++ b/src/main/java/net/pterodactylus/sone/fcp/DeletePostCommand.java @@ -48,7 +48,7 @@ public class DeletePostCommand extends AbstractSoneCommand { @Override public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException { Post post = getPost(parameters, "Post"); - if (!getCore().isLocalSone(post.getSone())) { + if (!post.getSone().isLocal()) { return new ErrorResponse(401, "Not allowed."); } return new Response("PostDeleted", new SimpleFieldSetBuilder().get()); diff --git a/src/main/java/net/pterodactylus/sone/fcp/DeleteReplyCommand.java b/src/main/java/net/pterodactylus/sone/fcp/DeleteReplyCommand.java index daed26d..33374d9 100644 --- a/src/main/java/net/pterodactylus/sone/fcp/DeleteReplyCommand.java +++ b/src/main/java/net/pterodactylus/sone/fcp/DeleteReplyCommand.java @@ -48,7 +48,7 @@ public class DeleteReplyCommand extends AbstractSoneCommand { @Override public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException { PostReply reply = getReply(parameters, "Reply"); - if (!getCore().isLocalSone(reply.getSone())) { + if (!reply.getSone().isLocal()) { return new ErrorResponse(401, "Not allowed."); } return new Response("ReplyDeleted", new SimpleFieldSetBuilder().get()); diff --git a/src/main/java/net/pterodactylus/sone/main/SonePlugin.java b/src/main/java/net/pterodactylus/sone/main/SonePlugin.java index 5b28b85..1bc8b5a 100644 --- a/src/main/java/net/pterodactylus/sone/main/SonePlugin.java +++ b/src/main/java/net/pterodactylus/sone/main/SonePlugin.java @@ -25,6 +25,8 @@ import java.util.logging.Logger; import net.pterodactylus.sone.core.Core; import net.pterodactylus.sone.core.FreenetInterface; import net.pterodactylus.sone.core.WebOfTrustUpdater; +import net.pterodactylus.sone.database.Database; +import net.pterodactylus.sone.database.memory.MemoryDatabase; import net.pterodactylus.sone.fcp.FcpInterface; import net.pterodactylus.sone.freenet.PluginStoreConfigurationBackend; import net.pterodactylus.sone.freenet.plugin.PluginConnector; @@ -188,12 +190,15 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr webOfTrustConnector = new WebOfTrustConnector(pluginConnector); identityManager = new IdentityManager(webOfTrustConnector, "Sone"); + /* create Sone database. */ + Database soneDatabase = new MemoryDatabase(); + /* create trust updater. */ WebOfTrustUpdater trustUpdater = new WebOfTrustUpdater(webOfTrustConnector); trustUpdater.init(); /* create core. */ - core = new Core(oldConfiguration, freenetInterface, identityManager, trustUpdater); + core = new Core(oldConfiguration, soneDatabase, freenetInterface, identityManager, trustUpdater); /* create the web interface. */ webInterface = new WebInterface(this); diff --git a/src/main/java/net/pterodactylus/sone/template/ProfileAccessor.java b/src/main/java/net/pterodactylus/sone/template/ProfileAccessor.java index f004967..19177c3 100644 --- a/src/main/java/net/pterodactylus/sone/template/ProfileAccessor.java +++ b/src/main/java/net/pterodactylus/sone/template/ProfileAccessor.java @@ -70,7 +70,7 @@ public class ProfileAccessor extends ReflectionAccessor { return null; } Sone remoteSone = profile.getSone(); - if (core.isLocalSone(remoteSone)) { + if (remoteSone.isLocal()) { /* always show your own avatars. */ return avatarId; } diff --git a/src/main/java/net/pterodactylus/sone/template/SoneAccessor.java b/src/main/java/net/pterodactylus/sone/template/SoneAccessor.java index eca8654..9d4bdc8 100644 --- a/src/main/java/net/pterodactylus/sone/template/SoneAccessor.java +++ b/src/main/java/net/pterodactylus/sone/template/SoneAccessor.java @@ -76,8 +76,6 @@ public class SoneAccessor extends ReflectionAccessor { Sone sone = (Sone) object; if (member.equals("niceName")) { return getNiceName(sone); - } else if (member.equals("local")) { - return core.isLocalSone(sone); } else if (member.equals("friend")) { Sone currentSone = (Sone) templateContext.get("currentSone"); return (currentSone != null) && currentSone.hasFriend(sone.getId()); diff --git a/src/main/java/net/pterodactylus/sone/text/SoneTextParser.java b/src/main/java/net/pterodactylus/sone/text/SoneTextParser.java index b3593ec..2c209d5 100644 --- a/src/main/java/net/pterodactylus/sone/text/SoneTextParser.java +++ b/src/main/java/net/pterodactylus/sone/text/SoneTextParser.java @@ -30,6 +30,7 @@ import net.pterodactylus.sone.core.PostProvider; import net.pterodactylus.sone.core.SoneProvider; import net.pterodactylus.sone.data.Post; import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.database.memory.MemorySone; import net.pterodactylus.util.io.Closer; import net.pterodactylus.util.logging.Logging; import freenet.keys.FreenetURI; @@ -245,7 +246,7 @@ public class SoneTextParser implements Parser { * don’t use create=true above, we don’t want * the empty shell. */ - sone = new Sone(soneId); + sone = new MemorySone(soneId, false); } parts.add(new SonePart(sone)); line = line.substring(50); diff --git a/src/main/java/net/pterodactylus/sone/web/CreateReplyPage.java b/src/main/java/net/pterodactylus/sone/web/CreateReplyPage.java index 82e4c51..be83549 100644 --- a/src/main/java/net/pterodactylus/sone/web/CreateReplyPage.java +++ b/src/main/java/net/pterodactylus/sone/web/CreateReplyPage.java @@ -58,7 +58,7 @@ public class CreateReplyPage extends SoneTemplatePage { String text = request.getHttpRequest().getPartAsStringFailsafe("text", 65536).trim(); String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256); if (request.getMethod() == Method.POST) { - Post post = webInterface.getCore().getPost(postId); + Post post = webInterface.getCore().getPost(postId, false); if (text.length() > 0) { String senderId = request.getHttpRequest().getPartAsStringFailsafe("sender", 43); Sone sender = webInterface.getCore().getLocalSone(senderId, false); diff --git a/src/main/java/net/pterodactylus/sone/web/DeleteAlbumPage.java b/src/main/java/net/pterodactylus/sone/web/DeleteAlbumPage.java index 2f2b766..e175870 100644 --- a/src/main/java/net/pterodactylus/sone/web/DeleteAlbumPage.java +++ b/src/main/java/net/pterodactylus/sone/web/DeleteAlbumPage.java @@ -54,7 +54,7 @@ public class DeleteAlbumPage extends SoneTemplatePage { if (album == null) { throw new RedirectException("invalid.html"); } - if (!webInterface.getCore().isLocalSone(album.getSone())) { + if (!album.getSone().isLocal()) { throw new RedirectException("noPermission.html"); } if (request.getHttpRequest().isPartSet("abortDelete")) { diff --git a/src/main/java/net/pterodactylus/sone/web/DeleteImagePage.java b/src/main/java/net/pterodactylus/sone/web/DeleteImagePage.java index 3bbaf3d..69b1d27 100644 --- a/src/main/java/net/pterodactylus/sone/web/DeleteImagePage.java +++ b/src/main/java/net/pterodactylus/sone/web/DeleteImagePage.java @@ -57,7 +57,7 @@ public class DeleteImagePage extends SoneTemplatePage { if (image == null) { throw new RedirectException("invalid.html"); } - if (!webInterface.getCore().isLocalSone(image.getSone())) { + if (!image.getSone().isLocal()) { throw new RedirectException("noPermission.html"); } if (request.getMethod() == Method.POST) { diff --git a/src/main/java/net/pterodactylus/sone/web/DeletePostPage.java b/src/main/java/net/pterodactylus/sone/web/DeletePostPage.java index 6689061..002cfbd 100644 --- a/src/main/java/net/pterodactylus/sone/web/DeletePostPage.java +++ b/src/main/java/net/pterodactylus/sone/web/DeletePostPage.java @@ -55,15 +55,15 @@ public class DeletePostPage extends SoneTemplatePage { if (request.getMethod() == Method.GET) { String postId = request.getHttpRequest().getParam("post"); String returnPage = request.getHttpRequest().getParam("returnPage"); - Post post = webInterface.getCore().getPost(postId); + Post post = webInterface.getCore().getPost(postId, false); templateContext.set("post", post); templateContext.set("returnPage", returnPage); return; } else if (request.getMethod() == Method.POST) { String postId = request.getHttpRequest().getPartAsStringFailsafe("post", 36); String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256); - Post post = webInterface.getCore().getPost(postId); - if (!webInterface.getCore().isLocalSone(post.getSone())) { + Post post = webInterface.getCore().getPost(postId, false); + if (!post.getSone().isLocal()) { throw new RedirectException("noPermission.html"); } if (request.getHttpRequest().isPartSet("confirmDelete")) { diff --git a/src/main/java/net/pterodactylus/sone/web/DeleteReplyPage.java b/src/main/java/net/pterodactylus/sone/web/DeleteReplyPage.java index 900acd9..b07dead 100644 --- a/src/main/java/net/pterodactylus/sone/web/DeleteReplyPage.java +++ b/src/main/java/net/pterodactylus/sone/web/DeleteReplyPage.java @@ -56,7 +56,7 @@ public class DeleteReplyPage extends SoneTemplatePage { PostReply reply = webInterface.getCore().getReply(replyId); String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256); if (request.getMethod() == Method.POST) { - if (!webInterface.getCore().isLocalSone(reply.getSone())) { + if (!reply.getSone().isLocal()) { throw new RedirectException("noPermission.html"); } if (request.getHttpRequest().isPartSet("confirmDelete")) { diff --git a/src/main/java/net/pterodactylus/sone/web/EditAlbumPage.java b/src/main/java/net/pterodactylus/sone/web/EditAlbumPage.java index d028f8d..fd28e41 100644 --- a/src/main/java/net/pterodactylus/sone/web/EditAlbumPage.java +++ b/src/main/java/net/pterodactylus/sone/web/EditAlbumPage.java @@ -57,7 +57,7 @@ public class EditAlbumPage extends SoneTemplatePage { if (album == null) { throw new RedirectException("invalid.html"); } - if (!webInterface.getCore().isLocalSone(album.getSone())) { + if (!album.getSone().isLocal()) { throw new RedirectException("noPermission.html"); } if ("true".equals(request.getHttpRequest().getPartAsStringFailsafe("moveLeft", 4))) { diff --git a/src/main/java/net/pterodactylus/sone/web/EditImagePage.java b/src/main/java/net/pterodactylus/sone/web/EditImagePage.java index b0297a8..0207f8c 100644 --- a/src/main/java/net/pterodactylus/sone/web/EditImagePage.java +++ b/src/main/java/net/pterodactylus/sone/web/EditImagePage.java @@ -60,7 +60,7 @@ public class EditImagePage extends SoneTemplatePage { if (image == null) { throw new RedirectException("invalid.html"); } - if (!webInterface.getCore().isLocalSone(image.getSone())) { + if (!image.getSone().isLocal()) { throw new RedirectException("noPermission.html"); } if ("true".equals(request.getHttpRequest().getPartAsStringFailsafe("moveLeft", 4))) { diff --git a/src/main/java/net/pterodactylus/sone/web/SearchPage.java b/src/main/java/net/pterodactylus/sone/web/SearchPage.java index 0f12abf..524e501 100644 --- a/src/main/java/net/pterodactylus/sone/web/SearchPage.java +++ b/src/main/java/net/pterodactylus/sone/web/SearchPage.java @@ -131,7 +131,7 @@ public class SearchPage extends SoneTemplatePage { redirectIfNotNull(getImageId(phrase), "imageBrowser.html?image="); } - Set sones = webInterface.getCore().getSones(); + Collection sones = webInterface.getCore().getSones(); Set> soneHits = getHits(sones, phrases, SoneStringGenerator.COMPLETE_GENERATOR); Set> postHits; diff --git a/src/main/java/net/pterodactylus/sone/web/ViewPostPage.java b/src/main/java/net/pterodactylus/sone/web/ViewPostPage.java index 98b2603..336157e 100644 --- a/src/main/java/net/pterodactylus/sone/web/ViewPostPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ViewPostPage.java @@ -72,7 +72,7 @@ public class ViewPostPage extends SoneTemplatePage { super.processTemplate(request, templateContext); String postId = request.getHttpRequest().getParam("post"); boolean raw = request.getHttpRequest().getParam("raw").equals("true"); - Post post = webInterface.getCore().getPost(postId); + Post post = webInterface.getCore().getPost(postId, false); templateContext.set("post", post); templateContext.set("raw", raw); } diff --git a/src/main/java/net/pterodactylus/sone/web/WebInterface.java b/src/main/java/net/pterodactylus/sone/web/WebInterface.java index 00f8cf9..75b5f50 100644 --- a/src/main/java/net/pterodactylus/sone/web/WebInterface.java +++ b/src/main/java/net/pterodactylus/sone/web/WebInterface.java @@ -372,7 +372,7 @@ public class WebInterface implements CoreListener { * currently logged in */ public Sone getCurrentSone(ToadletContext toadletContext, boolean create) { - Set localSones = getCore().getLocalSones(); + Collection localSones = getCore().getLocalSones(); if (localSones.size() == 1) { return localSones.iterator().next(); } @@ -725,8 +725,8 @@ public class WebInterface implements CoreListener { } /** - * Returns all {@link Core#isLocalSone(Sone) local Sone}s that are - * referenced by {@link SonePart}s in the given text (after parsing it using + * Returns all {@link Core#getLocalSones() local Sone}s that are referenced + * by {@link SonePart}s in the given text (after parsing it using * {@link SoneTextParser}). * * @param text @@ -789,15 +789,14 @@ public class WebInterface implements CoreListener { */ @Override public void newPostFound(Post post) { - boolean isLocal = getCore().isLocalSone(post.getSone()); - if (isLocal) { + if (post.getSone().isLocal()) { localPostNotification.add(post); } else { newPostNotification.add(post); } if (!hasFirstStartNotification()) { - notificationManager.addNotification(isLocal ? localPostNotification : newPostNotification); - if (!getMentionedSones(post.getText()).isEmpty() && !isLocal) { + notificationManager.addNotification(post.getSone().isLocal() ? localPostNotification : newPostNotification); + if (!getMentionedSones(post.getText()).isEmpty() && !post.getSone().isLocal()) { mentionNotification.add(post); notificationManager.addNotification(mentionNotification); } @@ -811,15 +810,14 @@ public class WebInterface implements CoreListener { */ @Override public void newReplyFound(PostReply reply) { - boolean isLocal = getCore().isLocalSone(reply.getSone()); - if (isLocal) { + if (reply.getSone().isLocal()) { localReplyNotification.add(reply); } else { newReplyNotification.add(reply); } if (!hasFirstStartNotification()) { - notificationManager.addNotification(isLocal ? localReplyNotification : newReplyNotification); - if (!getMentionedSones(reply.getText()).isEmpty() && !isLocal && (reply.getPost().getSone() != null) && (reply.getTime() <= System.currentTimeMillis())) { + notificationManager.addNotification(reply.getSone().isLocal() ? localReplyNotification : newReplyNotification); + if (!getMentionedSones(reply.getText()).isEmpty() && !reply.getSone().isLocal() && (reply.getPost().getSone() != null) && (reply.getTime() <= System.currentTimeMillis())) { mentionNotification.add(reply.getPost()); notificationManager.addNotification(mentionNotification); } diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/CreatePostAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/CreatePostAjaxPage.java index 53a5a51..2280935 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/CreatePostAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/CreatePostAjaxPage.java @@ -62,7 +62,7 @@ public class CreatePostAjaxPage extends JsonPage { return createErrorJsonObject("text-required"); } text = TextFilter.filter(request.getHttpRequest().getHeader("host"), text); - Post newPost = webInterface.getCore().createPost(sender, recipient, text); + Post newPost = webInterface.getCore().createPost(sender, recipient, System.currentTimeMillis(), text); return createSuccessJsonObject().put("postId", newPost.getId()).put("sone", sender.getId()).put("recipient", (newPost.getRecipient() != null) ? newPost.getRecipient().getId() : null); } diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/CreateReplyAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/CreateReplyAjaxPage.java index 20857f3..8626d38 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/CreateReplyAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/CreateReplyAjaxPage.java @@ -58,7 +58,7 @@ public class CreateReplyAjaxPage extends JsonPage { if (sender == null) { sender = getCurrentSone(request.getToadletContext()); } - Post post = webInterface.getCore().getPost(postId); + Post post = webInterface.getCore().getPost(postId, false); if ((post == null) || (post.getSone() == null)) { return createErrorJsonObject("invalid-post-id"); } diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/DeletePostAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/DeletePostAjaxPage.java index ea2310c..b822e52 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/DeletePostAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/DeletePostAjaxPage.java @@ -53,7 +53,7 @@ public class DeletePostAjaxPage extends JsonPage { if ((post == null) || (post.getSone() == null)) { return createErrorJsonObject("invalid-post-id"); } - if (!webInterface.getCore().isLocalSone(post.getSone())) { + if (!post.getSone().isLocal()) { return createErrorJsonObject("not-authorized"); } webInterface.getCore().deletePost(post); diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/DeleteReplyAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/DeleteReplyAjaxPage.java index 0442117..6bccf0b 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/DeleteReplyAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/DeleteReplyAjaxPage.java @@ -53,7 +53,7 @@ public class DeleteReplyAjaxPage extends JsonPage { if (reply == null) { return createErrorJsonObject("invalid-reply-id"); } - if (!webInterface.getCore().isLocalSone(reply.getSone())) { + if (!reply.getSone().isLocal()) { return createErrorJsonObject("not-authorized"); } webInterface.getCore().deleteReply(reply); diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/EditAlbumAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/EditAlbumAjaxPage.java index 92e1e48..0e72ea5 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/EditAlbumAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/EditAlbumAjaxPage.java @@ -54,7 +54,7 @@ public class EditAlbumAjaxPage extends JsonPage { if (album == null) { return createErrorJsonObject("invalid-album-id"); } - if (!webInterface.getCore().isLocalSone(album.getSone())) { + if (!album.getSone().isLocal()) { return createErrorJsonObject("not-authorized"); } if ("true".equals(request.getHttpRequest().getParam("moveLeft"))) { diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/EditImageAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/EditImageAjaxPage.java index 42f4285..4a1689b 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/EditImageAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/EditImageAjaxPage.java @@ -63,7 +63,7 @@ public class EditImageAjaxPage extends JsonPage { if (image == null) { return createErrorJsonObject("invalid-image-id"); } - if (!webInterface.getCore().isLocalSone(image.getSone())) { + if (!image.getSone().isLocal()) { return createErrorJsonObject("not-authorized"); } if ("true".equals(request.getHttpRequest().getParam("moveLeft"))) { diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/GetLikesAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/GetLikesAjaxPage.java index 293b4b0..34018af 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/GetLikesAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/GetLikesAjaxPage.java @@ -63,7 +63,7 @@ public class GetLikesAjaxPage extends JsonPage { return createErrorJsonObject("invalid-" + type + "-id"); } if ("post".equals(type)) { - Post post = webInterface.getCore().getPost(id); + Post post = webInterface.getCore().getPost(id, false); Set sones = webInterface.getCore().getLikes(post); return createSuccessJsonObject().put("likes", sones.size()).put("sones", getSones(sones)); } else if ("reply".equals(type)) { diff --git a/src/test/java/net/pterodactylus/sone/text/SoneTextParserTest.java b/src/test/java/net/pterodactylus/sone/text/SoneTextParserTest.java index e1857a0..3dc25e8 100644 --- a/src/test/java/net/pterodactylus/sone/text/SoneTextParserTest.java +++ b/src/test/java/net/pterodactylus/sone/text/SoneTextParserTest.java @@ -24,6 +24,7 @@ import java.util.Arrays; import junit.framework.TestCase; import net.pterodactylus.sone.core.SoneProvider; import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.database.memory.MemorySone; /** * JUnit test case for {@link SoneTextParser}. @@ -182,7 +183,7 @@ public class SoneTextParserTest extends TestCase { */ @Override public Sone getSone(final String soneId, boolean create) { - return new Sone(soneId) { + return new MemorySone(soneId, false) { /** * {@inheritDoc}