diff --git a/server/src/main/scala/com/lbs/server/BootConfig.scala b/server/src/main/scala/com/lbs/server/BootConfig.scala index aeb6d46..efb807f 100644 --- a/server/src/main/scala/com/lbs/server/BootConfig.scala +++ b/server/src/main/scala/com/lbs/server/BootConfig.scala @@ -23,7 +23,7 @@ */ package com.lbs.server -import akka.actor.{ActorRef, ActorSystem} +import akka.actor.ActorSystem import com.lbs.api.json.model.{AvailableVisitsTermPresentation, HistoricVisit, ReservedVisit} import com.lbs.bot.Bot import com.lbs.bot.telegram.TelegramBot @@ -162,7 +162,7 @@ class BootConfig { Some("cancel"), localization, originator)(actorSystem) @Bean - def router: ActorRef = actorSystem.actorOf(Router.props(authFactory)) + def router: Router = new Router(authFactory)(actorSystem) @Bean def telegram: TelegramBot = new TelegramBot(router ! _, telegramBotToken) diff --git a/server/src/main/scala/com/lbs/server/conversation/Account.scala b/server/src/main/scala/com/lbs/server/conversation/Account.scala index c7a3b98..47dc92a 100644 --- a/server/src/main/scala/com/lbs/server/conversation/Account.scala +++ b/server/src/main/scala/com/lbs/server/conversation/Account.scala @@ -23,7 +23,7 @@ */ package com.lbs.server.conversation -import akka.actor.{ActorRef, ActorSystem} +import akka.actor.ActorSystem import com.lbs.bot.model.Button import com.lbs.bot.{Bot, _} import com.lbs.server.conversation.Account._ @@ -33,7 +33,7 @@ import com.lbs.server.lang.{Localizable, Localization} import com.lbs.server.service.DataService import com.lbs.server.util.MessageExtractors.CallbackCommand -class Account(val userId: UserId, bot: Bot, dataService: DataService, val localization: Localization, router: ActorRef)(val actorSystem: ActorSystem) extends Conversation[Unit] with Localizable { +class Account(val userId: UserId, bot: Bot, dataService: DataService, val localization: Localization, router: Router)(val actorSystem: ActorSystem) extends Conversation[Unit] with Localizable { entryPoint(askAction) @@ -49,32 +49,33 @@ class Account(val userId: UserId, bot: Bot, dataService: DataService, val locali action match { case -1L => router ! cmd.copy(message = cmd.message.copy(text = Some("/login"))) - stay() case -2L => bot.sendMessage(userId.source, "Not implemented yet") - stay() case accountId => - val accountMaybe = dataService.findUserCredentialsByAccountId(userId.userId, accountId) - accountMaybe match { - case Some(account) => //account was found - val userMaybe = dataService.findUser(userId.userId) - userMaybe.foreach { user => - user.activeAccountId = accountId - dataService.saveUser(user) - router ! SwitchUser(UserId(account.userId, account.accountId, userId.source)) - bot.sendMessage(userId.source, lang.accountSwitched(account.username)) - } - stay() - case None => - error(s"This is not user [#${userId.userId}] account [#$accountId]") - stay() - } + switchAccount(accountId) } + end() } + + private def switchAccount(accountId: Long): Unit = { + val accountMaybe = dataService.findUserCredentialsByAccountId(userId.userId, accountId) + accountMaybe match { + case Some(account) => + val userMaybe = dataService.findUser(userId.userId) + userMaybe.foreach { user => + user.activeAccountId = accountId + dataService.saveUser(user) + router ! SwitchAccount(UserId(account.userId, account.accountId, userId.source)) + bot.sendMessage(userId.source, lang.accountSwitched(account.username)) + } + case None => + error(s"This is not user [#${userId.userId}] account [#$accountId]") + } + } } object Account { - case class SwitchUser(userId: UserId) + case class SwitchAccount(userId: UserId) } \ No newline at end of file diff --git a/server/src/main/scala/com/lbs/server/conversation/Chat.scala b/server/src/main/scala/com/lbs/server/conversation/Chat.scala index b736962..77716b6 100644 --- a/server/src/main/scala/com/lbs/server/conversation/Chat.scala +++ b/server/src/main/scala/com/lbs/server/conversation/Chat.scala @@ -50,64 +50,72 @@ class Chat(val userId: UserId, dataService: DataService, monitoringService: Moni entryPoint(helpChat) - private def helpChat: Step = dialogue(help) { - case Msg(cmd@TextCommand("/help"), _) => - help ! cmd - stay() - case Msg(cmd@TextCommand("/start"), _) => - help ! cmd - stay() - } + private def helpChat: Step = + dialogue(help) { + case Msg(cmd@TextCommand("/help"), _) => + help ! cmd + stay() + case Msg(cmd@TextCommand("/start"), _) => + help ! cmd + stay() + } - private def bookChat: Step = dialogue(book) { - case Msg(TextCommand("/book"), _) => - book.restart() - stay() - } + private def bookChat: Step = + dialogue(book) { + case Msg(TextCommand("/book"), _) => + book.restart() + stay() + } - private def historyChat: Step = dialogue(history) { - case Msg(TextCommand("/history"), _) => - history.restart() - stay() - } + private def historyChat: Step = + dialogue(history) { + case Msg(TextCommand("/history"), _) => + history.restart() + stay() + } - private def visitsChat: Step = dialogue(visits) { - case Msg(TextCommand("/reserved"), _) => - visits.restart() - stay() - } + private def visitsChat: Step = + dialogue(visits) { + case Msg(TextCommand("/reserved"), _) => + visits.restart() + stay() + } - private def bugChat: Step = dialogue(bug) { - case Msg(TextCommand("/bug"), _) => - bug.restart() - stay() - } + private def bugChat: Step = + dialogue(bug) { + case Msg(TextCommand("/bug"), _) => + bug.restart() + stay() + } - private def monitoringsChat: Step = dialogue(monitorings) { - case Msg(TextCommand("/monitorings"), _) => - monitorings.restart() - stay() - } + private def monitoringsChat: Step = + dialogue(monitorings) { + case Msg(TextCommand("/monitorings"), _) => + monitorings.restart() + stay() + } - private def settingsChat: Step = dialogue(settings) { - case Msg(TextCommand("/settings"), _) => - settings.restart() - stay() - } + private def settingsChat: Step = + dialogue(settings) { + case Msg(TextCommand("/settings"), _) => + settings.restart() + stay() + } - private def accountChat: Step = dialogue(account) { - case Msg(TextCommand("/accounts"), _) => - account.restart() - stay() - } + private def accountChat: Step = + dialogue(account) { + case Msg(TextCommand("/accounts"), _) => + account.restart() + stay() + } - private def dialogue(interactional: Interactional)(mainStateFunction: MessageProcessorFn): Step = + private def dialogue(interactional: Interactional)(mainMessageProcessor: MessageProcessorFn): Step = monologue { case event: Msg => - if (mainStateFunction.isDefinedAt(event)) mainStateFunction(event) + if (mainMessageProcessor.isDefinedAt(event)) mainMessageProcessor(event) else { - val secondaryStateFunction = secondaryState(interactional) - secondaryStateFunction(event) + val defaultMessageProcessor = secondaryState(interactional) + defaultMessageProcessor(event) } } @@ -139,7 +147,7 @@ class Chat(val userId: UserId, dataService: DataService, monitoringService: Moni case Msg(cmd@TextCommand("/accounts"), _) => self ! cmd goto(accountChat) - case Msg(cmd@TextCommand(MonitoringId(monitoringIdStr, scheduleIdStr, timeStr)), _) => + case Msg(TextCommand(MonitoringId(monitoringIdStr, scheduleIdStr, timeStr)), _) => val monitoringId = monitoringIdStr.toLong val scheduleId = scheduleIdStr.toLong val time = timeStr.toLong diff --git a/server/src/main/scala/com/lbs/server/conversation/Router.scala b/server/src/main/scala/com/lbs/server/conversation/Router.scala index b6c93b4..6f2c06f 100644 --- a/server/src/main/scala/com/lbs/server/conversation/Router.scala +++ b/server/src/main/scala/com/lbs/server/conversation/Router.scala @@ -23,17 +23,19 @@ */ package com.lbs.server.conversation -import akka.actor.{Actor, Cancellable, Props} +import akka.actor.{ActorSystem, Cancellable} import com.lbs.bot.model.{Command, MessageSource} import com.lbs.common.Logger -import com.lbs.server.conversation.Account.SwitchUser -import com.lbs.server.conversation.Router.DestroyChat +import com.lbs.server.conversation.Account.SwitchAccount +import com.lbs.server.conversation.base.Conversation import scala.collection.mutable import scala.concurrent.ExecutionContextExecutor import scala.concurrent.duration.DurationLong -class Router(authFactory: MessageSourceTo[Auth]) extends Actor with Logger { +class Router(authFactory: MessageSourceTo[Auth])(val actorSystem: ActorSystem) extends Conversation[Unit] with Logger { + + private case class DestroyChat(source: MessageSource) private val chats = mutable.Map.empty[MessageSource, Auth] @@ -41,38 +43,41 @@ class Router(authFactory: MessageSourceTo[Auth]) extends Actor with Logger { private val idleTimeout = 1.hour - private implicit val dispatcher: ExecutionContextExecutor = context.system.dispatcher + private implicit val dispatcher: ExecutionContextExecutor = actorSystem.dispatcher - override def receive: Receive = { - case cmd@Command(source, _, _) => - scheduleIdleChatDestroyer(source) - val chat = chats.get(source) match { - case Some(actor) => actor - case None => addNewChat(source) - } - chat ! cmd - case DestroyChat(source) => - destroyChat(source) - case SwitchUser(userId) => - switchUser(userId) - case what => info(s"Unknown message: $what") - } + entryPoint(routeMessage) - private def addNewChat(source: MessageSource): Auth = { - val actor = authFactory(source) - chats += source -> actor - actor + private def routeMessage: Step = + monologue { + case Msg(cmd@Command(source, _, _), _) => + val chat = instantiateChatOrGet(source) + chat ! cmd + stay() + case Msg(DestroyChat(source), _) => + info(s"Destroying chat for $source due to $idleTimeout of inactivity") + destroyChat(source) + stay() + case Msg(SwitchAccount(userId), _) => + switchAccount(userId) + stay() + case msg: Msg => + info(s"Unknown message received: $msg") + stay() + } + + private def instantiateChatOrGet(source: MessageSource) = { + scheduleIdleChatDestroyer(source) + chats.getOrElseUpdate(source, authFactory(source)) } private def destroyChat(source: MessageSource): Unit = { - info(s"Destroying chat for $source due to $idleTimeout of inactivity") timers.remove(source) removeChat(source) } - private def switchUser(userId: Login.UserId): Unit = { + private def switchAccount(userId: Login.UserId): Unit = { removeChat(userId.source) - addNewChat(userId.source) + chats += userId.source -> authFactory(userId.source) } private def removeChat(source: MessageSource): Unit = { @@ -81,20 +86,13 @@ class Router(authFactory: MessageSourceTo[Auth]) extends Actor with Logger { private def scheduleIdleChatDestroyer(source: MessageSource): Unit = { timers.remove(source).foreach(_.cancel()) - val cancellable = context.system.scheduler.scheduleOnce(idleTimeout) { + val cancellable = actorSystem.scheduler.scheduleOnce(idleTimeout) { self ! DestroyChat(source) } timers += source -> cancellable } - override def postStop(): Unit = { - chats.foreach(chat => removeChat(chat._1)) + beforeDestroy { + chats.foreach(chat => destroyChat(chat._1)) } -} - -object Router { - def props(authFactory: MessageSourceTo[Auth]) = Props(new Router(authFactory)) - - case class DestroyChat(source: MessageSource) - } \ No newline at end of file diff --git a/server/src/main/scala/com/lbs/server/conversation/base/Interactional.scala b/server/src/main/scala/com/lbs/server/conversation/base/Interactional.scala index bccdf75..2cf7d90 100644 --- a/server/src/main/scala/com/lbs/server/conversation/base/Interactional.scala +++ b/server/src/main/scala/com/lbs/server/conversation/base/Interactional.scala @@ -15,14 +15,14 @@ trait Interactional extends Logger { protected def actorSystem: ActorSystem - protected val self: Interactional = this - private[base] def initializeConversation(): Unit private[base] def executeCurrentStep(): Unit private[base] def makeStepTransition(any: Any): Unit + protected val self: Interactional = this + private var onDestroy: () => Unit = () => {} private def actorCreator: Actor = new Actor {