From: David ‘Bombe’ Roden Date: Mon, 5 Mar 2012 05:29:25 +0000 (+0100) Subject: Merge branch 'release-0.8' X-Git-Tag: 0.8^0 X-Git-Url: https://git.pterodactylus.net/?p=Sone.git;a=commitdiff_plain;h=85aa9c556ef8ac726694f0077a4f8df0bc912e5f;hp=0206f400c986a38cd91978059e98838c02af0b99 Merge branch 'release-0.8' --- diff --git a/pom.xml b/pom.xml index 279e6c7..a2b3eff 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 net.pterodactylus sone - 0.7.6 + 0.8 net.pterodactylus diff --git a/src/main/java/net/pterodactylus/sone/core/Core.java b/src/main/java/net/pterodactylus/sone/core/Core.java index a51a189..296d080 100644 --- a/src/main/java/net/pterodactylus/sone/core/Core.java +++ b/src/main/java/net/pterodactylus/sone/core/Core.java @@ -24,8 +24,8 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.Map.Entry; +import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.Level; @@ -40,11 +40,12 @@ 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.Profile.Field; import net.pterodactylus.sone.data.Reply; import net.pterodactylus.sone.data.Sone; import net.pterodactylus.sone.data.Sone.ShowCustomAvatars; +import net.pterodactylus.sone.data.Sone.SoneStatus; import net.pterodactylus.sone.data.TemporaryImage; -import net.pterodactylus.sone.data.Profile.Field; import net.pterodactylus.sone.fcp.FcpInterface; import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired; import net.pterodactylus.sone.freenet.wot.Identity; @@ -74,26 +75,6 @@ import freenet.keys.FreenetURI; */ public class Core extends AbstractService implements IdentityListener, UpdateListener, SoneProvider, PostProvider, SoneInsertListener, ImageInsertListener { - /** - * Enumeration for the possible states of a {@link Sone}. - * - * @author David ‘Bombe’ Roden - */ - public enum SoneStatus { - - /** The Sone is unknown, i.e. not yet downloaded. */ - unknown, - - /** The Sone is idle, i.e. not being downloaded or inserted. */ - idle, - - /** The Sone is currently being inserted. */ - inserting, - - /** The Sone is currently being downloaded. */ - downloading, - } - /** The logger. */ private static final Logger logger = Logging.getLogger(Core.class); @@ -133,10 +114,6 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis /** The FCP interface. */ private volatile FcpInterface fcpInterface; - /** The Sones’ statuses. */ - /* synchronize access on itself. */ - private final Map soneStatuses = new HashMap(); - /** The times Sones were followed. */ private final Map soneFollowingTimes = new HashMap(); @@ -160,29 +137,18 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis /* synchronize access on this on itself. */ private Map remoteSones = new HashMap(); - /** All new Sones. */ - private Set newSones = new HashSet(); - /** All known Sones. */ - /* synchronize access on {@link #newSones}. */ private Set knownSones = new HashSet(); /** All posts. */ private Map posts = new HashMap(); - /** All new posts. */ - private Set newPosts = new HashSet(); - /** All known posts. */ - /* synchronize access on {@link #newPosts}. */ private Set knownPosts = new HashSet(); /** All replies. */ private Map replies = new HashMap(); - /** All new replies. */ - private Set newReplies = new HashSet(); - /** All known replies. */ private Set knownReplies = new HashSet(); @@ -306,33 +272,6 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis } /** - * Returns the status of the given Sone. - * - * @param sone - * The Sone to get the status for - * @return The status of the Sone - */ - public SoneStatus getSoneStatus(Sone sone) { - synchronized (soneStatuses) { - return soneStatuses.get(sone); - } - } - - /** - * Sets the status of the given Sone. - * - * @param sone - * The Sone to set the status of - * @param soneStatus - * The status to set - */ - public void setSoneStatus(Sone sone, SoneStatus soneStatus) { - synchronized (soneStatuses) { - soneStatuses.put(sone, soneStatus); - } - } - - /** * Returns the Sone rescuer for the given local Sone. * * @param sone @@ -488,7 +427,6 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis if ((sone == null) && create) { sone = new Sone(id); localSones.put(id, sone); - setSoneStatus(sone, SoneStatus.unknown); } return sone; } @@ -521,7 +459,6 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis if ((sone == null) && create && (id != null) && (id.length() == 43)) { sone = new Sone(id); remoteSones.put(id, sone); - setSoneStatus(sone, SoneStatus.unknown); } return sone; } @@ -556,19 +493,6 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis } /** - * Returns whether the Sone with the given ID is a new Sone. - * - * @param soneId - * The ID of the sone to check for - * @return {@code true} if the given Sone is new, false otherwise - */ - public boolean isNewSone(String soneId) { - synchronized (newSones) { - return !knownSones.contains(soneId) && newSones.contains(soneId); - } - } - - /** * Returns whether the given Sone has been modified. * * @param sone @@ -645,20 +569,6 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis } /** - * Returns whether the given post ID is new. - * - * @param postId - * The post ID - * @return {@code true} if the post is considered to be new, {@code false} - * otherwise - */ - public boolean isNewPost(String postId) { - synchronized (newPosts) { - return !knownPosts.contains(postId) && newPosts.contains(postId); - } - } - - /** * Returns all posts that have the given Sone as recipient. * * @see Post#getRecipient() @@ -736,20 +646,6 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis } /** - * Returns whether the reply with the given ID is new. - * - * @param replyId - * The ID of the reply to check - * @return {@code true} if the reply is considered to be new, {@code false} - * otherwise - */ - public boolean isNewReply(String replyId) { - synchronized (newReplies) { - return !knownReplies.contains(replyId) && newReplies.contains(replyId); - } - } - - /** * Returns all Sones that have liked the given post. * * @param post @@ -967,12 +863,13 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis } sone.setLatestEdition(Numbers.safeParseLong(ownIdentity.getProperty("Sone.LatestEdition"), (long) 0)); sone.setClient(new Client("Sone", SonePlugin.VERSION.toString())); + sone.setKnown(true); /* TODO - load posts ’n stuff */ localSones.put(ownIdentity.getId(), sone); final SoneInserter soneInserter = new SoneInserter(this, freenetInterface, sone); soneInserter.addSoneInsertListener(this); soneInserters.put(sone, soneInserter); - setSoneStatus(sone, SoneStatus.idle); + sone.setStatus(SoneStatus.idle); loadSone(sone); soneInserter.start(); return sone; @@ -1024,12 +921,10 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis sone.setRequestUri(getSoneUri(identity.getRequestUri())); sone.setLatestEdition(Numbers.safeParseLong(identity.getProperty("Sone.LatestEdition"), (long) 0)); if (newSone) { - synchronized (newSones) { + synchronized (knownSones) { newSone = !knownSones.contains(sone.getId()); - if (newSone) { - newSones.add(sone.getId()); - } } + sone.setKnown(!newSone); if (newSone) { coreListenerManager.fireNewSoneFound(sone); for (Sone localSone : getLocalSones()) { @@ -1040,7 +935,6 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis } } soneDownloader.addSone(sone); - setSoneStatus(sone, SoneStatus.unknown); soneDownloaders.execute(new Runnable() { @Override @@ -1272,14 +1166,14 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis } } List storedPosts = storedSone.getPosts(); - synchronized (newPosts) { + synchronized (knownPosts) { for (Post post : sone.getPosts()) { - post.setSone(storedSone); + post.setSone(storedSone).setKnown(knownPosts.contains(post.getId())); if (!storedPosts.contains(post)) { if (post.getTime() < getSoneFollowingTime(sone)) { knownPosts.add(post.getId()); } else if (!knownPosts.contains(post.getId())) { - newPosts.add(post.getId()); + sone.setKnown(false); coreListenerManager.fireNewPostFound(post); } } @@ -1297,14 +1191,14 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis } } Set storedReplies = storedSone.getReplies(); - synchronized (newReplies) { + synchronized (knownReplies) { for (PostReply reply : sone.getReplies()) { - reply.setSone(storedSone); + reply.setSone(storedSone).setKnown(knownReplies.contains(reply.getId())); if (!storedReplies.contains(reply)) { if (reply.getTime() < getSoneFollowingTime(sone)) { knownReplies.add(reply.getId()); } else if (!knownReplies.contains(reply.getId())) { - newReplies.add(reply.getId()); + reply.setKnown(false); coreListenerManager.fireNewReplyFound(reply); } } @@ -1399,19 +1293,20 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis } /** - * Marks the given Sone as known. If the Sone was {@link #isNewPost(String) - * new} before, a {@link CoreListener#markSoneKnown(Sone)} event is fired. + * Marks the given Sone as known. If the Sone was not {@link Post#isKnown() + * known} before, a {@link CoreListener#markSoneKnown(Sone)} event is fired. * * @param sone * The Sone to mark as known */ public void markSoneKnown(Sone sone) { - synchronized (newSones) { - if (newSones.remove(sone.getId())) { + if (!sone.isKnown()) { + sone.setKnown(true); + synchronized (knownSones) { knownSones.add(sone.getId()); - coreListenerManager.fireMarkSoneKnown(sone); - touchConfiguration(); } + coreListenerManager.fireMarkSoneKnown(sone); + touchConfiguration(); } } @@ -1622,17 +1517,17 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis sone.setAlbums(topLevelAlbums); soneInserters.get(sone).setLastInsertFingerprint(lastInsertFingerprint); } - synchronized (newSones) { + synchronized (knownSones) { for (String friend : friends) { knownSones.add(friend); } } - synchronized (newPosts) { + synchronized (knownPosts) { for (Post post : posts) { knownPosts.add(post.getId()); } } - synchronized (newReplies) { + synchronized (knownReplies) { for (PostReply reply : replies) { knownReplies.add(reply.getId()); } @@ -1709,10 +1604,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis synchronized (posts) { posts.put(post.getId(), post); } - synchronized (newPosts) { - newPosts.add(post.getId()); - coreListenerManager.fireNewPostFound(post); - } + coreListenerManager.fireNewPostFound(post); sone.addPost(post); touchConfiguration(); localElementTicker.registerEvent(System.currentTimeMillis() + 10 * 1000, new Runnable() { @@ -1744,28 +1636,28 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis posts.remove(post.getId()); } coreListenerManager.firePostRemoved(post); - synchronized (newPosts) { - markPostKnown(post); - knownPosts.remove(post.getId()); - } + markPostKnown(post); touchConfiguration(); } /** - * Marks the given post as known, if it is currently a new post (according - * to {@link #isNewPost(String)}). + * Marks the given post as known, if it is currently not a known post + * (according to {@link Post#isKnown()}). * * @param post * The post to mark as known */ public void markPostKnown(Post post) { - synchronized (newPosts) { - if (newPosts.remove(post.getId())) { - knownPosts.add(post.getId()); - coreListenerManager.fireMarkPostKnown(post); + post.setKnown(true); + synchronized (knownPosts) { + coreListenerManager.fireMarkPostKnown(post); + if (knownPosts.add(post.getId())) { touchConfiguration(); } } + for (PostReply reply : getReplies(post)) { + markReplyKnown(reply); + } } /** @@ -1849,8 +1741,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis synchronized (replies) { replies.put(reply.getId(), reply); } - synchronized (newReplies) { - newReplies.add(reply.getId()); + synchronized (knownReplies) { coreListenerManager.fireNewReplyFound(reply); } sone.addReply(reply); @@ -1883,7 +1774,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis synchronized (replies) { replies.remove(reply.getId()); } - synchronized (newReplies) { + synchronized (knownReplies) { markReplyKnown(reply); knownReplies.remove(reply.getId()); } @@ -1892,17 +1783,17 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis } /** - * Marks the given reply as known, if it is currently a new reply (according - * to {@link #isNewReply(String)}). + * Marks the given reply as known, if it is currently not a known reply + * (according to {@link Reply#isKnown()}). * * @param reply * The reply to mark as known */ public void markReplyKnown(PostReply reply) { - synchronized (newReplies) { - if (newReplies.remove(reply.getId())) { - knownReplies.add(reply.getId()); - coreListenerManager.fireMarkReplyKnown(reply); + reply.setKnown(true); + synchronized (knownReplies) { + coreListenerManager.fireMarkReplyKnown(reply); + if (knownReplies.add(reply.getId())) { touchConfiguration(); } } @@ -2285,7 +2176,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis /* save known Sones. */ int soneCounter = 0; - synchronized (newSones) { + synchronized (knownSones) { for (String knownSoneId : knownSones) { configuration.getStringValue("KnownSone/" + soneCounter++ + "/ID").setValue(knownSoneId); } @@ -2305,7 +2196,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis /* save known posts. */ int postCounter = 0; - synchronized (newPosts) { + synchronized (knownPosts) { for (String knownPostId : knownPosts) { configuration.getStringValue("KnownPosts/" + postCounter++ + "/ID").setValue(knownPostId); } @@ -2314,7 +2205,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis /* save known replies. */ int replyCounter = 0; - synchronized (newReplies) { + synchronized (knownReplies) { for (String knownReplyId : knownReplies) { configuration.getStringValue("KnownReplies/" + replyCounter++ + "/ID").setValue(knownReplyId); } @@ -2414,7 +2305,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis if (knownSoneId == null) { break; } - synchronized (newSones) { + synchronized (knownSones) { knownSones.add(knownSoneId); } } @@ -2445,7 +2336,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis if (knownPostId == null) { break; } - synchronized (newPosts) { + synchronized (knownPosts) { knownPosts.add(knownPostId); } } @@ -2457,7 +2348,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis if (knownReplyId == null) { break; } - synchronized (newReplies) { + synchronized (knownReplies) { knownReplies.add(knownReplyId); } } @@ -2587,19 +2478,17 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis return; } synchronized (posts) { - synchronized (newPosts) { + synchronized (knownPosts) { for (Post post : sone.getPosts()) { posts.remove(post.getId()); - newPosts.remove(post.getId()); coreListenerManager.firePostRemoved(post); } } } synchronized (replies) { - synchronized (newReplies) { + synchronized (knownReplies) { for (PostReply reply : sone.getReplies()) { replies.remove(reply.getId()); - newReplies.remove(reply.getId()); coreListenerManager.fireReplyRemoved(reply); } } @@ -2607,10 +2496,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis synchronized (remoteSones) { remoteSones.remove(identity.getId()); } - synchronized (newSones) { - newSones.remove(identity.getId()); - coreListenerManager.fireSoneRemoved(sone); - } + coreListenerManager.fireSoneRemoved(sone); } // @@ -2731,8 +2617,8 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis * * @param insertionDelay * The insertion delay to validate - * @return {@code true} if the given insertion delay was valid, {@code - * false} otherwise + * @return {@code true} if the given insertion delay was valid, + * {@code false} otherwise */ public boolean validateInsertionDelay(Integer insertionDelay) { return options.getIntegerOption("InsertionDelay").validate(insertionDelay); diff --git a/src/main/java/net/pterodactylus/sone/core/Options.java b/src/main/java/net/pterodactylus/sone/core/Options.java index f280aae..bf51e02 100644 --- a/src/main/java/net/pterodactylus/sone/core/Options.java +++ b/src/main/java/net/pterodactylus/sone/core/Options.java @@ -17,11 +17,8 @@ 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; import net.pterodactylus.util.validation.Validator; @@ -133,7 +130,29 @@ public class Options { private Validator validator; /** The option watcher. */ - private final List> optionWatchers = new ArrayList>(); + private final OptionWatcher optionWatcher; + + /** + * Creates a new default option. + * + * @param defaultValue + * The default value of the option + */ + public DefaultOption(T defaultValue) { + this(defaultValue, (OptionWatcher) null); + } + + /** + * Creates a new default option. + * + * @param defaultValue + * The default value of the option + * @param validator + * The validator for value validation (may be {@code null}) + */ + public DefaultOption(T defaultValue, Validator validator) { + this(defaultValue, validator, null); + } /** * Creates a new default option. @@ -141,9 +160,9 @@ public class Options { * @param defaultValue * The default value of the option * @param optionWatchers - * The option watchers + * The option watchers (may be {@code null}) */ - public DefaultOption(T defaultValue, OptionWatcher... optionWatchers) { + public DefaultOption(T defaultValue, OptionWatcher optionWatchers) { this(defaultValue, null, optionWatchers); } @@ -153,14 +172,14 @@ public class Options { * @param defaultValue * The default value of the option * @param validator - * The validator for value validation - * @param optionWatchers - * The option watchers + * The validator for value validation (may be {@code null}) + * @param optionWatcher + * The option watcher (may be {@code null}) */ - public DefaultOption(T defaultValue, Validator validator, OptionWatcher... optionWatchers) { + public DefaultOption(T defaultValue, Validator validator, OptionWatcher optionWatcher) { this.defaultValue = defaultValue; this.validator = validator; - this.optionWatchers.addAll(Arrays.asList(optionWatchers)); + this.optionWatcher = optionWatcher; } /** @@ -209,7 +228,7 @@ public class Options { T oldValue = this.value; this.value = value; if (!get().equals(oldValue)) { - for (OptionWatcher optionWatcher : optionWatchers) { + if (optionWatcher != null) { optionWatcher.optionChanged(this, oldValue, get()); } } @@ -336,6 +355,7 @@ public class Options { * @return The enum option, or {@code null} if there is no enum option with * the given name */ + @SuppressWarnings("unchecked") public > Option getEnumOption(String name) { return (Option) enumOptions.get(name); } diff --git a/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java b/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java index 5024ab9..90f926a 100644 --- a/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java +++ b/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java @@ -26,7 +26,6 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; -import net.pterodactylus.sone.core.Core.SoneStatus; import net.pterodactylus.sone.data.Album; import net.pterodactylus.sone.data.Client; import net.pterodactylus.sone.data.Image; @@ -34,6 +33,7 @@ import net.pterodactylus.sone.data.Post; import net.pterodactylus.sone.data.PostReply; import net.pterodactylus.sone.data.Profile; import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.data.Sone.SoneStatus; import net.pterodactylus.util.collection.Pair; import net.pterodactylus.util.io.Closer; import net.pterodactylus.util.logging.Logging; @@ -153,7 +153,7 @@ public class SoneDownloader extends AbstractService { public Sone fetchSone(Sone sone, FreenetURI soneUri, boolean fetchOnly) { logger.log(Level.FINE, "Starting fetch for Sone “%s” from %s…", new Object[] { sone, soneUri }); FreenetURI requestUri = soneUri.setMetaString(new String[] { "sone.xml" }); - core.setSoneStatus(sone, SoneStatus.downloading); + sone.setStatus(SoneStatus.downloading); try { Pair fetchResults = freenetInterface.fetchUri(requestUri); if (fetchResults == null) { @@ -170,7 +170,7 @@ public class SoneDownloader extends AbstractService { } return parsedSone; } finally { - core.setSoneStatus(sone, (sone.getTime() == 0) ? SoneStatus.unknown : SoneStatus.idle); + sone.setStatus((sone.getTime() == 0) ? SoneStatus.unknown : SoneStatus.idle); } } diff --git a/src/main/java/net/pterodactylus/sone/core/SoneInserter.java b/src/main/java/net/pterodactylus/sone/core/SoneInserter.java index 4d9878f..ccf8a21 100644 --- a/src/main/java/net/pterodactylus/sone/core/SoneInserter.java +++ b/src/main/java/net/pterodactylus/sone/core/SoneInserter.java @@ -27,11 +27,11 @@ import java.util.Map; import java.util.logging.Level; 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.data.Sone.SoneStatus; import net.pterodactylus.sone.freenet.StringBucket; import net.pterodactylus.sone.main.SonePlugin; import net.pterodactylus.util.collection.ListBuilder; @@ -233,7 +233,7 @@ public class SoneInserter extends AbstractService { boolean success = false; try { - core.setSoneStatus(sone, SoneStatus.inserting); + sone.setStatus(SoneStatus.inserting); long insertTime = System.currentTimeMillis(); insertInformation.setTime(insertTime); soneInsertListenerManager.fireInsertStarted(); @@ -253,7 +253,7 @@ public class SoneInserter extends AbstractService { soneInsertListenerManager.fireInsertAborted(se1); logger.log(Level.WARNING, "Could not insert Sone “" + sone.getName() + "”!", se1); } finally { - core.setSoneStatus(sone, SoneStatus.idle); + sone.setStatus(SoneStatus.idle); } /* diff --git a/src/main/java/net/pterodactylus/sone/core/UpdateChecker.java b/src/main/java/net/pterodactylus/sone/core/UpdateChecker.java index 9c50f37..a6f7a21 100644 --- a/src/main/java/net/pterodactylus/sone/core/UpdateChecker.java +++ b/src/main/java/net/pterodactylus/sone/core/UpdateChecker.java @@ -49,7 +49,7 @@ public class UpdateChecker { 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 = 36; + private static final int LATEST_EDITION = 49; /** The Freenet interface. */ private final FreenetInterface freenetInterface; diff --git a/src/main/java/net/pterodactylus/sone/data/Post.java b/src/main/java/net/pterodactylus/sone/data/Post.java index a28bf9f..1a29480 100644 --- a/src/main/java/net/pterodactylus/sone/data/Post.java +++ b/src/main/java/net/pterodactylus/sone/data/Post.java @@ -65,6 +65,9 @@ public class Post { /** The text of the post. */ private volatile String text; + /** Whether the post is known. */ + private volatile boolean known; + /** * Creates a new post. * @@ -219,6 +222,27 @@ public class Post { return this; } + /** + * Returns whether this post is known. + * + * @return {@code true} if this post is known, {@code false} otherwise + */ + public boolean isKnown() { + return known; + } + + /** + * Sets whether this post is known. + * + * @param known + * {@code true} if this post is known, {@code false} otherwise + * @return This post + */ + public Post setKnown(boolean known) { + this.known = known; + return this; + } + // // OBJECT METHODS // diff --git a/src/main/java/net/pterodactylus/sone/data/Reply.java b/src/main/java/net/pterodactylus/sone/data/Reply.java index 780fe69..15d8287 100644 --- a/src/main/java/net/pterodactylus/sone/data/Reply.java +++ b/src/main/java/net/pterodactylus/sone/data/Reply.java @@ -69,6 +69,9 @@ public abstract class Reply> { /** The text of the reply. */ private volatile String text; + /** Whether the reply is known. */ + private volatile boolean known; + /** * Creates a new reply with the given ID. * @@ -187,6 +190,28 @@ public abstract class Reply> { return (T) this; } + /** + * Returns whether this reply is known. + * + * @return {@code true} if this reply is known, {@code false} otherwise + */ + public boolean isKnown() { + return known; + } + + /** + * Sets whether this reply is known. + * + * @param known + * {@code true} if this reply is known, {@code false} otherwise + * @return This reply + */ + @SuppressWarnings("unchecked") + public T setKnown(boolean known) { + this.known = known; + return (T) this; + } + // // OBJECT METHODS // diff --git a/src/main/java/net/pterodactylus/sone/data/Sone.java b/src/main/java/net/pterodactylus/sone/data/Sone.java index 901d676..c2f9ae1 100644 --- a/src/main/java/net/pterodactylus/sone/data/Sone.java +++ b/src/main/java/net/pterodactylus/sone/data/Sone.java @@ -21,9 +21,10 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; -import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CopyOnWriteArraySet; import java.util.logging.Level; import java.util.logging.Logger; @@ -48,6 +49,26 @@ import freenet.keys.FreenetURI; public class Sone implements Fingerprintable, Comparable { /** + * Enumeration for the possible states of a {@link Sone}. + * + * @author David ‘Bombe’ Roden + */ + public enum SoneStatus { + + /** The Sone is unknown, i.e. not yet downloaded. */ + unknown, + + /** The Sone is idle, i.e. not being downloaded or inserted. */ + idle, + + /** The Sone is currently being inserted. */ + inserting, + + /** The Sone is currently being downloaded. */ + downloading, + } + + /** * The possible values for the “show custom avatars” option. * * @author David ‘Bombe’ Roden @@ -170,29 +191,35 @@ public class Sone implements Fingerprintable, Comparable { /** The time of the last inserted update. */ private volatile long time; + /** The status of this Sone. */ + private volatile SoneStatus status = SoneStatus.unknown; + /** The profile of this Sone. */ private volatile Profile profile = new Profile(this); /** The client used by the Sone. */ private volatile Client client; + /** Whether this Sone is known. */ + private volatile boolean known; + /** All friend Sones. */ - private final Set friendSones = Collections.synchronizedSet(new HashSet()); + private final Set friendSones = new CopyOnWriteArraySet(); /** All posts. */ - private final Set posts = Collections.synchronizedSet(new HashSet()); + private final Set posts = new CopyOnWriteArraySet(); /** All replies. */ - private final Set replies = Collections.synchronizedSet(new HashSet()); + private final Set replies = new CopyOnWriteArraySet(); /** The IDs of all liked posts. */ - private final Set likedPostIds = Collections.synchronizedSet(new HashSet()); + private final Set likedPostIds = new CopyOnWriteArraySet(); /** The IDs of all liked replies. */ - private final Set likedReplyIds = Collections.synchronizedSet(new HashSet()); + private final Set likedReplyIds = new CopyOnWriteArraySet(); /** The albums of this Sone. */ - private final List albums = Collections.synchronizedList(new ArrayList()); + private final List albums = new CopyOnWriteArrayList(); /** Sone-specific options. */ private final Options options = new Options(); @@ -359,13 +386,37 @@ public class Sone implements Fingerprintable, Comparable { } /** + * Returns the status of this Sone. + * + * @return The status of this Sone + */ + public SoneStatus getStatus() { + return status; + } + + /** + * Sets the new status of this Sone. + * + * @param status + * The new status of this Sone + * @return This Sone + * @throws IllegalArgumentException + * if {@code status} is {@code null} + */ + public Sone setStatus(SoneStatus status) { + Validation.begin().isNotNull("Sone Status", status).check(); + this.status = status; + return this; + } + + /** * Returns a copy of the profile. If you want to update values in the * profile of this Sone, update the values in the returned {@link Profile} * and use {@link #setProfile(Profile)} to change the profile in this Sone. * * @return A copy of the profile */ - public synchronized Profile getProfile() { + public Profile getProfile() { return new Profile(profile); } @@ -377,7 +428,7 @@ public class Sone implements Fingerprintable, Comparable { * @param profile * The profile to set */ - public synchronized void setProfile(Profile profile) { + public void setProfile(Profile profile) { this.profile = new Profile(profile); } @@ -403,6 +454,27 @@ public class Sone implements Fingerprintable, Comparable { } /** + * Returns whether this Sone is known. + * + * @return {@code true} if this Sone is known, {@code false} otherwise + */ + public boolean isKnown() { + return known; + } + + /** + * Sets whether this Sone is known. + * + * @param known + * {@code true} if this Sone is known, {@code false} otherwise + * @return This Sone + */ + public Sone setKnown(boolean known) { + this.known = known; + return this; + } + + /** * Returns all friend Sones of this Sone. * * @return The friend Sones of this Sone @@ -471,7 +543,7 @@ public class Sone implements Fingerprintable, Comparable { * The new (and only) posts of this Sone * @return This Sone (for method chaining) */ - public synchronized Sone setPosts(Collection posts) { + public Sone setPosts(Collection posts) { synchronized (this) { this.posts.clear(); this.posts.addAll(posts); @@ -486,7 +558,7 @@ public class Sone implements Fingerprintable, Comparable { * @param post * The post to add */ - public synchronized void addPost(Post post) { + public void addPost(Post post) { if (post.getSone().equals(this) && posts.add(post)) { logger.log(Level.FINEST, "Adding %s to “%s”.", new Object[] { post, getName() }); } @@ -498,7 +570,7 @@ public class Sone implements Fingerprintable, Comparable { * @param post * The post to remove */ - public synchronized void removePost(Post post) { + public void removePost(Post post) { if (post.getSone().equals(this)) { posts.remove(post); } @@ -509,7 +581,7 @@ public class Sone implements Fingerprintable, Comparable { * * @return All replies this Sone made */ - public synchronized Set getReplies() { + public Set getReplies() { return Collections.unmodifiableSet(replies); } @@ -520,7 +592,7 @@ public class Sone implements Fingerprintable, Comparable { * The new (and only) replies of this Sone * @return This Sone (for method chaining) */ - public synchronized Sone setReplies(Collection replies) { + public Sone setReplies(Collection replies) { this.replies.clear(); this.replies.addAll(replies); return this; @@ -533,7 +605,7 @@ public class Sone implements Fingerprintable, Comparable { * @param reply * The reply to add */ - public synchronized void addReply(PostReply reply) { + public void addReply(PostReply reply) { if (reply.getSone().equals(this)) { replies.add(reply); } @@ -545,7 +617,7 @@ public class Sone implements Fingerprintable, Comparable { * @param reply * The reply to remove */ - public synchronized void removeReply(PostReply reply) { + public void removeReply(PostReply reply) { if (reply.getSone().equals(this)) { replies.remove(reply); } @@ -567,7 +639,7 @@ public class Sone implements Fingerprintable, Comparable { * All liked posts’ IDs * @return This Sone (for method chaining) */ - public synchronized Sone setLikePostIds(Set likedPostIds) { + public Sone setLikePostIds(Set likedPostIds) { this.likedPostIds.clear(); this.likedPostIds.addAll(likedPostIds); return this; @@ -592,7 +664,7 @@ public class Sone implements Fingerprintable, Comparable { * The ID of the post * @return This Sone (for method chaining) */ - public synchronized Sone addLikedPostId(String postId) { + public Sone addLikedPostId(String postId) { likedPostIds.add(postId); return this; } @@ -604,7 +676,7 @@ public class Sone implements Fingerprintable, Comparable { * The ID of the post * @return This Sone (for method chaining) */ - public synchronized Sone removeLikedPostId(String postId) { + public Sone removeLikedPostId(String postId) { likedPostIds.remove(postId); return this; } @@ -625,7 +697,7 @@ public class Sone implements Fingerprintable, Comparable { * All liked replies’ IDs * @return This Sone (for method chaining) */ - public synchronized Sone setLikeReplyIds(Set likedReplyIds) { + public Sone setLikeReplyIds(Set likedReplyIds) { this.likedReplyIds.clear(); this.likedReplyIds.addAll(likedReplyIds); return this; @@ -650,7 +722,7 @@ public class Sone implements Fingerprintable, Comparable { * The ID of the reply * @return This Sone (for method chaining) */ - public synchronized Sone addLikedReplyId(String replyId) { + public Sone addLikedReplyId(String replyId) { likedReplyIds.add(replyId); return this; } @@ -662,7 +734,7 @@ public class Sone implements Fingerprintable, Comparable { * The ID of the reply * @return This Sone (for method chaining) */ - public synchronized Sone removeLikedReplyId(String replyId) { + public Sone removeLikedReplyId(String replyId) { likedReplyIds.remove(replyId); return this; } @@ -717,7 +789,7 @@ public class Sone implements Fingerprintable, Comparable { * @param album * The album to add */ - public synchronized void addAlbum(Album album) { + public void addAlbum(Album album) { Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).check(); albums.add(album); } @@ -728,7 +800,7 @@ public class Sone implements Fingerprintable, Comparable { * @param albums * The albums of this Sone */ - public synchronized void setAlbums(Collection albums) { + public void setAlbums(Collection albums) { Validation.begin().isNotNull("Albums", albums).check(); this.albums.clear(); for (Album album : albums) { @@ -742,7 +814,7 @@ public class Sone implements Fingerprintable, Comparable { * @param album * The album to remove */ - public synchronized void removeAlbum(Album album) { + public void removeAlbum(Album album) { Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).check(); albums.remove(album); } diff --git a/src/main/java/net/pterodactylus/sone/fcp/AbstractSoneCommand.java b/src/main/java/net/pterodactylus/sone/fcp/AbstractSoneCommand.java index ab705d7..3a246c3 100644 --- a/src/main/java/net/pterodactylus/sone/fcp/AbstractSoneCommand.java +++ b/src/main/java/net/pterodactylus/sone/fcp/AbstractSoneCommand.java @@ -24,9 +24,9 @@ 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.Profile.Field; import net.pterodactylus.sone.data.Reply; import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.sone.data.Profile.Field; import net.pterodactylus.sone.freenet.SimpleFieldSetBuilder; import net.pterodactylus.sone.freenet.fcp.AbstractCommand; import net.pterodactylus.sone.freenet.fcp.Command; diff --git a/src/main/java/net/pterodactylus/sone/freenet/L10nFilter.java b/src/main/java/net/pterodactylus/sone/freenet/L10nFilter.java index 910e0d6..16dc9fb 100644 --- a/src/main/java/net/pterodactylus/sone/freenet/L10nFilter.java +++ b/src/main/java/net/pterodactylus/sone/freenet/L10nFilter.java @@ -23,9 +23,9 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import net.pterodactylus.sone.web.WebInterface; import net.pterodactylus.util.template.Filter; import net.pterodactylus.util.template.TemplateContext; -import freenet.l10n.BaseL10n; /** * {@link Filter} implementation replaces {@link String} values with their @@ -35,17 +35,17 @@ import freenet.l10n.BaseL10n; */ public class L10nFilter implements Filter { - /** The l10n handler. */ - private final BaseL10n l10n; + /** The web interface. */ + private final WebInterface webInterface; /** * Creates a new L10n filter. * - * @param l10n - * The l10n handler + * @param webInterface + * The Sone web interface */ - public L10nFilter(BaseL10n l10n) { - this.l10n = l10n; + public L10nFilter(WebInterface webInterface) { + this.webInterface = webInterface; } /** @@ -54,7 +54,7 @@ public class L10nFilter implements Filter { @Override public String format(TemplateContext templateContext, Object data, Map parameters) { if (parameters.isEmpty()) { - return l10n.getString(String.valueOf(data)); + return webInterface.getL10n().getString(String.valueOf(data)); } List parameterValues = new ArrayList(); int parameterIndex = 0; @@ -68,6 +68,6 @@ public class L10nFilter implements Filter { parameterValues.add(value); ++parameterIndex; } - return new MessageFormat(l10n.getString(String.valueOf(data)), new Locale(l10n.getSelectedLanguage().shortCode)).format(parameterValues.toArray()); + return new MessageFormat(webInterface.getL10n().getString(String.valueOf(data)), new Locale(webInterface.getL10n().getSelectedLanguage().shortCode)).format(parameterValues.toArray()); } } diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityManager.java b/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityManager.java index 69af0ca..8b95e1a 100644 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityManager.java +++ b/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityManager.java @@ -45,6 +45,7 @@ import net.pterodactylus.util.service.AbstractService; public class IdentityManager extends AbstractService { /** Object used for synchronization. */ + @SuppressWarnings("hiding") private final Object syncObject = new Object() { /* inner class for better lock names. */ }; diff --git a/src/main/java/net/pterodactylus/sone/main/SonePlugin.java b/src/main/java/net/pterodactylus/sone/main/SonePlugin.java index af52a7f..ffd5d92 100644 --- a/src/main/java/net/pterodactylus/sone/main/SonePlugin.java +++ b/src/main/java/net/pterodactylus/sone/main/SonePlugin.java @@ -83,7 +83,7 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr } /** The version. */ - public static final Version VERSION = new Version(0, 7, 6); + public static final Version VERSION = new Version(0, 8); /** The logger. */ private static final Logger logger = Logging.getLogger(SonePlugin.class); diff --git a/src/main/java/net/pterodactylus/sone/notify/ListNotificationFilters.java b/src/main/java/net/pterodactylus/sone/notify/ListNotificationFilters.java index 7b9eb49..c7b888f 100644 --- a/src/main/java/net/pterodactylus/sone/notify/ListNotificationFilters.java +++ b/src/main/java/net/pterodactylus/sone/notify/ListNotificationFilters.java @@ -164,6 +164,29 @@ public class ListNotificationFilters { } /** + * Filters the given posts, using {@link #isPostVisible(Sone, Post)} to + * decide whether a post should be contained in the returned list. If + * {@code currentSone} is not {@code null} it is used to filter out posts + * that are from Sones that are not followed or not trusted by the given + * Sone. + * + * @param posts + * The posts to filter + * @param currentSone + * The current Sone (may be {@code null}) + * @return The filtered posts + */ + public static List filterPosts(Collection posts, Sone currentSone) { + List filteredPosts = new ArrayList(); + for (Post post : posts) { + if (isPostVisible(currentSone, post)) { + filteredPosts.add(post); + } + } + return filteredPosts; + } + + /** * Checks whether a post is visible to the given Sone. A post is not * considered visible if one of the following statements is true: *
    diff --git a/src/main/java/net/pterodactylus/sone/template/ImageLinkFilter.java b/src/main/java/net/pterodactylus/sone/template/ImageLinkFilter.java index 1684be7..a79cb0b 100644 --- a/src/main/java/net/pterodactylus/sone/template/ImageLinkFilter.java +++ b/src/main/java/net/pterodactylus/sone/template/ImageLinkFilter.java @@ -71,7 +71,8 @@ public class ImageLinkFilter implements Filter { image = core.getImage((String) data, false); } else if (data instanceof Image) { image = (Image) data; - } else { + } + if (image == null) { return null; } String imageClass = parameters.get("class"); diff --git a/src/main/java/net/pterodactylus/sone/template/PostAccessor.java b/src/main/java/net/pterodactylus/sone/template/PostAccessor.java index bef94d6..93ff569 100644 --- a/src/main/java/net/pterodactylus/sone/template/PostAccessor.java +++ b/src/main/java/net/pterodactylus/sone/template/PostAccessor.java @@ -63,7 +63,7 @@ public class PostAccessor extends ReflectionAccessor { Sone currentSone = (Sone) templateContext.get("currentSone"); return (currentSone != null) && (currentSone.isLikedPostId(post.getId())); } else if (member.equals("new")) { - return core.isNewPost(post.getId()); + return !post.isKnown(); } else if (member.equals("bookmarked")) { return core.isBookmarked(post); } else if (member.equals("loaded")) { diff --git a/src/main/java/net/pterodactylus/sone/template/ProfileAccessor.java b/src/main/java/net/pterodactylus/sone/template/ProfileAccessor.java index 97be0cd..366e071 100644 --- a/src/main/java/net/pterodactylus/sone/template/ProfileAccessor.java +++ b/src/main/java/net/pterodactylus/sone/template/ProfileAccessor.java @@ -60,21 +60,28 @@ public class ProfileAccessor extends ReflectionAccessor { /* not logged in? don’t show custom avatars, then. */ return null; } + String avatarId = profile.getAvatar(); + if (avatarId == null) { + return null; + } + if (core.getImage(avatarId, false) == null) { + /* avatar ID but no matching image? show nothing. */ + return null; + } Sone remoteSone = profile.getSone(); if (core.isLocalSone(remoteSone)) { /* always show your own avatars. */ - return profile.getAvatar(); + return avatarId; } ShowCustomAvatars showCustomAvatars = currentSone.getOptions(). getEnumOption("ShowCustomAvatars").get(); if (showCustomAvatars == ShowCustomAvatars.NEVER) { return null; } - String avatarId = profile.getAvatar(); - if ((showCustomAvatars == ShowCustomAvatars.ALWAYS) || (avatarId == null)) { + if (showCustomAvatars == ShowCustomAvatars.ALWAYS) { return avatarId; } - if ((showCustomAvatars == ShowCustomAvatars.FOLLOWED) && currentSone.hasFriend(remoteSone.getId())) { - return avatarId; + if (showCustomAvatars == ShowCustomAvatars.FOLLOWED) { + return currentSone.hasFriend(remoteSone.getId()) ? avatarId : null; } Trust trust = core.getTrust(currentSone, remoteSone); if (trust == null) { @@ -83,7 +90,7 @@ public class ProfileAccessor extends ReflectionAccessor { if ((showCustomAvatars == ShowCustomAvatars.MANUALLY_TRUSTED) && (trust.getExplicit() != null) && (trust.getExplicit() > 0)) { return avatarId; } - if ((showCustomAvatars == ShowCustomAvatars.TRUSTED) && ((trust.getExplicit() != null) && (trust.getExplicit() > 0)) || ((trust.getImplicit() != null) && (trust.getImplicit() > 0))) { + if ((showCustomAvatars == ShowCustomAvatars.TRUSTED) && (((trust.getExplicit() != null) && (trust.getExplicit() > 0)) || ((trust.getImplicit() != null) && (trust.getImplicit() > 0)))) { return avatarId; } return null; diff --git a/src/main/java/net/pterodactylus/sone/template/ReplyAccessor.java b/src/main/java/net/pterodactylus/sone/template/ReplyAccessor.java index 24bcfd6..7b89713 100644 --- a/src/main/java/net/pterodactylus/sone/template/ReplyAccessor.java +++ b/src/main/java/net/pterodactylus/sone/template/ReplyAccessor.java @@ -58,7 +58,7 @@ public class ReplyAccessor extends ReflectionAccessor { Sone currentSone = (Sone) templateContext.get("currentSone"); return (currentSone != null) && (currentSone.isLikedReplyId(reply.getId())); } else if (member.equals("new")) { - return core.isNewReply(reply.getId()); + return !reply.isKnown(); } else if (member.equals("loaded")) { return reply.getSone() != null; } diff --git a/src/main/java/net/pterodactylus/sone/template/SoneAccessor.java b/src/main/java/net/pterodactylus/sone/template/SoneAccessor.java index ead6066..c134fc0 100644 --- a/src/main/java/net/pterodactylus/sone/template/SoneAccessor.java +++ b/src/main/java/net/pterodactylus/sone/template/SoneAccessor.java @@ -21,9 +21,9 @@ import java.util.logging.Level; import java.util.logging.Logger; import net.pterodactylus.sone.core.Core; -import net.pterodactylus.sone.core.Core.SoneStatus; import net.pterodactylus.sone.data.Profile; import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.data.Sone.SoneStatus; import net.pterodactylus.sone.freenet.wot.Trust; import net.pterodactylus.sone.web.WebInterface; import net.pterodactylus.sone.web.ajax.GetTimesAjaxPage; @@ -86,17 +86,17 @@ public class SoneAccessor extends ReflectionAccessor { } else if (member.equals("modified")) { return core.isModifiedSone(sone); } else if (member.equals("status")) { - return core.getSoneStatus(sone); + return sone.getStatus(); } else if (member.equals("unknown")) { - return core.getSoneStatus(sone) == SoneStatus.unknown; + return sone.getStatus() == SoneStatus.unknown; } else if (member.equals("idle")) { - return core.getSoneStatus(sone) == SoneStatus.idle; + return sone.getStatus() == SoneStatus.idle; } else if (member.equals("inserting")) { - return core.getSoneStatus(sone) == SoneStatus.inserting; + return sone.getStatus() == SoneStatus.inserting; } else if (member.equals("downloading")) { - return core.getSoneStatus(sone) == SoneStatus.downloading; + return sone.getStatus() == SoneStatus.downloading; } else if (member.equals("new")) { - return core.isNewSone(sone.getId()); + return !sone.isKnown(); } else if (member.equals("locked")) { return core.isLocked(sone); } else if (member.equals("lastUpdatedText")) { diff --git a/src/main/java/net/pterodactylus/sone/web/KnownSonesPage.java b/src/main/java/net/pterodactylus/sone/web/KnownSonesPage.java index 6ed5667..f32a47d 100644 --- a/src/main/java/net/pterodactylus/sone/web/KnownSonesPage.java +++ b/src/main/java/net/pterodactylus/sone/web/KnownSonesPage.java @@ -91,7 +91,7 @@ public class KnownSonesPage extends SoneTemplatePage { */ @Override public boolean filterObject(Sone sone) { - return webInterface.getCore().isNewSone(sone.getId()); + return !sone.isKnown(); } }); } else if ("not-new".equals(filter)) { @@ -101,7 +101,7 @@ public class KnownSonesPage extends SoneTemplatePage { */ @Override public boolean filterObject(Sone sone) { - return !webInterface.getCore().isNewSone(sone.getId()); + return sone.isKnown(); } }); } diff --git a/src/main/java/net/pterodactylus/sone/web/NewPage.java b/src/main/java/net/pterodactylus/sone/web/NewPage.java new file mode 100644 index 0000000..c7281e3 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/NewPage.java @@ -0,0 +1,82 @@ +/* + * Sone - NewPage.java - Copyright © 2012 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 . + */ + +package net.pterodactylus.sone.web; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import net.pterodactylus.sone.data.Post; +import net.pterodactylus.sone.data.PostReply; +import net.pterodactylus.sone.notify.ListNotificationFilters; +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.collection.Pagination; +import net.pterodactylus.util.number.Numbers; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; + +/** + * Page that displays all new posts and replies. The posts are filtered using + * {@link ListNotificationFilters#filterPosts(java.util.Collection, net.pterodactylus.sone.data.Sone)} + * and sorted by time. + * + * @author David ‘Bombe’ Roden + */ +public class NewPage extends SoneTemplatePage { + + /** + * Creates a new “new posts and replies” page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public NewPage(Template template, WebInterface webInterface) { + super("new.html", template, "Page.New.Title", webInterface); + } + + // + // SONETEMPLATEPAGE METHODS + // + + /** + * {@inherit} + */ + @Override + protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + super.processTemplate(request, templateContext); + + /* collect new elements from notifications. */ + Set posts = webInterface.getNewPosts(); + for (PostReply reply : webInterface.getNewReplies()) { + posts.add(reply.getPost()); + } + + /* filter and sort them. */ + List sortedPosts = ListNotificationFilters.filterPosts(new ArrayList(posts), webInterface.getCurrentSone(request.getToadletContext(), false)); + Collections.sort(sortedPosts, Post.TIME_COMPARATOR); + + /* paginate them. */ + Pagination pagination = new Pagination(sortedPosts, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(Numbers.safeParseInteger(request.getHttpRequest().getParam("page"), 0)); + templateContext.set("pagination", pagination); + templateContext.set("posts", pagination.getItems()); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/SearchPage.java b/src/main/java/net/pterodactylus/sone/web/SearchPage.java index 832f9d9..6e10b24 100644 --- a/src/main/java/net/pterodactylus/sone/web/SearchPage.java +++ b/src/main/java/net/pterodactylus/sone/web/SearchPage.java @@ -67,6 +67,7 @@ public class SearchPage extends SoneTemplatePage { /** Short-term cache. */ private final Cache, Set>> hitCache = new MemoryCache, Set>>(new ValueRetriever, Set>>() { + @Override @SuppressWarnings("synthetic-access") public CacheItem>> retrieve(List phrases) throws CacheException { Set posts = new HashSet(); diff --git a/src/main/java/net/pterodactylus/sone/web/WebInterface.java b/src/main/java/net/pterodactylus/sone/web/WebInterface.java index a007222..b1c61bb 100644 --- a/src/main/java/net/pterodactylus/sone/web/WebInterface.java +++ b/src/main/java/net/pterodactylus/sone/web/WebInterface.java @@ -138,9 +138,9 @@ import net.pterodactylus.util.web.RedirectPage; import net.pterodactylus.util.web.StaticPage; import net.pterodactylus.util.web.TemplatePage; import freenet.clients.http.SessionManager; +import freenet.clients.http.SessionManager.Session; import freenet.clients.http.ToadletContainer; import freenet.clients.http.ToadletContext; -import freenet.clients.http.SessionManager.Session; import freenet.l10n.BaseL10n; import freenet.support.api.HTTPRequest; @@ -243,7 +243,7 @@ public class WebInterface implements CoreListener { templateContextFactory.addFilter("html", new HtmlFilter()); templateContextFactory.addFilter("replace", new ReplaceFilter()); templateContextFactory.addFilter("store", new StoreFilter()); - templateContextFactory.addFilter("l10n", new L10nFilter(getL10n())); + templateContextFactory.addFilter("l10n", new L10nFilter(this)); templateContextFactory.addFilter("substring", new SubstringFilter()); templateContextFactory.addFilter("xml", new XmlFilter()); templateContextFactory.addFilter("change", new RequestChangeFilter()); @@ -583,6 +583,7 @@ public class WebInterface implements CoreListener { Template emptyTemplate = TemplateParser.parse(new StringReader("")); Template loginTemplate = TemplateParser.parse(createReader("/templates/login.html")); Template indexTemplate = TemplateParser.parse(createReader("/templates/index.html")); + Template newTemplate = TemplateParser.parse(createReader("/templates/new.html")); Template knownSonesTemplate = TemplateParser.parse(createReader("/templates/knownSones.html")); Template createSoneTemplate = TemplateParser.parse(createReader("/templates/createSone.html")); Template createPostTemplate = TemplateParser.parse(createReader("/templates/createPost.html")); @@ -613,6 +614,7 @@ public class WebInterface implements CoreListener { PageToadletFactory pageToadletFactory = new PageToadletFactory(sonePlugin.pluginRespirator().getHLSimpleClient(), "/Sone/"); pageToadlets.add(pageToadletFactory.createPageToadlet(new RedirectPage("", "index.html"))); pageToadlets.add(pageToadletFactory.createPageToadlet(new IndexPage(indexTemplate, this), "Index")); + pageToadlets.add(pageToadletFactory.createPageToadlet(new NewPage(newTemplate, this), "New")); pageToadlets.add(pageToadletFactory.createPageToadlet(new CreateSonePage(createSoneTemplate, this), "CreateSone")); pageToadlets.add(pageToadletFactory.createPageToadlet(new KnownSonesPage(knownSonesTemplate, this), "KnownSones")); pageToadlets.add(pageToadletFactory.createPageToadlet(new EditProfilePage(editProfileTemplate, this), "EditProfile")); @@ -888,7 +890,7 @@ public class WebInterface implements CoreListener { if (!getMentionedSones(reply.getText()).isEmpty()) { boolean isMentioned = false; for (PostReply existingReply : getCore().getReplies(reply.getPost())) { - isMentioned |= getCore().isNewReply(reply.getId()) && !getMentionedSones(existingReply.getText()).isEmpty(); + isMentioned |= !reply.isKnown() && !getMentionedSones(existingReply.getText()).isEmpty(); } if (!isMentioned) { mentionNotification.remove(reply.getPost()); diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/GetStatusAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/GetStatusAjaxPage.java index 1f23e4a..16bb133 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/GetStatusAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/GetStatusAjaxPage.java @@ -177,7 +177,7 @@ public class GetStatusAjaxPage extends JsonPage { jsonSone.put("id", sone.getId()); jsonSone.put("name", SoneAccessor.getNiceName(sone)); jsonSone.put("local", sone.getInsertUri() != null); - jsonSone.put("status", webInterface.getCore().getSoneStatus(sone).name()); + jsonSone.put("status", sone.getStatus().name()); jsonSone.put("modified", webInterface.getCore().isModifiedSone(sone)); jsonSone.put("locked", webInterface.getCore().isLocked(sone)); jsonSone.put("lastUpdatedUnknown", sone.getTime() == 0); diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/JsonPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/JsonPage.java index 4ee3b99..7c3abdb 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/JsonPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/JsonPage.java @@ -28,8 +28,8 @@ import net.pterodactylus.util.json.JsonObject; import net.pterodactylus.util.json.JsonUtils; import net.pterodactylus.util.web.Page; import net.pterodactylus.util.web.Response; -import freenet.clients.http.ToadletContext; import freenet.clients.http.SessionManager.Session; +import freenet.clients.http.ToadletContext; /** * A JSON page is a specialized {@link Page} that will always return a JSON diff --git a/src/main/resources/i18n/sone.de.properties b/src/main/resources/i18n/sone.de.properties new file mode 100644 index 0000000..d4e4fc3 --- /dev/null +++ b/src/main/resources/i18n/sone.de.properties @@ -0,0 +1,448 @@ +Navigation.Menu.Sone.Name=Sone +Navigation.Menu.Sone.Tooltip=Soziales Netzwerk in Freenet +Navigation.Menu.Sone.Item.Login.Name=Anmelden +Navigation.Menu.Sone.Item.Login.Tooltip=In Ihre Sone einloggen +Navigation.Menu.Sone.Item.Index.Name=Ihre Sone +Navigation.Menu.Sone.Item.Index.Tooltip=Zeigt Ihre Sone an +Navigation.Menu.Sone.Item.New.Name=Neue Nachrichten und Antworten +Navigation.Menu.Sone.Item.New.Tooltip=Zeigt neue Nachrichten und Antworten an +Navigation.Menu.Sone.Item.CreateSone.Name=Sone anlegen +Navigation.Menu.Sone.Item.CreateSone.Tooltip=Erstellt eine neue Sone +Navigation.Menu.Sone.Item.KnownSones.Name=Bekannte Sones +Navigation.Menu.Sone.Item.KnownSones.Tooltip=Zeigt alle bekannten Sones an +Navigation.Menu.Sone.Item.Bookmarks.Name=Favoriten +Navigation.Menu.Sone.Item.Bookmarks.Tooltip=Zeigt ihre Lieblingsnachrichten an +Navigation.Menu.Sone.Item.EditProfile.Name=Profil bearbeiten +Navigation.Menu.Sone.Item.EditProfile.Tooltip=Bearbeitet das Profil Ihrer Sone +Navigation.Menu.Sone.Item.ImageBrowser.Name=Bilder +Navigation.Menu.Sone.Item.ImageBrowser.Tooltip=Verwaltet Ihre Bilder +Navigation.Menu.Sone.Item.DeleteSone.Name=Sone löschen +Navigation.Menu.Sone.Item.DeleteSone.Tooltip=Löscht die aktuelle Sone +Navigation.Menu.Sone.Item.Logout.Name=Abmelden +Navigation.Menu.Sone.Item.Logout.Tooltip=Meldet die aktuelle Sone ab +Navigation.Menu.Sone.Item.Options.Name=Optionen +Navigation.Menu.Sone.Item.Options.Tooltip=Konfiguriert Sone +Navigation.Menu.Sone.Item.Rescue.Name=Sonerettung +Navigation.Menu.Sone.Item.Rescue.Tooltip=Rettet Ihre Sone +Navigation.Menu.Sone.Item.About.Name=Über +Navigation.Menu.Sone.Item.About.Tooltip=Informationen über Sone + +Page.About.Title=Über - Sone +Page.About.Page.Title=Über +Page.About.Flattr.Description=Wenn Sie Sone mögen und sich gerne bei mir dafür erkenntlichen zeigen möchten, können Sie einfach den Flattr-Button benutzen, den Sie am unteren Rand jeder Seite finden. Flattr ist eine nicht anonyme Seite für „Micropayment“, die ein bisschen wie ein Klingelbeutel funktioniert und für jeden User die monatlichen Ausgaben deckelt (mindestens jedoch 2 €). Mehr Informationen erhalten Sie auf {link}flattr.com{/link}. +Page.About.Homepage.Title=Homepage +Page.About.Homepage.Description=Mehr Informationen und den Sourcecode von Sone gibt es auf der {link}Sone Homepage{/link}. +Page.About.License.Title=Lizenz + +Page.Options.Title=Optionen - Sone +Page.Options.Page.Title=Optionen +Page.Options.Page.Description=Diese Optionen beeinflussen das Laufzeitverhalten von Sone. +Page.Options.Section.SoneSpecificOptions.Title=Sone-spezifische Optionen +Page.Options.Section.SoneSpecificOptions.NotLoggedIn=Diese Optionen sind nur verfügbar, wenn Sie {link}angemeldet{/link} sind. +Page.Options.Section.SoneSpecificOptions.LoggedIn=Diese Optionen sind nur verfügbar, wenn Sie angemeldet sind, und gelten nur für die angemeldete Sone. +Page.Options.Option.AutoFollow.Description=Wenn eine neue Sone gefunden wird, automatisch folgen. Bitte beachten Sie dabei, dass nur Sones gefolgt wird, die nach der Aktivierung dieser Option gefunden werden! +Page.Options.Option.EnableSoneInsertNotifications.Description=Zeige Benachrichtigungen an, wenn Ihre Sone hochgeladen wird, oder das Hochladen beendet wurde. +Page.Options.Option.ShowNotificationNewSones.Description=Zeige Benachrichtigungen für neue Sones. +Page.Options.Option.ShowNotificationNewPosts.Description=Zeige Benachrichtigungen für neue Nachrichten. +Page.Options.Option.ShowNotificationNewReplies.Description=Zeige Benachrichtigungen für neue Antworten. +Page.Options.Section.AvatarOptions.Title=Avataroptionen +Page.Options.Option.ShowAvatars.Description=Abhängig von der ausgewählten Option können Sie hier benutzerdefinierte Avatare deaktiveren. Wenn ein benutzerdefinierter Avatar unterdrückt wird, wird dafür der normale generierte Avatar angezeigt. +Page.Options.Option.ShowAvatars.Never.Description=Niemals benutzerdefinierte Avatare anzeigen. +Page.Options.Option.ShowAvatars.Followed.Description=Nur benutzerdefinierte Avatare von Sones, denen Sie folgen, anzeigen. +Page.Options.Option.ShowAvatars.ManuallyTrusted.Description=Nur benutzerdefinierte Avatare von Sones, denen Sie manuell einen Vertrauenswert von mehr als 0 zugewiesen haben, anzeigen. +Page.Options.Option.ShowAvatars.Trusted.Description=Nur benutzerdefinierte Avatare von Sones, die einen berechneten Vertrauenswert von mehr als 0 haben, anzeigen. +Page.Options.Option.ShowAvatars.Always.Description=Immer benutzerdefinierte Avatare anzeigen. Warnung: Benutzerdefinierte Avatare können beliebiges Bildmaterial enthalten! +Page.Options.Section.RuntimeOptions.Title=Laufzeitverhalten +Page.Options.Option.InsertionDelay.Description=Anzahl der Sekunden, die vor dem Hochladen einer Sone nach einer Änderung gewartet wird. +Page.Options.Option.PostsPerPage.Description=Anzahl der Nachrichten pro Seite. +Page.Options.Option.CharactersPerPost.Description=Die Anzahl der Zeichen, die eine Nachricht enthalten muss, damit sie gekürzt angezeigt wird (-1 für „nie kürzen“). Die Anzahl der tatsächlich angezeigten Zeichen wird in der nächsten Option konfiguriert. +Page.Options.Option.PostCutOffLength.Description=Die Anzahl der Zeichen, die von einer gekürzten Nachricht sichtbar sind (siehe Option hierüber). +Page.Options.Option.RequireFullAccess.Description=Zugriff auf Sone für alle Rechner, die keinen vollen Zugriff haben, unterbinden. +Page.Options.Section.TrustOptions.Title=Vertrauenseinstellungen +Page.Options.Option.PositiveTrust.Description=Die Menge an positivem Vertrauen, die bei einem Klick auf den Haken unter einer Nachricht zugewiesen werden soll. +Page.Options.Option.NegativeTrust.Description=Die Menge an negativem Vertrauen, die bei einem Klick auf das rote X unter einer Nachricht zugewiesen werden soll. Dieser Wert sollte negativ sein. +Page.Options.Option.TrustComment.Description=Der Kommentar, der im Web of Trust für Ihre Zuweisung angezeigt werden soll. +Page.Options.Section.FcpOptions.Title=FCP-Schnittstellenoptionen +Page.Options.Option.FcpInterfaceActive.Description=Die FCP-Schnittstelle aktivieren, um anderen Plugins und Programmen den Zugriff auf Ihr Sone-Plugin zu ermöglichen. +Page.Options.Option.FcpFullAccessRequired.Description=FCP-Verbindungen nur von „erlaubten Hosts“ erlauben (siehe {link}Knoten-Konfiguration, Abschnitt “FCP”{/link}). +Page.Options.Option.FcpFullAccessRequired.Value.No=Nein +Page.Options.Option.FcpFullAccessRequired.Value.Writing=Für Schreibzugriffe +Page.Options.Option.FcpFullAccessRequired.Value.Always=Immer +Page.Options.Section.Cleaning.Title=Aufräumen +Page.Options.Option.ClearOnNextRestart.Description=Setzt die Konfiguration des Sone-Plugins beim nächsten Start zurück. Vorsicht: {strong}Alle Informationen Ihrer Sones werden gelöscht{/strong}, also stellen Sie bitte sicher, dass Sie die notwendigen Sicherungen angefertigt haben! Damit diese Option aktiv wird, muss auch die folgende Option aktiviert werden. +Page.Options.Option.ReallyClearOnNextRestart.Description=Diese Option muss auf „ja“ gestellt werden, wenn Sie wirklich {strong}wirklich{/strong} sämtliche Informationen des Sone-Plugins beim nächsten Start entfernen möchten. +Page.Options.Warnings.ValueNotChanged=Diese Option wurde nicht geändert, weil Sie einen ungültigen Wert angegeben haben. +Page.Options.Button.Save=Speichern + +Page.Login.Title=Anmelden - Sone +Page.Login.Page.Title=Anmelden +Page.Login.Label.SelectSone=Sone auswählen: +Page.Login.Option.NoSone=Sone auswählen… + +Page.Login.CreateSone.Title=Sone anlegen + +Page.CreateSone.Title=Sone anlegen - Sone + +Page.DeleteSone.Title=Sone löschen - Sone +Page.DeleteSone.Page.Title=Sone „{sone}“ löschen? +Page.DeleteSone.Page.Description=Ihre Sone wird nicht wirklich aus Freenet gelöscht, weil das nicht möglich ist; es wird lediglich die Verbindung zum „Web of Trust“ getrennt. +Page.DeleteSone.Button.Yes=Ja, löschen. +Page.DeleteSone.Button.No=Nein, nicht löschen. + +Page.Index.Title=Ihre Sone - Sone +Page.Index.Label.Text=Ihre Nachricht: +Page.Index.Label.Sender=Absender: +Page.Index.Button.Post=Abschicken! +Page.Index.PostList.Title=Nachrichtenliste +Page.Index.PostList.Text.NoPostYet=Bisher hat noch niemand etwas geschrieben. Sie sollten sofort damit anfangen! + +Page.New.Title=Neue Nachrichten und Antworten - Sone +Page.New.Page.Title=Neue Nachrichten und Antworten +Page.New.NothingNew=Im Moment gibt es nichts Neues. + +Page.KnownSones.Title=Bekannte Sones - Sone +Page.KnownSones.Page.Title=Bekannte Sones +Page.KnownSones.Text.NoKnownSones=Für die aktuellen Filterkriterien können keine Sones gefunden werden. +Page.KnownSones.Label.Sort=Sortieren: +Page.KnownSones.Label.FilterSones=Sones filtern: +Page.KnownSones.Sort.Field.Name=Name +Page.KnownSones.Sort.Field.LastActivity=Letzte Aktivität +Page.KnownSones.Sort.Field.Posts=Anzahl der Nachrichten +Page.KnownSones.Sort.Field.Images=Anzahl der Bilder +Page.KnownSones.Sort.Order.Ascending=Aufsteigend +Page.KnownSones.Sort.Order.Descending=Absteigend +Page.KnownSones.Filter.Followed=Nur gefolgte Sones anzeigen +Page.KnownSones.Filter.NotFollowed=Gefolgte Sones nicht anzeigen +Page.KnownSones.Filter.New=Nur neue Sones anzeigen +Page.KnownSones.Filter.NotNew=Neue Sones nicht anzeigen +Page.KnownSones.Button.Apply=Anwenden +Page.KnownSones.Button.FollowAllSones=Allen Sones auf dieser Seite folgen +Page.KnownSones.Button.UnfollowAllSones=Alle Sones auf dieser Seite entfolgen + +Page.EditProfile.Title=Profil bearbeiten - Sone +Page.EditProfile.Page.Title=Profil bearbeiten +Page.EditProfile.Page.Description=Auf dieser Seite können Sie Ihr Profile bearbeiten. +Page.EditProfile.Page.Hint.Optionality=Bitte denken Sie daran: Jedes einzelne Feld auf dieser Seite ist optional! Keine der Angaben ist zwingend! Alles, was Sie hier angeben, wird möglicherweise für eine lange Zeit in Freenet gespeichert bleiben. +Page.EditProfile.Label.FirstName=Vorname: +Page.EditProfile.Label.MiddleName=Zweiter Vorname: +Page.EditProfile.Label.LastName=Nachname: +Page.EditProfile.Birthday.Title=Geburtstag +Page.EditProfile.Birthday.Label.Day=Tag: +Page.EditProfile.Birthday.Label.Month=Monat: +Page.EditProfile.Birthday.Label.Year=Jahr: +Page.EditProfile.Avatar.Title=Avatar +Page.EditProfile.Avatar.Description=Sie können eines Ihrer hochgeladenen Bilder als Avatar auswählen. Es sollte nicht größer als 64×64 Pixel, da das die größte Größe ist, die bei anderen Leuten im Webinterface angezeigt wird. (Bei Ihnen wird im Kopf der Seite eine Version in 80×80 Pixeln angezeigt.) +Page.EditProfile.Avatar.Delete=Kein Avatar +Page.EditProfile.Fields.Title=Benutzerdefinierte Felder +Page.EditProfile.Fields.Description=Sie können Ihrem Profil eigene Felder hinzufügen. Diese Felder können alles mögliche enthalten. Bedenken Sie jedoch, dass, wenn es um Anonymität geht, weniger manchmal mehr ist. +Page.EditProfile.Fields.Button.Edit=bearbeiten +Page.EditProfile.Fields.Button.MoveUp=nach oben +Page.EditProfile.Fields.Button.MoveDown=nach unten +Page.EditProfile.Fields.Button.Delete=löschen +Page.EditProfile.Fields.Button.ReallyDelete=wirklich löschen +Page.EditProfile.Fields.AddField.Title=Feld hinzufügen +Page.EditProfile.Fields.AddField.Label.Name=Name: +Page.EditProfile.Fields.AddField.Button.AddField=Feld hinzufügen +Page.EditProfile.Button.Save=Profil speichern +Page.EditProfile.Error.DuplicateFieldName=Das Feld „{fieldName}“ existiert bereits. + +Page.EditProfileField.Title=Benutzerdefiniertes Feld bearbeiten - Sone +Page.EditProfileField.Page.Title=Benutzerdefiniertes Feld bearbeiten +Page.EditProfileField.Text=Bitte geben Sie einen neuen Namen für dieses benutzerdefinierte Feld ein. +Page.EditProfileField.Error.DuplicateFieldName=Der von Ihnen eingegebene Feldname existiert bereits. +Page.EditProfileField.Button.Save=Ändern +Page.EditProfileField.Button.Reset=Aten Namen wiederherstellen +Page.EditProfileField.Button.Cancel=Namen nicht ändern + +Page.DeleteProfileField.Title=Benutzerdefiniertes Feld löschen - Sone +Page.DeleteProfileField.Page.Title=Benutzerdefiniertes Feld löschen +Page.DeleteProfileField.Text=Möchten Sie dieses Feld wirklich löschen? +Page.DeleteProfileField.Button.Yes=Ja, löschen +Page.DeleteProfileField.Button.No=Nein, nicht löschen + +Page.CreatePost.Title=Nachricht schreiben - Sone +Page.CreatePost.Page.Title=Nachricht schreiben +Page.CreatePost.Label.Text=Inhalt der Nachricht: +Page.CreatePost.Button.Post=Abschicken! +Page.CreatePost.Error.EmptyText=Sie haben leider keinen Text eingegeben. Sie sollten wirklich etwas mehr schreiben! + +Page.CreateReply.Title=Antwort schreiben - Sone +Page.CreateReply.Page.Title=Antwort schreiben +Page.CreateReply.Error.EmptyText=Sie haben leider keinen Text eingegeben. Sie sollten wirklich etwas mehr schreiben! +Page.CreateReply.Label.Text=Inhalt der Antwort: +Page.CreateReply.Button.Post=Abschicken! + +Page.ViewSone.Title=Sone anzeigen - Sone +Page.ViewSone.Page.TitleWithoutSone=Unbekannte Sone anzeigen +Page.ViewSone.NoSone.Description=Momentan ist keine Sone mit der ID {sone} bekannt. Wenn Sie eine bestimmte Sone gesucht haben, vergewissern Sie sich bitte, dass Sie in Ihrem „Web of Trust“ sichtbar ist: +Page.ViewSone.UnknownSone.Description=Diese Sone wurde noch nicht herunter geladen. Bitte versuchen Sie es in Kürze nochmal. +Page.ViewSone.UnknownSone.LinkToWebOfTrust=Auch, wenn diese Sone noch nicht herunter geladen wurde, so ist vielleicht ihr „Web of Trust“ Profil bereits verfügbar: +Page.ViewSone.WriteAMessage=Sie können hier eine Nachricht an diese Sone schreiben. Bitte beachten Sie, dass diese Nachricht für alle Sones sichtbar ist! +Page.ViewSone.PostList.Title=Nachrichten von {sone} +Page.ViewSone.PostList.Text.NoPostYet=Diese Sone hat noch nichts geschrieben. +Page.ViewSone.Profile.Title=Profil +Page.ViewSone.Profile.Label.Name=Name +Page.ViewSone.Profile.Label.Albums=Alben +Page.ViewSone.Profile.Albums.Text.All=Alle Alben +Page.ViewSone.Profile.Name.WoTLink=„Web of Trust“ Profil +Page.ViewSone.Replies.Title=Nachrichten, auf die {sone} geantwortet hat + +Page.ViewPost.Title=Nachricht anzeigen - Sone +Page.ViewPost.Page.Title=Nachricht von {sone} anzeigen +Page.ViewPost.Page.TitleUnknownSone=Unbekannte Nachricht anzeigen +Page.ViewPost.Text.UnknownPost=Diese Nachricht wurde noch nicht herunter geladen. + +Page.Like.Title=Nachricht gefällt - Sone +Page.Unlike.Title=Nachricht gefällt nicht - Sone + +Page.DeletePost.Title=Nachricht löschen - Sone +Page.DeletePost.Page.Title=Nachricht löschen +Page.DeletePost.Text.PostWillBeGone=Diese Nachricht wird aus Ihrer Sone gelöscht. Es ist jedoch technisch nicht möglich, sie auch aus Freenet zu entfernen; ältere Versionen Ihrer Sone werden diese Nachricht immer enthalten. +Page.DeletePost.Button.Yes=Ja, löschen. +Page.DeletePost.Button.No=Nein, nicht löschen. + +Page.DeleteReply.Title=Antwort löschen - Sone +Page.DeleteReply.Page.Title=Antwort löschen +Page.DeleteReply.Text.PostWillBeGone=Diese Antwort wird aus Ihrer Sone gelöscht. Es ist jedoch technisch nicht möglich, sie auch aus Freenet zu entfernen; ältere Versionen Ihrer Sone werden diese Antwort immer enthalten. +Page.DeleteReply.Button.Yes=Ja, löschen. +Page.DeleteReply.Button.No=Nein, nicht löschen. + +Page.LockSone.Title=Sone sperren - Sone + +Page.UnlockSone.Title=Sone entsperren - Sone + +Page.FollowSone.Title=Sone folgen - Sone + +Page.UnfollowSone.Title=Sone entfolgen - Sone + +Page.ImageBrowser.Title=Bildergallerie - Sone +Page.ImageBrowser.Album.Title=Album “{album}” +Page.ImageBrowser.Album.Error.NotFound.Text=Das gewünschte Album konnte nicht gefunden werden. Es ist möglich, dass es noch nicht herunter geladen oder bereits gelöscht wurde. +Page.ImageBrowser.Sone.Title=Alben von {sone} +Page.ImageBrowser.Sone.Error.NotFound.Text=Die gewünschte Sone konnte nicht gefunden werden. Es ist möglich, dass sie noch nicht herunter geladen wurde. +Page.ImageBrowser.Header.Albums=Alben +Page.ImageBrowser.Header.Images=Bilder +Page.ImageBrowser.Link.All=Alle Sones +Page.ImageBrowser.CreateAlbum.Button.CreateAlbum=Album anlegen +Page.ImageBrowser.Album.Edit.Title=Album bearbeiten +Page.ImageBrowser.Album.Delete.Title=Album löschen +Page.ImageBrowser.Album.Label.AlbumImage=Albumbild: +Page.ImageBrowser.Album.Label.Title=Titel: +Page.ImageBrowser.Album.Label.Description=Beschreibung: +Page.ImageBrowser.Album.AlbumImage.Choose=Albumbild auswählen… +Page.ImageBrowser.Album.Button.Save=Album speichern +Page.ImageBrowser.Album.Button.Delete=Album löschen +Page.ImageBrowser.Image.Edit.Title=Bild bearbeiten +Page.ImageBrowser.Image.Title.Label=Titel: +Page.ImageBrowser.Image.Description.Label=Beschreibung: +Page.ImageBrowser.Image.Button.MoveLeft=◀ +Page.ImageBrowser.Image.Button.Save=Bild speichern +Page.ImageBrowser.Image.Button.MoveRight=► +Page.ImageBrowser.Image.Delete.Title=Bild löschen +Page.ImageBrowser.Image.Button.Delete=Bild löschen + +Page.CreateAlbum.Title=Album anlegen - Sone +Page.CreateAlbum.Page.Title=Album anlegen +Page.CreateAlbum.Error.NameMissing=Sie haben keinen Namen für Ihr neues Album angegeben. + +Page.UploadImage.Title=Bild hochladen - Sone +Page.UploadImage.Error.InvalidImage=Das Bild, welches Sie hoch geladen haben, konnte nicht gelesen werden. Bitte laden Sie nur JPEG- (*.jpg, *.jpeg) oder PNG-Bilder (*.png) hoch! + +Page.EditImage.Title=Bild bearbeiten - Sone + +Page.DeleteImage.Title=Bild löschen - Sone +Page.DeleteImage.Page.Title=Bild löschen +Page.DeleteImage.Text.ImageWillBeGone=Das Bild „{image}“ wird aus Ihrem Album „{album}“ gelöscht. Wenn das Bild bereits in Freenet hoch geladen wurde, kann es von dort nicht mehr entfernt werden. Wollen Sie dieses Bild wirklich löschen? +Page.DeleteImage.Button.Yes=Ja, Bild löschen. +Page.DeleteImage.Button.No=Nein, Bild nicht löschen. + +Page.EditAlbum.Title=Album bearbeiten - Sone + +Page.DeleteAlbum.Title=Album löschen - Sone +Page.DeleteAlbum.Page.Title=Album löschen +Page.DeleteAlbum.Text.AlbumWillBeGone=Ihr Album „{title}“ wird gelöscht. Möchten Sie fortfahren? +Page.DeleteAlbum.Button.Yes=Ja, Album löschen. +Page.DeleteAlbum.Button.No=Nein, Album nicht löschen. + +Page.Trust.Title=Sone vertrauen - Sone + +Page.Distrust.Title=Sone misstrauen - Sone + +Page.Untrust.Title=Sone nicht vertrauen - Sone + +Page.MarkAsKnown.Title=Als gelesen markieren - Sone + +Page.Bookmark.Title=Als Favorit markieren - Sone +Page.Unbookmark.Title=Favoritenmarkierung entfernen - Sone +Page.Bookmarks.Title=Favoriten - Sone +Page.Bookmarks.Page.Title=Favoriten +Page.Bookmarks.Text.NoBookmarks=Sie haben bisher keine Nachrichten zu Ihren Favoriten erklärt. Sie können eine Nachricht zu einem Favoriten erklären, indem Sie den Stern unter der Nachricht anklicken. +Page.Bookmarks.Text.PostsNotLoaded=Einige Ihrer Favoriten können nicht angezeigt werden, weil sie noch nicht geladen wurden. Das kann passieren, wenn Sie Sone kürzlich erst neu gestartet haben, oder wenn die Nachricht gelöscht wurde. Wenn Sie sich sicher sind, dass alle favorisierten Nachrichten, die jetzt nicht angezeigt werden, nicht mehr existieren, können Sie sie {link}aus Ihren Favoriten entfernen{/link}. + +Page.Search.Title=Suchen - Sone +Page.Search.Page.Title=Suchergebnisse +Page.Search.Text.SoneHits=Diese Sones passen auf Ihre Suchbegriffe. +Page.Search.Text.PostHits=Dises Nachrichten passen auf Ihre Suchbegriffe. +Page.Search.Text.NoHits=Keine Sones oder Nachrichten passten auf Ihre Suchbegriffe. + +Page.Rescue.Title=Sone retten - Sone +Page.Rescue.Page.Title=Sone „{0}“ retten +Page.Rescue.Text.Description=Die Sonerettung stellt vorherige Versionen Ihrer Sone wieder her. Dieses kann notwendig werden, wenn die Konfiguration verloren geht. +Page.Rescue.Text.Procedure=Die Sonerettung lädt die letzte hoch geladene Version Ihrer Sone. Wenn die Version erfolgreich geladen wurde, können Sie sie in aller Ruhe inspizieren, z. B. in einem zweiten Browser-Tab, und bei Gefallen können Sie einfach Ihre Sone entsperren; die zuletzt geladene Version wird dann als aktuelle Version neu hoch geladen. Wenn eine ältere Version geladen werden soll, können Sie der Sonerettung einfach sagen, dass sie die nächstältere Version herunter laden soll. +Page.Rescue.Text.Fetching=Die Sonerettung versucht gerade, Version {0} Ihrer Sone herunter zu laden. +Page.Rescue.Text.Fetched=Die Sonerettung hat Version {0} Ihrer Sone herunter geladen. Bitte überprüfen Sie Ihre Nachrichten, Antworten und Ihr Profile. Bei Gefallen können Sie die Sone einfach entsperren. +Page.Rescue.Text.FetchedLast=Die Sonerettung hat die letzte verfügbare Version Ihrer Sone herunter geladen. Wenn bis jetzt keine Version dabei war, die Sie wiederherstellen möchten, haben Sie jetzt kein Glück. +Page.Rescue.Text.NotFetched=Die Sonerettung konnte Version {0} Ihrer Sone nicht herunter laden. Bitte versuchen Sie erneut, Version {0} herunter zu laden, oder versuchen Sie die nächstältere Version. +Page.Rescue.Label.NextEdition=Nächste Version: +Page.Rescue.Button.Fetch=Version herunter laden + +Page.NoPermission.Title=Unberechtigter Zugriff - Sone +Page.NoPermission.Page.Title=Unberechtigter Zugriff +Page.NoPermission.Text.NoPermission=Sie haben versucht, etwas zu tun, zu dem Sie nicht berechtigt sind. Bitte unterlassen Sie das, da wir sonst gezwungen sind, Gegenmaßnahmen zu ergreifen! + +Page.DismissNotification.Title=Benachrichtigung ausblenden - Sone + +Page.WotPluginMissing.Text.WotRequired=Da das „Web of Trust“ ein integraler Bestandteil von Sone ist, muss das „Web of Trust“ Plugin geladen sein, damit Sone funktionieren kann. +Page.WotPluginMissing.Text.LoadPlugin=Bitte laden Sie das „Web of Trust“ Plugin im {link}plugin manager{/link}. + +Page.Logout.Title=Abmelden - Sone + +Page.Invalid.Title=Ungültige Aktion ausgeführt - Sone +Page.Invalid.Page.Title=Ungültige Aktion ausgeführt +Page.Invalid.Text=Eine ungültige Aktion wurde ausgeführt, oder eine gültige Aktion hatte ungültige Parameter. Bitte kehren Sie zur {link}Hauptseite{/link} zurück und versuchen Sie Ihre Aktion erneut. Wenn der Fehler weiterhin besteht, haben Sie wahrscheinlich einen Programmierfehler gefunden. + +View.Search.Button.Search=Suchen + +View.CreateSone.Text.WotIdentityRequired=Um eine Sone anzulegen, brauchen Sie eine Identität aus dem {link}„Web of Trust“ Plugin{/link}. +View.CreateSone.Select.Default=Eine Identität auswählen +View.CreateSone.Text.NoIdentities=Sie haben keine Identitäten im „Web of Trust“ Plugin. Bitte erstellen Sie im {link}„Web of Trust“ Plugin{/link} eine neue Identität. +View.CreateSone.Text.NoNonSoneIdentities=Sie haben keine Identitäten im „Web of Trust“ Plugin, die noch nicht mit einer Sone verbunden sind. Benutzen Sie eine der verbleibenden Identitäten, um eine neue Sone anzulegen, oder erstellen Sie im {link}„Web of Trust“ Plugin{/link} eine neue Identität. +View.CreateSone.Button.Create=Sone anlegen +View.CreateSone.Text.Error.NoIdentity=Sie haben keine Identität ausgewählt. + +View.Sone.Label.LastUpdate=Letzte Aktivität: +View.Sone.Text.UnknownDate=unbekannt +View.Sone.Stats.Posts={0,number} {0,choice,0#Nachrichten|1#Nachricht|1 0) || (getPostElement(element).find(".create-reply").length == 0)) { return; } - commentElement = (function(postId, author) { + (function(postId, author, insertAfterThisElement) { separator = $(" · ").addClass("separator"); - var commentElement = $("
    Comment
    ").addClass("show-reply-form").click(function() { - replyElement = $("#sone .post#post-" + postId + " .create-reply"); - replyElement.removeClass("hidden"); - replyElement.removeClass("light"); - (function(replyElement) { - replyElement.find("input.reply-input").blur(function() { - if ($(this).hasClass("default")) { - replyElement.addClass("light"); - } - }).focus(function() { - replyElement.removeClass("light"); - }); - })(replyElement); - textArea = replyElement.find("input.reply-input").focus().data("textarea"); - if (author != getCurrentSoneId()) { - textArea.val(textArea.val() + "@sone://" + author + " "); - } + getTranslation("WebInterface.Button.Comment", function(text) { + commentElement = $("
    " + text + "
    ").addClass("show-reply-form").click(function() { + replyElement = $("#sone .post#post-" + postId + " .create-reply"); + replyElement.removeClass("hidden"); + replyElement.removeClass("light"); + (function(replyElement) { + replyElement.find("input.reply-input").blur(function() { + if ($(this).hasClass("default")) { + replyElement.addClass("light"); + } + }).focus(function() { + replyElement.removeClass("light"); + }); + })(replyElement); + textArea = replyElement.find("input.reply-input").focus().data("textarea"); + if (author != getCurrentSoneId()) { + textArea.val(textArea.val() + "@sone://" + author + " "); + } + }); + $(insertAfterThisElement).after(commentElement.clone(true)); + $(insertAfterThisElement).after(separator); }); - return commentElement; - })(postId, author); - $(insertAfterThisElement).after(commentElement.clone(true)); - $(insertAfterThisElement).after(separator); + })(postId, author, insertAfterThisElement); } var translations = {}; @@ -806,7 +807,7 @@ function ajaxifyPost(postElement) { $(".expand-post-text", getPostElement(this)).toggleClass("hidden"); $(".shrink-post-text", getPostElement(this)).toggleClass("hidden"); return false; - }) + }); }); /* ajaxify author/post links */ @@ -846,12 +847,14 @@ function ajaxifyPost(postElement) { }); /* mark everything as known on click. */ - $(postElement).click(function(event) { - if ($(event.target).hasClass("click-to-show")) { - return false; - } - markPostAsKnown(this); - }); + (function(postElement) { + $(postElement).click(function(event) { + if ($(event.target).hasClass("click-to-show")) { + return false; + } + markPostAsKnown(postElement, false); + }); + })(postElement); /* hide reply input field. */ $(postElement).find(".create-reply").addClass("hidden"); @@ -874,7 +877,7 @@ function ajaxifyPost(postElement) { } }); (function(postElement) { - var soneId = $(".sone-menu-id", postElement).text(); + var soneId = $(".sone-menu-id:first", postElement).text(); $(".sone-post-menu .follow", postElement).click(function() { var followElement = this; ajaxGet("followSone.ajax", { "sone": soneId, "formPassword": getFormPassword() }, function() { @@ -1062,7 +1065,7 @@ function ajaxifyNotification(notification) { } notification.find("form.mark-as-read button").click(function() { allIds = $(":input[name=id]", this.form).val().split(" "); - for (index = 0; index < allIds.length; index += 16) { + for (var index = 0; index < allIds.length; index += 16) { ids = allIds.slice(index, index + 16).join(" "); ajaxGet("markAsKnown.ajax", {"formPassword": getFormPassword(), "type": $(":input[name=type]", this.form).val(), "id": ids}); } @@ -1535,7 +1538,7 @@ function markPostAsKnown(postElements, skipRequest) { } $(".click-to-show", postElement).removeClass("new"); }); - markReplyAsKnown($(postElements).find(".reply")); + markReplyAsKnown($(postElements).find(".reply"), true); } function markReplyAsKnown(replyElements, skipRequest) { @@ -1923,8 +1926,10 @@ $(document).ready(function() { getTranslation("WebInterface.Confirmation.DeletePostButton", function(text) { getTranslation("WebInterface.Confirmation.DeleteReplyButton", function(text) { getTranslation("WebInterface.DefaultText.Reply", function(text) { - $("#sone .post").each(function() { - ajaxifyPost(this); + getTranslation("WebInterface.Button.Comment", function(text) { + $("#sone .post").each(function() { + ajaxifyPost(this); + }); }); }); }); diff --git a/src/main/resources/templates/about.html b/src/main/resources/templates/about.html index e04f4f2..7648c43 100644 --- a/src/main/resources/templates/about.html +++ b/src/main/resources/templates/about.html @@ -11,7 +11,7 @@

    <%= Page.About.Homepage.Title|l10n|html>

    - <%= Page.About.Homepage.Description|l10n|html|replace needle="{link}" replacement=''|replace needle="{/link}" replacement=''> + <%= Page.About.Homepage.Description|l10n|html|replace needle="{link}" replacement=''|replace needle="{/link}" replacement=''>

    <%= Page.About.License.Title|l10n|html>

    diff --git a/src/main/resources/templates/deleteSone.html b/src/main/resources/templates/deleteSone.html index 847e61d..cc6023f 100644 --- a/src/main/resources/templates/deleteSone.html +++ b/src/main/resources/templates/deleteSone.html @@ -1,6 +1,6 @@ <%include include/head.html> -

    <%= Page.DeleteSone.Page.Title|l10n|replace needle="{zone}" replacementKey=currentSone.name|html>

    +

    <%= Page.DeleteSone.Page.Title|l10n|replace needle="{sone}" replacementKey=currentSone.name|html>

    <%= Page.DeleteSone.Page.Description|l10n|html>

    diff --git a/src/main/resources/templates/new.html b/src/main/resources/templates/new.html new file mode 100644 index 0000000..8e9adf6 --- /dev/null +++ b/src/main/resources/templates/new.html @@ -0,0 +1,20 @@ +<%include include/head.html> + + + +

    <%= Page.New.Page.Title|l10n|html>

    + + <%include include/updateStatus.html> + +
    + <%= page|store key=pageParameter> + <%include include/pagination.html paginationName==pagination-index> + <%foreach pagination.items post> + <%include include/viewPost.html> + <%foreachelse> +
    <%= Page.New.NothingNew|l10n|html>
    + <%/foreach> + <%include include/pagination.html> +
    + +<%include include/tail.html> diff --git a/src/main/resources/templates/viewSone.html b/src/main/resources/templates/viewSone.html index 1d87e46..6dd3912 100644 --- a/src/main/resources/templates/viewSone.html +++ b/src/main/resources/templates/viewSone.html @@ -7,7 +7,10 @@

    <%= Page.ViewSone.Page.TitleWithoutSone|l10n|html>

    -

    <%= Page.ViewSone.NoSone.Description|l10n|replace needle="{sone}" replacementKey=soneId|html>

    +

    + <%= Page.ViewSone.NoSone.Description|l10n|replace needle="{sone}" replacementKey=soneId|html> + <%= Page.ViewSone.Profile.Name.WoTLink|l10n|html> +

    <%elseifnull sone.name>