Merge remote-tracking branch 'hernic/patch-1' into next
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Sun, 9 Jun 2013 08:25:19 +0000 (10:25 +0200)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Sun, 9 Jun 2013 08:25:19 +0000 (10:25 +0200)
pom.xml
src/main/java/net/pterodactylus/sone/core/Core.java
src/main/java/net/pterodactylus/sone/fcp/FcpInterface.java
src/main/java/net/pterodactylus/sone/template/ParserFilter.java
src/main/java/net/pterodactylus/sone/text/SoneTextParser.java
src/main/java/net/pterodactylus/sone/web/EditProfilePage.java
src/main/java/net/pterodactylus/sone/web/ajax/JsonPage.java
src/main/resources/i18n/sone.fr.properties
src/main/resources/templates/editProfile.html
src/test/java/net/pterodactylus/sone/text/SoneTextParserTest.java

diff --git a/pom.xml b/pom.xml
index 3b894ae..0e2cc60 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
                <dependency>
                        <groupId>net.pterodactylus</groupId>
                        <artifactId>utils</artifactId>
-                       <version>0.12.2</version>
+                       <version>0.12.3-SNAPSHOT</version>
                </dependency>
                <dependency>
                        <groupId>junit</groupId>
index c391582..5843b30 100644 (file)
@@ -59,6 +59,7 @@ import net.pterodactylus.util.config.ConfigurationException;
 import net.pterodactylus.util.logging.Logging;
 import net.pterodactylus.util.number.Numbers;
 import net.pterodactylus.util.service.AbstractService;
+import net.pterodactylus.util.thread.NamedThreadFactory;
 import net.pterodactylus.util.thread.Ticker;
 import net.pterodactylus.util.validation.EqualityValidator;
 import net.pterodactylus.util.validation.IntegerRangeValidator;
@@ -108,7 +109,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
        private final ImageInserter imageInserter;
 
        /** Sone downloader thread-pool. */
-       private final ExecutorService soneDownloaders = Executors.newFixedThreadPool(10);
+       private final ExecutorService soneDownloaders = Executors.newFixedThreadPool(10, new NamedThreadFactory("Sone Downloader %2$d"));
 
        /** The update checker. */
        private final UpdateChecker updateChecker;
@@ -1160,8 +1161,8 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                                                if (!storedPosts.contains(post)) {
                                                        if (post.getTime() < getSoneFollowingTime(sone)) {
                                                                knownPosts.add(post.getId());
+                                                               post.setKnown(true);
                                                        } else if (!knownPosts.contains(post.getId())) {
-                                                               sone.setKnown(false);
                                                                coreListenerManager.fireNewPostFound(post);
                                                        }
                                                }
@@ -1185,8 +1186,8 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                                                if (!storedReplies.contains(reply)) {
                                                        if (reply.getTime() < getSoneFollowingTime(sone)) {
                                                                knownReplies.add(reply.getId());
+                                                               reply.setKnown(true);
                                                        } else if (!knownReplies.contains(reply.getId())) {
-                                                               reply.setKnown(false);
                                                                coreListenerManager.fireNewReplyFound(reply);
                                                        }
                                                }
@@ -1579,6 +1580,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         * @return The created post
         */
        public Post createPost(Sone sone, Sone recipient, long time, String text) {
+               Validation.begin().isNotNull("Text", text).check().isGreater("Text Length", text.length(), 0).check();
                if (!isLocalSone(sone)) {
                        logger.log(Level.FINE, String.format("Tried to create post for non-local Sone: %s", sone));
                        return null;
@@ -1719,6 +1721,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         * @return The created reply
         */
        public PostReply createReply(Sone sone, Post post, long time, String text) {
+               Validation.begin().isNotNull("Text", text).check().isGreater("Text Length", text.trim().length(), 0).check();
                if (!isLocalSone(sone)) {
                        logger.log(Level.FINE, String.format("Tried to create reply for non-local Sone: %s", sone));
                        return null;
@@ -2412,7 +2415,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         */
        @Override
        public void identityUpdated(OwnIdentity ownIdentity, final Identity identity) {
-               new Thread(new Runnable() {
+               soneDownloaders.execute(new Runnable() {
 
                        @Override
                        @SuppressWarnings("synthetic-access")
@@ -2423,7 +2426,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                                soneDownloader.addSone(sone);
                                soneDownloader.fetchSone(sone);
                        }
-               }).start();
+               });
        }
 
        /**
index b3222be..c1ed953 100644 (file)
@@ -27,7 +27,6 @@ import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.freenet.fcp.Command.AccessType;
 import net.pterodactylus.sone.freenet.fcp.Command.ErrorResponse;
 import net.pterodactylus.sone.freenet.fcp.Command.Response;
-import net.pterodactylus.sone.freenet.fcp.FcpException;
 import net.pterodactylus.util.logging.Logging;
 import net.pterodactylus.util.validation.Validation;
 import freenet.pluginmanager.FredPluginFCP;
@@ -172,9 +171,9 @@ public class FcpInterface {
                        try {
                                Response response = command.execute(parameters, data, AccessType.values()[accessType]);
                                sendReply(pluginReplySender, identifier, response);
-                       } catch (FcpException fe1) {
+                       } catch (Exception e1) {
                                logger.log(Level.WARNING, "Could not process FCP command “%s”.", command);
-                               sendReply(pluginReplySender, identifier, new ErrorResponse("Error executing command: " + fe1.getMessage()));
+                               sendReply(pluginReplySender, identifier, new ErrorResponse("Error executing command: " + e1.getMessage()));
                        }
                } catch (PluginNotFoundException pnfe1) {
                        logger.log(Level.WARNING, "Could not find destination plugin: " + pluginReplySender);
index 75eff8b..b8af831 100644 (file)
@@ -20,7 +20,9 @@ package net.pterodactylus.sone.template;
 import java.io.IOException;
 import java.io.StringReader;
 import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
 import java.io.Writer;
+import java.net.URLEncoder;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -213,7 +215,12 @@ public class ParserFilter implements Filter {
         *            The part to render
         */
        private void render(Writer writer, LinkPart linkPart) {
-               renderLink(writer, "/?_CHECKED_HTTP_=" + linkPart.getLink(), linkPart.getText(), linkPart.getTitle(), "internet");
+               try {
+                       renderLink(writer, "/external-link/?_CHECKED_HTTP_=" + URLEncoder.encode(linkPart.getLink(), "UTF-8"), linkPart.getText(), linkPart.getTitle(), "internet");
+               } catch (UnsupportedEncodingException uee1) {
+                       /* not possible for UTF-8. */
+                       throw new RuntimeException("The JVM does not support UTF-8 encoding!", uee1);
+               }
        }
 
        /**
index baa3dc9..b3593ec 100644 (file)
@@ -55,28 +55,50 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
        private enum LinkType {
 
                /** Link is a KSK. */
-               KSK,
+               KSK("KSK@"),
 
                /** Link is a CHK. */
-               CHK,
+               CHK("CHK@"),
 
                /** Link is an SSK. */
-               SSK,
+               SSK("SSK@"),
 
                /** Link is a USK. */
-               USK,
+               USK("USK@"),
 
                /** Link is HTTP. */
-               HTTP,
+               HTTP("http://"),
 
                /** Link is HTTPS. */
-               HTTPS,
+               HTTPS("https://"),
 
                /** Link is a Sone. */
-               SONE,
+               SONE("sone://"),
 
                /** Link is a post. */
-               POST,
+               POST("post://");
+
+               /** The scheme identifying this link type. */
+               private final String scheme;
+
+               /**
+                * Creates a new link type identified by the given scheme.
+                *
+                * @param scheme
+                *            The scheme of the link type
+                */
+               private LinkType(String scheme) {
+                       this.scheme = scheme;
+               }
+
+               /**
+                * Returns the scheme of this link type.
+                *
+                * @return The scheme of this link type
+                */
+               public String getScheme() {
+                       return scheme;
+               }
 
        }
 
@@ -200,6 +222,20 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                                        }
                                        lineComplete = false;
 
+                                       Matcher matcher = whitespacePattern.matcher(line);
+                                       int nextSpace = matcher.find(0) ? matcher.start() : line.length();
+                                       String link = line.substring(0, nextSpace);
+                                       String name = link;
+                                       logger.log(Level.FINER, String.format("Found link: %s", link));
+                                       logger.log(Level.FINEST, String.format("CHK: %d, SSK: %d, USK: %d", nextChk, nextSsk, nextUsk));
+
+                                       /* if there is no text after the scheme, it’s not a link! */
+                                       if (link.equals(linkType.getScheme())) {
+                                               parts.add(new PlainTextPart(linkType.getScheme()));
+                                               line = line.substring(linkType.getScheme().length());
+                                               continue;
+                                       }
+
                                        if (linkType == LinkType.SONE) {
                                                if (line.length() >= (7 + 43)) {
                                                        String soneId = line.substring(7, 50);
@@ -235,12 +271,6 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                                                }
                                                continue;
                                        }
-                                       Matcher matcher = whitespacePattern.matcher(line);
-                                       int nextSpace = matcher.find(0) ? matcher.start() : line.length();
-                                       String link = line.substring(0, nextSpace);
-                                       String name = link;
-                                       logger.log(Level.FINER, String.format("Found link: %s", link));
-                                       logger.log(Level.FINEST, String.format("CHK: %d, SSK: %d, USK: %d", nextChk, nextSsk, nextUsk));
 
                                        if ((linkType == LinkType.KSK) || (linkType == LinkType.CHK) || (linkType == LinkType.SSK) || (linkType == LinkType.USK)) {
                                                FreenetURI uri;
index a1b759b..51a72b8 100644 (file)
@@ -77,7 +77,7 @@ public class EditProfilePage extends SoneTemplatePage {
                                birthDay = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("birth-day", 256).trim());
                                birthMonth = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("birth-month", 256).trim());
                                birthYear = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("birth-year", 256).trim());
-                               avatarId = request.getHttpRequest().getPartAsStringFailsafe("avatar-id", 36);
+                               avatarId = request.getHttpRequest().getPartAsStringFailsafe("avatarId", 36);
                                profile.setFirstName(firstName.length() > 0 ? firstName : null);
                                profile.setMiddleName(middleName.length() > 0 ? middleName : null);
                                profile.setLastName(lastName.length() > 0 ? lastName : null);
@@ -139,7 +139,7 @@ public class EditProfilePage extends SoneTemplatePage {
                templateContext.set("birthDay", birthDay);
                templateContext.set("birthMonth", birthMonth);
                templateContext.set("birthYear", birthYear);
-               templateContext.set("avatar-id", avatarId);
+               templateContext.set("avatarId", avatarId);
                templateContext.set("fields", fields);
        }
 
index 22eeec8..910abd5 100644 (file)
 
 package net.pterodactylus.sone.web.ajax;
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
 import java.net.URI;
 
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetPage;
 import net.pterodactylus.sone.web.page.FreenetRequest;
+import net.pterodactylus.util.io.Closer;
 import net.pterodactylus.util.json.JsonObject;
 import net.pterodactylus.util.json.JsonUtils;
 import net.pterodactylus.util.web.Page;
@@ -218,8 +222,12 @@ public abstract class JsonPage implements FreenetPage {
                                return response.setStatusCode(403).setStatusText("Forbidden").setContentType("application/json").write(JsonUtils.format(new JsonObject().put("success", false).put("error", "auth-required")));
                        }
                }
-               JsonObject jsonObject = createJsonObject(request);
-               return response.setStatusCode(200).setStatusText("OK").setContentType("application/json").write(JsonUtils.format(jsonObject));
+               try {
+                       JsonObject jsonObject = createJsonObject(request);
+                       return response.setStatusCode(200).setStatusText("OK").setContentType("application/json").write(JsonUtils.format(jsonObject));
+               } catch (Exception e1) {
+                       return response.setStatusCode(500).setStatusText(e1.getMessage()).setContentType("text/plain").write(dumpStackTrace(e1));
+               }
        }
 
        /**
@@ -230,4 +238,36 @@ public abstract class JsonPage implements FreenetPage {
                return false;
        }
 
+       //
+       // PRIVATE METHODS
+       //
+
+       /**
+        * Returns a byte array containing the stack trace of the given throwable.
+        *
+        * @param t
+        *            The throwable whose stack trace to dump into an array
+        * @return The array with the stack trace, or an empty array if the stack
+        *         trace could not be dumped
+        */
+       private static byte[] dumpStackTrace(Throwable t) {
+               ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+               OutputStreamWriter writer = null;
+               PrintWriter printWriter = null;
+               try {
+                       writer = new OutputStreamWriter(byteArrayOutputStream, "uTF-8");
+                       printWriter = new PrintWriter(writer);
+                       t.printStackTrace(printWriter);
+                       byteArrayOutputStream.flush();
+                       return byteArrayOutputStream.toByteArray();
+               } catch (IOException ioe1) {
+                       /* quite not possible. */
+                       return new byte[0];
+               } finally {
+                       Closer.close(printWriter);
+                       Closer.close(writer);
+                       Closer.close(byteArrayOutputStream);
+               }
+       }
+
 }
index 29a93ee..047a812 100644 (file)
@@ -376,18 +376,18 @@ View.Time.InTheFuture=dans le futur
 View.Time.AFewSecondsAgo=au cours des dernières secondes passées
 View.Time.HalfAMinuteAgo=au cours des 30 dernières secondes
 View.Time.AMinuteAgo=au cours de la dernière minute
-View.Time.XMinutesAgo=${min} il y a quelques minutes
+View.Time.XMinutesAgo=il y a environs {min} minutes
 View.Time.HalfAnHourAgo=au cours de la dernière demi heure
 View.Time.AnHourAgo=il y a environ une heure
-View.Time.XHoursAgo=${heure} au cours des dernières heures
+View.Time.XHoursAgo=Il y a environ ${hour} heures
 View.Time.ADayAgo=il y a environ un jour
-View.Time.XDaysAgo=${jour} il y a quelques jours
+View.Time.XDaysAgo=il y a plus ou moins ${day} jours
 View.Time.AWeekAgo=il y a environ une semaine
-View.Time.XWeeksAgo=${semaine} au cours des dernières semaines
+View.Time.XWeeksAgo=au cours des dernières ${week}semaines
 View.Time.AMonthAgo=au cours du dernier mois
-View.Time.XMonthsAgo=${mois} au cours des derniers mois
+View.Time.XMonthsAgo=au cours des derniers ${month} mois
 View.Time.AYearAgo=au cours de la dernière année
-View.Time.XYearsAgo=${année} au cours des dernières années
+View.Time.XYearsAgo=au cours des dernières ${year} années
 
 WebInterface.DefaultText.StatusUpdate=Exprimez-vous
 WebInterface.DefaultText.Message=Écrire un message...
index f0b8b56..ddc08a9 100644 (file)
 
                <ul id="avatar-selection">
                        <li id="no-avatar">
-                               <input type="radio" name="avatar-id" value="none"<%ifnull avatar-id> checked="checked"<%/if>/>
+                               <input type="radio" name="avatarId" value="none"<%ifnull avatarId> checked="checked"<%/if>/>
                                <%= Page.EditProfile.Avatar.Delete|l10n|html>
                        </li>
                        <%foreach currentSone.allImages image>
                                <li>
-                                       <input type="radio" name="avatar-id" value="<%image.id|html>"<%if avatar-id|match key=image.id> checked="checked"<%/if>/>
+                                       <input type="radio" name="avatarId" value="<%image.id|html>"<%if avatarId|match value=image.id> checked="checked"<%/if>/>
                                        <div class="post-avatar"><% image|image-link max-width==48 max-height==48 mode==enlarge title=image.title></div>
                                </li>
                        <%/foreach>
index 7baceaa..e1857a0 100644 (file)
@@ -19,6 +19,7 @@ package net.pterodactylus.sone.text;
 
 import java.io.IOException;
 import java.io.StringReader;
+import java.util.Arrays;
 
 import junit.framework.TestCase;
 import net.pterodactylus.sone.core.SoneProvider;
@@ -106,6 +107,24 @@ public class SoneTextParserTest extends TestCase {
                assertEquals("Part Text", "Some text.\n\nLink to [Sone|DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU] and stuff.", convertText(parts, PlainTextPart.class, SonePart.class));
        }
 
+       /**
+        * Test for a bug discovered in Sone 0.8.4 where a plain “http://” would be
+        * parsed into a link.
+        *
+        * @throws IOException
+        *             if an I/O error occurs
+        */
+       @SuppressWarnings({ "synthetic-access", "static-method" })
+       public void testEmpyHttpLinks() throws IOException {
+               SoneTextParser soneTextParser = new SoneTextParser(new TestSoneProvider(), null);
+               Iterable<Part> parts;
+
+               /* check empty http links. */
+               parts = soneTextParser.parse(null, new StringReader("Some text. Empty link: http:// – nice!"));
+               assertNotNull("Parts", parts);
+               assertEquals("Part Text", "Some text. Empty link: http:// – nice!", convertText(parts, PlainTextPart.class));
+       }
+
        //
        // PRIVATE METHODS
        //
@@ -133,7 +152,7 @@ public class SoneTextParserTest extends TestCase {
                                }
                        }
                        if (!classValid) {
-                               assertEquals("Part’s Class", null, part.getClass());
+                               fail("Part’s Class (" + part.getClass() + ") is not one of " + Arrays.toString(validClasses));
                        }
                        if (part instanceof PlainTextPart) {
                                text.append(((PlainTextPart) part).getText());