<modelVersion>4.0.0</modelVersion>
<groupId>net.pterodactylus</groupId>
<artifactId>sone</artifactId>
- <version>0.7.2</version>
+ <version>0.7.3</version>
<dependencies>
<dependency>
<groupId>net.pterodactylus</groupId>
<artifactId>utils</artifactId>
- <version>0.11.1</version>
+ <version>0.11.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<dependency>
<groupId>org.freenetproject</groupId>
<artifactId>fred</artifactId>
- <version>0.7.5.1336</version>
+ <version>0.7.5.1405</version>
<scope>provided</scope>
</dependency>
<dependency>
/* synchronize access on itself. */
private final Map<Sone, SoneStatus> soneStatuses = new HashMap<Sone, SoneStatus>();
+ /** The times Sones were followed. */
+ private final Map<Sone, Long> soneFollowingTimes = new HashMap<Sone, Long>();
+
/** Locked local Sones. */
/* synchronize on itself. */
private final Set<Sone> lockedSones = new HashSet<Sone>();
}
/**
+ * 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
}
Sone sone = addLocalSone(ownIdentity);
sone.getOptions().addBooleanOption("AutoFollow", new DefaultOption<Boolean>(false));
- sone.addFriend("nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI");
+ sone.getOptions().addBooleanOption("EnableSoneInsertNotifications", new DefaultOption<Boolean>(false));
+ sone.getOptions().addBooleanOption("ShowNotification/NewSones", new DefaultOption<Boolean>(true));
+ sone.getOptions().addBooleanOption("ShowNotification/NewPosts", new DefaultOption<Boolean>(true));
+ sone.getOptions().addBooleanOption("ShowNotification/NewReplies", new DefaultOption<Boolean>(true));
+ followSone(sone, getSone("nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI"));
touchConfiguration();
return sone;
}
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() {
}
/**
+ * 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.
*
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);
}
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);
}
/* initialize options. */
sone.getOptions().addBooleanOption("AutoFollow", new DefaultOption<Boolean>(false));
sone.getOptions().addBooleanOption("EnableSoneInsertNotifications", new DefaultOption<Boolean>(false));
+ sone.getOptions().addBooleanOption("ShowNotification/NewSones", new DefaultOption<Boolean>(true));
+ sone.getOptions().addBooleanOption("ShowNotification/NewPosts", new DefaultOption<Boolean>(true));
+ sone.getOptions().addBooleanOption("ShowNotification/NewReplies", new DefaultOption<Boolean>(true));
/* load Sone. */
String sonePrefix = "Sone/" + sone.getId();
/* 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) {
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);
}
/* 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();
configuration.getStringValue("KnownSone/" + soneCounter + "/ID").setValue(null);
}
+ /* save Sone following times. */
+ soneCounter = 0;
+ synchronized (soneFollowingTimes) {
+ for (Entry<Sone, Long> 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) {
}
}
+ /* 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) {
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;
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);
}
}
try {
return client.insertManifest(insertUri, manifestEntries, defaultFile);
} catch (InsertException ie1) {
- throw new SoneException(null, ie1);
+ throw new SoneException(ie1);
}
}
* {@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;
}
* @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;
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;
}
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;
}
}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core;
+
+/**
+ * Exception that signals a problem with an insert.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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);
+ }
+
+}
@Override
protected void serviceRun() {
long lastModificationTime = 0;
+ String lastInsertedFingerprint = lastInsertFingerprint;
String lastFingerprint = "";
while (!shouldStop()) { try {
/* check every seconds. */
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;
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);
lastFingerprint = fingerprint;
}
if (modified && (lastModificationTime > 0) && ((System.currentTimeMillis() - lastModificationTime) > (insertionDelay * 1000))) {
- lastInsertFingerprint = fingerprint;
+ lastInsertedFingerprint = fingerprint;
insertInformation = new InsertInformation(sone);
}
}
*/
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;
}
}
};
+ /** Filter that matches Sones that have at least one album. */
+ public static final Filter<Sone> HAS_ALBUM_FILTER = new Filter<Sone>() {
+
+ @Override
+ public boolean filterObject(Sone sone) {
+ return !sone.getAlbums().isEmpty();
+ }
+ };
+
/** The logger. */
private static final Logger logger = Logging.getLogger(Sone.class);
}
/**
- * 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<String> friends) {
- friendSones.clear();
- friendSones.addAll(friends);
- return this;
- }
-
- /**
* Returns whether this Sone has the given Sone as a friend Sone.
*
* @param friendSoneId
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
//
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;
Set<OwnIdentity> allOwnIdentities = getAllOwnIdentities();
for (OwnIdentity ownIdentity : allOwnIdentities) {
if (ownIdentity.getId().equals(id)) {
- return ownIdentity;
+ return new DefaultOwnIdentity(webOfTrustConnector, ownIdentity);
}
}
return null;
newOwnIdentities.put(ownIdentity.getId(), ownIdentity);
}
checkOwnIdentities(newOwnIdentities);
- return ownIdentities;
+ return Mappers.mappedSet(ownIdentities, new Mapper<OwnIdentity, OwnIdentity>() {
+
+ /**
+ * {@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();
/* 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));
}
}
}
/** 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);
public static List<Notification> filterNotifications(Collection<? extends Notification> notifications, Sone currentSone) {
List<Notification> filteredNotifications = new ArrayList<Notification>();
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<Post> filteredNotification = filterNewPostNotification((ListNotification<Post>) 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<PostReply> filteredNotification = filterNewReplyNotification((ListNotification<PostReply>) notification, currentSone);
if (filteredNotification != null) {
filteredNotifications.add(filteredNotification);
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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:
+ * <ul>
+ * <li>{@code previous}: returns the previous image in the image’s album, or
+ * {@code null} if the image is the first image of its album.</li>
+ * <li>{@code next}: returns the next image in the image’s album, or {@code
+ * null} if the image is the last image of its album.</li>
+ * </ul>
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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);
+ }
+
+}
public class ImageLinkFilter implements Filter {
/** The template to render for the <img> tag. */
- private static final Template linkTemplate = TemplateParser.parse(new StringReader("<img<%ifnull !class> 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("<img<%ifnull !class> 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;
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());
}
});
List<Post> sortedPosts = new ArrayList<Post>(loadedPosts);
Collections.sort(sortedPosts, Post.TIME_COMPARATOR);
- Pagination<Post> pagination = new Pagination<Post>(sortedPosts, 25).setPage(Numbers.safeParseInteger(request.getHttpRequest().getParam("page"), 0));
+ Pagination<Post> pagination = new Pagination<Post>(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());
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;
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());
}
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;
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) {
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;
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());
}
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;
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);
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);
}
}
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;
/**
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
-public class GetImagePage implements Page<FreenetRequest> {
+public class GetImagePage implements FreenetPage {
/** The Sone web interface. */
private final WebInterface webInterface;
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;
+ }
+
}
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;
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<Album> albums = new HashSet<Album>();
+ 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;
+ }
+
}
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));
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());
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);
}
}
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;
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;
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;
templateContext.set("raw", raw);
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isLinkExcepted(URI link) {
+ return true;
+ }
+
}
package net.pterodactylus.sone.web;
+import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
templateContext.set("repliedPosts", repliedPostPagination.getItems());
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isLinkExcepted(URI link) {
+ return true;
+ }
+
}
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;
/** The Sone text parser. */
private final SoneTextParser soneTextParser;
+ /** The parser filter. */
+ private final ParserFilter parserFilter;
+
/** The “new Sone” notification. */
private final ListNotification<Sone> newSoneNotification;
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());
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());
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)));
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;
}
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());
}
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.
*/
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;
}
//
}
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<String, String>().put("sone", image.getSone().getId()).get()));
}
}
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();
}
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);
}
/**
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;
+ }
+
}
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
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
-public abstract class JsonPage implements Page<FreenetRequest> {
+public abstract class JsonPage implements FreenetPage {
/** The path of the page. */
private final String path;
return response.setStatusCode(200).setStatusText("OK").setContentType("application/json").write(JsonUtils.format(jsonObject));
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isLinkExcepted(URI link) {
+ return false;
+ }
+
}
if (currentSone == null) {
return createErrorJsonObject("auth-required");
}
- currentSone.removeFriend(soneId);
- webInterface.getCore().touchConfiguration();
+ webInterface.getCore().unfollowSone(currentSone, soneId);
return createSuccessJsonObject();
}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public interface FreenetPage extends Page<FreenetRequest> {
+
+ /**
+ * 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);
+
+}
import java.io.IOException;
import java.io.StringWriter;
+import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
-public class FreenetTemplatePage implements Page<FreenetRequest>, LinkEnabledCallback {
+public class FreenetTemplatePage implements FreenetPage, LinkEnabledCallback {
/** The logger. */
private static final Logger logger = Logging.getLogger(FreenetTemplatePage.class);
return false;
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isLinkExcepted(URI link) {
+ return false;
+ }
+
//
// INTERFACE LinkEnabledCallback
//
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;
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
-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;
return true;
}
+ //
+ // LINKFILTEREXCEPTEDTOADLET METHODS
+ //
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isLinkExcepted(URI link) {
+ return (page instanceof FreenetPage) ? ((FreenetPage) page).isLinkExcepted(link) : false;
+ }
+
}
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.
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
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
width: 95%;
}
+#sone .image .album-sone {
+ font-size: 80%;
+}
+
#sone .image .image-title, #sone .album .album-title {
font-weight: bold;
}
}
});
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);
* @returns The ID of the currently shown Sone
*/
function getShownSoneId() {
- return $("#sone .sone-id").text();
+ return $("#sone .sone-id").first().text();
}
/**
<h1><%= Page.Bookmarks.Page.Title|l10n|html></h1>
<div id="posts">
+ <%= page|store key=pageParameter>
<%include include/pagination.html>
<%foreach posts post>
<%include include/viewPost.html>
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();
}
});
});
$("#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");
<h1 class="backlink"><%= Page.ImageBrowser.Album.Title|l10n|replace needle='{album}' replacementKey=album.title|html></h1>
<div class="backlinks">
+ <div class="backlink"><a href="imageBrowser.html?mode=gallery"><%= Page.ImageBrowser.Link.All|l10n|html></a></div>
+ <div class="separator">></div>
<%foreach album.backlinks backlink backlinks>
<div class="backlink">
<a href="<% backlink.target|html>"><% backlink.name|html></a>
<%/foreach>
</div>
- <p id="description"><% album.description|html></p>
+ <p id="description"><% album.description|parse sone=album.sone></p>
<%if album.sone.local>
<div class="show-edit-album hidden toggle-link"><a class="small-link">» <%= Page.ImageBrowser.Album.Edit.Title|l10n|html></a></div>
</div>
<div class="show-data">
<div class="image-title"><% image.title|html></div>
- <div class="image-description"><% image.description|html></div>
+ <div class="image-description"><% image.description|parse sone=image.sone></div>
</div>
<%if album.sone.local>
<form class="edit-image" action="editImage.html" method="post">
<input type="hidden" name="returnPage" value="<%request.uri|html>" />
<input type="hidden" name="image" value="<%image.id|html>" />
+ <div class="move-buttons">
+ <button <%first>class="hidden" <%/first>type="submit" name="moveLeft" value="true"><%= Page.ImageBrowser.Image.Button.MoveLeft|l10n|html></button>
+ <button <%last>class="hidden" <%/last>type="submit" name="moveRight" value="true"><%= Page.ImageBrowser.Image.Button.MoveRight|l10n|html></button>
+ </div>
+
<div class="edit-data hidden">
<div>
<input type="text" name="title" value="<%image.title|html>" />
<h1 class="backlink"><%image.title|html></h1>
<div class="backlinks">
+ <div class="backlink"><a href="imageBrowser.html?mode=gallery"><%= Page.ImageBrowser.Link.All|l10n|html></a></div>
+ <div class="separator">></div>
<%foreach image.album.backlinks backlink backlinks>
<div class="backlink">
<a href="<% backlink.target|html>"><% backlink.name|html></a>
<div class="separator">></div>
<%/if>
<%/foreach>
+ <%ifnull !image.previous><div class="backlink"><a href="imageBrowser.html?image=<%image.previous.id|html>">« <%image.previous.title|html></a></div><%/if>
+ <%ifnull !image.next><div class="backlink"><a href="imageBrowser.html?image=<%image.next.id|html>">» <%image.next.title|html></a></div><%/if>
</div>
<%ifnull image>
});
$("#create-album label").hide();
+ /* hide non-js move buttons. */
+ $(".move-buttons").hide();
+
hideAndShowBlock(".create-album", ".show-create-album", ".hide-create-album");
prepareAlbums();
<h1><%= Page.ImageBrowser.Sone.Title|l10n|replace needle='{sone}' replacementKey=sone.niceName|html></h1>
+ <div class="backlinks">
+ <div class="backlink"><a href="imageBrowser.html?mode=gallery"><%= Page.ImageBrowser.Link.All|l10n|html></a></div>
+ <div class="separator">></div>
+ <div class="backlink"><a href="imageBrowser.html?sone=<%sone.id|html>"><%sone.niceName|l10n|html></a></div>
+ </div>
+
<%include include/browseAlbums.html albums=sone.albums>
<%if sone.local>
<%/if>
+ <%elseif galleryRequested>
+
+ <%foreach albums album>
+ <%first><h2><%= Page.ImageBrowser.Header.Albums|l10n|html></h2><%/first>
+ <%if loop.count|mod divisor=3><div class="album-row"><%/if>
+ <div id="album-<% album.id|html>" class="album">
+ <div class="album-id hidden"><% album.id|html></div>
+ <div class="album-container">
+ <a href="imageBrowser.html?album=<% album.id|html>" title="<% album.title|html>">
+ <%ifnull album.albumImage>
+ <img src="images/unknown-image-0.png" width="333" height="250" alt="<% album.title|html> (<%album.sone.niceName|html>)" title="<% album.title|html> (<%album.sone.niceName|html>)" style="position: relative; top: 0px; left: -41px;" />
+ <%else><!-- TODO -->
+ <% album.albumImage|image-link max-width=250 max-height=250 mode=enlarge title==album.title>
+ <%/if>
+ </a>
+ </div>
+ <div class="show-data">
+ <div class="album-sone"><a href="imageBrowser.html?sone=<%album.sone.id|html>"><%album.sone.niceName|html></a></div>
+ <div class="album-title"><% album.title|html> (<%= View.Sone.Stats.Images|l10n 0=album.images.size>)</div>
+ <div class="album-description"><% album.description|parse sone=album.sone></div>
+ </div>
+ </div>
+ <%= false|store key=endRow>
+ <%if loop.count|mod divisor=3 offset=1><%= true|store key=endRow><%/if>
+ <%last><%= true|store key=endRow><%/last>
+ <%if endRow></div><%/if>
+ <%/foreach>
+
<%/if>
<%include include/tail.html>
</div>
<div class="show-data">
<div class="album-title"><% album.title|html> (<%= View.Sone.Stats.Images|l10n 0=album.images.size>)</div>
- <div class="album-description"><% album.description|html></div>
+ <div class="album-description"><% album.description|parse sone=album.sone></div>
</div>
<%if album.sone.local>
<form class="edit-album" action="editAlbum.html" method="post">
<input type="hidden" name="returnPage" value="<%request.uri|html>" />
<input type="hidden" name="album" value="<%album.id|html>" />
+ <div class="move-buttons">
+ <button <%first>class="hidden" <%/first>type="submit" name="moveLeft" value="true"><%= Page.ImageBrowser.Image.Button.MoveLeft|l10n|html></button>
+ <button <%last>class="hidden" <%/last>type="submit" name="moveRight" value="true"><%= Page.ImageBrowser.Image.Button.MoveRight|l10n|html></button>
+ </div>
+
<div class="edit-data hidden">
<div>
<input type="text" name="title" value="<%album.title|html>" />
<%= Page.Options.Option.EnableSoneInsertNotifications.Description|l10n|html>
</p>
+ <p>
+ <input type="checkbox" name="show-notification-new-sones"<%ifnull currentSone> disabled="disabled"<%/if><%if show-notification-new-sones> checked="checked"<%/if>/>
+ <%= Page.Options.Option.ShowNotificationNewSones.Description|l10n|html>
+ </p>
+
+ <p>
+ <input type="checkbox" name="show-notification-new-posts"<%ifnull currentSone> disabled="disabled"<%/if><%if show-notification-new-posts> checked="checked"<%/if>/>
+ <%= Page.Options.Option.ShowNotificationNewPosts.Description|l10n|html>
+ </p>
+
+ <p>
+ <input type="checkbox" name="show-notification-new-replies"<%ifnull currentSone> disabled="disabled"<%/if><%if show-notification-new-replies> checked="checked"<%/if>/>
+ <%= Page.Options.Option.ShowNotificationNewReplies.Description|l10n|html>
+ </p>
+
<h2><%= Page.Options.Section.RuntimeOptions.Title|l10n|html></h2>
<p><%= Page.Options.Option.InsertionDelay.Description|l10n|html></p>