<modelVersion>4.0.0</modelVersion>
<groupId>net.pterodactylus</groupId>
<artifactId>sone</artifactId>
- <version>0.7.1</version>
+ <version>0.7.2</version>
<dependencies>
<dependency>
<groupId>net.pterodactylus</groupId>
<artifactId>utils</artifactId>
- <version>0.11</version>
+ <version>0.11.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
import net.pterodactylus.sone.data.Client;
import net.pterodactylus.sone.data.Image;
import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostReply;
import net.pterodactylus.sone.data.Profile;
import net.pterodactylus.sone.data.Reply;
import net.pterodactylus.sone.data.Sone;
private Set<String> knownPosts = new HashSet<String>();
/** All replies. */
- private Map<String, Reply> replies = new HashMap<String, Reply>();
+ private Map<String, PostReply> replies = new HashMap<String, PostReply>();
/** All new replies. */
private Set<String> newReplies = new HashSet<String>();
* The ID of the reply to get
* @return The reply
*/
- public Reply getReply(String replyId) {
+ public PostReply getReply(String replyId) {
return getReply(replyId, true);
}
* to return {@code null} if no reply can be found
* @return The reply, or {@code null} if there is no such reply
*/
- public Reply getReply(String replyId, boolean create) {
+ public PostReply getReply(String replyId, boolean create) {
synchronized (replies) {
- Reply reply = replies.get(replyId);
+ PostReply reply = replies.get(replyId);
if (create && (reply == null)) {
- reply = new Reply(replyId);
+ reply = new PostReply(replyId);
replies.put(replyId, reply);
}
return reply;
* The post to get all replies for
* @return All replies for the given post
*/
- public List<Reply> getReplies(Post post) {
+ public List<PostReply> getReplies(Post post) {
Set<Sone> sones = getSones();
- List<Reply> replies = new ArrayList<Reply>();
+ List<PostReply> replies = new ArrayList<PostReply>();
for (Sone sone : sones) {
- for (Reply reply : sone.getReplies()) {
+ for (PostReply reply : sone.getReplies()) {
if (reply.getPost().equals(post)) {
replies.add(reply);
}
* The reply to get the liking Sones for
* @return The Sones that like the given reply
*/
- public Set<Sone> getLikes(Reply reply) {
+ public Set<Sone> getLikes(PostReply reply) {
Set<Sone> sones = new HashSet<Sone>();
for (Sone sone : getSones()) {
if (sone.getLikedReplyIds().contains(reply.getId())) {
}
synchronized (replies) {
if (!soneRescueMode) {
- for (Reply reply : storedSone.getReplies()) {
+ for (PostReply reply : storedSone.getReplies()) {
replies.remove(reply.getId());
if (!sone.getReplies().contains(reply)) {
coreListenerManager.fireReplyRemoved(reply);
}
}
}
- Set<Reply> storedReplies = storedSone.getReplies();
+ Set<PostReply> storedReplies = storedSone.getReplies();
synchronized (newReplies) {
- for (Reply reply : sone.getReplies()) {
+ for (PostReply reply : sone.getReplies()) {
reply.setSone(storedSone);
if (!storedReplies.contains(reply) && !knownReplies.contains(reply.getId())) {
newReplies.add(reply.getId());
for (Post post : sone.getPosts()) {
storedSone.addPost(post);
}
- for (Reply reply : sone.getReplies()) {
+ for (PostReply reply : sone.getReplies()) {
storedSone.addReply(reply);
}
for (String likedPostId : sone.getLikedPostIds()) {
}
/* load replies. */
- Set<Reply> replies = new HashSet<Reply>();
+ Set<PostReply> replies = new HashSet<PostReply>();
while (true) {
String replyPrefix = sonePrefix + "/Replies/" + replies.size();
String replyId = configuration.getStringValue(replyPrefix + "/ID").getValue(null);
}
}
synchronized (newReplies) {
- for (Reply reply : replies) {
+ for (PostReply reply : replies) {
knownReplies.add(reply.getId());
}
}
* The text of the reply
* @return The created reply
*/
- public Reply createReply(Sone sone, Post post, String text) {
+ public PostReply createReply(Sone sone, Post post, String text) {
return createReply(sone, post, System.currentTimeMillis(), text);
}
* The text of the reply
* @return The created reply
*/
- public Reply createReply(Sone sone, Post post, long time, String text) {
+ public PostReply createReply(Sone sone, Post post, long time, String text) {
if (!isLocalSone(sone)) {
logger.log(Level.FINE, "Tried to create reply for non-local Sone: %s", sone);
return null;
}
- final Reply reply = new Reply(sone, post, System.currentTimeMillis(), text);
+ final PostReply reply = new PostReply(sone, post, System.currentTimeMillis(), text);
synchronized (replies) {
replies.put(reply.getId(), reply);
}
* @param reply
* The reply to delete
*/
- public void deleteReply(Reply reply) {
+ public void deleteReply(PostReply reply) {
Sone sone = reply.getSone();
if (!isLocalSone(sone)) {
logger.log(Level.FINE, "Tried to delete non-local reply: %s", reply);
* @param reply
* The reply to mark as known
*/
- public void markReplyKnown(Reply reply) {
+ public void markReplyKnown(PostReply reply) {
synchronized (newReplies) {
if (newReplies.remove(reply.getId())) {
knownReplies.add(reply.getId());
logger.log(Level.INFO, "Saving Sone: %s", sone);
try {
- ((OwnIdentity) sone.getIdentity()).setProperty("Sone.LatestEdition", String.valueOf(sone.getLatestEdition()));
-
/* save Sone into configuration. */
String sonePrefix = "Sone/" + sone.getId();
configuration.getLongValue(sonePrefix + "/Time").setValue(sone.getTime());
/* save replies. */
int replyCounter = 0;
- for (Reply reply : sone.getReplies()) {
+ for (PostReply reply : sone.getReplies()) {
String replyPrefix = sonePrefix + "/Replies/" + replyCounter++;
configuration.getStringValue(replyPrefix + "/ID").setValue(reply.getId());
configuration.getStringValue(replyPrefix + "/Post/ID").setValue(reply.getPost().getId());
configuration.getStringValue(sonePrefix + "/Friends/" + friendCounter + "/ID").setValue(null);
/* save albums. first, collect in a flat structure, top-level first. */
- List<Album> albums = Sone.flattenAlbums(sone.getAlbums());
+ List<Album> albums = sone.getAllAlbums();
int albumCounter = 0;
for (Album album : albums) {
configuration.getBooleanValue(sonePrefix + "/Options/EnableSoneInsertNotifications").setValue(sone.getOptions().getBooleanOption("EnableSoneInsertNotifications").getReal());
configuration.save();
+
+ ((OwnIdentity) sone.getIdentity()).setProperty("Sone.LatestEdition", String.valueOf(sone.getLatestEdition()));
+
logger.log(Level.INFO, "Sone %s saved.", sone);
} catch (ConfigurationException ce1) {
logger.log(Level.WARNING, "Could not save Sone: " + sone, ce1);
configuration.getIntValue("Option/InsertionDelay").setValue(options.getIntegerOption("InsertionDelay").getReal());
configuration.getIntValue("Option/PostsPerPage").setValue(options.getIntegerOption("PostsPerPage").getReal());
configuration.getIntValue("Option/CharactersPerPost").setValue(options.getIntegerOption("CharactersPerPost").getReal());
+ configuration.getIntValue("Option/PostCutOffLength").setValue(options.getIntegerOption("PostCutOffLength").getReal());
configuration.getBooleanValue("Option/RequireFullAccess").setValue(options.getBooleanOption("RequireFullAccess").getReal());
configuration.getIntValue("Option/PositiveTrust").setValue(options.getIntegerOption("PositiveTrust").getReal());
configuration.getIntValue("Option/NegativeTrust").setValue(options.getIntegerOption("NegativeTrust").getReal());
}));
options.addIntegerOption("PostsPerPage", new DefaultOption<Integer>(10, new IntegerRangeValidator(1, Integer.MAX_VALUE)));
- options.addIntegerOption("CharactersPerPost", new DefaultOption<Integer>(200, new OrValidator<Integer>(new IntegerRangeValidator(50, Integer.MAX_VALUE), new EqualityValidator<Integer>(-1))));
+ options.addIntegerOption("CharactersPerPost", new DefaultOption<Integer>(400, new OrValidator<Integer>(new IntegerRangeValidator(50, Integer.MAX_VALUE), new EqualityValidator<Integer>(-1))));
+ options.addIntegerOption("PostCutOffLength", new DefaultOption<Integer>(200, new OrValidator<Integer>(new IntegerRangeValidator(50, Integer.MAX_VALUE), new EqualityValidator<Integer>(-1))));
options.addBooleanOption("RequireFullAccess", new DefaultOption<Boolean>(false));
options.addIntegerOption("PositiveTrust", new DefaultOption<Integer>(75, new IntegerRangeValidator(0, 100)));
options.addIntegerOption("NegativeTrust", new DefaultOption<Integer>(-25, new IntegerRangeValidator(-100, 100)));
loadConfigurationValue("InsertionDelay");
loadConfigurationValue("PostsPerPage");
loadConfigurationValue("CharactersPerPost");
+ loadConfigurationValue("PostCutOffLength");
options.getBooleanOption("RequireFullAccess").set(configuration.getBooleanValue("Option/RequireFullAccess").getValue(null));
loadConfigurationValue("PositiveTrust");
loadConfigurationValue("NegativeTrust");
}
synchronized (replies) {
synchronized (newReplies) {
- for (Reply reply : sone.getReplies()) {
+ for (PostReply reply : sone.getReplies()) {
replies.remove(reply.getId());
newReplies.remove(reply.getId());
coreListenerManager.fireReplyRemoved(reply);
}
/**
+ * Returns the number of characters the shortened post should have.
+ *
+ * @return The number of characters of the snippet
+ */
+ public int getPostCutOffLength() {
+ return options.getIntegerOption("PostCutOffLength").get();
+ }
+
+ /**
+ * Validates the number of characters after which to cut off the post.
+ *
+ * @param postCutOffLength
+ * The number of characters of the snippet
+ * @return {@code true} if the number of characters of the snippet is
+ * valid, {@code false} otherwise
+ */
+ public boolean validatePostCutOffLength(Integer postCutOffLength) {
+ return options.getIntegerOption("PostCutOffLength").validate(postCutOffLength);
+ }
+
+ /**
+ * Sets the number of characters the shortened post should have.
+ *
+ * @param postCutOffLength
+ * The number of characters of the snippet
+ * @return This preferences
+ */
+ public Preferences setPostCutOffLength(Integer postCutOffLength) {
+ options.getIntegerOption("PostCutOffLength").set(postCutOffLength);
+ return this;
+ }
+
+ /**
* Returns whether Sone requires full access to be even visible.
*
* @return {@code true} if Sone requires full access, {@code false}
import net.pterodactylus.sone.data.Image;
import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.PostReply;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.util.version.Version;
* @param reply
* The new reply
*/
- public void newReplyFound(Reply reply);
+ public void newReplyFound(PostReply reply);
/**
* Notifies a listener that the given Sone is now marked as known.
* @param reply
* The known reply
*/
- public void markReplyKnown(Reply reply);
+ public void markReplyKnown(PostReply reply);
/**
* Notifies a listener that the given Sone was removed.
* @param reply
* The removed reply
*/
- public void replyRemoved(Reply reply);
+ public void replyRemoved(PostReply reply);
/**
* Notifies a listener when a Sone was locked.
import net.pterodactylus.sone.data.Image;
import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.PostReply;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.util.event.AbstractListenerManager;
import net.pterodactylus.util.version.Version;
/**
* Notifies all listeners that a new reply has been found.
*
- * @see CoreListener#newReplyFound(Reply)
+ * @see CoreListener#newReplyFound(PostReply)
* @param reply
* The new reply
*/
- void fireNewReplyFound(Reply reply) {
+ void fireNewReplyFound(PostReply reply) {
for (CoreListener coreListener : getListeners()) {
coreListener.newReplyFound(reply);
}
* @param reply
* The known reply
*/
- void fireMarkReplyKnown(Reply reply) {
+ void fireMarkReplyKnown(PostReply reply) {
for (CoreListener coreListener : getListeners()) {
coreListener.markReplyKnown(reply);
}
/**
* Notifies all listener that the given reply was removed.
*
- * @see CoreListener#replyRemoved(Reply)
+ * @see CoreListener#replyRemoved(PostReply)
* @param reply
* The removed reply
*/
- void fireReplyRemoved(Reply reply) {
+ void fireReplyRemoved(PostReply reply) {
for (CoreListener coreListener : getListeners()) {
coreListener.replyRemoved(reply);
}
import net.pterodactylus.sone.data.Client;
import net.pterodactylus.sone.data.Image;
import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostReply;
import net.pterodactylus.sone.data.Profile;
-import net.pterodactylus.sone.data.Reply;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.util.collection.Pair;
import net.pterodactylus.util.io.Closer;
/* parse replies. */
SimpleXML repliesXml = soneXml.getNode("replies");
- Set<Reply> replies = new HashSet<Reply>();
+ Set<PostReply> replies = new HashSet<PostReply>();
if (repliesXml == null) {
/* TODO - mark Sone as bad. */
logger.log(Level.WARNING, "Downloaded Sone %s has no replies!", new Object[] { sone });
String id = albumXml.getValue("id", null);
String parentId = albumXml.getValue("parent", null);
String title = albumXml.getValue("title", null);
- String description = albumXml.getValue("description", null);
+ String description = albumXml.getValue("description", "");
String albumImageId = albumXml.getValue("album-image", null);
if ((id == null) || (title == null) || (description == null)) {
logger.log(Level.WARNING, "Downloaded Sone %s contains invalid album!", new Object[] { sone });
return null;
}
}
- Album album = core.getAlbum(id).setSone(sone).setTitle(title).setDescription(description).setAlbumImage(albumImageId);
+ Album album = core.getAlbum(id).setSone(sone).setTitle(title).setDescription(description);
if (parent != null) {
parent.addAlbum(album);
} else {
album.addImage(image);
}
}
+ album.setAlbumImage(albumImageId);
}
}
import net.pterodactylus.sone.core.Core.SoneStatus;
import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostReply;
import net.pterodactylus.sone.data.Reply;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.freenet.StringBucket;
soneProperties.put("insertUri", sone.getInsertUri());
soneProperties.put("profile", sone.getProfile());
soneProperties.put("posts", new ListBuilder<Post>(new ArrayList<Post>(sone.getPosts())).sort(Post.TIME_COMPARATOR).get());
- soneProperties.put("replies", new ListBuilder<Reply>(new ArrayList<Reply>(sone.getReplies())).sort(new ReverseComparator<Reply>(Reply.TIME_COMPARATOR)).get());
+ soneProperties.put("replies", new ListBuilder<PostReply>(new ArrayList<PostReply>(sone.getReplies())).sort(new ReverseComparator<Reply<?>>(Reply.TIME_COMPARATOR)).get());
soneProperties.put("likedPostIds", new HashSet<String>(sone.getLikedPostIds()));
soneProperties.put("likedReplyIds", new HashSet<String>(sone.getLikedReplyIds()));
- soneProperties.put("albums", Sone.flattenAlbums(sone.getAlbums()));
+ soneProperties.put("albums", sone.getAllAlbums());
}
//
--- /dev/null
+/*
+ * Sone - PostReply.java - Copyright © 2010–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.data;
+
+import java.util.UUID;
+
+/**
+ * A reply is like a {@link Post} but can never be posted on its own, it always
+ * refers to another {@link Post}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class PostReply extends Reply<PostReply> {
+
+ /** The Post this reply refers to. */
+ private volatile Post post;
+
+ /**
+ * Creates a new reply.
+ *
+ * @param id
+ * The ID of the reply
+ */
+ public PostReply(String id) {
+ this(id, null, null, 0, null);
+ }
+
+ /**
+ * Creates a new reply.
+ *
+ * @param sone
+ * The sone that posted the reply
+ * @param post
+ * The post to reply to
+ * @param text
+ * The text of the reply
+ */
+ public PostReply(Sone sone, Post post, String text) {
+ this(sone, post, System.currentTimeMillis(), text);
+ }
+
+ /**
+ * Creates a new reply-
+ *
+ * @param sone
+ * The sone that posted the reply
+ * @param post
+ * The post to reply to
+ * @param time
+ * The time of the reply
+ * @param text
+ * The text of the reply
+ */
+ public PostReply(Sone sone, Post post, long time, String text) {
+ this(UUID.randomUUID().toString(), sone, post, time, text);
+ }
+
+ /**
+ * Creates a new reply-
+ *
+ * @param sone
+ * The sone that posted the reply
+ * @param id
+ * The ID of the reply
+ * @param post
+ * The post to reply to
+ * @param time
+ * The time of the reply
+ * @param text
+ * The text of the reply
+ */
+ public PostReply(String id, Sone sone, Post post, long time, String text) {
+ super(id, sone, time, text);
+ this.post = post;
+ }
+
+ //
+ // ACCESSORS
+ //
+
+ /**
+ * Returns the post this reply refers to.
+ *
+ * @return The post this reply refers to
+ */
+ public Post getPost() {
+ return post;
+ }
+
+ /**
+ * Sets the post this reply refers to.
+ *
+ * @param post
+ * The post this reply refers to
+ * @return This reply (for method chaining)
+ */
+ public PostReply setPost(Post post) {
+ this.post = post;
+ return this;
+ }
+
+}
/*
- * Sone - Reply.java - Copyright © 2010 David Roden
+ * Sone - Reply.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
import net.pterodactylus.util.filter.Filter;
/**
- * A reply is like a {@link Post} but can never be posted on its own, it always
- * refers to another {@link Post}.
+ * Abstract base class for all replies.
*
+ * @param <T>
+ * The type of the reply
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
-public class Reply {
+public abstract class Reply<T extends Reply<T>> {
/** Comparator that sorts replies ascending by time. */
- public static final Comparator<Reply> TIME_COMPARATOR = new Comparator<Reply>() {
+ public static final Comparator<Reply<?>> TIME_COMPARATOR = new Comparator<Reply<?>>() {
+ /**
+ * {@inheritDoc}
+ */
@Override
- public int compare(Reply leftReply, Reply rightReply) {
+ public int compare(Reply<?> leftReply, Reply<?> rightReply) {
return (int) Math.max(Integer.MIN_VALUE, Math.min(Integer.MAX_VALUE, leftReply.getTime() - rightReply.getTime()));
}
};
/** Filter for replies with timestamps from the future. */
- public static final Filter<Reply> FUTURE_REPLIES_FILTER = new Filter<Reply>() {
+ public static final Filter<Reply<?>> FUTURE_REPLY_FILTER = new Filter<Reply<?>>() {
+ /**
+ * {@inheritDoc}
+ */
@Override
- public boolean filterObject(Reply reply) {
+ public boolean filterObject(Reply<?> reply) {
return reply.getTime() <= System.currentTimeMillis();
}
};
/** The ID of the reply. */
- private final UUID id;
+ private final String id;
- /** The Sone that posted this reply. */
+ /** The Sone that created this reply. */
private volatile Sone sone;
- /** The Post this reply refers to. */
- private volatile Post post;
-
/** The time of the reply. */
private volatile long time;
private volatile String text;
/**
- * Creates a new reply.
+ * Creates a new reply with the given ID.
*
* @param id
* The ID of the reply
*/
- public Reply(String id) {
- this(id, null, null, 0, null);
- }
-
- /**
- * Creates a new reply.
- *
- * @param sone
- * The sone that posted the reply
- * @param post
- * The post to reply to
- * @param text
- * The text of the reply
- */
- public Reply(Sone sone, Post post, String text) {
- this(sone, post, System.currentTimeMillis(), text);
+ protected Reply(String id) {
+ this(id, null, 0, null);
}
/**
- * Creates a new reply-
+ * Creates a new reply with a new random ID.
*
* @param sone
- * The sone that posted the reply
- * @param post
- * The post to reply to
+ * The Sone of the reply
* @param time
* The time of the reply
* @param text
* The text of the reply
*/
- public Reply(Sone sone, Post post, long time, String text) {
- this(UUID.randomUUID().toString(), sone, post, time, text);
+ protected Reply(Sone sone, long time, String text) {
+ this(UUID.randomUUID().toString(), sone, time, text);
}
/**
- * Creates a new reply-
+ * Creates a new reply.
*
- * @param sone
- * The sone that posted the reply
* @param id
* The ID of the reply
- * @param post
- * The post to reply to
+ * @param sone
+ * The Sone of the reply
* @param time
* The time of the reply
* @param text
* The text of the reply
*/
- public Reply(String id, Sone sone, Post post, long time, String text) {
- this.id = UUID.fromString(id);
+ protected Reply(String id, Sone sone, long time, String text) {
+ this.id = id;
this.sone = sone;
- this.post = post;
this.time = time;
this.text = text;
}
- //
- // ACCESSORS
- //
-
/**
* Returns the ID of the reply.
*
* @return The ID of the reply
*/
public String getId() {
- return id.toString();
+ return id;
}
/**
* The Sone that posted this reply
* @return This reply (for method chaining)
*/
- public Reply setSone(Sone sone) {
+ @SuppressWarnings("unchecked")
+ public T setSone(Sone sone) {
this.sone = sone;
- return this;
- }
-
- /**
- * Returns the post this reply refers to.
- *
- * @return The post this reply refers to
- */
- public Post getPost() {
- return post;
- }
-
- /**
- * Sets the post this reply refers to.
- *
- * @param post
- * The post this reply refers to
- * @return This reply (for method chaining)
- */
- public Reply setPost(Post post) {
- this.post = post;
- return this;
+ return (T) this;
}
/**
* The time of this reply (in milliseconds since Jan 1, 1970 UTC)
* @return This reply (for method chaining)
*/
- public Reply setTime(long time) {
+ @SuppressWarnings("unchecked")
+ public T setTime(long time) {
this.time = time;
- return this;
+ return (T) this;
}
/**
* The text of this reply
* @return This reply (for method chaining)
*/
- public Reply setText(String text) {
+ @SuppressWarnings("unchecked")
+ public T setText(String text) {
this.text = text;
- return this;
+ return (T) this;
}
//
*/
@Override
public boolean equals(Object object) {
- if (!(object instanceof Reply)) {
+ if (!(object instanceof Reply<?>)) {
return false;
}
- Reply reply = (Reply) object;
+ Reply<?> reply = (Reply<?>) object;
return reply.id.equals(id);
}
*/
@Override
public String toString() {
- return getClass().getName() + "[id=" + id + ",sone=" + sone + ",post=" + post + ",time=" + time + ",text=" + text + "]";
+ return getClass().getName() + "[id=" + id + ",sone=" + sone + ",time=" + time + ",text=" + text + "]";
}
}
}
};
+ /** Comparator that sorts Sones by number of images (descending). */
+ public static final Comparator<Sone> IMAGE_COUNT_COMPARATOR = new Comparator<Sone>() {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int compare(Sone leftSone, Sone rightSone) {
+ return rightSone.getAllImages().size() - leftSone.getAllImages().size();
+ }
+ };
+
/** Filter to remove Sones that have not been downloaded. */
public static final Filter<Sone> EMPTY_SONE_FILTER = new Filter<Sone>() {
private final Set<Post> posts = Collections.synchronizedSet(new HashSet<Post>());
/** All replies. */
- private final Set<Reply> replies = Collections.synchronizedSet(new HashSet<Reply>());
+ private final Set<PostReply> replies = Collections.synchronizedSet(new HashSet<PostReply>());
/** The IDs of all liked posts. */
private final Set<String> likedPostIds = Collections.synchronizedSet(new HashSet<String>());
*
* @return All replies this Sone made
*/
- public synchronized Set<Reply> getReplies() {
+ public synchronized Set<PostReply> getReplies() {
return Collections.unmodifiableSet(replies);
}
* The new (and only) replies of this Sone
* @return This Sone (for method chaining)
*/
- public synchronized Sone setReplies(Collection<Reply> replies) {
+ public synchronized Sone setReplies(Collection<PostReply> replies) {
this.replies.clear();
this.replies.addAll(replies);
return this;
* @param reply
* The reply to add
*/
- public synchronized void addReply(Reply reply) {
+ public synchronized void addReply(PostReply reply) {
if (reply.getSone().equals(this)) {
replies.add(reply);
}
* @param reply
* The reply to remove
*/
- public synchronized void removeReply(Reply reply) {
+ public synchronized void removeReply(PostReply reply) {
if (reply.getSone().equals(this)) {
replies.remove(reply);
}
}
/**
+ * Returns a flattened list of all albums of this Sone. The resulting list
+ * contains parent albums before child albums so that the resulting list can
+ * be parsed in a single pass.
+ *
+ * @return The flattened albums
+ */
+ public List<Album> getAllAlbums() {
+ List<Album> flatAlbums = new ArrayList<Album>();
+ flatAlbums.addAll(albums);
+ int lastAlbumIndex = 0;
+ while (lastAlbumIndex < flatAlbums.size()) {
+ int previousAlbumCount = flatAlbums.size();
+ for (Album album : new ArrayList<Album>(flatAlbums.subList(lastAlbumIndex, flatAlbums.size()))) {
+ flatAlbums.addAll(album.getAlbums());
+ }
+ lastAlbumIndex = previousAlbumCount;
+ }
+ return flatAlbums;
+ }
+
+ /**
+ * Returns all images of a Sone. Images of a album are inserted into this
+ * list before images of all child albums.
+ *
+ * @return The list of all images
+ */
+ public List<Image> getAllImages() {
+ List<Image> allImages = new ArrayList<Image>();
+ for (Album album : getAllAlbums()) {
+ allImages.addAll(album.getImages());
+ }
+ return allImages;
+ }
+
+ /**
* Adds an album to this Sone.
*
* @param album
}
fingerprint.append(")");
- List<Reply> replies = new ArrayList<Reply>(getReplies());
+ List<PostReply> replies = new ArrayList<PostReply>(getReplies());
Collections.sort(replies, Reply.TIME_COMPARATOR);
fingerprint.append("Replies(");
- for (Reply reply : replies) {
+ for (PostReply reply : replies) {
fingerprint.append("Reply(").append(reply.getId()).append(')');
}
fingerprint.append(')');
}
//
- // STATIC METHODS
- //
-
- /**
- * Flattens the given top-level albums so that the resulting list contains
- * parent albums before child albums and the resulting list can be parsed in
- * a single pass.
- *
- * @param albums
- * The albums to flatten
- * @return The flattened albums
- */
- public static List<Album> flattenAlbums(Collection<? extends Album> albums) {
- List<Album> flatAlbums = new ArrayList<Album>();
- flatAlbums.addAll(albums);
- int lastAlbumIndex = 0;
- while (lastAlbumIndex < flatAlbums.size()) {
- int previousAlbumCount = flatAlbums.size();
- for (Album album : new ArrayList<Album>(flatAlbums.subList(lastAlbumIndex, flatAlbums.size()))) {
- flatAlbums.addAll(album.getAlbums());
- }
- lastAlbumIndex = previousAlbumCount;
- }
- return flatAlbums;
- }
-
- //
// INTERFACE Comparable<Sone>
//
import net.pterodactylus.sone.core.Core;
import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostReply;
import net.pterodactylus.sone.data.Profile;
import net.pterodactylus.sone.data.Reply;
import net.pterodactylus.sone.data.Sone;
* if there is no reply ID stored under the given parameter
* name, or if the reply ID is invalid
*/
- protected Reply getReply(SimpleFieldSet simpleFieldSet, String parameterName) throws FcpException {
+ protected PostReply getReply(SimpleFieldSet simpleFieldSet, String parameterName) throws FcpException {
try {
String replyId = simpleFieldSet.getString(parameterName);
- Reply reply = core.getReply(replyId, false);
+ PostReply reply = core.getReply(replyId, false);
if (reply == null) {
throw new FcpException("Could not load reply from “" + replyId + "”.");
}
postBuilder.put(encodeLikes(core.getLikes(post), prefix + "Likes."));
if (includeReplies) {
- List<Reply> replies = core.getReplies(post);
+ List<PostReply> replies = core.getReplies(post);
postBuilder.put(encodeReplies(replies, prefix));
}
String postPrefix = prefix + postIndex++;
postBuilder.put(encodePost(post, postPrefix + ".", includeReplies));
if (includeReplies) {
- postBuilder.put(encodeReplies(Filters.filteredList(core.getReplies(post), Reply.FUTURE_REPLIES_FILTER), postPrefix + "."));
+ postBuilder.put(encodeReplies(Filters.filteredList(core.getReplies(post), Reply.FUTURE_REPLY_FILTER), postPrefix + "."));
}
}
* {@code null})
* @return The simple field set containing the replies
*/
- protected SimpleFieldSet encodeReplies(Collection<? extends Reply> replies, String prefix) {
+ protected SimpleFieldSet encodeReplies(Collection<? extends PostReply> replies, String prefix) {
SimpleFieldSetBuilder replyBuilder = new SimpleFieldSetBuilder();
int replyIndex = 0;
replyBuilder.put(prefix + "Replies.Count", replies.size());
- for (Reply reply : replies) {
+ for (PostReply reply : replies) {
String replyPrefix = prefix + "Replies." + replyIndex++ + ".";
replyBuilder.put(replyPrefix + "ID", reply.getId());
replyBuilder.put(replyPrefix + "Sone", reply.getSone().getId());
import net.pterodactylus.sone.core.Core;
import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostReply;
import net.pterodactylus.sone.data.Reply;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.freenet.SimpleFieldSetBuilder;
Sone sone = getSone(parameters, "Sone", true);
Post post = getPost(parameters, "Post");
String text = getString(parameters, "Text");
- Reply reply = getCore().createReply(sone, post, text);
+ PostReply reply = getCore().createReply(sone, post, text);
return new Response("ReplyCreated", new SimpleFieldSetBuilder().put("Reply", reply.getId()).get());
}
package net.pterodactylus.sone.fcp;
import net.pterodactylus.sone.core.Core;
-import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.PostReply;
import net.pterodactylus.sone.freenet.SimpleFieldSetBuilder;
import net.pterodactylus.sone.freenet.fcp.FcpException;
import freenet.support.SimpleFieldSet;
import freenet.support.api.Bucket;
/**
- * FCP command that deletes a {@link Reply}.
+ * FCP command that deletes a {@link PostReply}.
*
- * @see Core#deleteReply(Reply)
+ * @see Core#deleteReply(PostReply)
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
public class DeleteReplyCommand extends AbstractSoneCommand {
*/
@Override
public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException {
- Reply reply = getReply(parameters, "Reply");
+ PostReply reply = getReply(parameters, "Reply");
if (!getCore().isLocalSone(reply.getSone())) {
return new ErrorResponse(401, "Not allowed.");
}
package net.pterodactylus.sone.fcp;
import net.pterodactylus.sone.core.Core;
-import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.PostReply;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.freenet.SimpleFieldSetBuilder;
import net.pterodactylus.sone.freenet.fcp.FcpException;
*/
@Override
public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException {
- Reply reply = getReply(parameters, "Reply");
+ PostReply reply = getReply(parameters, "Reply");
Sone sone = getSone(parameters, "Sone", true);
sone.addLikedReplyId(reply.getId());
return new Response("ReplyLiked", new SimpleFieldSetBuilder().put("LikeCount", getCore().getLikes(reply).size()).get());
*/
public void stop() {
pluginConnector.removeConnectorListener(WOT_PLUGIN_NAME, PLUGIN_CONNECTION_IDENTIFIER, this);
+ synchronized (reply) {
+ reply.notifyAll();
+ }
}
/**
}
/** The version. */
- public static final Version VERSION = new Version(0, 7, 1);
+ public static final Version VERSION = new Version(0, 7, 2);
/** The logger. */
private static final Logger logger = Logging.getLogger(SonePlugin.class);
import java.util.List;
import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostReply;
import net.pterodactylus.sone.data.Reply;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.freenet.wot.OwnIdentity;
filteredNotifications.add(filteredNotification);
}
} else if (notification.getId().equals("new-reply-notification")) {
- ListNotification<Reply> filteredNotification = filterNewReplyNotification((ListNotification<Reply>) notification, currentSone);
+ ListNotification<PostReply> filteredNotification = filterNewReplyNotification((ListNotification<PostReply>) notification, currentSone);
if (filteredNotification != null) {
filteredNotifications.add(filteredNotification);
}
* @return The filtered new-reply notification, or {@code null} if the
* notification should be removed
*/
- public static ListNotification<Reply> filterNewReplyNotification(ListNotification<Reply> newReplyNotification, Sone currentSone) {
+ public static ListNotification<PostReply> filterNewReplyNotification(ListNotification<PostReply> newReplyNotification, Sone currentSone) {
if (currentSone == null) {
return null;
}
- List<Reply> newReplies = new ArrayList<Reply>();
- for (Reply reply : newReplyNotification.getElements()) {
+ List<PostReply> newReplies = new ArrayList<PostReply>();
+ for (PostReply reply : newReplyNotification.getElements()) {
if (isReplyVisible(currentSone, reply)) {
newReplies.add(reply);
}
if (newReplies.size() == newReplyNotification.getElements().size()) {
return newReplyNotification;
}
- ListNotification<Reply> filteredNotification = new ListNotification<Reply>(newReplyNotification);
+ ListNotification<PostReply> filteredNotification = new ListNotification<PostReply>(newReplyNotification);
filteredNotification.setElements(newReplies);
filteredNotification.setLastUpdateTime(newReplyNotification.getLastUpdatedTime());
return filteredNotification;
* @return {@code true} if the reply is considered visible, {@code false}
* otherwise
*/
- public static boolean isReplyVisible(Sone sone, Reply reply) {
+ public static boolean isReplyVisible(Sone sone, PostReply reply) {
Validation.begin().isNotNull("Reply", reply).check();
Post post = reply.getPost();
if (post == null) {
double scale = Math.max(maxWidth / (double) imageWidth, maxHeight / (double) imageHeight);
linkTemplateContext.set("width", (int) (imageWidth * scale + 0.5));
linkTemplateContext.set("height", (int) (imageHeight * scale + 0.5));
- if (scale >= 1) {
- linkTemplateContext.set("left", String.format("%dpx", (int) ((imageWidth * scale) - maxWidth) / 2));
- linkTemplateContext.set("top", String.format("%dpx", (int) ((imageHeight * scale) - maxHeight) / 2));
- } else {
- linkTemplateContext.set("left", String.format("%dpx", (int) (maxWidth - (imageWidth * scale)) / 2));
- linkTemplateContext.set("top", String.format("%dpx", (int) (maxHeight - (imageHeight * scale)) / 2));
- }
+ linkTemplateContext.set("left", String.format("%dpx", (int) (maxWidth - (imageWidth * scale)) / 2));
+ linkTemplateContext.set("top", String.format("%dpx", (int) (maxHeight - (imageHeight * scale)) / 2));
} else {
double scale = 1;
if ((imageWidth > maxWidth) || (imageHeight > maxHeight)) {
import net.pterodactylus.sone.text.SoneTextParser;
import net.pterodactylus.sone.text.SoneTextParserContext;
import net.pterodactylus.sone.web.page.FreenetRequest;
+import net.pterodactylus.util.number.Numbers;
import net.pterodactylus.util.template.Filter;
import net.pterodactylus.util.template.Template;
import net.pterodactylus.util.template.TemplateContext;
@Override
public Object format(TemplateContext templateContext, Object data, Map<String, String> parameters) {
String text = String.valueOf(data);
- int length = -1;
- try {
- length = Integer.parseInt(parameters.get("length"));
- } catch (NumberFormatException nfe1) {
- /* ignore. */
- }
- if ((length == -1) && (parameters.get("length") != null)) {
- try {
- length = Integer.parseInt(String.valueOf(templateContext.get(parameters.get("length"))));
- } catch (NumberFormatException nfe1) {
- /* ignore. */
- }
- }
+ int length = Numbers.safeParseInteger(parameters.get("length"), Numbers.safeParseInteger(templateContext.get(parameters.get("length")), -1));
+ int cutOffLength = Numbers.safeParseInteger(parameters.get("cut-off-length"), Numbers.safeParseInteger(templateContext.get(parameters.get("cut-off-length")), length));
String soneKey = parameters.get("sone");
if (soneKey == null) {
soneKey = "sone";
try {
Iterable<Part> parts = soneTextParser.parse(context, new StringReader(text));
if (length > -1) {
+ int allPartsLength = 0;
List<Part> shortenedParts = new ArrayList<Part>();
for (Part part : parts) {
if (part instanceof PlainTextPart) {
String longText = ((PlainTextPart) part).getText();
- if (length >= longText.length()) {
- shortenedParts.add(part);
- } else {
- shortenedParts.add(new PlainTextPart(longText.substring(0, length) + "…"));
+ if (allPartsLength < cutOffLength) {
+ if ((allPartsLength + longText.length()) > cutOffLength) {
+ shortenedParts.add(new PlainTextPart(longText.substring(0, cutOffLength - allPartsLength) + "…"));
+ } else {
+ shortenedParts.add(part);
+ }
}
- length -= longText.length();
+ allPartsLength += longText.length();
} else if (part instanceof LinkPart) {
- shortenedParts.add(part);
- length -= ((LinkPart) part).getText().length();
+ if (allPartsLength < cutOffLength) {
+ shortenedParts.add(part);
+ }
+ allPartsLength += ((LinkPart) part).getText().length();
} else {
- shortenedParts.add(part);
- }
- if (length <= 0) {
- break;
+ if (allPartsLength < cutOffLength) {
+ shortenedParts.add(part);
+ }
}
}
- parts = shortenedParts;
+ if (allPartsLength >= length) {
+ parts = shortenedParts;
+ }
}
render(parsedTextWriter, parts);
} catch (IOException ioe1) {
* The part to render
*/
private void render(Writer writer, SonePart sonePart) {
- renderLink(writer, "viewSone.html?sone=" + sonePart.getSone().getId(), SoneAccessor.getNiceName(sonePart.getSone()), SoneAccessor.getNiceName(sonePart.getSone()), "in-sone");
+ if ((sonePart.getSone() != null) && (sonePart.getSone().getName() != null)) {
+ renderLink(writer, "viewSone.html?sone=" + sonePart.getSone().getId(), SoneAccessor.getNiceName(sonePart.getSone()), SoneAccessor.getNiceName(sonePart.getSone()), "in-sone");
+ } else {
+ renderLink(writer, "/WebOfTrust/ShowIdentity?id=" + sonePart.getSone().getId(), sonePart.getSone().getId(), sonePart.getSone().getId(), "in-sone");
+ }
}
/**
public Object get(TemplateContext templateContext, Object object, String member) {
Post post = (Post) object;
if ("replies".equals(member)) {
- return Filters.filteredList(core.getReplies(post), Reply.FUTURE_REPLIES_FILTER);
+ return Filters.filteredList(core.getReplies(post), Reply.FUTURE_REPLY_FILTER);
} else if (member.equals("likes")) {
return core.getLikes(post);
} else if (member.equals("liked")) {
package net.pterodactylus.sone.template;
import net.pterodactylus.sone.core.Core;
+import net.pterodactylus.sone.data.PostReply;
import net.pterodactylus.sone.data.Reply;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.util.template.Accessor;
*/
@Override
public Object get(TemplateContext templateContext, Object object, String member) {
- Reply reply = (Reply) object;
+ PostReply reply = (PostReply) object;
if ("likes".equals(member)) {
return core.getLikes(reply);
} else if (member.equals("liked")) {
import java.util.Set;
import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.PostReply;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.util.template.Filter;
import net.pterodactylus.util.template.TemplateContext;
@Override
public Object format(TemplateContext templateContext, Object data, Map<String, String> parameters) {
@SuppressWarnings("unchecked")
- List<Reply> allReplies = (List<Reply>) data;
+ List<PostReply> allReplies = (List<PostReply>) data;
Map<Post, Set<Sone>> postSones = new HashMap<Post, Set<Sone>>();
- Map<Post, Set<Reply>> postReplies = new HashMap<Post, Set<Reply>>();
- for (Reply reply : allReplies) {
+ Map<Post, Set<PostReply>> postReplies = new HashMap<Post, Set<PostReply>>();
+ for (PostReply reply : allReplies) {
Post post = reply.getPost();
Set<Sone> sones = postSones.get(post);
if (sones == null) {
postSones.put(post, sones);
}
sones.add(reply.getSone());
- Set<Reply> replies = postReplies.get(post);
+ Set<PostReply> replies = postReplies.get(post);
if (replies == null) {
- replies = new HashSet<Reply>();
+ replies = new HashSet<PostReply>();
postReplies.put(post, replies);
}
replies.add(reply);
if (line.length() >= (7 + 43)) {
String soneId = line.substring(7, 50);
Sone sone = soneProvider.getSone(soneId, false);
- if ((sone != null) && (sone.getName() != null)) {
- parts.add(new SonePart(sone));
- } else {
- parts.add(new PlainTextPart(line.substring(0, 50)));
+ if (sone == null) {
+ /*
+ * don’t use create=true above, we don’t want the
+ * empty shell.
+ */
+ sone = new Sone(soneId);
}
+ parts.add(new SonePart(sone));
line = line.substring(50);
} else {
parts.add(new PlainTextPart(line));
package net.pterodactylus.sone.web;
-import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.PostReply;
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);
String replyId = request.getHttpRequest().getPartAsStringFailsafe("reply", 36);
- Reply reply = webInterface.getCore().getReply(replyId);
+ PostReply reply = webInterface.getCore().getReply(replyId);
String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
if (request.getMethod() == Method.POST) {
if (!webInterface.getCore().isLocalSone(reply.getSone())) {
} else {
Collections.sort(knownSones, Sone.POST_COUNT_COMPARATOR);
}
+ } else if ("images".equals(sortField)) {
+ if ("asc".equals(sortOrder)) {
+ Collections.sort(knownSones, new ReverseComparator<Sone>(Sone.IMAGE_COUNT_COMPARATOR));
+ } else {
+ Collections.sort(knownSones, Sone.IMAGE_COUNT_COMPARATOR);
+ }
} else {
if ("desc".equals(sortOrder)) {
Collections.sort(knownSones, new ReverseComparator<Sone>(Sone.NICE_NAME_COMPARATOR));
import java.util.StringTokenizer;
import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostReply;
import net.pterodactylus.sone.data.Reply;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.web.page.FreenetRequest;
}
webInterface.getCore().markPostKnown(post);
} else if (type.equals("reply")) {
- Reply reply = webInterface.getCore().getReply(id, false);
+ PostReply reply = webInterface.getCore().getReply(id, false);
if (reply == null) {
continue;
}
} else {
preferences.setCharactersPerPost(charactersPerPost);
}
+ Integer postCutOffLength = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("post-cut-off-length", 10), null);
+ if (!preferences.validatePostCutOffLength(postCutOffLength)) {
+ fieldErrors.add("post-cut-off-length");
+ } else {
+ preferences.setPostCutOffLength(postCutOffLength);
+ }
boolean requireFullAccess = request.getHttpRequest().isPartSet("require-full-access");
preferences.setRequireFullAccess(requireFullAccess);
Integer positiveTrust = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("positive-trust", 3));
templateContext.set("insertion-delay", preferences.getInsertionDelay());
templateContext.set("posts-per-page", preferences.getPostsPerPage());
templateContext.set("characters-per-post", preferences.getCharactersPerPost());
+ templateContext.set("post-cut-off-length", preferences.getPostCutOffLength());
templateContext.set("require-full-access", preferences.isRequireFullAccess());
templateContext.set("positive-trust", preferences.getPositiveTrust());
templateContext.set("negative-trust", preferences.getNegativeTrust());
import java.util.logging.Logger;
import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostReply;
import net.pterodactylus.sone.data.Profile;
import net.pterodactylus.sone.data.Profile.Field;
import net.pterodactylus.sone.data.Reply;
if (post.getRecipient() != null) {
postString.append(' ').append(SoneStringGenerator.NAME_GENERATOR.generateString(post.getRecipient()));
}
- for (Reply reply : Filters.filteredList(webInterface.getCore().getReplies(post), Reply.FUTURE_REPLIES_FILTER)) {
+ for (PostReply reply : Filters.filteredList(webInterface.getCore().getReplies(post), Reply.FUTURE_REPLY_FILTER)) {
postString.append(' ').append(SoneStringGenerator.NAME_GENERATOR.generateString(reply.getSone()));
postString.append(' ').append(reply.getText());
}
import java.util.Set;
import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.PostReply;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.template.SoneAccessor;
import net.pterodactylus.sone.web.page.FreenetRequest;
Pagination<Post> postPagination = new Pagination<Post>(sonePosts, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(Numbers.safeParseInteger(request.getHttpRequest().getParam("postPage"), 0));
templateContext.set("postPagination", postPagination);
templateContext.set("posts", postPagination.getItems());
- Set<Reply> replies = sone.getReplies();
- final Map<Post, List<Reply>> repliedPosts = new HashMap<Post, List<Reply>>();
- for (Reply reply : replies) {
+ Set<PostReply> replies = sone.getReplies();
+ final Map<Post, List<PostReply>> repliedPosts = new HashMap<Post, List<PostReply>>();
+ for (PostReply reply : replies) {
Post post = reply.getPost();
if (repliedPosts.containsKey(post) || sone.equals(post.getSone()) || (sone.equals(post.getRecipient()))) {
continue;
import net.pterodactylus.sone.data.Album;
import net.pterodactylus.sone.data.Image;
import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostReply;
import net.pterodactylus.sone.data.Reply;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.freenet.L10nFilter;
private final ListNotification<Post> newPostNotification;
/** The “new reply” notification. */
- private final ListNotification<Reply> newReplyNotification;
+ private final ListNotification<PostReply> newReplyNotification;
/** The invisible “local post” notification. */
private final ListNotification<Post> localPostNotification;
/** The invisible “local reply” notification. */
- private final ListNotification<Reply> localReplyNotification;
+ private final ListNotification<PostReply> localReplyNotification;
/** The “you have been mentioned” notification. */
private final ListNotification<Post> mentionNotification;
localPostNotification = new ListNotification<Post>("local-post-notification", "posts", localPostNotificationTemplate, false);
Template newReplyNotificationTemplate = TemplateParser.parse(createReader("/templates/notify/newReplyNotification.html"));
- newReplyNotification = new ListNotification<Reply>("new-reply-notification", "replies", newReplyNotificationTemplate, false);
+ newReplyNotification = new ListNotification<PostReply>("new-reply-notification", "replies", newReplyNotificationTemplate, false);
Template localReplyNotificationTemplate = TemplateParser.parse(createReader("/templates/notify/newReplyNotification.html"));
- localReplyNotification = new ListNotification<Reply>("local-reply-notification", "replies", localReplyNotificationTemplate, false);
+ localReplyNotification = new ListNotification<PostReply>("local-reply-notification", "replies", localReplyNotificationTemplate, false);
Template mentionNotificationTemplate = TemplateParser.parse(createReader("/templates/notify/mentionNotification.html"));
mentionNotification = new ListNotification<Post>("mention-notification", "posts", mentionNotificationTemplate, false);
*
* @return The new replies
*/
- public Set<Reply> getNewReplies() {
- return new SetBuilder<Reply>().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).get();
+ public Set<PostReply> getNewReplies() {
+ return new SetBuilder<PostReply>().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).get();
}
/**
* {@inheritDoc}
*/
@Override
- public void newReplyFound(Reply reply) {
+ public void newReplyFound(PostReply reply) {
boolean isLocal = getCore().isLocalSone(reply.getSone());
if (isLocal) {
localReplyNotification.add(reply);
* {@inheritDoc}
*/
@Override
- public void markReplyKnown(Reply reply) {
+ public void markReplyKnown(PostReply reply) {
newReplyNotification.remove(reply);
localReplyNotification.remove(reply);
mentionNotification.remove(reply.getPost());
* {@inheritDoc}
*/
@Override
- public void replyRemoved(Reply reply) {
+ public void replyRemoved(PostReply reply) {
newReplyNotification.remove(reply);
localReplyNotification.remove(reply);
}
package net.pterodactylus.sone.web.ajax;
import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.PostReply;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.text.TextFilter;
import net.pterodactylus.sone.web.WebInterface;
return createErrorJsonObject("invalid-post-id");
}
text = TextFilter.filter(request.getHttpRequest().getHeader("host"), text);
- Reply reply = webInterface.getCore().createReply(sender, post, text);
+ PostReply reply = webInterface.getCore().createReply(sender, post, text);
return createSuccessJsonObject().put("reply", reply.getId()).put("sone", sender.getId());
}
package net.pterodactylus.sone.web.ajax;
-import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.PostReply;
import net.pterodactylus.sone.web.WebInterface;
import net.pterodactylus.sone.web.page.FreenetRequest;
import net.pterodactylus.util.json.JsonObject;
@Override
protected JsonObject createJsonObject(FreenetRequest request) {
String replyId = request.getHttpRequest().getParam("reply");
- Reply reply = webInterface.getCore().getReply(replyId);
+ PostReply reply = webInterface.getCore().getReply(replyId);
if (reply == null) {
return createErrorJsonObject("invalid-reply-id");
}
import java.util.Set;
import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.PostReply;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.template.SoneAccessor;
import net.pterodactylus.sone.web.WebInterface;
Set<Sone> sones = webInterface.getCore().getLikes(post);
return createSuccessJsonObject().put("likes", sones.size()).put("sones", getSones(sones));
} else if ("reply".equals(type)) {
- Reply reply = webInterface.getCore().getReply(id);
+ PostReply reply = webInterface.getCore().getReply(id);
Set<Sone> sones = webInterface.getCore().getLikes(reply);
return createSuccessJsonObject().put("likes", sones.size()).put("sones", getSones(sones));
}
import java.io.StringWriter;
import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.PostReply;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.main.SonePlugin;
import net.pterodactylus.sone.notify.ListNotification;
if ("new-post-notification".equals(notificationId)) {
notification = ListNotificationFilters.filterNewPostNotification((ListNotification<Post>) notification, currentSone, false);
} else if ("new-reply-notification".equals(notificationId)) {
- notification = ListNotificationFilters.filterNewReplyNotification((ListNotification<Reply>) notification, currentSone);
+ notification = ListNotificationFilters.filterNewReplyNotification((ListNotification<PostReply>) notification, currentSone);
} else if ("mention-notification".equals(notificationId)) {
notification = ListNotificationFilters.filterNewPostNotification((ListNotification<Post>) notification, currentSone, false);
}
import java.io.StringWriter;
-import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.PostReply;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.web.WebInterface;
import net.pterodactylus.sone.web.page.FreenetRequest;
@Override
protected JsonObject createJsonObject(FreenetRequest request) {
String replyId = request.getHttpRequest().getParam("reply");
- Reply reply = webInterface.getCore().getReply(replyId);
+ PostReply reply = webInterface.getCore().getReply(replyId);
if ((reply == null) || (reply.getSone() == null)) {
return createErrorJsonObject("invalid-reply-id");
}
* The currently logged in Sone (to store in the template)
* @return The JSON representation of the reply
*/
- private JsonObject createJsonReply(FreenetRequest request, Reply reply, Sone currentSone) {
+ private JsonObject createJsonReply(FreenetRequest request, PostReply reply, Sone currentSone) {
JsonObject jsonReply = new JsonObject();
jsonReply.put("id", reply.getId());
jsonReply.put("postId", reply.getPost().getId());
import java.util.Set;
import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.PostReply;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.notify.ListNotificationFilters;
import net.pterodactylus.sone.template.SoneAccessor;
jsonPosts.add(jsonPost);
}
/* load new replies. */
- Set<Reply> newReplies = webInterface.getNewReplies();
+ Set<PostReply> newReplies = webInterface.getNewReplies();
if (currentSone != null) {
- newReplies = Filters.filteredSet(newReplies, new Filter<Reply>() {
+ newReplies = Filters.filteredSet(newReplies, new Filter<PostReply>() {
@Override
- public boolean filterObject(Reply reply) {
+ public boolean filterObject(PostReply reply) {
return ListNotificationFilters.isReplyVisible(currentSone, reply);
}
});
}
/* remove replies to unknown posts. */
- newReplies = Filters.filteredSet(newReplies, new Filter<Reply>() {
+ newReplies = Filters.filteredSet(newReplies, new Filter<PostReply>() {
@Override
- public boolean filterObject(Reply reply) {
+ public boolean filterObject(PostReply reply) {
return (reply.getPost() != null) && (reply.getPost().getSone() != null);
}
});
JsonArray jsonReplies = new JsonArray();
- for (Reply reply : newReplies) {
+ for (PostReply reply : newReplies) {
JsonObject jsonReply = new JsonObject();
jsonReply.put("id", reply.getId());
jsonReply.put("sone", reply.getSone().getId());
import java.util.Date;
import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.PostReply;
import net.pterodactylus.sone.web.WebInterface;
import net.pterodactylus.sone.web.page.FreenetRequest;
import net.pterodactylus.util.json.JsonObject;
if (allIds.length() > 0) {
String[] ids = allIds.split(",");
for (String id : ids) {
- Reply reply = webInterface.getCore().getReply(id, false);
+ PostReply reply = webInterface.getCore().getReply(id, false);
if (reply == null) {
continue;
}
import net.pterodactylus.sone.core.Core;
import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostReply;
import net.pterodactylus.sone.data.Reply;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.web.WebInterface;
}
core.markPostKnown(post);
} else if (type.equals("reply")) {
- Reply reply = core.getReply(id, false);
+ PostReply reply = core.getReply(id, false);
if (reply == null) {
continue;
}
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.Options.Option.CharactersPerPost.Description=The number of characters to display from a post before cutting it off and showing a link to expand it (-1 to disable).
+Page.Options.Option.CharactersPerPost.Description=The number of characters to display from a post before cutting it off and showing a link to expand it (-1 to disable). The actual length of the snippet is determined by the option below.
+Page.Options.Option.PostCutOffLength.Description=The number of characters that are displayed if a post is deemed to long (see option above).
Page.Options.Option.RequireFullAccess.Description=Whether to deny access to Sone to any host that has not been granted full access.
Page.Options.Section.TrustOptions.Title=Trust Settings
Page.Options.Option.PositiveTrust.Description=The amount of positive trust you want to assign to other Sones by clicking the checkmark below a post or reply.
Page.KnownSones.Title=Known Sones - Sone
Page.KnownSones.Page.Title=Known Sones
Page.KnownSones.Text.NoKnownSones=There are currently no known Sones.
+Page.KnownSones.Label.Sort=Sort:
+Page.KnownSones.Label.FollowedSones=Followed Sones:
+Page.KnownSones.Sort.Field.Name=Name
+Page.KnownSones.Sort.Field.LastActivity=Last activity
+Page.KnownSones.Sort.Field.Posts=Number of posts
+Page.KnownSones.Sort.Field.Images=Number of images
+Page.KnownSones.Sort.Order.Ascending=Ascending
+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
View.Sone.Text.UnknownDate=unknown
View.Sone.Stats.Posts={0,number} {0,choice,0#posts|1#post|1<posts}
View.Sone.Stats.Replies={0,number} {0,choice,0#replies|1#reply|1<replies}
+View.Sone.Stats.Images={0,number} {0,choice,0#images|1#image|1<images}
View.Sone.Button.UnlockSone=unlock
View.Sone.Button.UnlockSone.Tooltip=Allow this Sone to be inserted now
View.Sone.Button.LockSone=lock
WebInterface.DefaultText.EditImage.Title=Image title
WebInterface.DefaultText.EditImage.Description=Image description
WebInterface.DefaultText.Option.PostsPerPage=Number of posts to show on a page
-WebInterface.DefaultText.Option.CharactersPerPost=Number of characters per post after which to cut the post off
+WebInterface.DefaultText.Option.CharactersPerPost=Number of characters a post must have to be shortened
+WebInterface.DefaultText.Option.PostCutOffLength=Number of characters for the snippet of the shortened post
WebInterface.DefaultText.Option.PositiveTrust=The positive trust to assign
WebInterface.DefaultText.Option.NegativeTrust=The negative trust to assign
WebInterface.DefaultText.Option.TrustComment=The comment to set in the web of trust
#sone .image .image-description, #sone .album .album-description {
text-align: left;
- width: 195px;
+ width: 98%;
word-wrap: break-word;
max-height: 5em;
overflow: auto;
</a>
</div>
<div class="show-data">
- <div class="album-title"><% album.title|html></div>
+ <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>
<%if album.sone.local>
<div class="inner-menu">
<div>
<a class="author" href="viewSone.html?sone=<%sone.id|html>"><%sone.niceName|html></a>
- (<%= View.Sone.Stats.Posts|l10n 0=sone.posts.size>, <%= View.Sone.Stats.Replies|l10n 0=sone.replies.size>)
+ (<%= View.Sone.Stats.Posts|l10n 0=sone.posts.size>, <%= View.Sone.Stats.Replies|l10n 0=sone.replies.size><%if ! sone.allImages.size|match value=0>, <%= View.Sone.Stats.Images|l10n 0=sone.allImages.size><%/if>)
</div>
<div><a href="/WebOfTrust/ShowIdentity?id=<%sone.id|html>">» <% =View.Post.WebOfTrustLink|l10n|html></a></div>
<%foreach sone.albums album>
<%/if>
<% post.text|html|store key=originalText text=true>
<% post.text|parse sone=post.sone|store key=parsedText text=true>
- <% post.text|parse sone=post.sone length=core.preferences.charactersPerPost|store key=shortText text=true>
+ <% post.text|parse sone=post.sone length=core.preferences.charactersPerPost cut-off-length=core.preferences.postCutOffLength|store key=shortText text=true>
<div class="post-text raw-text<%if !raw> hidden<%/if>"><% originalText></div>
<div class="post-text text<%if raw> hidden<%/if><%if !shortText|match key=parsedText> hidden<%/if>"><% parsedText></div>
<div class="post-text short-text<%if raw> hidden<%/if><%if shortText|match key=parsedText> hidden<%/if>"><% shortText></div>
<div class="author profile-link"><a href="viewSone.html?sone=<% reply.sone.id|html>"><% reply.sone.niceName|html></a></div>
<% reply.text|html|store key=originalText text=true>
<% reply.text|parse sone=reply.sone|store key=parsedText text=true>
- <% reply.text|parse sone=reply.sone length=core.preferences.charactersPerPost|store key=shortText text=true>
+ <% reply.text|parse sone=reply.sone length=core.preferences.charactersPerPost cut-off-length=core.preferences.postCutOffLength|store key=shortText text=true>
<div class="reply-text raw-text<%if !raw> hidden<%/if>"><% originalText></div>
<div class="reply-text text<%if raw> hidden<%/if><%if !shortText|match key=parsedText> hidden<%/if>"><% parsedText></div>
<div class="reply-text short-text<%if raw> hidden<%/if><%if shortText|match key=parsedText> hidden<%/if>"><% shortText></div>
<div class="last-update"><%= View.Sone.Label.LastUpdate|l10n|html> <span class="time" title="<% sone.time|unknown|date format="MMM d, yyyy, HH:mm:ss">"><%sone.lastUpdatedText|html></span></div>
<div>
<div class="profile-link"><a href="viewSone.html?sone=<% sone.id|html>" title="<% sone.requestUri|html>"><% sone.niceName|html></a></div>
- <div class="sone-stats">(<%= View.Sone.Stats.Posts|l10n 0=sone.posts.size>, <%= View.Sone.Stats.Replies|l10n 0=sone.replies.size>)</div>
+ <div class="sone-stats">(<%= View.Sone.Stats.Posts|l10n 0=sone.posts.size>, <%= View.Sone.Stats.Replies|l10n 0=sone.replies.size><%if ! sone.allImages.size|match value=0>, <%= View.Sone.Stats.Images|l10n 0=sone.allImages.size><%/if>)</div>
</div>
<div class="short-request-uri"><% sone.requestUri|substring start=4 length=43|html></div>
<div class="hidden"><% sone.blacklisted></div>
<%include include/head.html>
<div class="page-id hidden">known-sones</div>
-
+
<script language="javascript">
$(document).ready(function() {
$("select[name=sort]").change(function() {
value = $(this).val();
- if ((value == "activity") || (value == "posts")) {
+ if ((value == "activity") || (value == "posts") || (value == "images")) {
$("select[name=order]").val("desc");
} else if (value == "name") {
$("select[name=order]").val("asc");
- }
+ }
});
$("#sort-options select").change(function() {
this.form.submit();
});
});
-
+
</script>
<h1><%= Page.KnownSones.Page.Title|l10n|html></h1>
-
+
<div id="sort-options">
<form action="knownSones.html" method="get">
<div>
- Sort:
+ <%= Page.KnownSones.Label.Sort|l10n|html>
<select name="sort">
- <option value="name"<%if sort|match value="name"> selected="selected"<%/if>>Name</option>
- <option value="activity"<%if sort|match value="activity"> selected="selected"<%/if>>Last activity</option>
- <option value="posts"<%if sort|match value="posts"> selected="selected"<%/if>>Number of posts</option>
+ <option value="name"<%if sort|match value="name"> selected="selected"<%/if>><%= Page.KnownSones.Sort.Field.Name|l10n|html></option>
+ <option value="activity"<%if sort|match value="activity"> selected="selected"<%/if>><%= Page.KnownSones.Sort.Field.LastActivity|l10n|html></option>
+ <option value="posts"<%if sort|match value="posts"> selected="selected"<%/if>><%= Page.KnownSones.Sort.Field.Posts|l10n|html></option>
+ <option value="images"<%if sort|match value="images"> selected="selected"<%/if>><%= Page.KnownSones.Sort.Field.Images|l10n|html></option>
</select>
<select name="order">
- <option value="asc"<%if order|match value="asc"> selected="selected"<%/if>>Ascending</option>
- <option value="desc"<%if order|match value="desc"> selected="selected"<%/if>>Descending</option>
+ <option value="asc"<%if order|match value="asc"> selected="selected"<%/if>><%= Page.KnownSones.Sort.Order.Ascending|l10n|html></option>
+ <option value="desc"<%if order|match value="desc"> selected="selected"<%/if>><%= Page.KnownSones.Sort.Order.Descending|l10n|html></option>
</select>
</div>
<%ifnull !currentSone>
<div>
- Followed Sones:
+ <%= Page.KnownSones.Label.FollowedSones|l10n|html>
<select name="followedSones">
<option value="none"></option>
- <option value="show-only"<%if followedSones|match value="show-only"> selected="selected"<%/if>>Show only followed Sones</option>
- <option value="hide"<%if followedSones|match value="hide"> selected="selected"<%/if>>Hide followed Sones</option>
+ <option value="show-only"<%if followedSones|match value="show-only"> selected="selected"<%/if>><%= Page.KnownSones.FollowedSones.ShowOnly|l10n|html></option>
+ <option value="hide"<%if followedSones|match value="hide"> selected="selected"<%/if>><%= Page.KnownSones.FollowedSones.Hide|l10n|html></option>
</select>
</div>
<%/if>
<div>
- <button type="submit">Apply</button>
+ <button type="submit"><%= Page.KnownSones.Button.Apply|l10n|html></button>
</div>
</form>
</div>
getTranslation("WebInterface.DefaultText.Option.CharactersPerPost", function(postsPerPageText) {
registerInputTextareaSwap("#sone #options input[name=characters-per-post]", postsPerPageText, "characters-per-post", true, true);
});
+ getTranslation("WebInterface.DefaultText.Option.PostCutOffLength", function(postCutOffLengthText) {
+ registerInputTextareaSwap("#sone #options input[name=post-cut-off-length]", postCutOffLengthText, "post-cut-off-length", true, true);
+ });
getTranslation("WebInterface.DefaultText.Option.PositiveTrust", function(positiveTrustText) {
registerInputTextareaSwap("#sone #options input[name=positive-trust]", positiveTrustText, "positive-trust", true, true);
});
<%/if>
<p><input type="text" name="characters-per-post" value="<% characters-per-post|html>" /></p>
+ <p><%= Page.Options.Option.PostCutOffLength.Description|l10n|html></p>
+ <%if =post-cut-off-length|in collection=fieldErrors>
+ <p class="warning"><%= Page.Options.Warnings.ValueNotChanged|l10n|html></p>
+ <%/if>
+ <p><input type="text" name="post-cut-off-length" value="<% post-cut-off-length|html>" /></p>
+
<p>
<input type="checkbox" name="require-full-access"<%if require-full-access> checked="checked"<%/if> />
<%= Page.Options.Option.RequireFullAccess.Description|l10n|html></p>