<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>
if (!soneRescueMode) {
for (Post post : storedSone.getPosts()) {
posts.remove(post.getId());
+ if (!sone.getPosts().contains(post)) {
+ coreListenerManager.firePostRemoved(post);
+ }
}
}
synchronized (newPosts) {
if (!soneRescueMode) {
for (Reply reply : storedSone.getReplies()) {
replies.remove(reply.getId());
+ if (!sone.getReplies().contains(reply)) {
+ coreListenerManager.fireReplyRemoved(reply);
+ }
}
}
synchronized (newReplies) {
*/
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);
+
}
}
}
+ /**
+ * 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);
+ }
+ }
+
}
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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>>();
}
/** 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.
* @return This post (for method chaining)
*/
public Post setRecipient(Sone recipient) {
- this.recipient = recipient;
+ if (!sone.equals(recipient)) {
+ this.recipient = recipient;
+ }
return this;
}
}
/** 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);
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));
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) {
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.
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;
+ }
}
* {@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);
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;
}
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);
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)));
/**
* 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);
}
}
* 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;
}
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");
}
/**
+ * {@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.
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
color: rgb(255, 172, 0);
}
+#sone a.internet {
+ color: rgb(255, 0, 0);
+}
+
#sone a img {
border: none;
}
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;
});
/* mark post and all replies as known on click. */
$(replyElement).click(function() {
- markPostAsKnown(getPostElement(replyElement));
+ markPostAsKnown(getPostElement(this));
});
}
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;
}
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() {
<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>