From: David ‘Bombe’ Roden Date: Wed, 16 Nov 2011 19:20:39 +0000 (+0100) Subject: Merge branch 'release-0.7.3' X-Git-Tag: 0.7.3^0 X-Git-Url: https://git.pterodactylus.net/?p=Sone.git;a=commitdiff_plain;h=7d5aff5d52afa5d9725ce252c846b1aca6f1a761;hp=43a21f859e9fec31096c1540148bdd44a8e3702f Merge branch 'release-0.7.3' --- diff --git a/pom.xml b/pom.xml index 35203cf..a0a267d 100644 --- a/pom.xml +++ b/pom.xml @@ -2,12 +2,12 @@ 4.0.0 net.pterodactylus sone - 0.7.2 + 0.7.3 net.pterodactylus utils - 0.11.1 + 0.11.2 junit @@ -18,7 +18,7 @@ org.freenetproject fred - 0.7.5.1336 + 0.7.5.1405 provided diff --git a/src/main/java/net/pterodactylus/sone/core/Core.java b/src/main/java/net/pterodactylus/sone/core/Core.java index ee5ec87..d3f50a1 100644 --- a/src/main/java/net/pterodactylus/sone/core/Core.java +++ b/src/main/java/net/pterodactylus/sone/core/Core.java @@ -136,6 +136,9 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis /* synchronize access on itself. */ private final Map soneStatuses = new HashMap(); + /** The times Sones were followed. */ + private final Map soneFollowingTimes = new HashMap(); + /** Locked local Sones. */ /* synchronize on itself. */ private final Set lockedSones = new HashSet(); @@ -588,6 +591,23 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis } /** + * Returns the time when the given was first followed by any local Sone. + * + * @param sone + * The Sone to get the time for + * @return The time (in milliseconds since Jan 1, 1970) the Sone has first + * been followed, or {@link Long#MAX_VALUE} + */ + public long getSoneFollowingTime(Sone sone) { + synchronized (soneFollowingTimes) { + if (soneFollowingTimes.containsKey(sone)) { + return soneFollowingTimes.get(sone); + } + return Long.MAX_VALUE; + } + } + + /** * Returns whether the target Sone is trusted by the origin Sone. * * @param origin @@ -1008,7 +1028,11 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis } Sone sone = addLocalSone(ownIdentity); sone.getOptions().addBooleanOption("AutoFollow", new DefaultOption(false)); - sone.addFriend("nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI"); + sone.getOptions().addBooleanOption("EnableSoneInsertNotifications", new DefaultOption(false)); + sone.getOptions().addBooleanOption("ShowNotification/NewSones", new DefaultOption(true)); + sone.getOptions().addBooleanOption("ShowNotification/NewPosts", new DefaultOption(true)); + sone.getOptions().addBooleanOption("ShowNotification/NewReplies", new DefaultOption(true)); + followSone(sone, getSone("nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI")); touchConfiguration(); return sone; } @@ -1041,13 +1065,11 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis coreListenerManager.fireNewSoneFound(sone); for (Sone localSone : getLocalSones()) { if (localSone.getOptions().getBooleanOption("AutoFollow").get()) { - localSone.addFriend(sone.getId()); - touchConfiguration(); + followSone(localSone, sone); } } } } - remoteSones.put(identity.getId(), sone); soneDownloader.addSone(sone); setSoneStatus(sone, SoneStatus.unknown); soneDownloaders.execute(new Runnable() { @@ -1064,6 +1086,90 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis } /** + * Lets the given local Sone follow the Sone with the given ID. + * + * @param sone + * The local Sone that should follow another Sone + * @param soneId + * The ID of the Sone to follow + */ + public void followSone(Sone sone, String soneId) { + Validation.begin().isNotNull("Sone", sone).isNotNull("Sone ID", soneId).check(); + followSone(sone, getSone(soneId)); + } + + /** + * Lets the given local Sone follow the other given Sone. If the given Sone + * was not followed by any local Sone before, this will mark all elements of + * the followed Sone as read that have been created before the current + * moment. + * + * @param sone + * The local Sone that should follow the other Sone + * @param followedSone + * The Sone that should be followed + */ + public void followSone(Sone sone, Sone followedSone) { + Validation.begin().isNotNull("Sone", sone).isNotNull("Followed Sone", followedSone).check(); + sone.addFriend(followedSone.getId()); + synchronized (soneFollowingTimes) { + if (!soneFollowingTimes.containsKey(followedSone)) { + long now = System.currentTimeMillis(); + soneFollowingTimes.put(followedSone, now); + for (Post post : followedSone.getPosts()) { + if (post.getTime() < now) { + markPostKnown(post); + } + } + for (PostReply reply : followedSone.getReplies()) { + if (reply.getTime() < now) { + markReplyKnown(reply); + } + } + } + } + touchConfiguration(); + } + + /** + * Lets the given local Sone unfollow the Sone with the given ID. + * + * @param sone + * The local Sone that should unfollow another Sone + * @param soneId + * The ID of the Sone being unfollowed + */ + public void unfollowSone(Sone sone, String soneId) { + Validation.begin().isNotNull("Sone", sone).isNotNull("Sone ID", soneId).check(); + unfollowSone(sone, getSone(soneId, false)); + } + + /** + * Lets the given local Sone unfollow the other given Sone. If the given + * local Sone is the last local Sone that followed the given Sone, its + * following time will be removed. + * + * @param sone + * The local Sone that should unfollow another Sone + * @param unfollowedSone + * The Sone being unfollowed + */ + public void unfollowSone(Sone sone, Sone unfollowedSone) { + Validation.begin().isNotNull("Sone", sone).isNotNull("Unfollowed Sone", unfollowedSone).check(); + sone.removeFriend(unfollowedSone.getId()); + boolean unfollowedSoneStillFollowed = false; + for (Sone localSone : getLocalSones()) { + unfollowedSoneStillFollowed |= localSone.hasFriend(unfollowedSone.getId()); + } + if (!unfollowedSoneStillFollowed) { + synchronized (soneFollowingTimes) { + soneFollowingTimes.remove(unfollowedSone); + } + } + touchConfiguration(); + } + + /** * Retrieves the trust relationship from the origin to the target. If the * trust relationship can not be retrieved, {@code null} is returned. * @@ -1195,9 +1301,13 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis synchronized (newPosts) { for (Post post : sone.getPosts()) { post.setSone(storedSone); - if (!storedPosts.contains(post) && !knownPosts.contains(post.getId())) { - newPosts.add(post.getId()); - coreListenerManager.fireNewPostFound(post); + if (!storedPosts.contains(post)) { + if (post.getTime() < getSoneFollowingTime(sone)) { + knownPosts.add(post.getId()); + } else if (!knownPosts.contains(post.getId())) { + newPosts.add(post.getId()); + coreListenerManager.fireNewPostFound(post); + } } posts.put(post.getId(), post); } @@ -1216,9 +1326,13 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis synchronized (newReplies) { for (PostReply reply : sone.getReplies()) { reply.setSone(storedSone); - if (!storedReplies.contains(reply) && !knownReplies.contains(reply.getId())) { - newReplies.add(reply.getId()); - coreListenerManager.fireNewReplyFound(reply); + if (!storedReplies.contains(reply)) { + if (reply.getTime() < getSoneFollowingTime(sone)) { + knownReplies.add(reply.getId()); + } else if (!knownReplies.contains(reply.getId())) { + newReplies.add(reply.getId()); + coreListenerManager.fireNewReplyFound(reply); + } } replies.put(reply.getId(), reply); } @@ -1343,6 +1457,9 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis /* initialize options. */ sone.getOptions().addBooleanOption("AutoFollow", new DefaultOption(false)); sone.getOptions().addBooleanOption("EnableSoneInsertNotifications", new DefaultOption(false)); + sone.getOptions().addBooleanOption("ShowNotification/NewSones", new DefaultOption(true)); + sone.getOptions().addBooleanOption("ShowNotification/NewPosts", new DefaultOption(true)); + sone.getOptions().addBooleanOption("ShowNotification/NewReplies", new DefaultOption(true)); /* load Sone. */ String sonePrefix = "Sone/" + sone.getId(); @@ -1505,6 +1622,9 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis /* load options. */ sone.getOptions().getBooleanOption("AutoFollow").set(configuration.getBooleanValue(sonePrefix + "/Options/AutoFollow").getValue(null)); sone.getOptions().getBooleanOption("EnableSoneInsertNotifications").set(configuration.getBooleanValue(sonePrefix + "/Options/EnableSoneInsertNotifications").getValue(null)); + sone.getOptions().getBooleanOption("ShowNotification/NewSones").set(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewSones").getValue(null)); + sone.getOptions().getBooleanOption("ShowNotification/NewPosts").set(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").getValue(null)); + sone.getOptions().getBooleanOption("ShowNotification/NewReplies").set(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").getValue(null)); /* if we’re still here, Sone was loaded successfully. */ synchronized (sone) { @@ -1514,7 +1634,9 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis sone.setReplies(replies); sone.setLikePostIds(likedPostIds); sone.setLikeReplyIds(likedReplyIds); - sone.setFriends(friends); + for (String friendId : friends) { + followSone(sone, friendId); + } sone.setAlbums(topLevelAlbums); soneInserters.get(sone).setLastInsertFingerprint(lastInsertFingerprint); } @@ -2131,6 +2253,9 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis /* save options. */ configuration.getBooleanValue(sonePrefix + "/Options/AutoFollow").setValue(sone.getOptions().getBooleanOption("AutoFollow").getReal()); + configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewSones").setValue(sone.getOptions().getBooleanOption("ShowNotification/NewSones").getReal()); + configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").setValue(sone.getOptions().getBooleanOption("ShowNotification/NewPosts").getReal()); + configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").setValue(sone.getOptions().getBooleanOption("ShowNotification/NewReplies").getReal()); configuration.getBooleanValue(sonePrefix + "/Options/EnableSoneInsertNotifications").setValue(sone.getOptions().getBooleanOption("EnableSoneInsertNotifications").getReal()); configuration.save(); @@ -2183,6 +2308,17 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis configuration.getStringValue("KnownSone/" + soneCounter + "/ID").setValue(null); } + /* save Sone following times. */ + soneCounter = 0; + synchronized (soneFollowingTimes) { + for (Entry soneFollowingTime : soneFollowingTimes.entrySet()) { + configuration.getStringValue("SoneFollowingTimes/" + soneCounter + "/Sone").setValue(soneFollowingTime.getKey().getId()); + configuration.getLongValue("SoneFollowingTimes/" + soneCounter + "/Time").setValue(soneFollowingTime.getValue()); + ++soneCounter; + } + configuration.getStringValue("SoneFollowingTimes/" + soneCounter + "/Sone").setValue(null); + } + /* save known posts. */ int postCounter = 0; synchronized (newPosts) { @@ -2299,6 +2435,20 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis } } + /* load Sone following times. */ + soneCounter = 0; + while (true) { + String soneId = configuration.getStringValue("SoneFollowingTimes/" + soneCounter + "/Sone").getValue(null); + if (soneId == null) { + break; + } + long time = configuration.getLongValue("SoneFollowingTimes/" + soneCounter + "/Time").getValue(Long.MAX_VALUE); + synchronized (soneFollowingTimes) { + soneFollowingTimes.put(getSone(soneId), time); + } + ++soneCounter; + } + /* load known posts. */ int postCounter = 0; while (true) { diff --git a/src/main/java/net/pterodactylus/sone/core/FreenetInterface.java b/src/main/java/net/pterodactylus/sone/core/FreenetInterface.java index 39f342c..c63231a 100644 --- a/src/main/java/net/pterodactylus/sone/core/FreenetInterface.java +++ b/src/main/java/net/pterodactylus/sone/core/FreenetInterface.java @@ -26,7 +26,6 @@ import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; -import net.pterodactylus.sone.core.SoneException.Type; import net.pterodactylus.sone.data.Image; import net.pterodactylus.sone.data.Sone; import net.pterodactylus.sone.data.TemporaryImage; @@ -154,7 +153,7 @@ public class FreenetInterface { ClientPutter clientPutter = client.insert(insertBlock, false, null, false, insertContext, insertToken, RequestStarter.INTERACTIVE_PRIORITY_CLASS); insertToken.setClientPutter(clientPutter); } catch (InsertException ie1) { - throw new SoneException(Type.INSERT_FAILED, "Could not start image insert.", ie1); + throw new SoneInsertException("Could not start image insert.", ie1); } } @@ -175,7 +174,7 @@ public class FreenetInterface { try { return client.insertManifest(insertUri, manifestEntries, defaultFile); } catch (InsertException ie1) { - throw new SoneException(null, ie1); + throw new SoneException(ie1); } } @@ -452,6 +451,14 @@ public class FreenetInterface { * {@inheritDoc} */ @Override + public void onGeneratedMetadata(Bucket metadata, BaseClientPutter clientPutter, ObjectContainer objectContainer) { + /* ignore, we don’t care. */ + } + + /** + * {@inheritDoc} + */ + @Override public void onGeneratedURI(FreenetURI generatedUri, BaseClientPutter clientPutter, ObjectContainer objectContainer) { resultingUri = generatedUri; } diff --git a/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java b/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java index db26d80..b085ade 100644 --- a/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java +++ b/src/main/java/net/pterodactylus/sone/core/SoneDownloader.java @@ -219,8 +219,10 @@ public class SoneDownloader extends AbstractService { * @param soneInputStream * The input stream to parse the Sone from * @return The parsed Sone + * @throws SoneException + * if a parse error occurs, or the protocol is invalid */ - public Sone parseSone(Sone originalSone, InputStream soneInputStream) { + public Sone parseSone(Sone originalSone, InputStream soneInputStream) throws SoneException { /* TODO - impose a size limit? */ Document document; @@ -336,8 +338,8 @@ public class SoneDownloader extends AbstractService { if (profileFieldsXml != null) { for (SimpleXML fieldXml : profileFieldsXml.getNodes("field")) { String fieldName = fieldXml.getValue("field-name", null); - String fieldValue = fieldXml.getValue("field-value", null); - if ((fieldName == null) || (fieldValue == null)) { + String fieldValue = fieldXml.getValue("field-value", ""); + if (fieldName == null) { logger.log(Level.WARNING, "Downloaded profile field for Sone %s with missing data! Name: %s, Value: %s", new Object[] { sone, fieldName, fieldValue }); return null; } diff --git a/src/main/java/net/pterodactylus/sone/core/SoneException.java b/src/main/java/net/pterodactylus/sone/core/SoneException.java index 271627e..683a148 100644 --- a/src/main/java/net/pterodactylus/sone/core/SoneException.java +++ b/src/main/java/net/pterodactylus/sone/core/SoneException.java @@ -25,88 +25,42 @@ package net.pterodactylus.sone.core; public class SoneException extends Exception { /** - * Defines the different error. This is an enum instead of custom exceptions - * to keep the number of exceptions down. Specialized exceptions might still - * exist, though. - */ - public static enum Type { - - /** An invalid Sone name was specified. */ - INVALID_SONE_NAME, - - /** An invalid URI was specified. */ - INVALID_URI, - - /** An insert failed. */ - INSERT_FAILED, - - } - - /** The type of the exception. */ - private final Type type; - - /** * Creates a new Sone exception. - * - * @param type - * The type of the occured error */ - public SoneException(Type type) { - this.type = type; + public SoneException() { + super(); } /** * Creates a new Sone exception. * - * @param type - * The type of the occured error * @param message * The message of the exception */ - public SoneException(Type type, String message) { + public SoneException(String message) { super(message); - this.type = type; } /** * Creates a new Sone exception. * - * @param type - * The type of the occured error * @param cause * The cause of the exception */ - public SoneException(Type type, Throwable cause) { + public SoneException(Throwable cause) { super(cause); - this.type = type; } /** * Creates a new Sone exception. * - * @param type - * The type of the occured error * @param message * The message of the exception * @param cause * The cause of the exception */ - public SoneException(Type type, String message, Throwable cause) { + public SoneException(String message, Throwable cause) { super(message, cause); - this.type = type; - } - - // - // ACCESSORS - // - - /** - * Returns the type of this exception. - * - * @return The type of this exception (may be {@code null}) - */ - public Type getType() { - return type; } } diff --git a/src/main/java/net/pterodactylus/sone/core/SoneInsertException.java b/src/main/java/net/pterodactylus/sone/core/SoneInsertException.java new file mode 100644 index 0000000..350c3d8 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/core/SoneInsertException.java @@ -0,0 +1,66 @@ +/* + * Sone - SoneInsertException.java - Copyright © 2011 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.core; + +/** + * Exception that signals a problem with an insert. + * + * @author David ‘Bombe’ Roden + */ +public class SoneInsertException extends SoneException { + + /** + * Creates a new Sone insert exception. + */ + public SoneInsertException() { + super(); + } + + /** + * Creates a new Sone insert exception. + * + * @param message + * The message of the exception + */ + public SoneInsertException(String message) { + super(message); + } + + /** + * Creates a new Sone insert exception. + * + * @param cause + * The cause of the exception + */ + public SoneInsertException(Throwable cause) { + super(cause); + } + + /** + * Creates a new Sone insert exception. + * + * @param message + * The message of the exception + * @param cause + * The cause of the exception + */ + public SoneInsertException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/core/SoneInserter.java b/src/main/java/net/pterodactylus/sone/core/SoneInserter.java index eb74dc0..4d9878f 100644 --- a/src/main/java/net/pterodactylus/sone/core/SoneInserter.java +++ b/src/main/java/net/pterodactylus/sone/core/SoneInserter.java @@ -190,6 +190,7 @@ public class SoneInserter extends AbstractService { @Override protected void serviceRun() { long lastModificationTime = 0; + String lastInsertedFingerprint = lastInsertFingerprint; String lastFingerprint = ""; while (!shouldStop()) { try { /* check every seconds. */ @@ -199,7 +200,7 @@ public class SoneInserter extends AbstractService { if (core.isLocked(sone)) { /* trigger redetection when the Sone is unlocked. */ synchronized (sone) { - modified = !sone.getFingerprint().equals(lastInsertFingerprint); + modified = !sone.getFingerprint().equals(lastInsertedFingerprint); } lastFingerprint = ""; lastModificationTime = 0; @@ -210,7 +211,7 @@ public class SoneInserter extends AbstractService { synchronized (sone) { String fingerprint = sone.getFingerprint(); if (!fingerprint.equals(lastFingerprint)) { - if (fingerprint.equals(lastInsertFingerprint)) { + if (fingerprint.equals(lastInsertedFingerprint)) { modified = false; lastModificationTime = 0; logger.log(Level.FINE, "Sone %s has been reverted to last insert state.", sone); @@ -222,7 +223,7 @@ public class SoneInserter extends AbstractService { lastFingerprint = fingerprint; } if (modified && (lastModificationTime > 0) && ((System.currentTimeMillis() - lastModificationTime) > (insertionDelay * 1000))) { - lastInsertFingerprint = fingerprint; + lastInsertedFingerprint = fingerprint; insertInformation = new InsertInformation(sone); } } @@ -261,9 +262,11 @@ public class SoneInserter extends AbstractService { */ if (success) { synchronized (sone) { - if (lastInsertFingerprint.equals(sone.getFingerprint())) { + if (lastInsertedFingerprint.equals(sone.getFingerprint())) { logger.log(Level.FINE, "Sone “%s” was not modified further, resetting counter…", new Object[] { sone }); lastModificationTime = 0; + lastInsertFingerprint = lastInsertedFingerprint; + core.touchConfiguration(); modified = false; } } diff --git a/src/main/java/net/pterodactylus/sone/data/Sone.java b/src/main/java/net/pterodactylus/sone/data/Sone.java index ca970c2..8b2ec2a 100644 --- a/src/main/java/net/pterodactylus/sone/data/Sone.java +++ b/src/main/java/net/pterodactylus/sone/data/Sone.java @@ -115,6 +115,15 @@ public class Sone implements Fingerprintable, Comparable { }; + /** Filter that matches Sones that have at least one album. */ + public static final Filter HAS_ALBUM_FILTER = new Filter() { + + @Override + public boolean filterObject(Sone sone) { + return !sone.getAlbums().isEmpty(); + } + }; + /** The logger. */ private static final Logger logger = Logging.getLogger(Sone.class); @@ -380,19 +389,6 @@ public class Sone implements Fingerprintable, Comparable { } /** - * Sets all friends of this Sone at once. - * - * @param friends - * The new (and only) friends of this Sone - * @return This Sone (for method chaining) - */ - public Sone setFriends(Collection friends) { - friendSones.clear(); - friendSones.addAll(friends); - return this; - } - - /** * Returns whether this Sone has the given Sone as a friend Sone. * * @param friendSoneId diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentity.java b/src/main/java/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentity.java index ab46b43..be2a3e7 100644 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentity.java +++ b/src/main/java/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentity.java @@ -57,6 +57,22 @@ public class DefaultOwnIdentity extends DefaultIdentity implements OwnIdentity { this.insertUri = insertUri; } + /** + * Copy constructor for an own identity. + * + * @param webOfTrustConnector + * The web of trust connector + * @param ownIdentity + * The own identity to copy + */ + public DefaultOwnIdentity(WebOfTrustConnector webOfTrustConnector, OwnIdentity ownIdentity) { + super(webOfTrustConnector, ownIdentity.getId(), ownIdentity.getNickname(), ownIdentity.getRequestUri()); + this.webOfTrustConnector = webOfTrustConnector; + this.insertUri = ownIdentity.getInsertUri(); + setContextsPrivate(ownIdentity.getContexts()); + setPropertiesPrivate(ownIdentity.getProperties()); + } + // // ACCESSORS // 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 3c97e55..69af0ca 100644 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityManager.java +++ b/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityManager.java @@ -26,6 +26,8 @@ import java.util.logging.Level; import java.util.logging.Logger; import net.pterodactylus.sone.freenet.plugin.PluginException; +import net.pterodactylus.util.collection.Mapper; +import net.pterodactylus.util.collection.Mappers; import net.pterodactylus.util.logging.Logging; import net.pterodactylus.util.service.AbstractService; @@ -140,7 +142,7 @@ public class IdentityManager extends AbstractService { Set allOwnIdentities = getAllOwnIdentities(); for (OwnIdentity ownIdentity : allOwnIdentities) { if (ownIdentity.getId().equals(id)) { - return ownIdentity; + return new DefaultOwnIdentity(webOfTrustConnector, ownIdentity); } } return null; @@ -159,7 +161,17 @@ public class IdentityManager extends AbstractService { newOwnIdentities.put(ownIdentity.getId(), ownIdentity); } checkOwnIdentities(newOwnIdentities); - return ownIdentities; + return Mappers.mappedSet(ownIdentities, new Mapper() { + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("synthetic-access") + public OwnIdentity map(OwnIdentity input) { + return new DefaultOwnIdentity(webOfTrustConnector, input); + } + }); } catch (WebOfTrustException wote1) { logger.log(Level.WARNING, "Could not load all own identities!", wote1); return Collections.emptySet(); @@ -295,15 +307,17 @@ public class IdentityManager extends AbstractService { /* find removed own identities: */ for (OwnIdentity oldOwnIdentity : currentOwnIdentities.values()) { - if (!newOwnIdentities.containsKey(oldOwnIdentity.getId())) { - identityListenerManager.fireOwnIdentityRemoved(oldOwnIdentity); + OwnIdentity newOwnIdentity = newOwnIdentities.get(oldOwnIdentity.getId()); + if ((newOwnIdentity == null) || ((context != null) && oldOwnIdentity.hasContext(context) && !newOwnIdentity.hasContext(context))) { + identityListenerManager.fireOwnIdentityRemoved(new DefaultOwnIdentity(webOfTrustConnector, oldOwnIdentity)); } } /* find added own identities. */ for (OwnIdentity currentOwnIdentity : newOwnIdentities.values()) { - if (!currentOwnIdentities.containsKey(currentOwnIdentity.getId())) { - identityListenerManager.fireOwnIdentityAdded(currentOwnIdentity); + OwnIdentity oldOwnIdentity = currentOwnIdentities.get(currentOwnIdentity.getId()); + if (((oldOwnIdentity == null) && ((context == null) || currentOwnIdentity.hasContext(context))) || ((oldOwnIdentity != null) && (context != null) && (!oldOwnIdentity.hasContext(context) && currentOwnIdentity.hasContext(context)))) { + identityListenerManager.fireOwnIdentityAdded(new DefaultOwnIdentity(webOfTrustConnector, currentOwnIdentity)); } } diff --git a/src/main/java/net/pterodactylus/sone/main/SonePlugin.java b/src/main/java/net/pterodactylus/sone/main/SonePlugin.java index d93317a..b35c556 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, 2); + public static final Version VERSION = new Version(0, 7, 3); /** 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 beeb81e..7b9eb49 100644 --- a/src/main/java/net/pterodactylus/sone/notify/ListNotificationFilters.java +++ b/src/main/java/net/pterodactylus/sone/notify/ListNotificationFilters.java @@ -55,12 +55,23 @@ public class ListNotificationFilters { public static List filterNotifications(Collection notifications, Sone currentSone) { List filteredNotifications = new ArrayList(); for (Notification notification : notifications) { - if (notification.getId().equals("new-post-notification")) { + if (notification.getId().equals("new-sone-notification")) { + if ((currentSone != null) && (!currentSone.getOptions().getBooleanOption("ShowNotification/NewSones").get())) { + continue; + } + filteredNotifications.add(notification); + } else if (notification.getId().equals("new-post-notification")) { + if ((currentSone != null) && (!currentSone.getOptions().getBooleanOption("ShowNotification/NewPosts").get())) { + continue; + } ListNotification filteredNotification = filterNewPostNotification((ListNotification) notification, currentSone, true); if (filteredNotification != null) { filteredNotifications.add(filteredNotification); } } else if (notification.getId().equals("new-reply-notification")) { + if ((currentSone != null) && (!currentSone.getOptions().getBooleanOption("ShowNotification/NewReplies").get())) { + continue; + } ListNotification filteredNotification = filterNewReplyNotification((ListNotification) notification, currentSone); if (filteredNotification != null) { filteredNotifications.add(filteredNotification); diff --git a/src/main/java/net/pterodactylus/sone/template/ImageAccessor.java b/src/main/java/net/pterodactylus/sone/template/ImageAccessor.java new file mode 100644 index 0000000..1c06d31 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/template/ImageAccessor.java @@ -0,0 +1,64 @@ +/* + * Sone - ImageAccessor.java - Copyright © 2011 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.template; + +import net.pterodactylus.sone.data.Album; +import net.pterodactylus.sone.data.Image; +import net.pterodactylus.util.template.Accessor; +import net.pterodactylus.util.template.ReflectionAccessor; +import net.pterodactylus.util.template.TemplateContext; + +/** + * {@link Accessor} implementation for {@link Image} objects. It adds the + * following properties: + *
    + *
  • {@code previous}: returns the previous image in the image’s album, or + * {@code null} if the image is the first image of its album.
  • + *
  • {@code next}: returns the next image in the image’s album, or {@code + * null} if the image is the last image of its album.
  • + *
+ * + * @author David ‘Bombe’ Roden + */ +public class ImageAccessor extends ReflectionAccessor { + + /** + * {@inheritDoc} + */ + @Override + public Object get(TemplateContext templateContext, Object object, String member) { + Image image = (Image) object; + if ("next".equals(member)) { + Album album = image.getAlbum(); + int imagePosition = album.getImages().indexOf(image); + if (imagePosition < album.getImages().size() - 1) { + return album.getImages().get(imagePosition + 1); + } + return null; + } else if ("previous".equals(member)) { + Album album = image.getAlbum(); + int imagePosition = album.getImages().indexOf(image); + if (imagePosition > 0) { + return album.getImages().get(imagePosition - 1); + } + return null; + } + return super.get(templateContext, object, member); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/template/ImageLinkFilter.java b/src/main/java/net/pterodactylus/sone/template/ImageLinkFilter.java index 063a1de..d7b5eea 100644 --- a/src/main/java/net/pterodactylus/sone/template/ImageLinkFilter.java +++ b/src/main/java/net/pterodactylus/sone/template/ImageLinkFilter.java @@ -39,7 +39,7 @@ import net.pterodactylus.util.template.TemplateParser; public class ImageLinkFilter implements Filter { /** The template to render for the <img> tag. */ - private static final Template linkTemplate = TemplateParser.parse(new StringReader(" class=\"<%class|css>\"<%/if> src=\"<%src|html>\" alt=\"<%alt|html>\" title=\"<%title|html>\" width=\"<%width|html>\" height=\"<%height|html>\" style=\"position: relative;<%ifnull ! top>top: <% top|html>;<%/if><%ifnull ! left>left: <% left|html>;<%/if>\"/>")); + private static final Template linkTemplate = TemplateParser.parse(new StringReader(" class=\"<%class|css>\"<%/if> src=\"<%src|html><%if forceDownload>?forcedownload=true<%/if>\" alt=\"<%alt|html>\" title=\"<%title|html>\" width=\"<%width|html>\" height=\"<%height|html>\" style=\"position: relative;<%ifnull ! top>top: <% top|html>;<%/if><%ifnull ! left>left: <% left|html>;<%/if>\"/>")); /** The template context factory. */ private final TemplateContextFactory templateContextFactory; @@ -73,6 +73,7 @@ public class ImageLinkFilter implements Filter { linkTemplateContext.set("class", imageClass); if (image.isInserted()) { linkTemplateContext.set("src", "/" + image.getKey()); + linkTemplateContext.set("forceDownload", true); } else { linkTemplateContext.set("src", "getImage.html?image=" + image.getId()); } diff --git a/src/main/java/net/pterodactylus/sone/web/BookmarksPage.java b/src/main/java/net/pterodactylus/sone/web/BookmarksPage.java index ad1717b..41a3e65 100644 --- a/src/main/java/net/pterodactylus/sone/web/BookmarksPage.java +++ b/src/main/java/net/pterodactylus/sone/web/BookmarksPage.java @@ -70,7 +70,7 @@ public class BookmarksPage extends SoneTemplatePage { }); List sortedPosts = new ArrayList(loadedPosts); Collections.sort(sortedPosts, Post.TIME_COMPARATOR); - Pagination pagination = new Pagination(sortedPosts, 25).setPage(Numbers.safeParseInteger(request.getHttpRequest().getParam("page"), 0)); + 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()); templateContext.set("postsNotLoaded", allPosts.size() != loadedPosts.size()); diff --git a/src/main/java/net/pterodactylus/sone/web/CreateAlbumPage.java b/src/main/java/net/pterodactylus/sone/web/CreateAlbumPage.java index a695952..9072ea4 100644 --- a/src/main/java/net/pterodactylus/sone/web/CreateAlbumPage.java +++ b/src/main/java/net/pterodactylus/sone/web/CreateAlbumPage.java @@ -19,6 +19,7 @@ package net.pterodactylus.sone.web; import net.pterodactylus.sone.data.Album; import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.text.TextFilter; import net.pterodactylus.sone.web.page.FreenetRequest; import net.pterodactylus.util.template.Template; import net.pterodactylus.util.template.TemplateContext; @@ -64,7 +65,7 @@ public class CreateAlbumPage extends SoneTemplatePage { String parentId = request.getHttpRequest().getPartAsStringFailsafe("parent", 36); Album parent = webInterface.getCore().getAlbum(parentId, false); Album album = webInterface.getCore().createAlbum(currentSone, parent); - album.setTitle(name).setDescription(description); + album.setTitle(name).setDescription(TextFilter.filter(request.getHttpRequest().getHeader("host"), description)); webInterface.getCore().touchConfiguration(); throw new RedirectException("imageBrowser.html?album=" + album.getId()); } diff --git a/src/main/java/net/pterodactylus/sone/web/EditAlbumPage.java b/src/main/java/net/pterodactylus/sone/web/EditAlbumPage.java index fd4bf7c..dc5c8fa 100644 --- a/src/main/java/net/pterodactylus/sone/web/EditAlbumPage.java +++ b/src/main/java/net/pterodactylus/sone/web/EditAlbumPage.java @@ -18,6 +18,8 @@ package net.pterodactylus.sone.web; import net.pterodactylus.sone.data.Album; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.text.TextFilter; import net.pterodactylus.sone.web.page.FreenetRequest; import net.pterodactylus.util.template.Template; import net.pterodactylus.util.template.TemplateContext; @@ -49,6 +51,7 @@ public class EditAlbumPage extends SoneTemplatePage { protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException { super.processTemplate(request, templateContext); if (request.getMethod() == Method.POST) { + Sone currentSone = getCurrentSone(request.getToadletContext()); String albumId = request.getHttpRequest().getPartAsStringFailsafe("album", 36); Album album = webInterface.getCore().getAlbum(albumId, false); if (album == null) { @@ -57,6 +60,25 @@ public class EditAlbumPage extends SoneTemplatePage { if (!webInterface.getCore().isLocalSone(album.getSone())) { throw new RedirectException("noPermission.html"); } + if ("true".equals(request.getHttpRequest().getPartAsStringFailsafe("moveLeft", 4))) { + if (album.getParent() == null) { + currentSone.moveAlbumUp(album); + webInterface.getCore().touchConfiguration(); + throw new RedirectException("imageBrowser.html?sone=" + currentSone.getId()); + } + album.getParent().moveAlbumUp(album); + webInterface.getCore().touchConfiguration(); + throw new RedirectException("imageBrowser.html?album=" + album.getParent().getId()); + } else if ("true".equals(request.getHttpRequest().getPartAsStringFailsafe("moveRight", 4))) { + if (album.getParent() == null) { + currentSone.moveAlbumDown(album); + webInterface.getCore().touchConfiguration(); + throw new RedirectException("imageBrowser.html?sone=" + currentSone.getId()); + } + album.getParent().moveAlbumDown(album); + webInterface.getCore().touchConfiguration(); + throw new RedirectException("imageBrowser.html?album=" + album.getParent().getId()); + } String albumImageId = request.getHttpRequest().getPartAsStringFailsafe("album-image", 36); if (webInterface.getCore().getImage(albumImageId, false) == null) { albumImageId = null; @@ -68,7 +90,7 @@ public class EditAlbumPage extends SoneTemplatePage { return; } String description = request.getHttpRequest().getPartAsStringFailsafe("description", 1000).trim(); - album.setTitle(title).setDescription(description); + album.setTitle(title).setDescription(TextFilter.filter(request.getHttpRequest().getHeader("host"), description)); webInterface.getCore().touchConfiguration(); throw new RedirectException("imageBrowser.html?album=" + album.getId()); } diff --git a/src/main/java/net/pterodactylus/sone/web/EditImagePage.java b/src/main/java/net/pterodactylus/sone/web/EditImagePage.java index 7e9eb7e..a0939db 100644 --- a/src/main/java/net/pterodactylus/sone/web/EditImagePage.java +++ b/src/main/java/net/pterodactylus/sone/web/EditImagePage.java @@ -18,6 +18,7 @@ package net.pterodactylus.sone.web; import net.pterodactylus.sone.data.Image; +import net.pterodactylus.sone.text.TextFilter; import net.pterodactylus.sone.web.page.FreenetRequest; import net.pterodactylus.util.template.Template; import net.pterodactylus.util.template.TemplateContext; @@ -73,7 +74,7 @@ public class EditImagePage extends SoneTemplatePage { templateContext.set("titleMissing", true); } image.setTitle(title); - image.setDescription(description); + image.setDescription(TextFilter.filter(request.getHttpRequest().getHeader("host"), description)); } webInterface.getCore().touchConfiguration(); throw new RedirectException(returnPage); diff --git a/src/main/java/net/pterodactylus/sone/web/FollowSonePage.java b/src/main/java/net/pterodactylus/sone/web/FollowSonePage.java index 143e0ba..4083b99 100644 --- a/src/main/java/net/pterodactylus/sone/web/FollowSonePage.java +++ b/src/main/java/net/pterodactylus/sone/web/FollowSonePage.java @@ -55,9 +55,11 @@ public class FollowSonePage extends SoneTemplatePage { Sone currentSone = getCurrentSone(request.getToadletContext()); String soneIds = request.getHttpRequest().getPartAsStringFailsafe("sone", 1200); for (String soneId : soneIds.split("[ ,]+")) { - currentSone.addFriend(soneId); + if (webInterface.getCore().hasSone(soneId)) { + webInterface.getCore().followSone(currentSone, soneId); + webInterface.getCore().markSoneKnown(webInterface.getCore().getSone(soneId)); + } } - webInterface.getCore().touchConfiguration(); throw new RedirectException(returnPage); } } diff --git a/src/main/java/net/pterodactylus/sone/web/GetImagePage.java b/src/main/java/net/pterodactylus/sone/web/GetImagePage.java index 29556c9..8f0c474 100644 --- a/src/main/java/net/pterodactylus/sone/web/GetImagePage.java +++ b/src/main/java/net/pterodactylus/sone/web/GetImagePage.java @@ -18,10 +18,11 @@ package net.pterodactylus.sone.web; import java.io.IOException; +import java.net.URI; import net.pterodactylus.sone.data.TemporaryImage; +import net.pterodactylus.sone.web.page.FreenetPage; import net.pterodactylus.sone.web.page.FreenetRequest; -import net.pterodactylus.util.web.Page; import net.pterodactylus.util.web.Response; /** @@ -29,7 +30,7 @@ import net.pterodactylus.util.web.Response; * * @author David ‘Bombe’ Roden */ -public class GetImagePage implements Page { +public class GetImagePage implements FreenetPage { /** The Sone web interface. */ private final WebInterface webInterface; @@ -74,4 +75,12 @@ public class GetImagePage implements Page { return response.setStatusCode(200).setStatusText("OK").setContentType(contentType).addHeader("Content-Disposition", "attachment; filename=" + temporaryImage.getId() + "." + contentType.substring(contentType.lastIndexOf('/') + 1)).write(temporaryImage.getImageData()); } + /** + * {@inheritDoc} + */ + @Override + public boolean isLinkExcepted(URI link) { + return false; + } + } diff --git a/src/main/java/net/pterodactylus/sone/web/ImageBrowserPage.java b/src/main/java/net/pterodactylus/sone/web/ImageBrowserPage.java index ed31283..406a762 100644 --- a/src/main/java/net/pterodactylus/sone/web/ImageBrowserPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ImageBrowserPage.java @@ -17,6 +17,10 @@ package net.pterodactylus.sone.web; +import java.net.URI; +import java.util.HashSet; +import java.util.Set; + import net.pterodactylus.sone.data.Album; import net.pterodactylus.sone.data.Image; import net.pterodactylus.sone.data.Sone; @@ -67,13 +71,34 @@ public class ImageBrowserPage extends SoneTemplatePage { templateContext.set("image", image); return; } - Sone sone = getCurrentSone(request.getToadletContext(), false); String soneId = request.getHttpRequest().getParam("sone", null); if (soneId != null) { - sone = webInterface.getCore().getSone(soneId, false); + Sone sone = webInterface.getCore().getSone(soneId, false); + templateContext.set("soneRequested", true); + templateContext.set("sone", sone); + return; } + String mode = request.getHttpRequest().getParam("mode", null); + if ("gallery".equals(mode)) { + templateContext.set("galleryRequested", true); + Set albums = new HashSet(); + for (Sone sone : webInterface.getCore().getSones()) { + albums.addAll(sone.getAllAlbums()); + } + templateContext.set("albums", albums); + return; + } + Sone sone = getCurrentSone(request.getToadletContext(), false); templateContext.set("soneRequested", true); templateContext.set("sone", sone); } + /** + * {@inheritDoc} + */ + @Override + public boolean isLinkExcepted(URI link) { + return true; + } + } diff --git a/src/main/java/net/pterodactylus/sone/web/OptionsPage.java b/src/main/java/net/pterodactylus/sone/web/OptionsPage.java index 7b4a634..49b1275 100644 --- a/src/main/java/net/pterodactylus/sone/web/OptionsPage.java +++ b/src/main/java/net/pterodactylus/sone/web/OptionsPage.java @@ -67,6 +67,12 @@ public class OptionsPage extends SoneTemplatePage { currentSone.getOptions().getBooleanOption("AutoFollow").set(autoFollow); boolean enableSoneInsertNotifications = request.getHttpRequest().isPartSet("enable-sone-insert-notifications"); currentSone.getOptions().getBooleanOption("EnableSoneInsertNotifications").set(enableSoneInsertNotifications); + boolean showNotificationNewSones = request.getHttpRequest().isPartSet("show-notification-new-sones"); + currentSone.getOptions().getBooleanOption("ShowNotification/NewSones").set(showNotificationNewSones); + boolean showNotificationNewPosts = request.getHttpRequest().isPartSet("show-notification-new-posts"); + currentSone.getOptions().getBooleanOption("ShowNotification/NewPosts").set(showNotificationNewPosts); + boolean showNotificationNewReplies = request.getHttpRequest().isPartSet("show-notification-new-replies"); + currentSone.getOptions().getBooleanOption("ShowNotification/NewReplies").set(showNotificationNewReplies); webInterface.getCore().touchConfiguration(); } Integer insertionDelay = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("insertion-delay", 16)); @@ -130,6 +136,9 @@ public class OptionsPage extends SoneTemplatePage { if (currentSone != null) { templateContext.set("auto-follow", currentSone.getOptions().getBooleanOption("AutoFollow").get()); templateContext.set("enable-sone-insert-notifications", currentSone.getOptions().getBooleanOption("EnableSoneInsertNotifications").get()); + templateContext.set("show-notification-new-sones", currentSone.getOptions().getBooleanOption("ShowNotification/NewSones").get()); + templateContext.set("show-notification-new-posts", currentSone.getOptions().getBooleanOption("ShowNotification/NewPosts").get()); + templateContext.set("show-notification-new-replies", currentSone.getOptions().getBooleanOption("ShowNotification/NewReplies").get()); } templateContext.set("insertion-delay", preferences.getInsertionDelay()); templateContext.set("posts-per-page", preferences.getPostsPerPage()); diff --git a/src/main/java/net/pterodactylus/sone/web/UnfollowSonePage.java b/src/main/java/net/pterodactylus/sone/web/UnfollowSonePage.java index d8e53ce..f07880d 100644 --- a/src/main/java/net/pterodactylus/sone/web/UnfollowSonePage.java +++ b/src/main/java/net/pterodactylus/sone/web/UnfollowSonePage.java @@ -55,9 +55,8 @@ public class UnfollowSonePage extends SoneTemplatePage { Sone currentSone = getCurrentSone(request.getToadletContext()); String soneIds = request.getHttpRequest().getPartAsStringFailsafe("sone", 2000); for (String soneId : soneIds.split("[ ,]+")) { - currentSone.removeFriend(soneId); + webInterface.getCore().unfollowSone(currentSone, soneId); } - webInterface.getCore().touchConfiguration(); throw new RedirectException(returnPage); } } diff --git a/src/main/java/net/pterodactylus/sone/web/UploadImagePage.java b/src/main/java/net/pterodactylus/sone/web/UploadImagePage.java index e0aa1e1..e160c32 100644 --- a/src/main/java/net/pterodactylus/sone/web/UploadImagePage.java +++ b/src/main/java/net/pterodactylus/sone/web/UploadImagePage.java @@ -33,6 +33,7 @@ import javax.imageio.stream.ImageInputStream; import net.pterodactylus.sone.data.Album; import net.pterodactylus.sone.data.Sone; import net.pterodactylus.sone.data.TemporaryImage; +import net.pterodactylus.sone.text.TextFilter; import net.pterodactylus.sone.web.page.FreenetRequest; import net.pterodactylus.util.io.Closer; import net.pterodactylus.util.io.StreamCopier; @@ -120,7 +121,7 @@ public class UploadImagePage extends SoneTemplatePage { String mimeType = getMimeType(imageData); TemporaryImage temporaryImage = webInterface.getCore().createTemporaryImage(mimeType, imageData); image = webInterface.getCore().createImage(currentSone, parent, temporaryImage); - image.setTitle(name).setDescription(description).setWidth(uploadedImage.getWidth(null)).setHeight(uploadedImage.getHeight(null)); + image.setTitle(name).setDescription(TextFilter.filter(request.getHttpRequest().getHeader("host"), description)).setWidth(uploadedImage.getWidth(null)).setHeight(uploadedImage.getHeight(null)); } catch (IOException ioe1) { logger.log(Level.WARNING, "Could not read uploaded image!", ioe1); return; diff --git a/src/main/java/net/pterodactylus/sone/web/ViewPostPage.java b/src/main/java/net/pterodactylus/sone/web/ViewPostPage.java index 4cc9864..d0ca904 100644 --- a/src/main/java/net/pterodactylus/sone/web/ViewPostPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ViewPostPage.java @@ -17,6 +17,8 @@ package net.pterodactylus.sone.web; +import java.net.URI; + import net.pterodactylus.sone.data.Post; import net.pterodactylus.sone.template.SoneAccessor; import net.pterodactylus.sone.web.page.FreenetRequest; @@ -75,4 +77,12 @@ public class ViewPostPage extends SoneTemplatePage { templateContext.set("raw", raw); } + /** + * {@inheritDoc} + */ + @Override + public boolean isLinkExcepted(URI link) { + return true; + } + } diff --git a/src/main/java/net/pterodactylus/sone/web/ViewSonePage.java b/src/main/java/net/pterodactylus/sone/web/ViewSonePage.java index 5fe356d..57d4070 100644 --- a/src/main/java/net/pterodactylus/sone/web/ViewSonePage.java +++ b/src/main/java/net/pterodactylus/sone/web/ViewSonePage.java @@ -17,6 +17,7 @@ package net.pterodactylus.sone.web; +import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -115,4 +116,12 @@ public class ViewSonePage extends SoneTemplatePage { templateContext.set("repliedPosts", repliedPostPagination.getItems()); } + /** + * {@inheritDoc} + */ + @Override + public boolean isLinkExcepted(URI link) { + return true; + } + } diff --git a/src/main/java/net/pterodactylus/sone/web/WebInterface.java b/src/main/java/net/pterodactylus/sone/web/WebInterface.java index 7320ad5..5ba3be7 100644 --- a/src/main/java/net/pterodactylus/sone/web/WebInterface.java +++ b/src/main/java/net/pterodactylus/sone/web/WebInterface.java @@ -53,6 +53,7 @@ import net.pterodactylus.sone.template.CollectionAccessor; import net.pterodactylus.sone.template.CssClassNameFilter; import net.pterodactylus.sone.template.HttpRequestAccessor; import net.pterodactylus.sone.template.IdentityAccessor; +import net.pterodactylus.sone.template.ImageAccessor; import net.pterodactylus.sone.template.ImageLinkFilter; import net.pterodactylus.sone.template.JavascriptFilter; import net.pterodactylus.sone.template.ParserFilter; @@ -170,6 +171,9 @@ public class WebInterface implements CoreListener { /** The Sone text parser. */ private final SoneTextParser soneTextParser; + /** The parser filter. */ + private final ParserFilter parserFilter; + /** The “new Sone” notification. */ private final ListNotification newSoneNotification; @@ -228,6 +232,7 @@ public class WebInterface implements CoreListener { templateContextFactory.addAccessor(Post.class, new PostAccessor(getCore())); templateContextFactory.addAccessor(Reply.class, new ReplyAccessor(getCore())); templateContextFactory.addAccessor(Album.class, new AlbumAccessor()); + templateContextFactory.addAccessor(Image.class, new ImageAccessor()); templateContextFactory.addAccessor(Identity.class, new IdentityAccessor(getCore())); templateContextFactory.addAccessor(Trust.class, new TrustAccessor()); templateContextFactory.addAccessor(HTTPRequest.class, new HttpRequestAccessor()); @@ -242,7 +247,7 @@ public class WebInterface implements CoreListener { templateContextFactory.addFilter("match", new MatchFilter()); templateContextFactory.addFilter("css", new CssClassNameFilter()); templateContextFactory.addFilter("js", new JavascriptFilter()); - templateContextFactory.addFilter("parse", new ParserFilter(getCore(), templateContextFactory, soneTextParser)); + templateContextFactory.addFilter("parse", parserFilter = new ParserFilter(getCore(), templateContextFactory, soneTextParser)); templateContextFactory.addFilter("unknown", new UnknownDateFilter(getL10n(), "View.Sone.Text.UnknownDate")); templateContextFactory.addFilter("format", new FormatFilter()); templateContextFactory.addFilter("sort", new CollectionSortFilter()); @@ -668,7 +673,7 @@ public class WebInterface implements CoreListener { pageToadlets.add(pageToadletFactory.createPageToadlet(new FollowSoneAjaxPage(this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new UnfollowSoneAjaxPage(this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new EditAlbumAjaxPage(this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new EditImageAjaxPage(this))); + pageToadlets.add(pageToadletFactory.createPageToadlet(new EditImageAjaxPage(this, parserFilter))); pageToadlets.add(pageToadletFactory.createPageToadlet(new TrustAjaxPage(this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new DistrustAjaxPage(this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new UntrustAjaxPage(this))); diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/EditAlbumAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/EditAlbumAjaxPage.java index 53f0466..8bc07ef 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/EditAlbumAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/EditAlbumAjaxPage.java @@ -18,6 +18,7 @@ package net.pterodactylus.sone.web.ajax; import net.pterodactylus.sone.data.Album; +import net.pterodactylus.sone.text.TextFilter; import net.pterodactylus.sone.web.WebInterface; import net.pterodactylus.sone.web.page.FreenetRequest; import net.pterodactylus.util.json.JsonObject; @@ -68,7 +69,7 @@ public class EditAlbumAjaxPage extends JsonPage { } String title = request.getHttpRequest().getParam("title").trim(); String description = request.getHttpRequest().getParam("description").trim(); - album.setTitle(title).setDescription(description); + album.setTitle(title).setDescription(TextFilter.filter(request.getHttpRequest().getHeader("host"), description)); webInterface.getCore().touchConfiguration(); return createSuccessJsonObject().put("albumId", album.getId()).put("title", album.getTitle()).put("description", album.getDescription()); } diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/EditImageAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/EditImageAjaxPage.java index 17d171b..3c4de8e 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/EditImageAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/EditImageAjaxPage.java @@ -18,9 +18,13 @@ package net.pterodactylus.sone.web.ajax; import net.pterodactylus.sone.data.Image; +import net.pterodactylus.sone.template.ParserFilter; +import net.pterodactylus.sone.text.TextFilter; import net.pterodactylus.sone.web.WebInterface; import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.collection.MapBuilder; import net.pterodactylus.util.json.JsonObject; +import net.pterodactylus.util.template.TemplateContext; /** * Page that stores a user’s image modifications. @@ -29,14 +33,20 @@ import net.pterodactylus.util.json.JsonObject; */ public class EditImageAjaxPage extends JsonPage { + /** Parser for image descriptions. */ + private final ParserFilter parserFilter; + /** * Creates a new edit image AJAX page. * * @param webInterface * The Sone web interface + * @param parserFilter + * The parser filter for image descriptions */ - public EditImageAjaxPage(WebInterface webInterface) { + public EditImageAjaxPage(WebInterface webInterface, ParserFilter parserFilter) { super("editImage.ajax", webInterface); + this.parserFilter = parserFilter; } // @@ -68,9 +78,9 @@ public class EditImageAjaxPage extends JsonPage { } String title = request.getHttpRequest().getParam("title").trim(); String description = request.getHttpRequest().getParam("description").trim(); - image.setTitle(title).setDescription(description); + image.setTitle(title).setDescription(TextFilter.filter(request.getHttpRequest().getHeader("host"), description)); webInterface.getCore().touchConfiguration(); - return createSuccessJsonObject().put("imageId", image.getId()).put("title", image.getTitle()).put("description", image.getDescription()); + return createSuccessJsonObject().put("imageId", image.getId()).put("title", image.getTitle()).put("description", image.getDescription()).put("parsedDescription", (String) parserFilter.format(new TemplateContext(), image.getDescription(), new MapBuilder().put("sone", image.getSone().getId()).get())); } } diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/FollowSoneAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/FollowSoneAjaxPage.java index 5c3e5f4..764fa28 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/FollowSoneAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/FollowSoneAjaxPage.java @@ -52,8 +52,8 @@ public class FollowSoneAjaxPage extends JsonPage { if (currentSone == null) { return createErrorJsonObject("auth-required"); } - currentSone.addFriend(soneId); - webInterface.getCore().touchConfiguration(); + webInterface.getCore().followSone(currentSone, soneId); + webInterface.getCore().markSoneKnown(webInterface.getCore().getSone(soneId)); return createSuccessJsonObject(); } 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 038cfb8..5f2103a 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/GetStatusAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/GetStatusAjaxPage.java @@ -145,7 +145,7 @@ public class GetStatusAjaxPage extends JsonPage { jsonReply.put("postSone", reply.getPost().getSone().getId()); jsonReplies.add(jsonReply); } - return createSuccessJsonObject().put("loggedIn", currentSone != null).put("sones", jsonSones).put("notifications", jsonNotificationInformations).put("newPosts", jsonPosts).put("newReplies", jsonReplies); + return createSuccessJsonObject().put("loggedIn", currentSone != null).put("options", createJsonOptions(currentSone)).put("sones", jsonSones).put("notifications", jsonNotificationInformations).put("newPosts", jsonPosts).put("newReplies", jsonReplies); } /** @@ -209,4 +209,23 @@ public class GetStatusAjaxPage extends JsonPage { return jsonNotification; } + /** + * Creates a JSON object that contains all options that are currently in + * effect for the given Sone (or overall, if the given Sone is {@code null} + * ). + * + * @param currentSone + * The current Sone (may be {@code null}) + * @return The current options + */ + private JsonObject createJsonOptions(Sone currentSone) { + JsonObject options = new JsonObject(); + if (currentSone != null) { + options.put("ShowNotification/NewSones", currentSone.getOptions().getBooleanOption("ShowNotification/NewSones").get()); + options.put("ShowNotification/NewPosts", currentSone.getOptions().getBooleanOption("ShowNotification/NewPosts").get()); + options.put("ShowNotification/NewReplies", currentSone.getOptions().getBooleanOption("ShowNotification/NewReplies").get()); + } + return options; + } + } 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 1e5e8ed..4ee3b99 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/JsonPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/JsonPage.java @@ -18,16 +18,18 @@ package net.pterodactylus.sone.web.ajax; import java.io.IOException; +import java.net.URI; import net.pterodactylus.sone.data.Sone; import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.sone.web.page.FreenetPage; import net.pterodactylus.sone.web.page.FreenetRequest; import net.pterodactylus.util.json.JsonObject; import net.pterodactylus.util.json.JsonUtils; import net.pterodactylus.util.web.Page; import net.pterodactylus.util.web.Response; -import freenet.clients.http.SessionManager.Session; import freenet.clients.http.ToadletContext; +import freenet.clients.http.SessionManager.Session; /** * A JSON page is a specialized {@link Page} that will always return a JSON @@ -35,7 +37,7 @@ import freenet.clients.http.ToadletContext; * * @author David ‘Bombe’ Roden */ -public abstract class JsonPage implements Page { +public abstract class JsonPage implements FreenetPage { /** The path of the page. */ private final String path; @@ -218,4 +220,12 @@ public abstract class JsonPage implements Page { return response.setStatusCode(200).setStatusText("OK").setContentType("application/json").write(JsonUtils.format(jsonObject)); } + /** + * {@inheritDoc} + */ + @Override + public boolean isLinkExcepted(URI link) { + return false; + } + } diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/UnfollowSoneAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/UnfollowSoneAjaxPage.java index 26f1a11..5d94d52 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/UnfollowSoneAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/UnfollowSoneAjaxPage.java @@ -52,8 +52,7 @@ public class UnfollowSoneAjaxPage extends JsonPage { if (currentSone == null) { return createErrorJsonObject("auth-required"); } - currentSone.removeFriend(soneId); - webInterface.getCore().touchConfiguration(); + webInterface.getCore().unfollowSone(currentSone, soneId); return createSuccessJsonObject(); } diff --git a/src/main/java/net/pterodactylus/sone/web/page/FreenetPage.java b/src/main/java/net/pterodactylus/sone/web/page/FreenetPage.java new file mode 100644 index 0000000..d4bbc82 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/page/FreenetPage.java @@ -0,0 +1,42 @@ +/* + * Sone - FreenetPage.java - Copyright © 2011 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.page; + +import java.net.URI; + +import net.pterodactylus.util.web.Page; + +/** + * Freenet-specific {@link Page} extension that adds the capability to allow a + * link to a page to be unharmed by Freenet’s content filter. + * + * @author David ‘Bombe’ Roden + */ +public interface FreenetPage extends Page { + + /** + * Returns whether the given should be excepted from being filtered. + * + * @param link + * The link to check + * @return {@code true} if the link should not be filtered, {@code false} if + * it should be filtered + */ + public boolean isLinkExcepted(URI link); + +} diff --git a/src/main/java/net/pterodactylus/sone/web/page/FreenetTemplatePage.java b/src/main/java/net/pterodactylus/sone/web/page/FreenetTemplatePage.java index 5c027e5..e7d6e28 100644 --- a/src/main/java/net/pterodactylus/sone/web/page/FreenetTemplatePage.java +++ b/src/main/java/net/pterodactylus/sone/web/page/FreenetTemplatePage.java @@ -19,6 +19,7 @@ package net.pterodactylus.sone.web.page; import java.io.IOException; import java.io.StringWriter; +import java.net.URI; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -47,7 +48,7 @@ import freenet.support.HTMLNode; * * @author David ‘Bombe’ Roden */ -public class FreenetTemplatePage implements Page, LinkEnabledCallback { +public class FreenetTemplatePage implements FreenetPage, LinkEnabledCallback { /** The logger. */ private static final Logger logger = Logging.getLogger(FreenetTemplatePage.class); @@ -252,6 +253,14 @@ public class FreenetTemplatePage implements Page, LinkEnabledCal return false; } + /** + * {@inheritDoc} + */ + @Override + public boolean isLinkExcepted(URI link) { + return false; + } + // // INTERFACE LinkEnabledCallback // diff --git a/src/main/java/net/pterodactylus/sone/web/page/PageToadlet.java b/src/main/java/net/pterodactylus/sone/web/page/PageToadlet.java index 2d1f35a..8245251 100644 --- a/src/main/java/net/pterodactylus/sone/web/page/PageToadlet.java +++ b/src/main/java/net/pterodactylus/sone/web/page/PageToadlet.java @@ -26,6 +26,7 @@ import net.pterodactylus.util.web.Page; import net.pterodactylus.util.web.Response; import freenet.client.HighLevelSimpleClient; import freenet.clients.http.LinkEnabledCallback; +import freenet.clients.http.LinkFilterExceptedToadlet; import freenet.clients.http.Toadlet; import freenet.clients.http.ToadletContext; import freenet.clients.http.ToadletContextClosedException; @@ -39,7 +40,7 @@ import freenet.support.io.Closer; * * @author David ‘Bombe’ Roden */ -public class PageToadlet extends Toadlet implements LinkEnabledCallback { +public class PageToadlet extends Toadlet implements LinkEnabledCallback, LinkFilterExceptedToadlet { /** The name of the menu item. */ private final String menuName; @@ -174,4 +175,16 @@ public class PageToadlet extends Toadlet implements LinkEnabledCallback { return true; } + // + // LINKFILTEREXCEPTEDTOADLET METHODS + // + + /** + * {@inheritDoc} + */ + @Override + public boolean isLinkExcepted(URI link) { + return (page instanceof FreenetPage) ? ((FreenetPage) page).isLinkExcepted(link) : false; + } + } diff --git a/src/main/resources/i18n/sone.en.properties b/src/main/resources/i18n/sone.en.properties index 4210930..afc445a 100644 --- a/src/main/resources/i18n/sone.en.properties +++ b/src/main/resources/i18n/sone.en.properties @@ -40,6 +40,9 @@ Page.Options.Section.SoneSpecificOptions.NotLoggedIn=These options are only avai 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. Note that this will only follow Sones that are discovered after you activate this option! Page.Options.Option.EnableSoneInsertNotifications.Description=If enabled, this will display notifications every time your Sone is being inserted or finishes inserting. +Page.Options.Option.ShowNotificationNewSones.Description=Show notifications for new Sones. +Page.Options.Option.ShowNotificationNewPosts.Description=Show notifications for new posts. +Page.Options.Option.ShowNotificationNewReplies.Description=Show notifications for new replies. 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. @@ -98,8 +101,8 @@ Page.KnownSones.Sort.Order.Descending=Descending Page.KnownSones.FollowedSones.ShowOnly=Show only followed Sones Page.KnownSones.FollowedSones.Hide=Hide followed Sones Page.KnownSones.Button.Apply=Apply -Page.KnownSones.Button.FollowAllSones=Follow all Sones -Page.KnownSones.Button.UnfollowAllSones=Unfollow all Sones +Page.KnownSones.Button.FollowAllSones=Follow all Sones on this page +Page.KnownSones.Button.UnfollowAllSones=Unfollow all Sones on this page Page.EditProfile.Title=Edit Profile - Sone Page.EditProfile.Page.Title=Edit Profile @@ -201,6 +204,7 @@ Page.ImageBrowser.Sone.Title=Albums of {sone} Page.ImageBrowser.Sone.Error.NotFound.Text=The requested Sone could not be found. It is possible that it has not yet been downloaded. Page.ImageBrowser.Header.Albums=Albums Page.ImageBrowser.Header.Images=Images +Page.ImageBrowser.Link.All=All Sones Page.ImageBrowser.CreateAlbum.Button.CreateAlbum=Create Album Page.ImageBrowser.Album.Edit.Title=Edit Album Page.ImageBrowser.Album.Delete.Title=Delete Album diff --git a/src/main/resources/static/css/sone.css b/src/main/resources/static/css/sone.css index f30bb03..e46540a 100644 --- a/src/main/resources/static/css/sone.css +++ b/src/main/resources/static/css/sone.css @@ -684,6 +684,10 @@ textarea { width: 95%; } +#sone .image .album-sone { + font-size: 80%; +} + #sone .image .image-title, #sone .album .album-title { font-weight: bold; } diff --git a/src/main/resources/static/javascript/sone.js b/src/main/resources/static/javascript/sone.js index bbd039c..a3dbc64 100644 --- a/src/main/resources/static/javascript/sone.js +++ b/src/main/resources/static/javascript/sone.js @@ -1193,17 +1193,17 @@ function getStatus() { } }); if (!foundNotification) { - if (notificationId == "new-sone-notification") { + if (notificationId == "new-sone-notification" && (data.options["ShowNotification/NewSones"] == true)) { $(".new-sone-id", this).each(function(index, element) { soneId = $(this).text(); markSoneAsKnown(getSone(soneId), true); }); - } else if (notificationId == "new-post-notification") { + } else if (notificationId == "new-post-notification" && (data.options["ShowNotification/NewPosts"] == true)) { $(".post-id", this).each(function(index, element) { postId = $(this).text(); markPostAsKnown(getPost(postId), true); }); - } else if (notificationId == "new-reply-notification") { + } else if (notificationId == "new-reply-notification" && (data.options["ShowNotification/NewReplies"] == true)) { $(".reply-id", this).each(function(index, element) { replyId = $(this).text(); markReplyAsKnown(getReply(replyId), true); @@ -1347,7 +1347,7 @@ function isViewSonePage() { * @returns The ID of the currently shown Sone */ function getShownSoneId() { - return $("#sone .sone-id").text(); + return $("#sone .sone-id").first().text(); } /** diff --git a/src/main/resources/templates/bookmarks.html b/src/main/resources/templates/bookmarks.html index 8768b5a..cf4e7ce 100644 --- a/src/main/resources/templates/bookmarks.html +++ b/src/main/resources/templates/bookmarks.html @@ -5,6 +5,7 @@

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

+ <%= page|store key=pageParameter> <%include include/pagination.html> <%foreach posts post> <%include include/viewPost.html> diff --git a/src/main/resources/templates/imageBrowser.html b/src/main/resources/templates/imageBrowser.html index d97fc25..58ed8ab 100644 --- a/src/main/resources/templates/imageBrowser.html +++ b/src/main/resources/templates/imageBrowser.html @@ -118,9 +118,9 @@ ajaxGet("editImage.ajax", { "formPassword": getFormPassword(), "image": imageId, "title": title, "description": description }, function(data) { if (data && data.success) { getImage(data.imageId).find(".image-title").text(data.title); - getImage(data.imageId).find(".image-description").text(data.description); + getImage(data.imageId).find(".image-description").html(data.parsedDescription); getImage(data.imageId).find(":input[name='title']").attr("defaultValue", title); - getImage(data.imageId).find(":input[name='description']").attr("defaultValue", description); + getImage(data.imageId).find(":input[name='description']").attr("defaultValue", data.description); cancelImageEditing(); } }); @@ -298,6 +298,9 @@ }); $("#edit-album label").hide(); + /* hide non-js image move buttons. */ + $(".move-buttons").hide(); + hideAndShowBlock("div.edit-album", ".show-edit-album", ".hide-edit-album"); hideAndShowBlock("div.create-album", ".show-create-album", ".hide-create-album"); hideAndShowBlock("div.upload-image", ".show-upload-image", ".hide-upload-image"); @@ -312,6 +315,8 @@

<%= Page.ImageBrowser.Album.Title|l10n|replace needle='{album}' replacementKey=album.title|html>

<% image.title|html>
-
<% image.description|html>
+
<% image.description|parse sone=image.sone>
<%if album.sone.local>
@@ -386,6 +391,11 @@ +
+ + +
+