2 * Sone - AbstractSoneCommand.java - Copyright © 2011–2019 David Roden
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 package net.pterodactylus.sone.fcp
20 import com.google.common.base.Optional
21 import freenet.node.FSParseException
22 import freenet.support.SimpleFieldSet
23 import net.pterodactylus.sone.core.Core
24 import net.pterodactylus.sone.data.Post
25 import net.pterodactylus.sone.data.PostReply
26 import net.pterodactylus.sone.data.Sone
27 import net.pterodactylus.sone.freenet.SimpleFieldSetBuilder
28 import net.pterodactylus.sone.freenet.fcp.AbstractCommand
29 import net.pterodactylus.sone.freenet.fcp.Command
30 import net.pterodactylus.sone.freenet.fcp.FcpException
31 import net.pterodactylus.sone.template.SoneAccessor
32 import net.pterodactylus.sone.utils.asOptional
33 import net.pterodactylus.sone.utils.let
34 import net.pterodactylus.sone.utils.throwOnNullIf
37 * Abstract base implementation of a [Command] with Sone-related helper
40 abstract class AbstractSoneCommand
41 @JvmOverloads protected constructor(
42 protected val core: Core,
43 @get:JvmName("requiresWriteAccess")
44 val requiresWriteAccess: Boolean = false) : AbstractCommand() {
46 @Throws(FcpException::class)
47 protected fun getSone(simpleFieldSet: SimpleFieldSet, parameterName: String, localOnly: Boolean): Sone =
48 getSone(simpleFieldSet, parameterName, localOnly, true).get()
50 @Throws(FcpException::class)
51 protected fun getSone(simpleFieldSet: SimpleFieldSet, parameterName: String, localOnly: Boolean, mandatory: Boolean): Optional<Sone> {
52 val soneId = simpleFieldSet.get(parameterName)
53 .throwOnNullIf(mandatory) { FcpException("Could not load Sone ID from “$parameterName”.") }
54 ?: return Optional.absent()
55 val sone = core.getSone(soneId)
56 if (mandatory && sone == null || sone != null && localOnly && !sone.isLocal) {
57 throw FcpException("Could not load Sone from “$soneId”.")
59 return sone.asOptional()
62 @Throws(FcpException::class)
63 protected fun getPost(simpleFieldSet: SimpleFieldSet, parameterName: String): Post {
65 val postId = simpleFieldSet.getString(parameterName)
66 return core.getPost(postId)
67 ?: throw FcpException("Could not load post from “$postId”.")
68 } catch (fspe1: FSParseException) {
69 throw FcpException("Could not post ID from “$parameterName”.", fspe1)
73 @Throws(FcpException::class)
74 protected fun getReply(simpleFieldSet: SimpleFieldSet, parameterName: String): PostReply {
76 val replyId = simpleFieldSet.getString(parameterName)
77 return core.getPostReply(replyId)
78 ?: throw FcpException("Could not load reply from “$replyId”.")
79 } catch (fspe1: FSParseException) {
80 throw FcpException("Could not reply ID from “$parameterName”.", fspe1)
84 protected fun encodePost(post: Post, prefix: String, includeReplies: Boolean): SimpleFieldSet = SimpleFieldSetBuilder().apply {
85 put(prefix + "ID", post.id)
86 put(prefix + "Sone", post.sone.id)
87 post.recipientId.let { put(prefix + "Recipient", it) }
88 put(prefix + "Time", post.time)
89 put(prefix + "Text", encodeString(post.text))
90 put(encodeLikes(core.getLikes(post), "${prefix}Likes."))
92 val replies = core.getReplies(post.id)
93 put(encodeReplies(replies, prefix))
97 protected fun encodePosts(posts: Collection<Post>, prefix: String, includeReplies: Boolean): SimpleFieldSet = SimpleFieldSetBuilder().apply {
98 put(prefix + "Count", posts.size)
99 posts.forEachIndexed { postIndex, post ->
100 put(encodePost(post, "$prefix$postIndex.", includeReplies))
104 private fun encodeReplies(replies: Collection<PostReply>, prefix: String): SimpleFieldSet = SimpleFieldSetBuilder().apply {
105 put(prefix + "Replies.Count", replies.size)
106 replies.forEachIndexed { replyIndex, reply ->
107 val replyPrefix = "${prefix}Replies.$replyIndex."
108 put(replyPrefix + "ID", reply.id)
109 put(replyPrefix + "Sone", reply.sone.id)
110 put(replyPrefix + "Time", reply.time)
111 put(replyPrefix + "Text", encodeString(reply.text))
112 put(encodeLikes(core.getLikes(reply), "${replyPrefix}Likes."))
116 override fun toString() = "${javaClass.name}[requiresWriteAccess=$requiresWriteAccess]"
119 fun encodeString(text: String) = text
120 .replace("\\\\".toRegex(), "\\\\\\\\")
121 .replace("\n".toRegex(), "\\\\n")
122 .replace("\r".toRegex(), "\\\\r")
124 fun encodeSone(sone: Sone, prefix: String, localSone: Optional<Sone>): SimpleFieldSet = SimpleFieldSetBuilder().apply {
125 put(prefix + "ID", sone.id)
126 put(prefix + "Name", sone.name)
127 put(prefix + "NiceName", SoneAccessor.getNiceName(sone))
128 put(prefix + "LastUpdated", sone.time)
129 localSone.let { put(prefix + "Followed", it.hasFriend(sone.id).toString()) }
130 val profile = sone.profile
131 put(prefix + "Field.Count", profile.fields.size)
132 profile.fields.forEachIndexed { fieldIndex, field ->
133 put(prefix + "Field." + fieldIndex + ".Name", field.name)
134 put(prefix + "Field." + fieldIndex + ".Value", field.value)
138 fun encodeSones(sones: Collection<Sone>, prefix: String): SimpleFieldSet = SimpleFieldSetBuilder().apply {
139 put(prefix + "Count", sones.size)
140 sones.forEachIndexed { soneIndex, sone ->
141 put(encodeSone(sone, "$prefix$soneIndex.", Optional.absent()))
145 fun encodeLikes(likes: Collection<Sone>, prefix: String): SimpleFieldSet = SimpleFieldSetBuilder().apply {
146 put(prefix + "Count", likes.size)
147 likes.forEachIndexed { index, sone -> put("$prefix$index.ID", sone.id) }