/* * Sone - AbstractSoneCommand.java - Copyright © 2011–2016 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.fcp; import java.util.Collection; import java.util.List; import net.pterodactylus.sone.core.Core; import net.pterodactylus.sone.data.Post; import net.pterodactylus.sone.data.PostReply; import net.pterodactylus.sone.data.Profile; import net.pterodactylus.sone.data.Profile.Field; import net.pterodactylus.sone.data.Reply; import net.pterodactylus.sone.data.Sone; import net.pterodactylus.sone.freenet.SimpleFieldSetBuilder; import net.pterodactylus.sone.freenet.fcp.AbstractCommand; import net.pterodactylus.sone.freenet.fcp.Command; import net.pterodactylus.sone.freenet.fcp.FcpException; import net.pterodactylus.sone.template.SoneAccessor; import com.google.common.base.Optional; import com.google.common.collect.Collections2; import freenet.node.FSParseException; import freenet.support.SimpleFieldSet; /** * Abstract base implementation of a {@link Command} with Sone-related helper * methods. * * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a> */ public abstract class AbstractSoneCommand extends AbstractCommand { /** The Sone core. */ private final Core core; /** Whether this command needs write access. */ private final boolean writeAccess; /** * Creates a new abstract Sone FCP command. * * @param core * The Sone core */ protected AbstractSoneCommand(Core core) { this(core, false); } /** * Creates a new abstract Sone FCP command. * * @param core * The Sone core * @param writeAccess * {@code true} if this command requires write access, * {@code false} otherwise */ protected AbstractSoneCommand(Core core, boolean writeAccess) { this.core = core; this.writeAccess = writeAccess; } // // ACCESSORS // /** * Returns the Sone core. * * @return The Sone core */ protected Core getCore() { return core; } /** * Returns whether this command requires write access. * * @return {@code true} if this command require write access, {@code false} * otherwise */ public boolean requiresWriteAccess() { return writeAccess; } // // PROTECTED METHODS // /** * Encodes text in a way that makes it possible for the text to be stored in * a {@link SimpleFieldSet}. Backslashes, CR, and LF are prepended with a * backslash. * * @param text * The text to encode * @return The encoded text */ protected static String encodeString(String text) { return text.replaceAll("\\\\", "\\\\").replaceAll("\n", "\\\\n").replaceAll("\r", "\\\\r"); } /** * Returns a Sone whose ID is a parameter in the given simple field set. * * @param simpleFieldSet * The simple field set containing the ID of the Sone * @param parameterName * The name under which the Sone ID is stored in the simple field * set * @param localOnly * {@code true} to only return local Sones, {@code false} to * return any Sones * @return The Sone * @throws FcpException * if there is no Sone ID stored under the given parameter name, * or if the Sone ID is invalid */ protected Sone getSone(SimpleFieldSet simpleFieldSet, String parameterName, boolean localOnly) throws FcpException { return getSone(simpleFieldSet, parameterName, localOnly, true).get(); } /** * Returns a Sone whose ID is a parameter in the given simple field set. * * @param simpleFieldSet * The simple field set containing the ID of the Sone * @param parameterName * The name under which the Sone ID is stored in the simple field * set * @param localOnly * {@code true} to only return local Sones, {@code false} to * return any Sones * @param mandatory * {@code true} if a valid Sone ID is required, {@code false} * otherwise * @return The Sone, or {@code null} if {@code mandatory} is {@code false} * and the Sone ID is invalid * @throws FcpException * if there is no Sone ID stored under the given parameter name, * or if {@code mandatory} is {@code true} and the Sone ID is * invalid */ protected Optional<Sone> getSone(SimpleFieldSet simpleFieldSet, String parameterName, boolean localOnly, boolean mandatory) throws FcpException { String soneId = simpleFieldSet.get(parameterName); if (mandatory && (soneId == null)) { throw new FcpException("Could not load Sone ID from “" + parameterName + "”."); } Optional<Sone> sone = core.getSone(soneId); if ((mandatory && !sone.isPresent()) || (mandatory && sone.isPresent() && (localOnly && !sone.get().isLocal()))) { throw new FcpException("Could not load Sone from “" + soneId + "”."); } return sone; } /** * Returns a post whose ID is a parameter in the given simple field set. * * @param simpleFieldSet * The simple field set containing the ID of the post * @param parameterName * The name under which the post ID is stored in the simple field * set * @return The post * @throws FcpException * if there is no post ID stored under the given parameter name, * or if the post ID is invalid */ protected Post getPost(SimpleFieldSet simpleFieldSet, String parameterName) throws FcpException { try { String postId = simpleFieldSet.getString(parameterName); Optional<Post> post = core.getPost(postId); if (!post.isPresent()) { throw new FcpException("Could not load post from “" + postId + "”."); } return post.get(); } catch (FSParseException fspe1) { throw new FcpException("Could not post ID from “" + parameterName + "”.", fspe1); } } /** * Returns a reply whose ID is a parameter in the given simple field set. * * @param simpleFieldSet * The simple field set containing the ID of the reply * @param parameterName * The name under which the reply ID is stored in the simple * field set * @return The reply * @throws FcpException * if there is no reply ID stored under the given parameter * name, or if the reply ID is invalid */ protected PostReply getReply(SimpleFieldSet simpleFieldSet, String parameterName) throws FcpException { try { String replyId = simpleFieldSet.getString(parameterName); Optional<PostReply> reply = core.getPostReply(replyId); if (!reply.isPresent()) { throw new FcpException("Could not load reply from “" + replyId + "”."); } return reply.get(); } catch (FSParseException fspe1) { throw new FcpException("Could not reply ID from “" + parameterName + "”.", fspe1); } } /** * Creates a simple field set from the given Sone, including {@link Profile} * information. * * @param sone * The Sone to encode * @param prefix * The prefix for the field names (may be empty but not {@code * null}) * @param localSone * An optional local Sone that is used for Sone-specific data, * such as if the Sone is followed by the local Sone * @return The simple field set containing the given Sone */ protected static SimpleFieldSet encodeSone(Sone sone, String prefix, Optional<Sone> localSone) { SimpleFieldSetBuilder soneBuilder = new SimpleFieldSetBuilder(); soneBuilder.put(prefix + "Name", sone.getName()); soneBuilder.put(prefix + "NiceName", SoneAccessor.getNiceName(sone)); soneBuilder.put(prefix + "LastUpdated", sone.getTime()); if (localSone.isPresent()) { soneBuilder.put(prefix + "Followed", String.valueOf(localSone.get().hasFriend(sone.getId()))); } Profile profile = sone.getProfile(); soneBuilder.put(prefix + "Field.Count", profile.getFields().size()); int fieldIndex = 0; for (Field field : profile.getFields()) { soneBuilder.put(prefix + "Field." + fieldIndex + ".Name", field.getName()); soneBuilder.put(prefix + "Field." + fieldIndex + ".Value", field.getValue()); ++fieldIndex; } return soneBuilder.get(); } /** * Creates a simple field set from the given collection of Sones. * * @param sones * The Sones to encode * @param prefix * The prefix for the field names (may be empty but not * {@code null}) * @return The simple field set containing the given Sones */ protected static SimpleFieldSet encodeSones(Collection<? extends Sone> sones, String prefix) { SimpleFieldSetBuilder soneBuilder = new SimpleFieldSetBuilder(); int soneIndex = 0; soneBuilder.put(prefix + "Count", sones.size()); for (Sone sone : sones) { String sonePrefix = prefix + soneIndex++ + "."; soneBuilder.put(sonePrefix + "ID", sone.getId()); soneBuilder.put(sonePrefix + "Name", sone.getName()); soneBuilder.put(sonePrefix + "NiceName", SoneAccessor.getNiceName(sone)); soneBuilder.put(sonePrefix + "Time", sone.getTime()); } return soneBuilder.get(); } /** * Creates a simple field set from the given post. * * @param post * The post to encode * @param prefix * The prefix for the field names (may be empty but not * {@code null}) * @param includeReplies * {@code true} to include replies, {@code false} to not include * replies * @return The simple field set containing the post */ protected SimpleFieldSet encodePost(Post post, String prefix, boolean includeReplies) { SimpleFieldSetBuilder postBuilder = new SimpleFieldSetBuilder(); postBuilder.put(prefix + "ID", post.getId()); postBuilder.put(prefix + "Sone", post.getSone().getId()); if (post.getRecipientId().isPresent()) { postBuilder.put(prefix + "Recipient", post.getRecipientId().get()); } postBuilder.put(prefix + "Time", post.getTime()); postBuilder.put(prefix + "Text", encodeString(post.getText())); postBuilder.put(encodeLikes(core.getLikes(post), prefix + "Likes.")); if (includeReplies) { List<PostReply> replies = core.getReplies(post.getId()); postBuilder.put(encodeReplies(replies, prefix)); } return postBuilder.get(); } /** * Creates a simple field set from the given collection of posts. * * @param posts * The posts to encode * @param prefix * The prefix for the field names (may be empty but not * {@code null}) * @param includeReplies * {@code true} to include the replies, {@code false} to not * include the replies * @return The simple field set containing the posts */ protected SimpleFieldSet encodePosts(Collection<? extends Post> posts, String prefix, boolean includeReplies) { SimpleFieldSetBuilder postBuilder = new SimpleFieldSetBuilder(); int postIndex = 0; postBuilder.put(prefix + "Count", posts.size()); for (Post post : posts) { String postPrefix = prefix + postIndex++; postBuilder.put(encodePost(post, postPrefix + ".", includeReplies)); if (includeReplies) { postBuilder.put(encodeReplies(Collections2.filter(core.getReplies(post.getId()), Reply.FUTURE_REPLY_FILTER), postPrefix + ".")); } } return postBuilder.get(); } /** * Creates a simple field set from the given collection of replies. * * @param replies * The replies to encode * @param prefix * The prefix for the field names (may be empty, but not * {@code null}) * @return The simple field set containing the replies */ protected static SimpleFieldSet encodeReplies(Collection<? extends PostReply> replies, String prefix) { SimpleFieldSetBuilder replyBuilder = new SimpleFieldSetBuilder(); int replyIndex = 0; replyBuilder.put(prefix + "Replies.Count", replies.size()); for (PostReply reply : replies) { String replyPrefix = prefix + "Replies." + replyIndex++ + "."; replyBuilder.put(replyPrefix + "ID", reply.getId()); replyBuilder.put(replyPrefix + "Sone", reply.getSone().getId()); replyBuilder.put(replyPrefix + "Time", reply.getTime()); replyBuilder.put(replyPrefix + "Text", encodeString(reply.getText())); } return replyBuilder.get(); } /** * Creates a simple field set from the given collection of Sones that like * an element. * * @param likes * The liking Sones * @param prefix * The prefix for the field names (may be empty but not * {@code null}) * @return The simple field set containing the likes */ protected static SimpleFieldSet encodeLikes(Collection<? extends Sone> likes, String prefix) { SimpleFieldSetBuilder likesBuilder = new SimpleFieldSetBuilder(); int likeIndex = 0; likesBuilder.put(prefix + "Count", likes.size()); for (Sone sone : likes) { String sonePrefix = prefix + likeIndex++ + "."; likesBuilder.put(sonePrefix + "ID", sone.getId()); } return likesBuilder.get(); } // // OBJECT METHODS // /** * {@inheritDoc} */ @Override public String toString() { return getClass().getName() + "[writeAccess=" + writeAccess + "]"; } }