3f9bd82187b63502409766cc8956c72b732ca736
[Sone.git] / src / main / kotlin / net / pterodactylus / sone / fcp / AbstractSoneCommand.kt
1 /*
2  * Sone - AbstractSoneCommand.java - Copyright © 2011–2019 David Roden
3  *
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.
8  *
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.
13  *
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/>.
16  */
17
18 package net.pterodactylus.sone.fcp
19
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
35
36 /**
37  * Abstract base implementation of a [Command] with Sone-related helper
38  * methods.
39  */
40 abstract class AbstractSoneCommand
41 @JvmOverloads protected constructor(
42                 protected val core: Core,
43                 @get:JvmName("requiresWriteAccess")
44                 val requiresWriteAccess: Boolean = false) : AbstractCommand() {
45
46         @Throws(FcpException::class)
47         protected fun getSone(simpleFieldSet: SimpleFieldSet, parameterName: String, localOnly: Boolean): Sone =
48                         getSone(simpleFieldSet, parameterName, localOnly, true).get()
49
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”.")
58                 }
59                 return sone.asOptional()
60         }
61
62         @Throws(FcpException::class)
63         protected fun getPost(simpleFieldSet: SimpleFieldSet, parameterName: String): Post {
64                 try {
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)
70                 }
71         }
72
73         @Throws(FcpException::class)
74         protected fun getReply(simpleFieldSet: SimpleFieldSet, parameterName: String): PostReply {
75                 try {
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)
81                 }
82         }
83
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."))
91                 if (includeReplies) {
92                         val replies = core.getReplies(post.id)
93                         put(encodeReplies(replies, prefix))
94                 }
95         }.get()
96
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))
101                 }
102         }.get()
103
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."))
113                 }
114         }.get()
115
116         override fun toString() = "${javaClass.name}[requiresWriteAccess=$requiresWriteAccess]"
117 }
118
119 fun encodeString(text: String) = text
120                 .replace("\\\\".toRegex(), "\\\\\\\\")
121                 .replace("\n".toRegex(), "\\\\n")
122                 .replace("\r".toRegex(), "\\\\r")
123
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)
135         }
136 }.get()
137
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()))
142         }
143 }.get()
144
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) }
148 }.get()