-
Notifications
You must be signed in to change notification settings - Fork 8
/
Core.scala
236 lines (200 loc) · 7.94 KB
/
Core.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
package ru.org.codingteam.horta.core
import akka.actor._
import akka.pattern.ask
import akka.util.Timeout
import org.joda.time.DateTime
import ru.org.codingteam.horta.database.{RepositoryFactory, PersistentStore}
import ru.org.codingteam.horta.messages._
import ru.org.codingteam.horta.plugins.HelperPlugin.HelperPlugin
import ru.org.codingteam.horta.plugins._
import ru.org.codingteam.horta.plugins.bash.BashPlugin
import ru.org.codingteam.horta.plugins.dice.DiceRoller
import ru.org.codingteam.horta.plugins.karma.KarmaPlugin
import ru.org.codingteam.horta.plugins.log.LogPlugin
import ru.org.codingteam.horta.plugins.mail.MailPlugin
import ru.org.codingteam.horta.plugins.markov.MarkovPlugin
import ru.org.codingteam.horta.plugins.pet.PetPlugin
import ru.org.codingteam.horta.plugins.visitor.VisitorPlugin
import ru.org.codingteam.horta.plugins.wtf.WtfPlugin
import ru.org.codingteam.horta.protocol.jabber.JabberProtocol
import ru.org.codingteam.horta.plugins.htmlreader.HtmlReaderPlugin
import ru.org.codingteam.horta.security._
import scala.concurrent.duration._
import scala.concurrent.{Await, Future}
import scala.language.postfixOps
/**
* Horta core actor. Manages all plugins, routes global messages.
*/
class Core extends Actor with ActorLogging {
import context.dispatcher
implicit val timeout = Timeout(60 seconds)
/**
* List of plugin props to be started.
*/
val plugins: List[Props] = List(
Props[FortunePlugin],
Props[AccessPlugin],
Props[LogPlugin],
Props[VisitorPlugin],
Props[WtfPlugin],
Props[MailPlugin],
Props[PetPlugin],
Props[MarkovPlugin],
Props[VersionPlugin],
Props[BashPlugin],
Props[DiceRoller],
Props[HtmlReaderPlugin],
Props[HelperPlugin],
Props[KarmaPlugin]
)
/**
* List of registered commands.
*/
var commands = Map[String, List[(ActorRef, CommandDefinition)]]()
/**
* List of plugins receiving all the messages.
*/
var messageReceivers = List[ActorRef]()
/**
* List of plugins receiving room notifications.
*/
var roomReceivers = List[ActorRef]()
/**
* List of plugins receiving the user notifications.
*/
var participantReceivers = List[ActorRef]()
val parsers = List(SlashParsers, DollarParsers)
override def preStart() {
val definitions = getPluginDefinitions
parseNotifications(definitions)
commands = Core.getCommands(definitions)
commands foreach (command => log.info(s"Registered command: $command"))
val storages = Core.getStorages(definitions)
// TODO: What is the Akka way to create these?
val store = context.actorOf(Props(classOf[PersistentStore], storages), "store")
val protocol = context.actorOf(Props[JabberProtocol], "jabber")
}
override def receive = {
case CoreMessage(time, credential, text) => processMessage(time, credential, text)
case CoreRoomJoin(time, roomJID, actor) => processRoomJoin(time, roomJID, actor)
case CoreRoomLeave(time, roomJID) => processRoomLeave(time, roomJID)
case CoreRoomTopicChanged(time, roomId, text, actor) => processRoomTopicChanged(time, roomId, text, actor)
case CoreParticipantJoined(time, roomJID, participantJID, actor) => processParticipantJoin(time, roomJID, participantJID, actor)
case CoreParticipantLeft(time, roomJID, participantJID, reason, actor) =>
processParticipantLeave(time, roomJID, participantJID, reason, actor)
case CoreGetCommands => sender ! Core.getCommandsDescription(getPluginDefinitions)
}
private def getPluginDefinitions: List[(ActorRef, PluginDefinition)] = {
val responses = Future.sequence(for (plugin <- plugins) yield {
val actor = context.actorOf(plugin)
ask(actor, GetPluginDefinition).mapTo[PluginDefinition].map(definition => (actor, definition))
})
Await.result(responses, Duration.Inf)
}
private def parseNotifications(definitions: List[(ActorRef, PluginDefinition)]) = {
for ((actor, definition) <- definitions) {
definition.notifications match {
case Notifications(messages, rooms, participants) =>
if (messages) {
messageReceivers ::= actor
}
if (rooms) {
roomReceivers ::= actor
}
if (participants) {
participantReceivers ::= actor
}
}
}
}
private def processMessage(time: DateTime, credential: Credential, text: String) {
val command = parseCommand(text)
command match {
case Some((name, arguments)) =>
executeCommand(sender, credential, name, arguments)
case None =>
}
for (plugin <- messageReceivers) {
plugin ! ProcessMessage(time, credential, text)
}
}
private def processRoomJoin(time: DateTime, roomJID: String, actor: ActorRef) {
for (plugin <- roomReceivers) {
plugin ! ProcessRoomJoin(time, roomJID, actor)
}
}
private def processRoomLeave(time: DateTime, roomJID: String) {
for (plugin <- roomReceivers) {
plugin ! ProcessRoomLeave(time, roomJID)
}
}
private def processRoomTopicChanged(time: DateTime, roomId: String, text: String, roomActor: ActorRef) {
for (plugin <- roomReceivers) {
plugin ! ProcessRoomTopicChange(time, roomId, text)
}
}
private def processParticipantJoin(time: DateTime, roomJID: String, participantJID: String, roomActor: ActorRef) {
for (plugin <- participantReceivers) {
plugin ! ProcessParticipantJoin(time, roomJID, participantJID, roomActor)
}
}
private def processParticipantLeave(time: DateTime,
roomJID: String,
participantJID: String,
reason: LeaveReason,
roomActor: ActorRef) {
for (plugin <- participantReceivers) {
plugin ! ProcessParticipantLeave(time, roomJID, participantJID, reason, roomActor)
}
}
private def parseCommand(message: String): Option[(String, Array[String])] = {
parsers.toStream.map(p =>
p.parse(p.command, message) match {
case p.Success((name, arguments), _) => Some(name.asInstanceOf[String] -> arguments.asInstanceOf[Array[String]])
case _ => None
}
).flatten.headOption
}
/**
* Executes the command.
* @param credential credential of user who has sent the command.
* @param name command name.
* @param arguments command arguments.
*/
private def executeCommand(sender: ActorRef, credential: Credential, name: String, arguments: Array[String]) {
val executors = commands.get(name)
executors match {
case Some(executors) =>
executors foreach {
case (plugin, CommandDefinition(level, _, token)) if accessGranted(level, credential) =>
plugin ! ProcessCommand(credential, token, arguments)
}
case None =>
}
}
private def accessGranted(access: AccessLevel, user: Credential) = {
access match {
case GlobalAccess => user.access == GlobalAccess
case RoomAdminAccess => user.access == GlobalAccess || user.access == RoomAdminAccess
case CommonAccess => true
}
}
}
object Core {
private def getCommands(pluginDefinitions: List[(ActorRef, PluginDefinition)]): Map[String, List[(ActorRef, CommandDefinition)]] = {
val commands = for ((actor, pluginDefinition) <- pluginDefinitions) yield {
for (command <- pluginDefinition.commands) yield (command.name, actor, command)
}
val groups = commands.flatten.groupBy(_._1).map(tuple => (tuple._1, tuple._2.map {
case (_, actor, command) => (actor, command)
}))
groups
}
private def getCommandsDescription(pluginDefinitions: List[(ActorRef, PluginDefinition)]) =
pluginDefinitions.map(t => t._2.name -> t._2.commands.map(cd => cd.name -> cd.level)).toMap
private def getStorages(pluginDefinitions: Seq[(ActorRef, PluginDefinition)]): Map[String, RepositoryFactory] = {
pluginDefinitions.toStream.flatMap { case (_, definition) =>
definition.repositoryFactory.map(factory => (definition.name, factory))
}.toMap
}
}