<modelVersion>4.0.0</modelVersion>
<groupId>net.pterodactylus</groupId>
<artifactId>sone</artifactId>
- <version>0.6.2</version>
+ <version>0.6.3</version>
<dependencies>
<dependency>
<groupId>net.pterodactylus</groupId>
<artifactId>utils</artifactId>
- <version>0.9.4</version>
+ <version>0.9.5</version>
</dependency>
<dependency>
<groupId>junit</groupId>
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
}
Sone sone = addLocalSone(ownIdentity);
sone.getOptions().addBooleanOption("AutoFollow", new DefaultOption<Boolean>(false));
+ sone.addFriend("nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI");
saveSone(sone);
return sone;
}
@Override
@SuppressWarnings("synthetic-access")
public void run() {
- soneDownloader.fetchSone(sone);
+ soneDownloader.fetchSone(sone, sone.getRequestUri());
}
}, "Sone Downloader").start();
@Override
public void identityRemoved(OwnIdentity ownIdentity, Identity identity) {
trustedIdentities.get(ownIdentity).remove(identity);
+ boolean foundIdentity = false;
+ for (Entry<OwnIdentity, Set<Identity>> trustedIdentity : trustedIdentities.entrySet()) {
+ if (trustedIdentity.getKey().equals(ownIdentity)) {
+ continue;
+ }
+ if (trustedIdentity.getValue().contains(identity)) {
+ foundIdentity = true;
+ }
+ }
+ if (foundIdentity) {
+ /* some local identity still trusts this identity, don’t remove. */
+ return;
+ }
+ Sone sone = getSone(identity.getId(), false);
+ if (sone == null) {
+ /* TODO - we don’t have the Sone anymore. should this happen? */
+ return;
+ }
+ synchronized (posts) {
+ synchronized (newPosts) {
+ for (Post post : sone.getPosts()) {
+ posts.remove(post.getId());
+ newPosts.remove(post.getId());
+ coreListenerManager.firePostRemoved(post);
+ }
+ }
+ }
+ synchronized (replies) {
+ synchronized (newReplies) {
+ for (Reply reply : sone.getReplies()) {
+ replies.remove(reply.getId());
+ newReplies.remove(reply.getId());
+ coreListenerManager.fireReplyRemoved(reply);
+ }
+ }
+ }
+ synchronized (remoteSones) {
+ remoteSones.remove(identity.getId());
+ }
+ synchronized (newSones) {
+ newSones.remove(identity.getId());
+ }
}
//
synchronized (sone) {
if (lastInsertFingerprint.equals(sone.getFingerprint())) {
logger.log(Level.FINE, "Sone “%s” was not modified further, resetting counter…", new Object[] { sone });
- core.saveSone(sone);
lastModificationTime = 0;
modified = false;
}
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
- private static class InsertInformation {
+ private class InsertInformation {
/** All properties of the Sone, copied for thread safety. */
private final Map<String, Object> soneProperties = new HashMap<String, Object>();
TemplateContext templateContext = templateContextFactory.createTemplateContext();
templateContext.set("currentSone", soneProperties);
+ templateContext.set("currentEdition", core.getUpdateChecker().getLatestEdition());
templateContext.set("version", SonePlugin.VERSION);
StringWriter writer = new StringWriter();
StringBucket bucket = null;
private static final String SONE_HOMEPAGE = "USK@nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI,DuQSUZiI~agF8c-6tjsFFGuZ8eICrzWCILB60nT8KKo,AQACAAE/sone/";
/** The current latest known edition. */
- private static final int LATEST_EDITION = 25;
+ private static final int LATEST_EDITION = 35;
/** The Freenet interface. */
private final FreenetInterface freenetInterface;
}
/** The version. */
- public static final Version VERSION = new Version(0, 6, 2);
+ public static final Version VERSION = new Version(0, 6, 3);
/** The logger. */
private static final Logger logger = Logging.getLogger(SonePlugin.class);
import net.pterodactylus.sone.data.Post;
import net.pterodactylus.sone.data.Reply;
import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.freenet.wot.OwnIdentity;
+import net.pterodactylus.sone.freenet.wot.Trust;
import net.pterodactylus.util.notify.Notification;
+import net.pterodactylus.util.validation.Validation;
/**
* Filter for {@link ListNotification}s.
}
List<Post> newPosts = new ArrayList<Post>();
for (Post post : newPostNotification.getElements()) {
- if (currentSone.hasFriend(post.getSone().getId()) || currentSone.equals(post.getSone()) || currentSone.equals(post.getRecipient())) {
+ if (isPostVisible(currentSone, post)) {
newPosts.add(post);
}
}
}
List<Reply> newReplies = new ArrayList<Reply>();
for (Reply reply : newReplyNotification.getElements()) {
- if (((reply.getPost().getSone() != null) && currentSone.hasFriend(reply.getPost().getSone().getId())) || currentSone.equals(reply.getPost().getSone()) || currentSone.equals(reply.getPost().getRecipient())) {
+ if (isPostVisible(currentSone, reply.getPost())) {
newReplies.add(reply);
}
}
return null;
}
+ /**
+ * Checks whether a post is visible to the given Sone. A post is not
+ * considered visible if one of the following statements is true:
+ * <ul>
+ * <li>The post does not have a Sone.</li>
+ * <li>The Sone of the post is not the given Sone, the given Sone does not
+ * follow the post’s Sone, and the given Sone is not the recipient of the
+ * post.</li>
+ * <li>The trust relationship between the two Sones can not be retrieved.</li>
+ * <li>The given Sone has explicitely assigned negative trust to the post’s
+ * Sone.</li>
+ * <li>The given Sone has not explicitely assigned negative trust to the
+ * post’s Sone but the implicit trust is negative.</li>
+ * </ul>
+ * If none of these statements is true the post is considered visible.
+ *
+ * @param sone
+ * The Sone that checks for a post’s visibility
+ * @param post
+ * The post to check for visibility
+ * @return {@code true} if the post is considered visible, {@code false}
+ * otherwise
+ */
+ public static boolean isPostVisible(Sone sone, Post post) {
+ Validation.begin().isNotNull("Sone", sone).isNotNull("Post", post).check().isNotNull("Sone’s Identity", sone.getIdentity()).check().isInstanceOf("Sone’s Identity", sone.getIdentity(), OwnIdentity.class).check();
+ Sone postSone = post.getSone();
+ if (postSone == null) {
+ return false;
+ }
+ Trust trust = postSone.getIdentity().getTrust((OwnIdentity) sone.getIdentity());
+ if (trust != null) {
+ if ((trust.getExplicit() != null) && (trust.getExplicit() < 0)) {
+ return false;
+ }
+ if ((trust.getExplicit() == null) && (trust.getImplicit() != null) && (trust.getImplicit() < 0)) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ if ((!postSone.equals(sone)) && !sone.hasFriend(postSone.getId()) && !sone.equals(post.getRecipient())) {
+ return false;
+ }
+ return true;
+ }
+
}
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.text.FreenetLinkParser;
import net.pterodactylus.sone.text.FreenetLinkParserContext;
+import net.pterodactylus.sone.web.page.Page.Request;
import net.pterodactylus.util.template.Filter;
import net.pterodactylus.util.template.TemplateContext;
import net.pterodactylus.util.template.TemplateContextFactory;
if (sone == null) {
sone = core.getSone(soneKey, false);
}
- FreenetLinkParserContext context = new FreenetLinkParserContext(sone);
+ FreenetLinkParserContext context = new FreenetLinkParserContext((Request) templateContext.get("request"), sone);
try {
return linkParser.parse(context, new StringReader(text));
} catch (IOException ioe1) {
}
emptyLines = 0;
boolean lineComplete = true;
+
+ /* filter http(s) links to own node. */
+ String hostHeader = (context.getRequest() != null) ? context.getRequest().getHttpRequest().getHeader("host") : null;
+ logger.log(Level.FINEST, "hostHeader: %s", hostHeader);
+ if (hostHeader != null) {
+ for (String toRemove : new String[] { "http://" + hostHeader + "/", "https://" + hostHeader + "/", "http://" + hostHeader, "https://" + hostHeader }) {
+ while (line.indexOf(toRemove) != -1) {
+ line = line.replace(toRemove, "");
+ }
+ }
+ }
+
while (line.length() > 0) {
int nextKsk = line.indexOf("KSK@");
int nextChk = line.indexOf("CHK@");
} else if (linkType == LinkType.POST) {
String postId = link.substring(7);
Post post = core.getPost(postId, false);
- if (post != null) {
+ if ((post != null) && (post.getSone() != null)) {
String postText = post.getText();
postText = postText.substring(0, Math.min(postText.length(), 20)) + "…";
Sone postSone = post.getSone();
package net.pterodactylus.sone.text;
import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.web.page.Page.Request;
/**
* {@link ParserContext} implementation for the {@link FreenetLinkParser}. It
*/
public class FreenetLinkParserContext implements ParserContext {
+ /** The request being processed. */
+ private final Request request;
+
/** The posting Sone. */
private final Sone postingSone;
/**
* Creates a new link parser context.
*
+ * @param request
+ * The request being processed
* @param postingSone
* The posting Sone
*/
- public FreenetLinkParserContext(Sone postingSone) {
+ public FreenetLinkParserContext(Request request, Sone postingSone) {
+ this.request = request;
this.postingSone = postingSone;
}
/**
+ * Returns the request that is currently being processed.
+ *
+ * @return The request being processed
+ */
+ public Request getRequest() {
+ return request;
+ }
+
+ /**
* Returns the Sone that provided the text that is being parsed.
*
* @return The posting Sone
import net.pterodactylus.sone.data.Post;
import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.notify.ListNotificationFilters;
import net.pterodactylus.util.collection.Pagination;
+import net.pterodactylus.util.filter.Filter;
import net.pterodactylus.util.filter.Filters;
import net.pterodactylus.util.number.Numbers;
import net.pterodactylus.util.template.Template;
@Override
protected void processTemplate(Request request, TemplateContext templateContext) throws RedirectException {
super.processTemplate(request, templateContext);
- Sone currentSone = getCurrentSone(request.getToadletContext());
+ final Sone currentSone = getCurrentSone(request.getToadletContext());
List<Post> allPosts = new ArrayList<Post>();
allPosts.addAll(currentSone.getPosts());
for (String friendSoneId : currentSone.getFriends()) {
}
}
}
+ allPosts = Filters.filteredList(allPosts, new Filter<Post>() {
+
+ @Override
+ public boolean filterObject(Post post) {
+ return ListNotificationFilters.isPostVisible(currentSone, post);
+ }
+ });
allPosts = Filters.filteredList(allPosts, Post.FUTURE_POSTS_FILTER);
Collections.sort(allPosts, Post.TIME_COMPARATOR);
Pagination<Post> pagination = new Pagination<Post>(allPosts, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(Numbers.safeParseInteger(request.getHttpRequest().getParam("page"), 0));
import net.pterodactylus.sone.data.Profile.Field;
import net.pterodactylus.sone.data.Reply;
import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.util.collection.Converter;
-import net.pterodactylus.util.collection.Converters;
+import net.pterodactylus.util.collection.Mapper;
+import net.pterodactylus.util.collection.Mappers;
import net.pterodactylus.util.collection.Pagination;
import net.pterodactylus.util.filter.Filter;
import net.pterodactylus.util.filter.Filters;
Collections.sort(sortedPostHits, Hit.DESCENDING_COMPARATOR);
/* extract Sones and posts. */
- List<Sone> resultSones = Converters.convertList(sortedSoneHits, new HitConverter<Sone>());
- List<Post> resultPosts = Converters.convertList(sortedPostHits, new HitConverter<Post>());
+ List<Sone> resultSones = Mappers.mappedList(sortedSoneHits, new HitMapper<Sone>());
+ List<Post> resultPosts = Mappers.mappedList(sortedPostHits, new HitMapper<Post>());
/* pagination. */
Pagination<Sone> sonePagination = new Pagination<Sone>(resultSones, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(Numbers.safeParseInteger(request.getHttpRequest().getParam("sonePage"), 0));
* The type of the object to extract
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
- public static class HitConverter<T> implements Converter<Hit<T>, T> {
+ public static class HitMapper<T> implements Mapper<Hit<T>, T> {
/**
* {@inheritDoc}
*/
@Override
- public T convert(Hit<T> input) {
+ public T map(Hit<T> input) {
return input.getObject();
}
if (post == null) {
return createErrorJsonObject("invalid-post-id");
}
- return createSuccessJsonObject().put("post", createJsonPost(post, getCurrentSone(request.getToadletContext())));
+ return createSuccessJsonObject().put("post", createJsonPost(request, post, getCurrentSone(request.getToadletContext())));
}
/**
* The currently logged in Sone (to store in the template)
* @return The JSON representation of the post
*/
- private JsonObject createJsonPost(Post post, Sone currentSone) {
+ private JsonObject createJsonPost(Request request, Post post, Sone currentSone) {
JsonObject jsonPost = new JsonObject();
jsonPost.put("id", post.getId());
jsonPost.put("sone", post.getSone().getId());
jsonPost.put("time", post.getTime());
StringWriter stringWriter = new StringWriter();
TemplateContext templateContext = webInterface.getTemplateContextFactory().createTemplateContext();
+ templateContext.set("request", request);
templateContext.set("post", post);
templateContext.set("currentSone", currentSone);
templateContext.set("localSones", webInterface.getCore().getLocalSones());
if ((reply == null) || (reply.getSone() == null)) {
return createErrorJsonObject("invalid-reply-id");
}
- return createSuccessJsonObject().put("reply", createJsonReply(reply, getCurrentSone(request.getToadletContext())));
+ return createSuccessJsonObject().put("reply", createJsonReply(request, reply, getCurrentSone(request.getToadletContext())));
}
/**
* The currently logged in Sone (to store in the template)
* @return The JSON representation of the reply
*/
- private JsonObject createJsonReply(Reply reply, Sone currentSone) {
+ private JsonObject createJsonReply(Request request, Reply reply, Sone currentSone) {
JsonObject jsonReply = new JsonObject();
jsonReply.put("id", reply.getId());
jsonReply.put("postId", reply.getPost().getId());
jsonReply.put("time", reply.getTime());
StringWriter stringWriter = new StringWriter();
TemplateContext templateContext = webInterface.getTemplateContextFactory().createTemplateContext();
+ templateContext.set("request", request);
templateContext.set("reply", reply);
templateContext.set("currentSone", currentSone);
try {
@Override
public boolean filterObject(Reply reply) {
- return currentSone.hasFriend(reply.getPost().getSone().getId()) || currentSone.equals(reply.getPost().getSone()) || currentSone.equals(reply.getPost().getRecipient());
+ return (reply.getPost() != null) && (reply.getPost().getSone() != null) && (currentSone.hasFriend(reply.getPost().getSone().getId()) || currentSone.equals(reply.getPost().getSone()) || currentSone.equals(reply.getPost().getRecipient()));
}
});
Page.Options.Section.SoneSpecificOptions.Title=Sone-specific Options
Page.Options.Section.SoneSpecificOptions.NotLoggedIn=These options are only available if you are {link}logged in{/link}.
Page.Options.Section.SoneSpecificOptions.LoggedIn=These options are only available while you are logged in and they are only valid for the Sone you are logged in as.
-Page.Options.Option.AutoFollow.Description=If a new Sone is discovered, follow it automatically.
+Page.Options.Option.AutoFollow.Description=If a new Sone is discovered, follow it automatically. Note that this will only follow Sones that are discovered after you activate this option!
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.ViewSone.PostList.Text.NoPostYet=This Sone has not yet posted anything.
Page.ViewSone.Profile.Title=Profile
Page.ViewSone.Profile.Label.Name=Name
+Page.ViewSone.Profile.Name.WoTLink=Web of trust profile
Page.ViewSone.Replies.Title=Posts {sone} has replied to
Page.ViewPost.Title=View Post - Sone
<h1>Sone “<% currentSone.name|html>”</h1>
<p>
This page should not be viewed directly; it is merely a reminder
- that you should install the <i>Sone</i> plugin.
+ that you should install the <a href="/USK@nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI,DuQSUZiI~agF8c-6tjsFFGuZ8eICrzWCILB60nT8KKo,AQACAAE/sone/<% currentEdition>/">Sone</a> plugin.
</p>
</body>
</html>
+<form class="mark-as-read" action="markAsKnown.html" method="post">
+ <input type="hidden" name="formPassword" value="<% formPassword|html>" />
+ <input type="hidden" name="returnPage" value="<% request.uri|html>" />
+ <input type="hidden" name="type" value="post" />
+ <input type="hidden" name="id" value="<%foreach posts post><% post.id|html><%notlast> <%/notlast><%/foreach>" />
+ <button type="submit" name="mark-read" value="true"><%= Notification.NewPost.Button.MarkRead|l10n|html></button>
+</form>
<div class="short-text hidden">
<%= Notification.NewPost.ShortText|l10n|html>
<a class="link" onclick="showNotificationDetails('<%notification.id|html>'); return false;"><%= Notification.ClickHereToRead|l10n|html></a>
</div>
<div class="text">
- <form class="mark-as-read" action="markAsKnown.html" method="post">
- <input type="hidden" name="formPassword" value="<% formPassword|html>" />
- <input type="hidden" name="returnPage" value="<% request.uri|html>" />
- <input type="hidden" name="type" value="post" />
- <input type="hidden" name="id" value="<%foreach posts post><% post.id|html><%notlast> <%/notlast><%/foreach>" />
- <button type="submit" name="mark-read" value="true"><%= Notification.NewPost.Button.MarkRead|l10n|html></button>
- </form>
<%= Notification.NewPost.Text|l10n|html>
<%foreach posts post>
<div class="hidden post-id"><%post.id|html></div>
+<form class="mark-as-read" action="markAsKnown.html" method="post">
+ <input type="hidden" name="formPassword" value="<% formPassword|html>" />
+ <input type="hidden" name="returnPage" value="<% request.uri|html>" />
+ <input type="hidden" name="type" value="reply" />
+ <input type="hidden" name="id" value="<%foreach replies reply><% reply.id|html><%notlast> <%/notlast><%/foreach>" />
+ <button type="submit" name="mark-read" value="true"><%= Notification.NewPost.Button.MarkRead|l10n|html></button>
+</form>
<div class="short-text hidden">
<%= Notification.NewReply.ShortText|l10n|html>
<a class="link" onclick="showNotificationDetails('<%notification.id|html>'); return false;"><%= Notification.ClickHereToRead|l10n|html></a>
</div>
<div class="text">
- <form class="mark-as-read" action="markAsKnown.html" method="post">
- <input type="hidden" name="formPassword" value="<% formPassword|html>" />
- <input type="hidden" name="returnPage" value="<% request.uri|html>" />
- <input type="hidden" name="type" value="reply" />
- <input type="hidden" name="id" value="<%foreach replies reply><% reply.id|html><%notlast> <%/notlast><%/foreach>" />
- <button type="submit" name="mark-read" value="true"><%= Notification.NewPost.Button.MarkRead|l10n|html></button>
- </form>
<%foreach replies reply><div class="hidden reply-id"><%reply.id|html></div><%/foreach>
<%= Notification.NewReply.Text|l10n|html>
<%foreach replies postGroup|replyGroup>
+<form class="mark-as-read" action="markAsKnown.html" method="post">
+ <input type="hidden" name="formPassword" value="<% formPassword|html>" />
+ <input type="hidden" name="returnPage" value="<% request.uri|html>" />
+ <input type="hidden" name="type" value="sone" />
+ <input type="hidden" name="id" value="<%foreach sones sone><% sone.id|html><%notlast> <%/notlast><%/foreach>" />
+ <button type="submit" name="mark-read" value="true"><%= Notification.NewPost.Button.MarkRead|l10n|html></button>
+</form>
<div class="short-text hidden">
<%= Notification.NewSone.ShortText|l10n|html>
<a class="link" onclick="showNotificationDetails('<%notification.id|html>'); return false;"><%= Notification.ClickHereToRead|l10n|html></a>
</div>
<div class="text">
- <form class="mark-as-read" action="markAsKnown.html" method="post">
- <input type="hidden" name="formPassword" value="<% formPassword|html>" />
- <input type="hidden" name="returnPage" value="<% request.uri|html>" />
- <input type="hidden" name="type" value="sone" />
- <input type="hidden" name="id" value="<%foreach sones sone><% sone.id|html><%notlast> <%/notlast><%/foreach>" />
- <button type="submit" name="mark-read" value="true"><%= Notification.NewPost.Button.MarkRead|l10n|html></button>
- </form>
<%= Notification.NewSone.Text|l10n|html>
<%foreach sones sone>
<div class="hidden sone-id"><% sone.id|html></div>
<div class="profile-field">
<div class="name"><%= Page.ViewSone.Profile.Label.Name|l10n|html></div>
- <div class="value"><a href="/WebOfTrust/ShowIdentity?id=<% sone.id|html>"><% sone.niceName|html></a></div>
+ <div class="value"><% sone.niceName|html> (<a href="/WebOfTrust/ShowIdentity?id=<% sone.id|html>"><%= Page.ViewSone.Profile.Name.WoTLink|l10n|html></a>)</div>
</div>
<%foreach sone.profile.fields field>
TemplateContextFactory templateContextFactory = new TemplateContextFactory();
templateContextFactory.addFilter("html", new HtmlFilter());
FreenetLinkParser parser = new FreenetLinkParser(null, templateContextFactory);
- FreenetLinkParserContext context = new FreenetLinkParserContext(null);
+ FreenetLinkParserContext context = new FreenetLinkParserContext(null, null);
Part part;
part = parser.parse(context, new StringReader("Text."));