Merge branch 'release-0.7.2' 0.7.2
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Tue, 4 Oct 2011 04:13:58 +0000 (06:13 +0200)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Tue, 4 Oct 2011 04:13:58 +0000 (06:13 +0200)
46 files changed:
pom.xml
src/main/java/net/pterodactylus/sone/core/Core.java
src/main/java/net/pterodactylus/sone/core/CoreListener.java
src/main/java/net/pterodactylus/sone/core/CoreListenerManager.java
src/main/java/net/pterodactylus/sone/core/SoneDownloader.java
src/main/java/net/pterodactylus/sone/core/SoneInserter.java
src/main/java/net/pterodactylus/sone/data/PostReply.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/data/Reply.java
src/main/java/net/pterodactylus/sone/data/Sone.java
src/main/java/net/pterodactylus/sone/fcp/AbstractSoneCommand.java
src/main/java/net/pterodactylus/sone/fcp/CreateReplyCommand.java
src/main/java/net/pterodactylus/sone/fcp/DeleteReplyCommand.java
src/main/java/net/pterodactylus/sone/fcp/LikeReplyCommand.java
src/main/java/net/pterodactylus/sone/freenet/wot/WebOfTrustConnector.java
src/main/java/net/pterodactylus/sone/main/SonePlugin.java
src/main/java/net/pterodactylus/sone/notify/ListNotificationFilters.java
src/main/java/net/pterodactylus/sone/template/ImageLinkFilter.java
src/main/java/net/pterodactylus/sone/template/ParserFilter.java
src/main/java/net/pterodactylus/sone/template/PostAccessor.java
src/main/java/net/pterodactylus/sone/template/ReplyAccessor.java
src/main/java/net/pterodactylus/sone/template/ReplyGroupFilter.java
src/main/java/net/pterodactylus/sone/text/SoneTextParser.java
src/main/java/net/pterodactylus/sone/web/DeleteReplyPage.java
src/main/java/net/pterodactylus/sone/web/KnownSonesPage.java
src/main/java/net/pterodactylus/sone/web/MarkAsKnownPage.java
src/main/java/net/pterodactylus/sone/web/OptionsPage.java
src/main/java/net/pterodactylus/sone/web/SearchPage.java
src/main/java/net/pterodactylus/sone/web/ViewSonePage.java
src/main/java/net/pterodactylus/sone/web/WebInterface.java
src/main/java/net/pterodactylus/sone/web/ajax/CreateReplyAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/DeleteReplyAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetLikesAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetNotificationAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetReplyAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetStatusAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetTimesAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/MarkAsKnownAjaxPage.java
src/main/resources/i18n/sone.en.properties
src/main/resources/static/css/sone.css
src/main/resources/templates/include/browseAlbums.html
src/main/resources/templates/include/soneMenu.html
src/main/resources/templates/include/viewPost.html
src/main/resources/templates/include/viewReply.html
src/main/resources/templates/include/viewSone.html
src/main/resources/templates/knownSones.html
src/main/resources/templates/options.html

diff --git a/pom.xml b/pom.xml
index fa78bcb..35203cf 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -2,12 +2,12 @@
        <modelVersion>4.0.0</modelVersion>
        <groupId>net.pterodactylus</groupId>
        <artifactId>sone</artifactId>
-       <version>0.7.1</version>
+       <version>0.7.2</version>
        <dependencies>
                <dependency>
                        <groupId>net.pterodactylus</groupId>
                        <artifactId>utils</artifactId>
-                       <version>0.11</version>
+                       <version>0.11.1</version>
                </dependency>
                <dependency>
                        <groupId>junit</groupId>
index 8aac5d7..ee5ec87 100644 (file)
@@ -38,6 +38,7 @@ 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;
@@ -173,7 +174,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
        private Set<String> knownPosts = new HashSet<String>();
 
        /** All replies. */
-       private Map<String, Reply> replies = new HashMap<String, Reply>();
+       private Map<String, PostReply> replies = new HashMap<String, PostReply>();
 
        /** All new replies. */
        private Set<String> newReplies = new HashSet<String>();
@@ -676,7 +677,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         *            The ID of the reply to get
         * @return The reply
         */
-       public Reply getReply(String replyId) {
+       public PostReply getReply(String replyId) {
                return getReply(replyId, true);
        }
 
@@ -692,11 +693,11 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         *            to return {@code null} if no reply can be found
         * @return The reply, or {@code null} if there is no such reply
         */
-       public Reply getReply(String replyId, boolean create) {
+       public PostReply getReply(String replyId, boolean create) {
                synchronized (replies) {
-                       Reply reply = replies.get(replyId);
+                       PostReply reply = replies.get(replyId);
                        if (create && (reply == null)) {
-                               reply = new Reply(replyId);
+                               reply = new PostReply(replyId);
                                replies.put(replyId, reply);
                        }
                        return reply;
@@ -710,11 +711,11 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         *            The post to get all replies for
         * @return All replies for the given post
         */
-       public List<Reply> getReplies(Post post) {
+       public List<PostReply> getReplies(Post post) {
                Set<Sone> sones = getSones();
-               List<Reply> replies = new ArrayList<Reply>();
+               List<PostReply> replies = new ArrayList<PostReply>();
                for (Sone sone : sones) {
-                       for (Reply reply : sone.getReplies()) {
+                       for (PostReply reply : sone.getReplies()) {
                                if (reply.getPost().equals(post)) {
                                        replies.add(reply);
                                }
@@ -762,7 +763,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         *            The reply to get the liking Sones for
         * @return The Sones that like the given reply
         */
-       public Set<Sone> getLikes(Reply reply) {
+       public Set<Sone> getLikes(PostReply reply) {
                Set<Sone> sones = new HashSet<Sone>();
                for (Sone sone : getSones()) {
                        if (sone.getLikedReplyIds().contains(reply.getId())) {
@@ -1204,16 +1205,16 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                        }
                        synchronized (replies) {
                                if (!soneRescueMode) {
-                                       for (Reply reply : storedSone.getReplies()) {
+                                       for (PostReply reply : storedSone.getReplies()) {
                                                replies.remove(reply.getId());
                                                if (!sone.getReplies().contains(reply)) {
                                                        coreListenerManager.fireReplyRemoved(reply);
                                                }
                                        }
                                }
-                               Set<Reply> storedReplies = storedSone.getReplies();
+                               Set<PostReply> storedReplies = storedSone.getReplies();
                                synchronized (newReplies) {
-                                       for (Reply reply : sone.getReplies()) {
+                                       for (PostReply reply : sone.getReplies()) {
                                                reply.setSone(storedSone);
                                                if (!storedReplies.contains(reply) && !knownReplies.contains(reply.getId())) {
                                                        newReplies.add(reply.getId());
@@ -1249,7 +1250,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                                        for (Post post : sone.getPosts()) {
                                                storedSone.addPost(post);
                                        }
-                                       for (Reply reply : sone.getReplies()) {
+                                       for (PostReply reply : sone.getReplies()) {
                                                storedSone.addReply(reply);
                                        }
                                        for (String likedPostId : sone.getLikedPostIds()) {
@@ -1395,7 +1396,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                }
 
                /* load replies. */
-               Set<Reply> replies = new HashSet<Reply>();
+               Set<PostReply> replies = new HashSet<PostReply>();
                while (true) {
                        String replyPrefix = sonePrefix + "/Replies/" + replies.size();
                        String replyId = configuration.getStringValue(replyPrefix + "/ID").getValue(null);
@@ -1528,7 +1529,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                        }
                }
                synchronized (newReplies) {
-                       for (Reply reply : replies) {
+                       for (PostReply reply : replies) {
                                knownReplies.add(reply.getId());
                        }
                }
@@ -1718,7 +1719,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         *            The text of the reply
         * @return The created reply
         */
-       public Reply createReply(Sone sone, Post post, String text) {
+       public PostReply createReply(Sone sone, Post post, String text) {
                return createReply(sone, post, System.currentTimeMillis(), text);
        }
 
@@ -1735,12 +1736,12 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         *            The text of the reply
         * @return The created reply
         */
-       public Reply createReply(Sone sone, Post post, long time, String text) {
+       public PostReply createReply(Sone sone, Post post, long time, String text) {
                if (!isLocalSone(sone)) {
                        logger.log(Level.FINE, "Tried to create reply for non-local Sone: %s", sone);
                        return null;
                }
-               final Reply reply = new Reply(sone, post, System.currentTimeMillis(), text);
+               final PostReply reply = new PostReply(sone, post, System.currentTimeMillis(), text);
                synchronized (replies) {
                        replies.put(reply.getId(), reply);
                }
@@ -1769,7 +1770,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         * @param reply
         *            The reply to delete
         */
-       public void deleteReply(Reply reply) {
+       public void deleteReply(PostReply reply) {
                Sone sone = reply.getSone();
                if (!isLocalSone(sone)) {
                        logger.log(Level.FINE, "Tried to delete non-local reply: %s", reply);
@@ -1793,7 +1794,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         * @param reply
         *            The reply to mark as known
         */
-       public void markReplyKnown(Reply reply) {
+       public void markReplyKnown(PostReply reply) {
                synchronized (newReplies) {
                        if (newReplies.remove(reply.getId())) {
                                knownReplies.add(reply.getId());
@@ -2028,8 +2029,6 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
 
                logger.log(Level.INFO, "Saving Sone: %s", sone);
                try {
-                       ((OwnIdentity) sone.getIdentity()).setProperty("Sone.LatestEdition", String.valueOf(sone.getLatestEdition()));
-
                        /* save Sone into configuration. */
                        String sonePrefix = "Sone/" + sone.getId();
                        configuration.getLongValue(sonePrefix + "/Time").setValue(sone.getTime());
@@ -2066,7 +2065,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
 
                        /* save replies. */
                        int replyCounter = 0;
-                       for (Reply reply : sone.getReplies()) {
+                       for (PostReply reply : sone.getReplies()) {
                                String replyPrefix = sonePrefix + "/Replies/" + replyCounter++;
                                configuration.getStringValue(replyPrefix + "/ID").setValue(reply.getId());
                                configuration.getStringValue(replyPrefix + "/Post/ID").setValue(reply.getPost().getId());
@@ -2097,7 +2096,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                        configuration.getStringValue(sonePrefix + "/Friends/" + friendCounter + "/ID").setValue(null);
 
                        /* save albums. first, collect in a flat structure, top-level first. */
-                       List<Album> albums = Sone.flattenAlbums(sone.getAlbums());
+                       List<Album> albums = sone.getAllAlbums();
 
                        int albumCounter = 0;
                        for (Album album : albums) {
@@ -2135,6 +2134,9 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                        configuration.getBooleanValue(sonePrefix + "/Options/EnableSoneInsertNotifications").setValue(sone.getOptions().getBooleanOption("EnableSoneInsertNotifications").getReal());
 
                        configuration.save();
+
+                       ((OwnIdentity) sone.getIdentity()).setProperty("Sone.LatestEdition", String.valueOf(sone.getLatestEdition()));
+
                        logger.log(Level.INFO, "Sone %s saved.", sone);
                } catch (ConfigurationException ce1) {
                        logger.log(Level.WARNING, "Could not save Sone: " + sone, ce1);
@@ -2161,6 +2163,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                        configuration.getIntValue("Option/InsertionDelay").setValue(options.getIntegerOption("InsertionDelay").getReal());
                        configuration.getIntValue("Option/PostsPerPage").setValue(options.getIntegerOption("PostsPerPage").getReal());
                        configuration.getIntValue("Option/CharactersPerPost").setValue(options.getIntegerOption("CharactersPerPost").getReal());
+                       configuration.getIntValue("Option/PostCutOffLength").setValue(options.getIntegerOption("PostCutOffLength").getReal());
                        configuration.getBooleanValue("Option/RequireFullAccess").setValue(options.getBooleanOption("RequireFullAccess").getReal());
                        configuration.getIntValue("Option/PositiveTrust").setValue(options.getIntegerOption("PositiveTrust").getReal());
                        configuration.getIntValue("Option/NegativeTrust").setValue(options.getIntegerOption("NegativeTrust").getReal());
@@ -2234,7 +2237,8 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
 
                }));
                options.addIntegerOption("PostsPerPage", new DefaultOption<Integer>(10, new IntegerRangeValidator(1, Integer.MAX_VALUE)));
-               options.addIntegerOption("CharactersPerPost", new DefaultOption<Integer>(200, new OrValidator<Integer>(new IntegerRangeValidator(50, Integer.MAX_VALUE), new EqualityValidator<Integer>(-1))));
+               options.addIntegerOption("CharactersPerPost", new DefaultOption<Integer>(400, new OrValidator<Integer>(new IntegerRangeValidator(50, Integer.MAX_VALUE), new EqualityValidator<Integer>(-1))));
+               options.addIntegerOption("PostCutOffLength", new DefaultOption<Integer>(200, new OrValidator<Integer>(new IntegerRangeValidator(50, Integer.MAX_VALUE), new EqualityValidator<Integer>(-1))));
                options.addBooleanOption("RequireFullAccess", new DefaultOption<Boolean>(false));
                options.addIntegerOption("PositiveTrust", new DefaultOption<Integer>(75, new IntegerRangeValidator(0, 100)));
                options.addIntegerOption("NegativeTrust", new DefaultOption<Integer>(-25, new IntegerRangeValidator(-100, 100)));
@@ -2274,6 +2278,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                loadConfigurationValue("InsertionDelay");
                loadConfigurationValue("PostsPerPage");
                loadConfigurationValue("CharactersPerPost");
+               loadConfigurationValue("PostCutOffLength");
                options.getBooleanOption("RequireFullAccess").set(configuration.getBooleanValue("Option/RequireFullAccess").getValue(null));
                loadConfigurationValue("PositiveTrust");
                loadConfigurationValue("NegativeTrust");
@@ -2453,7 +2458,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                }
                synchronized (replies) {
                        synchronized (newReplies) {
-                               for (Reply reply : sone.getReplies()) {
+                               for (PostReply reply : sone.getReplies()) {
                                        replies.remove(reply.getId());
                                        newReplies.remove(reply.getId());
                                        coreListenerManager.fireReplyRemoved(reply);
@@ -2676,6 +2681,39 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                }
 
                /**
+                * Returns the number of characters the shortened post should have.
+                *
+                * @return The number of characters of the snippet
+                */
+               public int getPostCutOffLength() {
+                       return options.getIntegerOption("PostCutOffLength").get();
+               }
+
+               /**
+                * Validates the number of characters after which to cut off the post.
+                *
+                * @param postCutOffLength
+                *            The number of characters of the snippet
+                * @return {@code true} if the number of characters of the snippet is
+                *         valid, {@code false} otherwise
+                */
+               public boolean validatePostCutOffLength(Integer postCutOffLength) {
+                       return options.getIntegerOption("PostCutOffLength").validate(postCutOffLength);
+               }
+
+               /**
+                * Sets the number of characters the shortened post should have.
+                *
+                * @param postCutOffLength
+                *            The number of characters of the snippet
+                * @return This preferences
+                */
+               public Preferences setPostCutOffLength(Integer postCutOffLength) {
+                       options.getIntegerOption("PostCutOffLength").set(postCutOffLength);
+                       return this;
+               }
+
+               /**
                 * Returns whether Sone requires full access to be even visible.
                 *
                 * @return {@code true} if Sone requires full access, {@code false}
index 1658745..2e7b337 100644 (file)
@@ -21,7 +21,7 @@ import java.util.EventListener;
 
 import net.pterodactylus.sone.data.Image;
 import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.util.version.Version;
 
@@ -55,7 +55,7 @@ public interface CoreListener extends EventListener {
         * @param reply
         *            The new reply
         */
-       public void newReplyFound(Reply reply);
+       public void newReplyFound(PostReply reply);
 
        /**
         * Notifies a listener that the given Sone is now marked as known.
@@ -79,7 +79,7 @@ public interface CoreListener extends EventListener {
         * @param reply
         *            The known reply
         */
-       public void markReplyKnown(Reply reply);
+       public void markReplyKnown(PostReply reply);
 
        /**
         * Notifies a listener that the given Sone was removed.
@@ -103,7 +103,7 @@ public interface CoreListener extends EventListener {
         * @param reply
         *            The removed reply
         */
-       public void replyRemoved(Reply reply);
+       public void replyRemoved(PostReply reply);
 
        /**
         * Notifies a listener when a Sone was locked.
index 5748ffc..9951ccd 100644 (file)
@@ -19,7 +19,7 @@ package net.pterodactylus.sone.core;
 
 import net.pterodactylus.sone.data.Image;
 import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.util.event.AbstractListenerManager;
 import net.pterodactylus.util.version.Version;
@@ -74,11 +74,11 @@ public class CoreListenerManager extends AbstractListenerManager<Core, CoreListe
        /**
         * Notifies all listeners that a new reply has been found.
         *
-        * @see CoreListener#newReplyFound(Reply)
+        * @see CoreListener#newReplyFound(PostReply)
         * @param reply
         *            The new reply
         */
-       void fireNewReplyFound(Reply reply) {
+       void fireNewReplyFound(PostReply reply) {
                for (CoreListener coreListener : getListeners()) {
                        coreListener.newReplyFound(reply);
                }
@@ -115,7 +115,7 @@ public class CoreListenerManager extends AbstractListenerManager<Core, CoreListe
         * @param reply
         *            The known reply
         */
-       void fireMarkReplyKnown(Reply reply) {
+       void fireMarkReplyKnown(PostReply reply) {
                for (CoreListener coreListener : getListeners()) {
                        coreListener.markReplyKnown(reply);
                }
@@ -150,11 +150,11 @@ public class CoreListenerManager extends AbstractListenerManager<Core, CoreListe
        /**
         * Notifies all listener that the given reply was removed.
         *
-        * @see CoreListener#replyRemoved(Reply)
+        * @see CoreListener#replyRemoved(PostReply)
         * @param reply
         *            The removed reply
         */
-       void fireReplyRemoved(Reply reply) {
+       void fireReplyRemoved(PostReply reply) {
                for (CoreListener coreListener : getListeners()) {
                        coreListener.replyRemoved(reply);
                }
index e8d452b..db26d80 100644 (file)
@@ -31,8 +31,8 @@ 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.util.collection.Pair;
 import net.pterodactylus.util.io.Closer;
@@ -383,7 +383,7 @@ public class SoneDownloader extends AbstractService {
 
                /* parse replies. */
                SimpleXML repliesXml = soneXml.getNode("replies");
-               Set<Reply> replies = new HashSet<Reply>();
+               Set<PostReply> replies = new HashSet<PostReply>();
                if (repliesXml == null) {
                        /* TODO - mark Sone as bad. */
                        logger.log(Level.WARNING, "Downloaded Sone %s has no replies!", new Object[] { sone });
@@ -442,7 +442,7 @@ public class SoneDownloader extends AbstractService {
                                String id = albumXml.getValue("id", null);
                                String parentId = albumXml.getValue("parent", null);
                                String title = albumXml.getValue("title", null);
-                               String description = albumXml.getValue("description", null);
+                               String description = albumXml.getValue("description", "");
                                String albumImageId = albumXml.getValue("album-image", null);
                                if ((id == null) || (title == null) || (description == null)) {
                                        logger.log(Level.WARNING, "Downloaded Sone %s contains invalid album!", new Object[] { sone });
@@ -456,7 +456,7 @@ public class SoneDownloader extends AbstractService {
                                                return null;
                                        }
                                }
-                               Album album = core.getAlbum(id).setSone(sone).setTitle(title).setDescription(description).setAlbumImage(albumImageId);
+                               Album album = core.getAlbum(id).setSone(sone).setTitle(title).setDescription(description);
                                if (parent != null) {
                                        parent.addAlbum(album);
                                } else {
@@ -489,6 +489,7 @@ public class SoneDownloader extends AbstractService {
                                                album.addImage(image);
                                        }
                                }
+                               album.setAlbumImage(albumImageId);
                        }
                }
 
index 44f8bbc..eb74dc0 100644 (file)
@@ -29,6 +29,7 @@ import java.util.logging.Logger;
 
 import net.pterodactylus.sone.core.Core.SoneStatus;
 import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Reply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.freenet.StringBucket;
@@ -299,10 +300,10 @@ public class SoneInserter extends AbstractService {
                        soneProperties.put("insertUri", sone.getInsertUri());
                        soneProperties.put("profile", sone.getProfile());
                        soneProperties.put("posts", new ListBuilder<Post>(new ArrayList<Post>(sone.getPosts())).sort(Post.TIME_COMPARATOR).get());
-                       soneProperties.put("replies", new ListBuilder<Reply>(new ArrayList<Reply>(sone.getReplies())).sort(new ReverseComparator<Reply>(Reply.TIME_COMPARATOR)).get());
+                       soneProperties.put("replies", new ListBuilder<PostReply>(new ArrayList<PostReply>(sone.getReplies())).sort(new ReverseComparator<Reply<?>>(Reply.TIME_COMPARATOR)).get());
                        soneProperties.put("likedPostIds", new HashSet<String>(sone.getLikedPostIds()));
                        soneProperties.put("likedReplyIds", new HashSet<String>(sone.getLikedReplyIds()));
-                       soneProperties.put("albums", Sone.flattenAlbums(sone.getAlbums()));
+                       soneProperties.put("albums", sone.getAllAlbums());
                }
 
                //
diff --git a/src/main/java/net/pterodactylus/sone/data/PostReply.java b/src/main/java/net/pterodactylus/sone/data/PostReply.java
new file mode 100644 (file)
index 0000000..3222d9d
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * Sone - PostReply.java - Copyright © 2010–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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.data;
+
+import java.util.UUID;
+
+/**
+ * A reply is like a {@link Post} but can never be posted on its own, it always
+ * refers to another {@link Post}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class PostReply extends Reply<PostReply> {
+
+       /** The Post this reply refers to. */
+       private volatile Post post;
+
+       /**
+        * Creates a new reply.
+        *
+        * @param id
+        *            The ID of the reply
+        */
+       public PostReply(String id) {
+               this(id, null, null, 0, null);
+       }
+
+       /**
+        * Creates a new reply.
+        *
+        * @param sone
+        *            The sone that posted the reply
+        * @param post
+        *            The post to reply to
+        * @param text
+        *            The text of the reply
+        */
+       public PostReply(Sone sone, Post post, String text) {
+               this(sone, post, System.currentTimeMillis(), text);
+       }
+
+       /**
+        * Creates a new reply-
+        *
+        * @param sone
+        *            The sone that posted the reply
+        * @param post
+        *            The post to reply to
+        * @param time
+        *            The time of the reply
+        * @param text
+        *            The text of the reply
+        */
+       public PostReply(Sone sone, Post post, long time, String text) {
+               this(UUID.randomUUID().toString(), sone, post, time, text);
+       }
+
+       /**
+        * Creates a new reply-
+        *
+        * @param sone
+        *            The sone that posted the reply
+        * @param id
+        *            The ID of the reply
+        * @param post
+        *            The post to reply to
+        * @param time
+        *            The time of the reply
+        * @param text
+        *            The text of the reply
+        */
+       public PostReply(String id, Sone sone, Post post, long time, String text) {
+               super(id, sone, time, text);
+               this.post = post;
+       }
+
+       //
+       // ACCESSORS
+       //
+
+       /**
+        * Returns the post this reply refers to.
+        *
+        * @return The post this reply refers to
+        */
+       public Post getPost() {
+               return post;
+       }
+
+       /**
+        * Sets the post this reply refers to.
+        *
+        * @param post
+        *            The post this reply refers to
+        * @return This reply (for method chaining)
+        */
+       public PostReply setPost(Post post) {
+               this.post = post;
+               return this;
+       }
+
+}
index 2dacfbe..780fe69 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Reply.java - Copyright © 2010 David Roden
+ * Sone - Reply.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
@@ -23,42 +23,46 @@ import java.util.UUID;
 import net.pterodactylus.util.filter.Filter;
 
 /**
- * A reply is like a {@link Post} but can never be posted on its own, it always
- * refers to another {@link Post}.
+ * Abstract base class for all replies.
  *
+ * @param <T>
+ *            The type of the reply
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
-public class Reply {
+public abstract class Reply<T extends Reply<T>> {
 
        /** Comparator that sorts replies ascending by time. */
-       public static final Comparator<Reply> TIME_COMPARATOR = new Comparator<Reply>() {
+       public static final Comparator<Reply<?>> TIME_COMPARATOR = new Comparator<Reply<?>>() {
 
+               /**
+                * {@inheritDoc}
+                */
                @Override
-               public int compare(Reply leftReply, Reply rightReply) {
+               public int compare(Reply<?> leftReply, Reply<?> rightReply) {
                        return (int) Math.max(Integer.MIN_VALUE, Math.min(Integer.MAX_VALUE, leftReply.getTime() - rightReply.getTime()));
                }
 
        };
 
        /** Filter for replies with timestamps from the future. */
-       public static final Filter<Reply> FUTURE_REPLIES_FILTER = new Filter<Reply>() {
+       public static final Filter<Reply<?>> FUTURE_REPLY_FILTER = new Filter<Reply<?>>() {
 
+               /**
+                * {@inheritDoc}
+                */
                @Override
-               public boolean filterObject(Reply reply) {
+               public boolean filterObject(Reply<?> reply) {
                        return reply.getTime() <= System.currentTimeMillis();
                }
 
        };
 
        /** The ID of the reply. */
-       private final UUID id;
+       private final String id;
 
-       /** The Sone that posted this reply. */
+       /** The Sone that created this reply. */
        private volatile Sone sone;
 
-       /** The Post this reply refers to. */
-       private volatile Post post;
-
        /** The time of the reply. */
        private volatile long time;
 
@@ -66,78 +70,55 @@ public class Reply {
        private volatile String text;
 
        /**
-        * Creates a new reply.
+        * Creates a new reply with the given ID.
         *
         * @param id
         *            The ID of the reply
         */
-       public Reply(String id) {
-               this(id, null, null, 0, null);
-       }
-
-       /**
-        * Creates a new reply.
-        *
-        * @param sone
-        *            The sone that posted the reply
-        * @param post
-        *            The post to reply to
-        * @param text
-        *            The text of the reply
-        */
-       public Reply(Sone sone, Post post, String text) {
-               this(sone, post, System.currentTimeMillis(), text);
+       protected Reply(String id) {
+               this(id, null, 0, null);
        }
 
        /**
-        * Creates a new reply-
+        * Creates a new reply with a new random ID.
         *
         * @param sone
-        *            The sone that posted the reply
-        * @param post
-        *            The post to reply to
+        *            The Sone of the reply
         * @param time
         *            The time of the reply
         * @param text
         *            The text of the reply
         */
-       public Reply(Sone sone, Post post, long time, String text) {
-               this(UUID.randomUUID().toString(), sone, post, time, text);
+       protected Reply(Sone sone, long time, String text) {
+               this(UUID.randomUUID().toString(), sone, time, text);
        }
 
        /**
-        * Creates a new reply-
+        * Creates a new reply.
         *
-        * @param sone
-        *            The sone that posted the reply
         * @param id
         *            The ID of the reply
-        * @param post
-        *            The post to reply to
+        * @param sone
+        *            The Sone of the reply
         * @param time
         *            The time of the reply
         * @param text
         *            The text of the reply
         */
-       public Reply(String id, Sone sone, Post post, long time, String text) {
-               this.id = UUID.fromString(id);
+       protected Reply(String id, Sone sone, long time, String text) {
+               this.id = id;
                this.sone = sone;
-               this.post = post;
                this.time = time;
                this.text = text;
        }
 
-       //
-       // ACCESSORS
-       //
-
        /**
         * Returns the ID of the reply.
         *
         * @return The ID of the reply
         */
        public String getId() {
-               return id.toString();
+               return id;
        }
 
        /**
@@ -156,30 +137,10 @@ public class Reply {
         *            The Sone that posted this reply
         * @return This reply (for method chaining)
         */
-       public Reply setSone(Sone sone) {
+       @SuppressWarnings("unchecked")
+       public T setSone(Sone sone) {
                this.sone = sone;
-               return this;
-       }
-
-       /**
-        * Returns the post this reply refers to.
-        *
-        * @return The post this reply refers to
-        */
-       public Post getPost() {
-               return post;
-       }
-
-       /**
-        * Sets the post this reply refers to.
-        *
-        * @param post
-        *            The post this reply refers to
-        * @return This reply (for method chaining)
-        */
-       public Reply setPost(Post post) {
-               this.post = post;
-               return this;
+               return (T) this;
        }
 
        /**
@@ -198,9 +159,10 @@ public class Reply {
         *            The time of this reply (in milliseconds since Jan 1, 1970 UTC)
         * @return This reply (for method chaining)
         */
-       public Reply setTime(long time) {
+       @SuppressWarnings("unchecked")
+       public T setTime(long time) {
                this.time = time;
-               return this;
+               return (T) this;
        }
 
        /**
@@ -219,9 +181,10 @@ public class Reply {
         *            The text of this reply
         * @return This reply (for method chaining)
         */
-       public Reply setText(String text) {
+       @SuppressWarnings("unchecked")
+       public T setText(String text) {
                this.text = text;
-               return this;
+               return (T) this;
        }
 
        //
@@ -241,10 +204,10 @@ public class Reply {
         */
        @Override
        public boolean equals(Object object) {
-               if (!(object instanceof Reply)) {
+               if (!(object instanceof Reply<?>)) {
                        return false;
                }
-               Reply reply = (Reply) object;
+               Reply<?> reply = (Reply<?>) object;
                return reply.id.equals(id);
        }
 
@@ -253,7 +216,7 @@ public class Reply {
         */
        @Override
        public String toString() {
-               return getClass().getName() + "[id=" + id + ",sone=" + sone + ",post=" + post + ",time=" + time + ",text=" + text + "]";
+               return getClass().getName() + "[id=" + id + ",sone=" + sone + ",time=" + time + ",text=" + text + "]";
        }
 
 }
index e124387..ca970c2 100644 (file)
@@ -84,6 +84,18 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
                }
        };
 
+       /** Comparator that sorts Sones by number of images (descending). */
+       public static final Comparator<Sone> IMAGE_COUNT_COMPARATOR = new Comparator<Sone>() {
+
+               /**
+                * {@inheritDoc}
+                */
+               @Override
+               public int compare(Sone leftSone, Sone rightSone) {
+                       return rightSone.getAllImages().size() - leftSone.getAllImages().size();
+               }
+       };
+
        /** Filter to remove Sones that have not been downloaded. */
        public static final Filter<Sone> EMPTY_SONE_FILTER = new Filter<Sone>() {
 
@@ -138,7 +150,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
        private final Set<Post> posts = Collections.synchronizedSet(new HashSet<Post>());
 
        /** All replies. */
-       private final Set<Reply> replies = Collections.synchronizedSet(new HashSet<Reply>());
+       private final Set<PostReply> replies = Collections.synchronizedSet(new HashSet<PostReply>());
 
        /** The IDs of all liked posts. */
        private final Set<String> likedPostIds = Collections.synchronizedSet(new HashSet<String>());
@@ -477,7 +489,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *
         * @return All replies this Sone made
         */
-       public synchronized Set<Reply> getReplies() {
+       public synchronized Set<PostReply> getReplies() {
                return Collections.unmodifiableSet(replies);
        }
 
@@ -488,7 +500,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *            The new (and only) replies of this Sone
         * @return This Sone (for method chaining)
         */
-       public synchronized Sone setReplies(Collection<Reply> replies) {
+       public synchronized Sone setReplies(Collection<PostReply> replies) {
                this.replies.clear();
                this.replies.addAll(replies);
                return this;
@@ -501,7 +513,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * @param reply
         *            The reply to add
         */
-       public synchronized void addReply(Reply reply) {
+       public synchronized void addReply(PostReply reply) {
                if (reply.getSone().equals(this)) {
                        replies.add(reply);
                }
@@ -513,7 +525,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * @param reply
         *            The reply to remove
         */
-       public synchronized void removeReply(Reply reply) {
+       public synchronized void removeReply(PostReply reply) {
                if (reply.getSone().equals(this)) {
                        replies.remove(reply);
                }
@@ -645,6 +657,41 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
        }
 
        /**
+        * Returns a flattened list of all albums of this Sone. The resulting list
+        * contains parent albums before child albums so that the resulting list can
+        * be parsed in a single pass.
+        *
+        * @return The flattened albums
+        */
+       public List<Album> getAllAlbums() {
+               List<Album> flatAlbums = new ArrayList<Album>();
+               flatAlbums.addAll(albums);
+               int lastAlbumIndex = 0;
+               while (lastAlbumIndex < flatAlbums.size()) {
+                       int previousAlbumCount = flatAlbums.size();
+                       for (Album album : new ArrayList<Album>(flatAlbums.subList(lastAlbumIndex, flatAlbums.size()))) {
+                               flatAlbums.addAll(album.getAlbums());
+                       }
+                       lastAlbumIndex = previousAlbumCount;
+               }
+               return flatAlbums;
+       }
+
+       /**
+        * Returns all images of a Sone. Images of a album are inserted into this
+        * list before images of all child albums.
+        *
+        * @return The list of all images
+        */
+       public List<Image> getAllImages() {
+               List<Image> allImages = new ArrayList<Image>();
+               for (Album album : getAllAlbums()) {
+                       allImages.addAll(album.getImages());
+               }
+               return allImages;
+       }
+
+       /**
         * Adds an album to this Sone.
         *
         * @param album
@@ -747,10 +794,10 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
                }
                fingerprint.append(")");
 
-               List<Reply> replies = new ArrayList<Reply>(getReplies());
+               List<PostReply> replies = new ArrayList<PostReply>(getReplies());
                Collections.sort(replies, Reply.TIME_COMPARATOR);
                fingerprint.append("Replies(");
-               for (Reply reply : replies) {
+               for (PostReply reply : replies) {
                        fingerprint.append("Reply(").append(reply.getId()).append(')');
                }
                fingerprint.append(')');
@@ -781,33 +828,6 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
        }
 
        //
-       // STATIC METHODS
-       //
-
-       /**
-        * Flattens the given top-level albums so that the resulting list contains
-        * parent albums before child albums and the resulting list can be parsed in
-        * a single pass.
-        *
-        * @param albums
-        *            The albums to flatten
-        * @return The flattened albums
-        */
-       public static List<Album> flattenAlbums(Collection<? extends Album> albums) {
-               List<Album> flatAlbums = new ArrayList<Album>();
-               flatAlbums.addAll(albums);
-               int lastAlbumIndex = 0;
-               while (lastAlbumIndex < flatAlbums.size()) {
-                       int previousAlbumCount = flatAlbums.size();
-                       for (Album album : new ArrayList<Album>(flatAlbums.subList(lastAlbumIndex, flatAlbums.size()))) {
-                               flatAlbums.addAll(album.getAlbums());
-                       }
-                       lastAlbumIndex = previousAlbumCount;
-               }
-               return flatAlbums;
-       }
-
-       //
        // INTERFACE Comparable<Sone>
        //
 
index d88872e..ab705d7 100644 (file)
@@ -22,6 +22,7 @@ import java.util.List;
 
 import net.pterodactylus.sone.core.Core;
 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;
@@ -205,10 +206,10 @@ public abstract class AbstractSoneCommand extends AbstractCommand {
         *             if there is no reply ID stored under the given parameter
         *             name, or if the reply ID is invalid
         */
-       protected Reply getReply(SimpleFieldSet simpleFieldSet, String parameterName) throws FcpException {
+       protected PostReply getReply(SimpleFieldSet simpleFieldSet, String parameterName) throws FcpException {
                try {
                        String replyId = simpleFieldSet.getString(parameterName);
-                       Reply reply = core.getReply(replyId, false);
+                       PostReply reply = core.getReply(replyId, false);
                        if (reply == null) {
                                throw new FcpException("Could not load reply from “" + replyId + "”.");
                        }
@@ -305,7 +306,7 @@ public abstract class AbstractSoneCommand extends AbstractCommand {
                postBuilder.put(encodeLikes(core.getLikes(post), prefix + "Likes."));
 
                if (includeReplies) {
-                       List<Reply> replies = core.getReplies(post);
+                       List<PostReply> replies = core.getReplies(post);
                        postBuilder.put(encodeReplies(replies, prefix));
                }
 
@@ -334,7 +335,7 @@ public abstract class AbstractSoneCommand extends AbstractCommand {
                        String postPrefix = prefix + postIndex++;
                        postBuilder.put(encodePost(post, postPrefix + ".", includeReplies));
                        if (includeReplies) {
-                               postBuilder.put(encodeReplies(Filters.filteredList(core.getReplies(post), Reply.FUTURE_REPLIES_FILTER), postPrefix + "."));
+                               postBuilder.put(encodeReplies(Filters.filteredList(core.getReplies(post), Reply.FUTURE_REPLY_FILTER), postPrefix + "."));
                        }
                }
 
@@ -351,12 +352,12 @@ public abstract class AbstractSoneCommand extends AbstractCommand {
         *            {@code null})
         * @return The simple field set containing the replies
         */
-       protected SimpleFieldSet encodeReplies(Collection<? extends Reply> replies, String prefix) {
+       protected SimpleFieldSet encodeReplies(Collection<? extends PostReply> replies, String prefix) {
                SimpleFieldSetBuilder replyBuilder = new SimpleFieldSetBuilder();
 
                int replyIndex = 0;
                replyBuilder.put(prefix + "Replies.Count", replies.size());
-               for (Reply reply : replies) {
+               for (PostReply reply : replies) {
                        String replyPrefix = prefix + "Replies." + replyIndex++ + ".";
                        replyBuilder.put(replyPrefix + "ID", reply.getId());
                        replyBuilder.put(replyPrefix + "Sone", reply.getSone().getId());
index 880d51e..9fc78d6 100644 (file)
@@ -19,6 +19,7 @@ package net.pterodactylus.sone.fcp;
 
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Reply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.freenet.SimpleFieldSetBuilder;
@@ -52,7 +53,7 @@ public class CreateReplyCommand extends AbstractSoneCommand {
                Sone sone = getSone(parameters, "Sone", true);
                Post post = getPost(parameters, "Post");
                String text = getString(parameters, "Text");
-               Reply reply = getCore().createReply(sone, post, text);
+               PostReply reply = getCore().createReply(sone, post, text);
                return new Response("ReplyCreated", new SimpleFieldSetBuilder().put("Reply", reply.getId()).get());
        }
 
index 4ac9c15..614b23e 100644 (file)
 package net.pterodactylus.sone.fcp;
 
 import net.pterodactylus.sone.core.Core;
-import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.freenet.SimpleFieldSetBuilder;
 import net.pterodactylus.sone.freenet.fcp.FcpException;
 import freenet.support.SimpleFieldSet;
 import freenet.support.api.Bucket;
 
 /**
- * FCP command that deletes a {@link Reply}.
+ * FCP command that deletes a {@link PostReply}.
  *
- * @see Core#deleteReply(Reply)
+ * @see Core#deleteReply(PostReply)
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class DeleteReplyCommand extends AbstractSoneCommand {
@@ -47,7 +47,7 @@ public class DeleteReplyCommand extends AbstractSoneCommand {
         */
        @Override
        public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException {
-               Reply reply = getReply(parameters, "Reply");
+               PostReply reply = getReply(parameters, "Reply");
                if (!getCore().isLocalSone(reply.getSone())) {
                        return new ErrorResponse(401, "Not allowed.");
                }
index 2c8f04f..61257e7 100644 (file)
@@ -18,7 +18,7 @@
 package net.pterodactylus.sone.fcp;
 
 import net.pterodactylus.sone.core.Core;
-import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.freenet.SimpleFieldSetBuilder;
 import net.pterodactylus.sone.freenet.fcp.FcpException;
@@ -47,7 +47,7 @@ public class LikeReplyCommand extends AbstractSoneCommand {
         */
        @Override
        public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException {
-               Reply reply = getReply(parameters, "Reply");
+               PostReply reply = getReply(parameters, "Reply");
                Sone sone = getSone(parameters, "Sone", true);
                sone.addLikedReplyId(reply.getId());
                return new Response("ReplyLiked", new SimpleFieldSetBuilder().put("LikeCount", getCore().getLikes(reply).size()).get());
index de30a5d..019cdb2 100644 (file)
@@ -74,6 +74,9 @@ public class WebOfTrustConnector implements ConnectorListener {
         */
        public void stop() {
                pluginConnector.removeConnectorListener(WOT_PLUGIN_NAME, PLUGIN_CONNECTION_IDENTIFIER, this);
+               synchronized (reply) {
+                       reply.notifyAll();
+               }
        }
 
        /**
index 8aa7487..d93317a 100644 (file)
@@ -83,7 +83,7 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr
        }
 
        /** The version. */
-       public static final Version VERSION = new Version(0, 7, 1);
+       public static final Version VERSION = new Version(0, 7, 2);
 
        /** The logger. */
        private static final Logger logger = Logging.getLogger(SonePlugin.class);
index 3db8912..beeb81e 100644 (file)
@@ -22,6 +22,7 @@ import java.util.Collection;
 import java.util.List;
 
 import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Reply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.freenet.wot.OwnIdentity;
@@ -60,7 +61,7 @@ public class ListNotificationFilters {
                                        filteredNotifications.add(filteredNotification);
                                }
                        } else if (notification.getId().equals("new-reply-notification")) {
-                               ListNotification<Reply> filteredNotification = filterNewReplyNotification((ListNotification<Reply>) notification, currentSone);
+                               ListNotification<PostReply> filteredNotification = filterNewReplyNotification((ListNotification<PostReply>) notification, currentSone);
                                if (filteredNotification != null) {
                                        filteredNotifications.add(filteredNotification);
                                }
@@ -129,12 +130,12 @@ public class ListNotificationFilters {
         * @return The filtered new-reply notification, or {@code null} if the
         *         notification should be removed
         */
-       public static ListNotification<Reply> filterNewReplyNotification(ListNotification<Reply> newReplyNotification, Sone currentSone) {
+       public static ListNotification<PostReply> filterNewReplyNotification(ListNotification<PostReply> newReplyNotification, Sone currentSone) {
                if (currentSone == null) {
                        return null;
                }
-               List<Reply> newReplies = new ArrayList<Reply>();
-               for (Reply reply : newReplyNotification.getElements()) {
+               List<PostReply> newReplies = new ArrayList<PostReply>();
+               for (PostReply reply : newReplyNotification.getElements()) {
                        if (isReplyVisible(currentSone, reply)) {
                                newReplies.add(reply);
                        }
@@ -145,7 +146,7 @@ public class ListNotificationFilters {
                if (newReplies.size() == newReplyNotification.getElements().size()) {
                        return newReplyNotification;
                }
-               ListNotification<Reply> filteredNotification = new ListNotification<Reply>(newReplyNotification);
+               ListNotification<PostReply> filteredNotification = new ListNotification<PostReply>(newReplyNotification);
                filteredNotification.setElements(newReplies);
                filteredNotification.setLastUpdateTime(newReplyNotification.getLastUpdatedTime());
                return filteredNotification;
@@ -237,7 +238,7 @@ public class ListNotificationFilters {
         * @return {@code true} if the reply is considered visible, {@code false}
         *         otherwise
         */
-       public static boolean isReplyVisible(Sone sone, Reply reply) {
+       public static boolean isReplyVisible(Sone sone, PostReply reply) {
                Validation.begin().isNotNull("Reply", reply).check();
                Post post = reply.getPost();
                if (post == null) {
index 9e758cf..063a1de 100644 (file)
@@ -82,13 +82,8 @@ public class ImageLinkFilter implements Filter {
                        double scale = Math.max(maxWidth / (double) imageWidth, maxHeight / (double) imageHeight);
                        linkTemplateContext.set("width", (int) (imageWidth * scale + 0.5));
                        linkTemplateContext.set("height", (int) (imageHeight * scale + 0.5));
-                       if (scale >= 1) {
-                               linkTemplateContext.set("left", String.format("%dpx", (int) ((imageWidth * scale) - maxWidth) / 2));
-                               linkTemplateContext.set("top", String.format("%dpx", (int) ((imageHeight * scale) - maxHeight) / 2));
-                       } else {
-                               linkTemplateContext.set("left", String.format("%dpx", (int) (maxWidth - (imageWidth * scale)) / 2));
-                               linkTemplateContext.set("top", String.format("%dpx", (int) (maxHeight - (imageHeight * scale)) / 2));
-                       }
+                       linkTemplateContext.set("left", String.format("%dpx", (int) (maxWidth - (imageWidth * scale)) / 2));
+                       linkTemplateContext.set("top", String.format("%dpx", (int) (maxHeight - (imageHeight * scale)) / 2));
                } else {
                        double scale = 1;
                        if ((imageWidth > maxWidth) || (imageHeight > maxHeight)) {
index 3494c21..b53e969 100644 (file)
@@ -36,6 +36,7 @@ import net.pterodactylus.sone.text.SonePart;
 import net.pterodactylus.sone.text.SoneTextParser;
 import net.pterodactylus.sone.text.SoneTextParserContext;
 import net.pterodactylus.sone.web.page.FreenetRequest;
+import net.pterodactylus.util.number.Numbers;
 import net.pterodactylus.util.template.Filter;
 import net.pterodactylus.util.template.Template;
 import net.pterodactylus.util.template.TemplateContext;
@@ -87,19 +88,8 @@ public class ParserFilter implements Filter {
        @Override
        public Object format(TemplateContext templateContext, Object data, Map<String, String> parameters) {
                String text = String.valueOf(data);
-               int length = -1;
-               try {
-                       length = Integer.parseInt(parameters.get("length"));
-               } catch (NumberFormatException nfe1) {
-                       /* ignore. */
-               }
-               if ((length == -1) && (parameters.get("length") != null)) {
-                       try {
-                               length = Integer.parseInt(String.valueOf(templateContext.get(parameters.get("length"))));
-                       } catch (NumberFormatException nfe1) {
-                               /* ignore. */
-                       }
-               }
+               int length = Numbers.safeParseInteger(parameters.get("length"), Numbers.safeParseInteger(templateContext.get(parameters.get("length")), -1));
+               int cutOffLength = Numbers.safeParseInteger(parameters.get("cut-off-length"), Numbers.safeParseInteger(templateContext.get(parameters.get("cut-off-length")), length));
                String soneKey = parameters.get("sone");
                if (soneKey == null) {
                        soneKey = "sone";
@@ -114,27 +104,33 @@ public class ParserFilter implements Filter {
                try {
                        Iterable<Part> parts = soneTextParser.parse(context, new StringReader(text));
                        if (length > -1) {
+                               int allPartsLength = 0;
                                List<Part> shortenedParts = new ArrayList<Part>();
                                for (Part part : parts) {
                                        if (part instanceof PlainTextPart) {
                                                String longText = ((PlainTextPart) part).getText();
-                                               if (length >= longText.length()) {
-                                                       shortenedParts.add(part);
-                                               } else {
-                                                       shortenedParts.add(new PlainTextPart(longText.substring(0, length) + "…"));
+                                               if (allPartsLength < cutOffLength) {
+                                                       if ((allPartsLength + longText.length()) > cutOffLength) {
+                                                               shortenedParts.add(new PlainTextPart(longText.substring(0, cutOffLength - allPartsLength) + "…"));
+                                                       } else {
+                                                               shortenedParts.add(part);
+                                                       }
                                                }
-                                               length -= longText.length();
+                                               allPartsLength += longText.length();
                                        } else if (part instanceof LinkPart) {
-                                               shortenedParts.add(part);
-                                               length -= ((LinkPart) part).getText().length();
+                                               if (allPartsLength < cutOffLength) {
+                                                       shortenedParts.add(part);
+                                               }
+                                               allPartsLength += ((LinkPart) part).getText().length();
                                        } else {
-                                               shortenedParts.add(part);
-                                       }
-                                       if (length <= 0) {
-                                               break;
+                                               if (allPartsLength < cutOffLength) {
+                                                       shortenedParts.add(part);
+                                               }
                                        }
                                }
-                               parts = shortenedParts;
+                               if (allPartsLength >= length) {
+                                       parts = shortenedParts;
+                               }
                        }
                        render(parsedTextWriter, parts);
                } catch (IOException ioe1) {
@@ -233,7 +229,11 @@ public class ParserFilter implements Filter {
         *            The part to render
         */
        private void render(Writer writer, SonePart sonePart) {
-               renderLink(writer, "viewSone.html?sone=" + sonePart.getSone().getId(), SoneAccessor.getNiceName(sonePart.getSone()), SoneAccessor.getNiceName(sonePart.getSone()), "in-sone");
+               if ((sonePart.getSone() != null) && (sonePart.getSone().getName() != null)) {
+                       renderLink(writer, "viewSone.html?sone=" + sonePart.getSone().getId(), SoneAccessor.getNiceName(sonePart.getSone()), SoneAccessor.getNiceName(sonePart.getSone()), "in-sone");
+               } else {
+                       renderLink(writer, "/WebOfTrust/ShowIdentity?id=" + sonePart.getSone().getId(), sonePart.getSone().getId(), sonePart.getSone().getId(), "in-sone");
+               }
        }
 
        /**
index 99d6845..bef94d6 100644 (file)
@@ -56,7 +56,7 @@ public class PostAccessor extends ReflectionAccessor {
        public Object get(TemplateContext templateContext, Object object, String member) {
                Post post = (Post) object;
                if ("replies".equals(member)) {
-                       return Filters.filteredList(core.getReplies(post), Reply.FUTURE_REPLIES_FILTER);
+                       return Filters.filteredList(core.getReplies(post), Reply.FUTURE_REPLY_FILTER);
                } else if (member.equals("likes")) {
                        return core.getLikes(post);
                } else if (member.equals("liked")) {
index 1e54d53..24bcfd6 100644 (file)
@@ -18,6 +18,7 @@
 package net.pterodactylus.sone.template;
 
 import net.pterodactylus.sone.core.Core;
+import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Reply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.util.template.Accessor;
@@ -50,7 +51,7 @@ public class ReplyAccessor extends ReflectionAccessor {
         */
        @Override
        public Object get(TemplateContext templateContext, Object object, String member) {
-               Reply reply = (Reply) object;
+               PostReply reply = (PostReply) object;
                if ("likes".equals(member)) {
                        return core.getLikes(reply);
                } else if (member.equals("liked")) {
index 2567284..8bc17a7 100644 (file)
@@ -24,7 +24,7 @@ import java.util.Map;
 import java.util.Set;
 
 import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.util.template.Filter;
 import net.pterodactylus.util.template.TemplateContext;
@@ -44,10 +44,10 @@ public class ReplyGroupFilter implements Filter {
        @Override
        public Object format(TemplateContext templateContext, Object data, Map<String, String> parameters) {
                @SuppressWarnings("unchecked")
-               List<Reply> allReplies = (List<Reply>) data;
+               List<PostReply> allReplies = (List<PostReply>) data;
                Map<Post, Set<Sone>> postSones = new HashMap<Post, Set<Sone>>();
-               Map<Post, Set<Reply>> postReplies = new HashMap<Post, Set<Reply>>();
-               for (Reply reply : allReplies) {
+               Map<Post, Set<PostReply>> postReplies = new HashMap<Post, Set<PostReply>>();
+               for (PostReply reply : allReplies) {
                        Post post = reply.getPost();
                        Set<Sone> sones = postSones.get(post);
                        if (sones == null) {
@@ -55,9 +55,9 @@ public class ReplyGroupFilter implements Filter {
                                postSones.put(post, sones);
                        }
                        sones.add(reply.getSone());
-                       Set<Reply> replies = postReplies.get(post);
+                       Set<PostReply> replies = postReplies.get(post);
                        if (replies == null) {
-                               replies = new HashSet<Reply>();
+                               replies = new HashSet<PostReply>();
                                postReplies.put(post, replies);
                        }
                        replies.add(reply);
index 051c02a..5c12831 100644 (file)
@@ -202,11 +202,14 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                                        if (line.length() >= (7 + 43)) {
                                                String soneId = line.substring(7, 50);
                                                Sone sone = soneProvider.getSone(soneId, false);
-                                               if ((sone != null) && (sone.getName() != null)) {
-                                                       parts.add(new SonePart(sone));
-                                               } else {
-                                                       parts.add(new PlainTextPart(line.substring(0, 50)));
+                                               if (sone == null) {
+                                                       /*
+                                                        * don’t use create=true above, we don’t want the
+                                                        * empty shell.
+                                                        */
+                                                       sone = new Sone(soneId);
                                                }
+                                               parts.add(new SonePart(sone));
                                                line = line.substring(50);
                                        } else {
                                                parts.add(new PlainTextPart(line));
index 226e0d3..4502617 100644 (file)
@@ -17,7 +17,7 @@
 
 package net.pterodactylus.sone.web;
 
-import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.util.template.Template;
 import net.pterodactylus.util.template.TemplateContext;
@@ -53,7 +53,7 @@ public class DeleteReplyPage extends SoneTemplatePage {
        protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException {
                super.processTemplate(request, templateContext);
                String replyId = request.getHttpRequest().getPartAsStringFailsafe("reply", 36);
-               Reply reply = webInterface.getCore().getReply(replyId);
+               PostReply reply = webInterface.getCore().getReply(replyId);
                String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
                if (request.getMethod() == Method.POST) {
                        if (!webInterface.getCore().isLocalSone(reply.getSone())) {
index e5acad5..0e27265 100644 (file)
@@ -97,6 +97,12 @@ public class KnownSonesPage extends SoneTemplatePage {
                        } else {
                                Collections.sort(knownSones, Sone.POST_COUNT_COMPARATOR);
                        }
+               } else if ("images".equals(sortField)) {
+                       if ("asc".equals(sortOrder)) {
+                               Collections.sort(knownSones, new ReverseComparator<Sone>(Sone.IMAGE_COUNT_COMPARATOR));
+                       } else {
+                               Collections.sort(knownSones, Sone.IMAGE_COUNT_COMPARATOR);
+                       }
                } else {
                        if ("desc".equals(sortOrder)) {
                                Collections.sort(knownSones, new ReverseComparator<Sone>(Sone.NICE_NAME_COMPARATOR));
index 987fa1f..8cca4d1 100644 (file)
@@ -20,6 +20,7 @@ package net.pterodactylus.sone.web;
 import java.util.StringTokenizer;
 
 import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Reply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.page.FreenetRequest;
@@ -70,7 +71,7 @@ public class MarkAsKnownPage extends SoneTemplatePage {
                                }
                                webInterface.getCore().markPostKnown(post);
                        } else if (type.equals("reply")) {
-                               Reply reply = webInterface.getCore().getReply(id, false);
+                               PostReply reply = webInterface.getCore().getReply(id, false);
                                if (reply == null) {
                                        continue;
                                }
index 75e73a0..7b4a634 100644 (file)
@@ -87,6 +87,12 @@ public class OptionsPage extends SoneTemplatePage {
                        } else {
                                preferences.setCharactersPerPost(charactersPerPost);
                        }
+                       Integer postCutOffLength = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("post-cut-off-length", 10), null);
+                       if (!preferences.validatePostCutOffLength(postCutOffLength)) {
+                               fieldErrors.add("post-cut-off-length");
+                       } else {
+                               preferences.setPostCutOffLength(postCutOffLength);
+                       }
                        boolean requireFullAccess = request.getHttpRequest().isPartSet("require-full-access");
                        preferences.setRequireFullAccess(requireFullAccess);
                        Integer positiveTrust = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("positive-trust", 3));
@@ -128,6 +134,7 @@ public class OptionsPage extends SoneTemplatePage {
                templateContext.set("insertion-delay", preferences.getInsertionDelay());
                templateContext.set("posts-per-page", preferences.getPostsPerPage());
                templateContext.set("characters-per-post", preferences.getCharactersPerPost());
+               templateContext.set("post-cut-off-length", preferences.getPostCutOffLength());
                templateContext.set("require-full-access", preferences.isRequireFullAccess());
                templateContext.set("positive-trust", preferences.getPositiveTrust());
                templateContext.set("negative-trust", preferences.getNegativeTrust());
index 91e2a08..832f9d9 100644 (file)
@@ -28,6 +28,7 @@ import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Profile;
 import net.pterodactylus.sone.data.Profile.Field;
 import net.pterodactylus.sone.data.Reply;
@@ -365,7 +366,7 @@ public class SearchPage extends SoneTemplatePage {
                        if (post.getRecipient() != null) {
                                postString.append(' ').append(SoneStringGenerator.NAME_GENERATOR.generateString(post.getRecipient()));
                        }
-                       for (Reply reply : Filters.filteredList(webInterface.getCore().getReplies(post), Reply.FUTURE_REPLIES_FILTER)) {
+                       for (PostReply reply : Filters.filteredList(webInterface.getCore().getReplies(post), Reply.FUTURE_REPLY_FILTER)) {
                                postString.append(' ').append(SoneStringGenerator.NAME_GENERATOR.generateString(reply.getSone()));
                                postString.append(' ').append(reply.getText());
                        }
index 5d463c0..5fe356d 100644 (file)
@@ -26,7 +26,7 @@ import java.util.Map;
 import java.util.Set;
 
 import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.template.SoneAccessor;
 import net.pterodactylus.sone.web.page.FreenetRequest;
@@ -91,9 +91,9 @@ public class ViewSonePage extends SoneTemplatePage {
                Pagination<Post> postPagination = new Pagination<Post>(sonePosts, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(Numbers.safeParseInteger(request.getHttpRequest().getParam("postPage"), 0));
                templateContext.set("postPagination", postPagination);
                templateContext.set("posts", postPagination.getItems());
-               Set<Reply> replies = sone.getReplies();
-               final Map<Post, List<Reply>> repliedPosts = new HashMap<Post, List<Reply>>();
-               for (Reply reply : replies) {
+               Set<PostReply> replies = sone.getReplies();
+               final Map<Post, List<PostReply>> repliedPosts = new HashMap<Post, List<PostReply>>();
+               for (PostReply reply : replies) {
                        Post post = reply.getPost();
                        if (repliedPosts.containsKey(post) || sone.equals(post.getSone()) || (sone.equals(post.getRecipient()))) {
                                continue;
index 7f53419..7320ad5 100644 (file)
@@ -40,6 +40,7 @@ import net.pterodactylus.sone.core.CoreListener;
 import net.pterodactylus.sone.data.Album;
 import net.pterodactylus.sone.data.Image;
 import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Reply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.freenet.L10nFilter;
@@ -176,13 +177,13 @@ public class WebInterface implements CoreListener {
        private final ListNotification<Post> newPostNotification;
 
        /** The “new reply” notification. */
-       private final ListNotification<Reply> newReplyNotification;
+       private final ListNotification<PostReply> newReplyNotification;
 
        /** The invisible “local post” notification. */
        private final ListNotification<Post> localPostNotification;
 
        /** The invisible “local reply” notification. */
-       private final ListNotification<Reply> localReplyNotification;
+       private final ListNotification<PostReply> localReplyNotification;
 
        /** The “you have been mentioned” notification. */
        private final ListNotification<Post> mentionNotification;
@@ -266,10 +267,10 @@ public class WebInterface implements CoreListener {
                localPostNotification = new ListNotification<Post>("local-post-notification", "posts", localPostNotificationTemplate, false);
 
                Template newReplyNotificationTemplate = TemplateParser.parse(createReader("/templates/notify/newReplyNotification.html"));
-               newReplyNotification = new ListNotification<Reply>("new-reply-notification", "replies", newReplyNotificationTemplate, false);
+               newReplyNotification = new ListNotification<PostReply>("new-reply-notification", "replies", newReplyNotificationTemplate, false);
 
                Template localReplyNotificationTemplate = TemplateParser.parse(createReader("/templates/notify/newReplyNotification.html"));
-               localReplyNotification = new ListNotification<Reply>("local-reply-notification", "replies", localReplyNotificationTemplate, false);
+               localReplyNotification = new ListNotification<PostReply>("local-reply-notification", "replies", localReplyNotificationTemplate, false);
 
                Template mentionNotificationTemplate = TemplateParser.parse(createReader("/templates/notify/mentionNotification.html"));
                mentionNotification = new ListNotification<Post>("mention-notification", "posts", mentionNotificationTemplate, false);
@@ -464,8 +465,8 @@ public class WebInterface implements CoreListener {
         *
         * @return The new replies
         */
-       public Set<Reply> getNewReplies() {
-               return new SetBuilder<Reply>().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).get();
+       public Set<PostReply> getNewReplies() {
+               return new SetBuilder<PostReply>().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).get();
        }
 
        /**
@@ -805,7 +806,7 @@ public class WebInterface implements CoreListener {
         * {@inheritDoc}
         */
        @Override
-       public void newReplyFound(Reply reply) {
+       public void newReplyFound(PostReply reply) {
                boolean isLocal = getCore().isLocalSone(reply.getSone());
                if (isLocal) {
                        localReplyNotification.add(reply);
@@ -845,7 +846,7 @@ public class WebInterface implements CoreListener {
         * {@inheritDoc}
         */
        @Override
-       public void markReplyKnown(Reply reply) {
+       public void markReplyKnown(PostReply reply) {
                newReplyNotification.remove(reply);
                localReplyNotification.remove(reply);
                mentionNotification.remove(reply.getPost());
@@ -872,7 +873,7 @@ public class WebInterface implements CoreListener {
         * {@inheritDoc}
         */
        @Override
-       public void replyRemoved(Reply reply) {
+       public void replyRemoved(PostReply reply) {
                newReplyNotification.remove(reply);
                localReplyNotification.remove(reply);
        }
index 9f5c882..337a238 100644 (file)
@@ -18,7 +18,7 @@
 package net.pterodactylus.sone.web.ajax;
 
 import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.text.TextFilter;
 import net.pterodactylus.sone.web.WebInterface;
@@ -63,7 +63,7 @@ public class CreateReplyAjaxPage extends JsonPage {
                        return createErrorJsonObject("invalid-post-id");
                }
                text = TextFilter.filter(request.getHttpRequest().getHeader("host"), text);
-               Reply reply = webInterface.getCore().createReply(sender, post, text);
+               PostReply reply = webInterface.getCore().createReply(sender, post, text);
                return createSuccessJsonObject().put("reply", reply.getId()).put("sone", sender.getId());
        }
 
index 7bea4cf..76126f4 100644 (file)
@@ -17,7 +17,7 @@
 
 package net.pterodactylus.sone.web.ajax;
 
-import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.util.json.JsonObject;
@@ -49,7 +49,7 @@ public class DeleteReplyAjaxPage extends JsonPage {
        @Override
        protected JsonObject createJsonObject(FreenetRequest request) {
                String replyId = request.getHttpRequest().getParam("reply");
-               Reply reply = webInterface.getCore().getReply(replyId);
+               PostReply reply = webInterface.getCore().getReply(replyId);
                if (reply == null) {
                        return createErrorJsonObject("invalid-reply-id");
                }
index 539be3d..12a9e8d 100644 (file)
@@ -23,7 +23,7 @@ import java.util.List;
 import java.util.Set;
 
 import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.template.SoneAccessor;
 import net.pterodactylus.sone.web.WebInterface;
@@ -67,7 +67,7 @@ public class GetLikesAjaxPage extends JsonPage {
                        Set<Sone> sones = webInterface.getCore().getLikes(post);
                        return createSuccessJsonObject().put("likes", sones.size()).put("sones", getSones(sones));
                } else if ("reply".equals(type)) {
-                       Reply reply = webInterface.getCore().getReply(id);
+                       PostReply reply = webInterface.getCore().getReply(id);
                        Set<Sone> sones = webInterface.getCore().getLikes(reply);
                        return createSuccessJsonObject().put("likes", sones.size()).put("sones", getSones(sones));
                }
index dfac8e4..6cdbf12 100644 (file)
@@ -21,7 +21,7 @@ import java.io.IOException;
 import java.io.StringWriter;
 
 import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.main.SonePlugin;
 import net.pterodactylus.sone.notify.ListNotification;
@@ -89,7 +89,7 @@ public class GetNotificationAjaxPage extends JsonPage {
                        if ("new-post-notification".equals(notificationId)) {
                                notification = ListNotificationFilters.filterNewPostNotification((ListNotification<Post>) notification, currentSone, false);
                        } else if ("new-reply-notification".equals(notificationId)) {
-                               notification = ListNotificationFilters.filterNewReplyNotification((ListNotification<Reply>) notification, currentSone);
+                               notification = ListNotificationFilters.filterNewReplyNotification((ListNotification<PostReply>) notification, currentSone);
                        } else if ("mention-notification".equals(notificationId)) {
                                notification = ListNotificationFilters.filterNewPostNotification((ListNotification<Post>) notification, currentSone, false);
                        }
index 2eef58a..f38e0f2 100644 (file)
@@ -19,7 +19,7 @@ package net.pterodactylus.sone.web.ajax;
 
 import java.io.StringWriter;
 
-import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
@@ -62,7 +62,7 @@ public class GetReplyAjaxPage extends JsonPage {
        @Override
        protected JsonObject createJsonObject(FreenetRequest request) {
                String replyId = request.getHttpRequest().getParam("reply");
-               Reply reply = webInterface.getCore().getReply(replyId);
+               PostReply reply = webInterface.getCore().getReply(replyId);
                if ((reply == null) || (reply.getSone() == null)) {
                        return createErrorJsonObject("invalid-reply-id");
                }
@@ -92,7 +92,7 @@ public class GetReplyAjaxPage extends JsonPage {
         *            The currently logged in Sone (to store in the template)
         * @return The JSON representation of the reply
         */
-       private JsonObject createJsonReply(FreenetRequest request, Reply reply, Sone currentSone) {
+       private JsonObject createJsonReply(FreenetRequest request, PostReply reply, Sone currentSone) {
                JsonObject jsonReply = new JsonObject();
                jsonReply.put("id", reply.getId());
                jsonReply.put("postId", reply.getPost().getId());
index eaa6506..038cfb8 100644 (file)
@@ -26,7 +26,7 @@ import java.util.List;
 import java.util.Set;
 
 import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.notify.ListNotificationFilters;
 import net.pterodactylus.sone.template.SoneAccessor;
@@ -117,27 +117,27 @@ public class GetStatusAjaxPage extends JsonPage {
                        jsonPosts.add(jsonPost);
                }
                /* load new replies. */
-               Set<Reply> newReplies = webInterface.getNewReplies();
+               Set<PostReply> newReplies = webInterface.getNewReplies();
                if (currentSone != null) {
-                       newReplies = Filters.filteredSet(newReplies, new Filter<Reply>() {
+                       newReplies = Filters.filteredSet(newReplies, new Filter<PostReply>() {
 
                                @Override
-                               public boolean filterObject(Reply reply) {
+                               public boolean filterObject(PostReply reply) {
                                        return ListNotificationFilters.isReplyVisible(currentSone, reply);
                                }
 
                        });
                }
                /* remove replies to unknown posts. */
-               newReplies = Filters.filteredSet(newReplies, new Filter<Reply>() {
+               newReplies = Filters.filteredSet(newReplies, new Filter<PostReply>() {
 
                        @Override
-                       public boolean filterObject(Reply reply) {
+                       public boolean filterObject(PostReply reply) {
                                return (reply.getPost() != null) && (reply.getPost().getSone() != null);
                        }
                });
                JsonArray jsonReplies = new JsonArray();
-               for (Reply reply : newReplies) {
+               for (PostReply reply : newReplies) {
                        JsonObject jsonReply = new JsonObject();
                        jsonReply.put("id", reply.getId());
                        jsonReply.put("sone", reply.getSone().getId());
index 1711a5b..cec9967 100644 (file)
@@ -22,7 +22,7 @@ import java.text.SimpleDateFormat;
 import java.util.Date;
 
 import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.util.json.JsonObject;
@@ -75,7 +75,7 @@ public class GetTimesAjaxPage extends JsonPage {
                if (allIds.length() > 0) {
                        String[] ids = allIds.split(",");
                        for (String id : ids) {
-                               Reply reply = webInterface.getCore().getReply(id, false);
+                               PostReply reply = webInterface.getCore().getReply(id, false);
                                if (reply == null) {
                                        continue;
                                }
index 42ee8b2..0e9b6b7 100644 (file)
@@ -19,6 +19,7 @@ package net.pterodactylus.sone.web.ajax;
 
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Reply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
@@ -62,7 +63,7 @@ public class MarkAsKnownAjaxPage extends JsonPage {
                                }
                                core.markPostKnown(post);
                        } else if (type.equals("reply")) {
-                               Reply reply = core.getReply(id, false);
+                               PostReply reply = core.getReply(id, false);
                                if (reply == null) {
                                        continue;
                                }
index 4da0855..4210930 100644 (file)
@@ -43,7 +43,8 @@ Page.Options.Option.EnableSoneInsertNotifications.Description=If enabled, this w
 Page.Options.Section.RuntimeOptions.Title=Runtime Behaviour
 Page.Options.Option.InsertionDelay.Description=The number of seconds the Sone inserter waits after a modification of a Sone before it is being inserted.
 Page.Options.Option.PostsPerPage.Description=The number of posts to display on a page before pagination controls are being shown.
-Page.Options.Option.CharactersPerPost.Description=The number of characters to display from a post before cutting it off and showing a link to expand it (-1 to disable).
+Page.Options.Option.CharactersPerPost.Description=The number of characters to display from a post before cutting it off and showing a link to expand it (-1 to disable). The actual length of the snippet is determined by the option below.
+Page.Options.Option.PostCutOffLength.Description=The number of characters that are displayed if a post is deemed to long (see option above).
 Page.Options.Option.RequireFullAccess.Description=Whether to deny access to Sone to any host that has not been granted full access.
 Page.Options.Section.TrustOptions.Title=Trust Settings
 Page.Options.Option.PositiveTrust.Description=The amount of positive trust you want to assign to other Sones by clicking the checkmark below a post or reply.
@@ -86,6 +87,17 @@ Page.Index.PostList.Text.NoPostYet=Nobody has written any posts yet. You should
 Page.KnownSones.Title=Known Sones - Sone
 Page.KnownSones.Page.Title=Known Sones
 Page.KnownSones.Text.NoKnownSones=There are currently no known Sones.
+Page.KnownSones.Label.Sort=Sort:
+Page.KnownSones.Label.FollowedSones=Followed Sones:
+Page.KnownSones.Sort.Field.Name=Name
+Page.KnownSones.Sort.Field.LastActivity=Last activity
+Page.KnownSones.Sort.Field.Posts=Number of posts
+Page.KnownSones.Sort.Field.Images=Number of images
+Page.KnownSones.Sort.Order.Ascending=Ascending
+Page.KnownSones.Sort.Order.Descending=Descending
+Page.KnownSones.FollowedSones.ShowOnly=Show only followed Sones
+Page.KnownSones.FollowedSones.Hide=Hide followed Sones
+Page.KnownSones.Button.Apply=Apply
 Page.KnownSones.Button.FollowAllSones=Follow all Sones
 Page.KnownSones.Button.UnfollowAllSones=Unfollow all Sones
 
@@ -290,6 +302,7 @@ View.Sone.Label.LastUpdate=Last update:
 View.Sone.Text.UnknownDate=unknown
 View.Sone.Stats.Posts={0,number} {0,choice,0#posts|1#post|1<posts}
 View.Sone.Stats.Replies={0,number} {0,choice,0#replies|1#reply|1<replies}
+View.Sone.Stats.Images={0,number} {0,choice,0#images|1#image|1<images}
 View.Sone.Button.UnlockSone=unlock
 View.Sone.Button.UnlockSone.Tooltip=Allow this Sone to be inserted now
 View.Sone.Button.LockSone=lock
@@ -373,7 +386,8 @@ WebInterface.DefaultText.UploadImage.Description=Image description
 WebInterface.DefaultText.EditImage.Title=Image title
 WebInterface.DefaultText.EditImage.Description=Image description
 WebInterface.DefaultText.Option.PostsPerPage=Number of posts to show on a page
-WebInterface.DefaultText.Option.CharactersPerPost=Number of characters per post after which to cut the post off
+WebInterface.DefaultText.Option.CharactersPerPost=Number of characters a post must have to be shortened
+WebInterface.DefaultText.Option.PostCutOffLength=Number of characters for the snippet of the shortened post
 WebInterface.DefaultText.Option.PositiveTrust=The positive trust to assign
 WebInterface.DefaultText.Option.NegativeTrust=The negative trust to assign
 WebInterface.DefaultText.Option.TrustComment=The comment to set in the web of trust
index 6ad458e..f30bb03 100644 (file)
@@ -690,7 +690,7 @@ textarea {
 
 #sone .image .image-description, #sone .album .album-description {
        text-align: left;
-       width: 195px;
+       width: 98%;
        word-wrap: break-word;
        max-height: 5em;
        overflow: auto;
index b77bcd1..d925a3c 100644 (file)
@@ -13,7 +13,7 @@
                        </a>
                </div>
                <div class="show-data">
-                       <div class="album-title"><% album.title|html></div>
+                       <div class="album-title"><% album.title|html> (<%= View.Sone.Stats.Images|l10n 0=album.images.size>)</div>
                        <div class="album-description"><% album.description|html></div>
                </div>
                <%if album.sone.local>
index 87114cc..128e389 100644 (file)
@@ -4,7 +4,7 @@
        <div class="inner-menu">
                <div>
                        <a class="author" href="viewSone.html?sone=<%sone.id|html>"><%sone.niceName|html></a>
-                       (<%= View.Sone.Stats.Posts|l10n 0=sone.posts.size>, <%= View.Sone.Stats.Replies|l10n 0=sone.replies.size>)
+                       (<%= View.Sone.Stats.Posts|l10n 0=sone.posts.size>, <%= View.Sone.Stats.Replies|l10n 0=sone.replies.size><%if ! sone.allImages.size|match value=0>, <%= View.Sone.Stats.Images|l10n 0=sone.allImages.size><%/if>)
                </div>
                <div><a href="/WebOfTrust/ShowIdentity?id=<%sone.id|html>">» <% =View.Post.WebOfTrustLink|l10n|html></a></div>
                <%foreach sone.albums album>
index 931365d..a478bba 100644 (file)
@@ -24,7 +24,7 @@
                        <%/if>
                        <% post.text|html|store key=originalText text=true>
                        <% post.text|parse sone=post.sone|store key=parsedText text=true>
-                       <% post.text|parse sone=post.sone length=core.preferences.charactersPerPost|store key=shortText text=true>
+                       <% post.text|parse sone=post.sone length=core.preferences.charactersPerPost cut-off-length=core.preferences.postCutOffLength|store key=shortText text=true>
                        <div class="post-text raw-text<%if !raw> hidden<%/if>"><% originalText></div>
                        <div class="post-text text<%if raw> hidden<%/if><%if !shortText|match key=parsedText> hidden<%/if>"><% parsedText></div>
                        <div class="post-text short-text<%if raw> hidden<%/if><%if shortText|match key=parsedText> hidden<%/if>"><% shortText></div>
index ec62887..f8e3e64 100644 (file)
@@ -12,7 +12,7 @@
                        <div class="author profile-link"><a href="viewSone.html?sone=<% reply.sone.id|html>"><% reply.sone.niceName|html></a></div>
                        <% reply.text|html|store key=originalText text=true>
                        <% reply.text|parse sone=reply.sone|store key=parsedText text=true>
-                       <% reply.text|parse sone=reply.sone length=core.preferences.charactersPerPost|store key=shortText text=true>
+                       <% reply.text|parse sone=reply.sone length=core.preferences.charactersPerPost cut-off-length=core.preferences.postCutOffLength|store key=shortText text=true>
                        <div class="reply-text raw-text<%if !raw> hidden<%/if>"><% originalText></div>
                        <div class="reply-text text<%if raw> hidden<%/if><%if !shortText|match key=parsedText> hidden<%/if>"><% parsedText></div>
                        <div class="reply-text short-text<%if raw> hidden<%/if><%if shortText|match key=parsedText> hidden<%/if>"><% shortText></div>
index ba5c8e4..59ba792 100644 (file)
@@ -8,7 +8,7 @@
        <div class="last-update"><%= View.Sone.Label.LastUpdate|l10n|html> <span class="time" title="<% sone.time|unknown|date format="MMM d, yyyy, HH:mm:ss">"><%sone.lastUpdatedText|html></span></div>
        <div>
                <div class="profile-link"><a href="viewSone.html?sone=<% sone.id|html>" title="<% sone.requestUri|html>"><% sone.niceName|html></a></div>
-               <div class="sone-stats">(<%= View.Sone.Stats.Posts|l10n 0=sone.posts.size>, <%= View.Sone.Stats.Replies|l10n 0=sone.replies.size>)</div>
+               <div class="sone-stats">(<%= View.Sone.Stats.Posts|l10n 0=sone.posts.size>, <%= View.Sone.Stats.Replies|l10n 0=sone.replies.size><%if ! sone.allImages.size|match value=0>, <%= View.Sone.Stats.Images|l10n 0=sone.allImages.size><%/if>)</div>
        </div>
        <div class="short-request-uri"><% sone.requestUri|substring start=4 length=43|html></div>
        <div class="hidden"><% sone.blacklisted></div>
index 260a321..7b42d53 100644 (file)
@@ -1,53 +1,54 @@
 <%include include/head.html>
 
        <div class="page-id hidden">known-sones</div>
-       
+
        <script language="javascript">
 
                $(document).ready(function() {
                        $("select[name=sort]").change(function() {
                                value = $(this).val();
-                               if ((value == "activity") || (value == "posts")) {
+                               if ((value == "activity") || (value == "posts") || (value == "images")) {
                                        $("select[name=order]").val("desc");
                                } else if (value == "name") {
                                        $("select[name=order]").val("asc");
-                               } 
+                               }
                        });
                        $("#sort-options select").change(function() {
                                this.form.submit();
                        });
                });
-       
+
        </script>
 
        <h1><%= Page.KnownSones.Page.Title|l10n|html></h1>
-       
+
        <div id="sort-options">
                <form action="knownSones.html" method="get">
                        <div>
-                               Sort:
+                               <%= Page.KnownSones.Label.Sort|l10n|html>
                                <select name="sort">
-                                       <option value="name"<%if sort|match value="name"> selected="selected"<%/if>>Name</option>
-                                       <option value="activity"<%if sort|match value="activity"> selected="selected"<%/if>>Last activity</option>
-                                       <option value="posts"<%if sort|match value="posts"> selected="selected"<%/if>>Number of posts</option>
+                                       <option value="name"<%if sort|match value="name"> selected="selected"<%/if>><%= Page.KnownSones.Sort.Field.Name|l10n|html></option>
+                                       <option value="activity"<%if sort|match value="activity"> selected="selected"<%/if>><%= Page.KnownSones.Sort.Field.LastActivity|l10n|html></option>
+                                       <option value="posts"<%if sort|match value="posts"> selected="selected"<%/if>><%= Page.KnownSones.Sort.Field.Posts|l10n|html></option>
+                                       <option value="images"<%if sort|match value="images"> selected="selected"<%/if>><%= Page.KnownSones.Sort.Field.Images|l10n|html></option>
                                </select>
                                <select name="order">
-                                       <option value="asc"<%if order|match value="asc"> selected="selected"<%/if>>Ascending</option>
-                                       <option value="desc"<%if order|match value="desc"> selected="selected"<%/if>>Descending</option>
+                                       <option value="asc"<%if order|match value="asc"> selected="selected"<%/if>><%= Page.KnownSones.Sort.Order.Ascending|l10n|html></option>
+                                       <option value="desc"<%if order|match value="desc"> selected="selected"<%/if>><%= Page.KnownSones.Sort.Order.Descending|l10n|html></option>
                                </select>
                        </div>
                        <%ifnull !currentSone>
                                <div>
-                                       Followed Sones:
+                                       <%= Page.KnownSones.Label.FollowedSones|l10n|html>
                                        <select name="followedSones">
                                                <option value="none"></option>
-                                               <option value="show-only"<%if followedSones|match value="show-only"> selected="selected"<%/if>>Show only followed Sones</option>
-                                               <option value="hide"<%if followedSones|match value="hide"> selected="selected"<%/if>>Hide followed Sones</option>
+                                               <option value="show-only"<%if followedSones|match value="show-only"> selected="selected"<%/if>><%= Page.KnownSones.FollowedSones.ShowOnly|l10n|html></option>
+                                               <option value="hide"<%if followedSones|match value="hide"> selected="selected"<%/if>><%= Page.KnownSones.FollowedSones.Hide|l10n|html></option>
                                        </select>
                                </div>
                        <%/if>
                        <div>
-                               <button type="submit">Apply</button>
+                               <button type="submit"><%= Page.KnownSones.Button.Apply|l10n|html></button>
                        </div>
                </form>
        </div>
index 39d579f..2adf57e 100644 (file)
@@ -11,6 +11,9 @@
                        getTranslation("WebInterface.DefaultText.Option.CharactersPerPost", function(postsPerPageText) {
                                registerInputTextareaSwap("#sone #options input[name=characters-per-post]", postsPerPageText, "characters-per-post", true, true);
                        });
+                       getTranslation("WebInterface.DefaultText.Option.PostCutOffLength", function(postCutOffLengthText) {
+                               registerInputTextareaSwap("#sone #options input[name=post-cut-off-length]", postCutOffLengthText, "post-cut-off-length", true, true);
+                       });
                        getTranslation("WebInterface.DefaultText.Option.PositiveTrust", function(positiveTrustText) {
                                registerInputTextareaSwap("#sone #options input[name=positive-trust]", positiveTrustText, "positive-trust", true, true);
                        });
                <%/if>
                <p><input type="text" name="characters-per-post" value="<% characters-per-post|html>" /></p>
 
+               <p><%= Page.Options.Option.PostCutOffLength.Description|l10n|html></p>
+               <%if =post-cut-off-length|in collection=fieldErrors>
+                       <p class="warning"><%= Page.Options.Warnings.ValueNotChanged|l10n|html></p>
+               <%/if>
+               <p><input type="text" name="post-cut-off-length" value="<% post-cut-off-length|html>" /></p>
+
                <p>
                        <input type="checkbox" name="require-full-access"<%if require-full-access> checked="checked"<%/if> />
                        <%= Page.Options.Option.RequireFullAccess.Description|l10n|html></p>