Merge branch 'master' into next
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Fri, 10 Dec 2010 21:24:33 +0000 (22:24 +0100)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Fri, 10 Dec 2010 21:24:33 +0000 (22:24 +0100)
16 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/Options.java
src/main/java/net/pterodactylus/sone/data/Post.java
src/main/java/net/pterodactylus/sone/main/SonePlugin.java
src/main/java/net/pterodactylus/sone/template/PostAccessor.java
src/main/java/net/pterodactylus/sone/text/FreenetLinkParser.java
src/main/java/net/pterodactylus/sone/text/TemplatePart.java
src/main/java/net/pterodactylus/sone/web/LoginPage.java
src/main/java/net/pterodactylus/sone/web/WebInterface.java
src/main/resources/i18n/sone.en.properties
src/main/resources/static/css/sone.css
src/main/resources/static/javascript/sone.js
src/main/resources/templates/viewPost.html

diff --git a/pom.xml b/pom.xml
index 1173ece..a93599b 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -2,7 +2,7 @@
        <modelVersion>4.0.0</modelVersion>
        <groupId>net.pterodactylus</groupId>
        <artifactId>sone</artifactId>
-       <version>0.3.2-RC3</version>
+       <version>0.3.2</version>
        <dependencies>
                <dependency>
                        <groupId>net.pterodactylus</groupId>
index 52ebfab..c99b055 100644 (file)
@@ -885,6 +885,9 @@ public class Core implements IdentityListener {
                                if (!soneRescueMode) {
                                        for (Post post : storedSone.getPosts()) {
                                                posts.remove(post.getId());
+                                               if (!sone.getPosts().contains(post)) {
+                                                       coreListenerManager.firePostRemoved(post);
+                                               }
                                        }
                                }
                                synchronized (newPosts) {
@@ -902,6 +905,9 @@ public class Core implements IdentityListener {
                                if (!soneRescueMode) {
                                        for (Reply reply : storedSone.getReplies()) {
                                                replies.remove(reply.getId());
+                                               if (!sone.getReplies().contains(reply)) {
+                                                       coreListenerManager.fireReplyRemoved(reply);
+                                               }
                                        }
                                }
                                synchronized (newReplies) {
index 595d11a..950e890 100644 (file)
@@ -95,4 +95,20 @@ public interface CoreListener extends EventListener {
         */
        public void markReplyKnown(Reply reply);
 
+       /**
+        * Notifies a listener that the given post was removed.
+        *
+        * @param post
+        *            The removed post
+        */
+       public void postRemoved(Post post);
+
+       /**
+        * Notifies a listener that the given reply was removed.
+        *
+        * @param reply
+        *            The removed reply
+        */
+       public void replyRemoved(Reply reply);
+
 }
index 0fd8b1a..7ba226b 100644 (file)
@@ -145,4 +145,30 @@ public class CoreListenerManager extends AbstractListenerManager<Core, CoreListe
                }
        }
 
+       /**
+        * Notifies all listener that the given post was removed.
+        *
+        * @see CoreListener#postRemoved(Post)
+        * @param post
+        *            The removed post
+        */
+       void firePostRemoved(Post post) {
+               for (CoreListener coreListener : getListeners()) {
+                       coreListener.postRemoved(post);
+               }
+       }
+
+       /**
+        * Notifies all listener that the given reply was removed.
+        *
+        * @see CoreListener#replyRemoved(Reply)
+        * @param reply
+        *            The removed reply
+        */
+       void fireReplyRemoved(Reply reply) {
+               for (CoreListener coreListener : getListeners()) {
+                       coreListener.replyRemoved(reply);
+               }
+       }
+
 }
index cbe88c8..f77c085 100644 (file)
@@ -2,6 +2,7 @@ package net.pterodactylus.sone.core;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -93,7 +94,7 @@ public class Options {
                private final T defaultValue;
 
                /** The current value. */
-               private T value;
+               private volatile T value;
 
                /** The option watcher. */
                private final List<OptionWatcher<T>> optionWatchers = new ArrayList<OptionWatcher<T>>();
@@ -155,10 +156,10 @@ public class Options {
        }
 
        /** Holds all {@link Boolean} {@link Option}s. */
-       private final Map<String, Option<Boolean>> booleanOptions = new HashMap<String, Option<Boolean>>();
+       private final Map<String, Option<Boolean>> booleanOptions = Collections.synchronizedMap(new HashMap<String, Option<Boolean>>());
 
        /** Holds all {@link Integer} {@link Option}s. */
-       private final Map<String, Option<Integer>> integerOptions = new HashMap<String, Option<Integer>>();
+       private final Map<String, Option<Integer>> integerOptions = Collections.synchronizedMap(new HashMap<String, Option<Integer>>());
 
        /**
         * Adds a boolean option.
index 21ba42d..964a054 100644 (file)
@@ -159,7 +159,9 @@ public class Post {
         * @return This post (for method chaining)
         */
        public Post setRecipient(Sone recipient) {
-               this.recipient = recipient;
+               if (!sone.equals(recipient)) {
+                       this.recipient = recipient;
+               }
                return this;
        }
 
index b4fa468..fbf8b1a 100644 (file)
@@ -79,7 +79,7 @@ public class SonePlugin implements FredPlugin, FredPluginL10n, FredPluginBaseL10
        }
 
        /** The version. */
-       public static final Version VERSION = new Version("RC3", 0, 3, 2);
+       public static final Version VERSION = new Version(0, 3, 2);
 
        /** The logger. */
        private static final Logger logger = Logging.getLogger(SonePlugin.class);
@@ -151,12 +151,12 @@ public class SonePlugin implements FredPlugin, FredPluginL10n, FredPluginBaseL10
                        oldConfiguration = new Configuration(new MapConfigurationBackend(new File("sone.properties"), false));
                        newConfiguration = oldConfiguration;
                } catch (ConfigurationException ce1) {
-                       logger.log(Level.INFO, "Could not load configuration file, trying plugin store…");
+                       logger.log(Level.INFO, "Could not load configuration file, trying plugin store…", ce1);
                        try {
                                newConfiguration = new Configuration(new MapConfigurationBackend(new File("sone.properties"), true));
                                logger.log(Level.INFO, "Created new configuration file.");
                        } catch (ConfigurationException ce2) {
-                               logger.log(Level.SEVERE, "Could not create configuration file, using Plugin Store!");
+                               logger.log(Level.SEVERE, "Could not create configuration file, using Plugin Store!", ce2);
                        }
                        try {
                                oldConfiguration = new Configuration(new PluginStoreConfigurationBackend(pluginRespirator));
index 746ed25..495d805 100644 (file)
@@ -75,6 +75,9 @@ public class PostAccessor extends ReflectionAccessor {
                        return core.isNewPost(post.getId(), false);
                } else if (member.equals("text")) {
                        String text = post.getText();
+                       if (text == null) {
+                               return null;
+                       }
                        try {
                                return linkParser.parse(new StringReader(text));
                        } catch (IOException ioe1) {
index f36b6df..1d7c6b7 100644 (file)
@@ -40,7 +40,7 @@ public class FreenetLinkParser implements Parser {
        private static final Logger logger = Logging.getLogger(FreenetLinkParser.class);
 
        /** Pattern to detect whitespace. */
-       private static final Pattern whitespacePattern = Pattern.compile("[\u0020\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u200c\u200d\u202f\u205f\u2060\u2800\u3000]");
+       private static final Pattern whitespacePattern = Pattern.compile("[\\u000a\u0020\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u200c\u200d\u202f\u205f\u2060\u2800\u3000]");
 
        /**
         * Enumeration for all recognized link types.
@@ -50,16 +50,46 @@ public class FreenetLinkParser implements Parser {
        private enum LinkType {
 
                /** Link is a KSK. */
-               KSK,
+               KSK(true),
 
                /** Link is a CHK. */
-               CHK,
+               CHK(true),
 
                /** Link is an SSK. */
-               SSK,
+               SSK(true),
 
                /** Link is a USK. */
-               USK
+               USK(true),
+
+               /** Link is HTTP. */
+               HTTP(false),
+
+               /** Link is HTTPS. */
+               HTTPS(false);
+
+               /** Whether this link type links to freenet. */
+               private final boolean anonymous;
+
+               /**
+                * Creates a new link type.
+                *
+                * @param anonymous
+                *            {@code true} if this link type links to freenet,
+                *            {@code false} otherwise
+                */
+               private LinkType(boolean anonymous) {
+                       this.anonymous = anonymous;
+               }
+
+               /**
+                * Returns whether this link type links anonymously to within freenet.
+                *
+                * @return {@code true} if this link type links to within freenet,
+                *         {@code false} otherwise
+                */
+               public boolean isAnonymous() {
+                       return anonymous;
+               }
 
        }
 
@@ -84,6 +114,7 @@ public class FreenetLinkParser implements Parser {
         * {@inheritDoc}
         */
        @Override
+       @SuppressWarnings("null")
        public Part parse(Reader source) throws IOException {
                PartContainer parts = new PartContainer();
                BufferedReader bufferedReader = (source instanceof BufferedReader) ? (BufferedReader) source : new BufferedReader(source);
@@ -95,7 +126,9 @@ public class FreenetLinkParser implements Parser {
                                int nextChk = line.indexOf("CHK@");
                                int nextSsk = line.indexOf("SSK@");
                                int nextUsk = line.indexOf("USK@");
-                               if ((nextKsk == -1) && (nextChk == -1) && (nextSsk == -1) && (nextUsk == -1)) {
+                               int nextHttp = line.indexOf("http://");
+                               int nextHttps = line.indexOf("https://");
+                               if ((nextKsk == -1) && (nextChk == -1) && (nextSsk == -1) && (nextUsk == -1) && (nextHttp == -1) && (nextHttps == -1)) {
                                        parts.add(createPlainTextPart(line));
                                        break;
                                }
@@ -117,6 +150,14 @@ public class FreenetLinkParser implements Parser {
                                        next = nextUsk;
                                        linkType = LinkType.USK;
                                }
+                               if ((nextHttp > -1) && (nextHttp < next)) {
+                                       next = nextHttp;
+                                       linkType = LinkType.HTTP;
+                               }
+                               if ((nextHttps > -1) && (nextHttps < next)) {
+                                       next = nextHttps;
+                                       linkType = LinkType.HTTPS;
+                               }
                                if ((next >= 8) && (line.substring(next - 8, next).equals("freenet:"))) {
                                        next -= 8;
                                        line = line.substring(0, next) + line.substring(next + 8);
@@ -127,12 +168,26 @@ public class FreenetLinkParser implements Parser {
                                        parts.add(createPlainTextPart(line.substring(0, next)));
                                        String link = line.substring(next, nextSpace);
                                        String name = link;
-                                       logger.log(Level.FINER, "Found link: " + link);
+                                       logger.log(Level.FINER, "Found link: %s", link);
                                        logger.log(Level.FINEST, "Next: %d, CHK: %d, SSK: %d, USK: %d", new Object[] { next, nextChk, nextSsk, nextUsk });
                                        if (((linkType == LinkType.CHK) || (linkType == LinkType.SSK) || (linkType == LinkType.USK)) && (link.length() > 98) && (link.charAt(47) == ',') && (link.charAt(91) == ',') && (link.charAt(99) == '/')) {
                                                name = link.substring(0, 47) + "…" + link.substring(99);
+                                       } else if ((linkType == LinkType.HTTP) || (linkType == LinkType.HTTPS)) {
+                                               name = link.substring(linkType == LinkType.HTTP ? 7 : 8);
+                                               int firstSlash = name.indexOf('/');
+                                               int lastSlash = name.lastIndexOf('/');
+                                               if ((lastSlash - firstSlash) > 3) {
+                                                       name = name.substring(0, firstSlash + 1) + "…" + name.substring(lastSlash);
+                                               }
+                                               if (name.endsWith("/")) {
+                                                       name = name.substring(0, name.length() - 1);
+                                               }
+                                               if (((name.indexOf('/') > -1) && (name.indexOf('.') < name.lastIndexOf('.', name.indexOf('/'))) || ((name.indexOf('/') == -1) && (name.indexOf('.') < name.lastIndexOf('.')))) && name.startsWith("www.")) {
+                                                       name = name.substring(4);
+                                               }
+                                               link = "?_CHECKED_HTTP_=" + link;
                                        }
-                                       parts.add(createLinkPart(link, name));
+                                       parts.add(createLinkPart(linkType.isAnonymous(), link, name));
                                        line = line.substring(nextSpace);
                                } else {
                                        parts.add(createPlainTextPart(line.substring(0, next + 4)));
@@ -161,14 +216,17 @@ public class FreenetLinkParser implements Parser {
        /**
         * Creates a new link part based on a template.
         *
+        * @param anonymous
+        *            {@code true} if this link points to within freenet,
+        *            {@code false} if it points to the internet
         * @param link
         *            The target of the link
         * @param name
         *            The name of the link
         * @return The part that displays the link
         */
-       private Part createLinkPart(String link, String name) {
-               return new TemplatePart(templateFactory.createTemplate(new StringReader("<a href=\"/<% link|html>\"><% name|html></a>"))).set("link", link).set("name", name);
+       private Part createLinkPart(boolean anonymous, String link, String name) {
+               return new TemplatePart(templateFactory.createTemplate(new StringReader("<a <%if !anonymous>class=\"internet\" <%/if>href=\"<% link|html>\"><% name|html></a>"))).set("anonymous", anonymous).set("link", "/" + link).set("name", name);
        }
 
 }
index 2b27a89..1663e08 100644 (file)
@@ -55,7 +55,7 @@ public class TemplatePart implements Part {
         *            The value of the variable
         * @return This template part (for method chaining)
         */
-       public TemplatePart set(String key, String value) {
+       public TemplatePart set(String key, Object value) {
                template.set(key, value);
                return this;
        }
index c34d8ca..dcfd8a7 100644 (file)
@@ -68,7 +68,7 @@ public class LoginPage extends SoneTemplatePage {
                template.set("sones", localSones);
                if (request.getMethod() == Method.POST) {
                        String soneId = request.getHttpRequest().getPartAsStringFailsafe("sone-id", 100);
-                       Sone selectedSone = webInterface.getCore().getLocalSone(soneId);
+                       Sone selectedSone = webInterface.getCore().getLocalSone(soneId, false);
                        if (selectedSone != null) {
                                setCurrentSone(request.getToadletContext(), selectedSone);
                                throw new RedirectException("index.html");
index 1612e28..ee8b9bf 100644 (file)
@@ -575,6 +575,22 @@ public class WebInterface implements CoreListener {
        }
 
        /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void postRemoved(Post post) {
+               /* TODO */
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void replyRemoved(Reply reply) {
+               /* TODO */
+       }
+
+       /**
         * Template provider implementation that uses
         * {@link WebInterface#createReader(String)} to load templates for
         * inclusion.
index 32ff6c6..d0c44f5 100644 (file)
@@ -95,6 +95,8 @@ Page.ViewSone.PostList.Text.NoPostYet=This Sone has not yet posted anything.
 
 Page.ViewPost.Title=View Post - Sone
 Page.ViewPost.Page.Title=View Post by {sone}
+Page.ViewPost.Page.TitleUnknownSone=View Unknown Post
+Page.ViewPost.Text.UnknownPost=This post has not yet been downloaded.
 
 Page.Like.Title=Like Post - Sone
 Page.Unlike.Title=Unlike Post - Sone
index 7f7c4af..c978cf2 100644 (file)
@@ -49,6 +49,10 @@ textarea {
        color: rgb(255, 172, 0);
 }
 
+#sone a.internet {
+       color: rgb(255, 0, 0);
+}
+
 #sone a img {
        border: none;
 }
index 69c11e0..acb1d46 100644 (file)
@@ -449,16 +449,18 @@ function ajaxifyPost(postElement) {
                inputField = $(this.form).find(":input:enabled").get(0);
                postId = getPostId(this);
                text = $(inputField).val();
-               postReply(postId, text, function(success, error, replyId) {
-                       if (success) {
-                               $(inputField).val("");
-                               loadNewReply(replyId);
-                               markPostAsKnown(getPostElement(inputField));
-                               $("#sone .post#" + postId + " .create-reply").addClass("hidden");
-                       } else {
-                               alert(error);
-                       }
-               });
+               (function(postId, text, inputField) {
+                       postReply(postId, text, function(success, error, replyId) {
+                               if (success) {
+                                       $(inputField).val("");
+                                       loadNewReply(replyId);
+                                       markPostAsKnown(getPostElement(inputField));
+                                       $("#sone .post#" + postId + " .create-reply").addClass("hidden");
+                               } else {
+                                       alert(error);
+                               }
+                       });
+               })(postId, text, inputField);
                return false;
        });
 
@@ -534,7 +536,7 @@ function ajaxifyReply(replyElement) {
 
        /* mark post and all replies as known on click. */
        $(replyElement).click(function() {
-               markPostAsKnown(getPostElement(replyElement));
+               markPostAsKnown(getPostElement(this));
        });
 }
 
@@ -677,9 +679,12 @@ function loadNewPost(postId) {
        if (postId in loadedPosts) {
                return;
        }
-       loadedPosts[postId] = true;
        $.getJSON("getPost.ajax", { "post" : postId }, function(data, textStatus) {
                if ((data != null) && data.success) {
+                       if (data.post.id in loadedPosts) {
+                               return;
+                       }
+                       loadedPosts[data.post.id] = true;
                        if (!isIndexPage() && !(isViewSonePage() && ((getShownSoneId() == data.post.sone) || (getShownSoneId() == data.post.recipient)))) {
                                return;
                        }
@@ -707,10 +712,13 @@ function loadNewReply(replyId) {
        if (replyId in loadedReplies) {
                return;
        }
-       loadedReplies[replyId] = true;
        $.getJSON("getReply.ajax", { "reply": replyId }, function(data, textStatus) {
                /* find post. */
                if ((data != null) && data.success) {
+                       if (data.reply.id in loadedReplies) {
+                               return;
+                       }
+                       loadedReplies[data.reply.id] = true;
                        $("#sone .post#" + data.reply.postId).each(function() {
                                var firstNewerReply = null;
                                $(this).find(".replies .reply").each(function() {
index 3de8ebc..a61f858 100644 (file)
@@ -3,8 +3,14 @@
        <div class="page-id hidden">view-post</div>
        <div class="post-id hidden"><% post.id|html></div>
 
-       <h1><%= Page.ViewPost.Page.Title|l10n|insert needle="{sone}" key=post.sone.niceName|html></h1>
+       <%ifnull post.sone>
+               <h1><%= Page.ViewPost.Page.TitleUnknownSone|l10n|html></h1>
 
-       <%include include/viewPost.html>
+               <p><%= Page.ViewPost.Text.UnknownPost|l10n|html></p>
+       <%else>
+               <h1><%= Page.ViewPost.Page.Title|l10n|insert needle="{sone}" key=post.sone.niceName|html></h1>
+
+               <%include include/viewPost.html>
+       <%/if>
 
 <%include include/tail.html>