ChatRoom.scala 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. package models
  2. import akka.actor._
  3. import scala.concurrent.duration._
  4. import scala.language.postfixOps
  5. import play.api._
  6. import play.api.libs.json._
  7. import play.api.libs.iteratee._
  8. import play.api.libs.concurrent._
  9. import akka.util.Timeout
  10. import akka.pattern.ask
  11. import play.api.Play.current
  12. import play.api.libs.concurrent.Execution.Implicits._
  13. object Robot {
  14. def apply(chatRoom: ActorRef) {
  15. // Create an Iteratee that logs all messages to the console.
  16. val loggerIteratee = Iteratee.foreach[JsValue](event => Logger("robot").info(event.toString))
  17. implicit val timeout = Timeout(1 second)
  18. // Make the robot join the room
  19. chatRoom ? (Join("Robot")) map {
  20. case Connected(robotChannel) =>
  21. // Apply this Enumerator on the logger.
  22. robotChannel |>> loggerIteratee
  23. }
  24. // Make the robot talk every 30 seconds
  25. Akka.system.scheduler.schedule(
  26. 30 seconds,
  27. 30 seconds,
  28. chatRoom,
  29. Talk("Robot", "I'm still alive")
  30. )
  31. }
  32. }
  33. object ChatRoom {
  34. implicit val timeout = Timeout(1 second)
  35. lazy val default = {
  36. val roomActor = Akka.system.actorOf(Props[ChatRoom])
  37. // Create a bot user (just for fun)
  38. Robot(roomActor)
  39. roomActor
  40. }
  41. def join(username:String):scala.concurrent.Future[(Iteratee[JsValue,_],Enumerator[JsValue])] = {
  42. (default ? Join(username)).map {
  43. case Connected(enumerator) =>
  44. // Create an Iteratee to consume the feed
  45. val iteratee = Iteratee.foreach[JsValue] { event =>
  46. default ! Talk(username, (event \ "text").as[String])
  47. }.map { _ =>
  48. default ! Quit(username)
  49. }
  50. (iteratee,enumerator)
  51. case CannotConnect(error) =>
  52. // Connection error
  53. // A finished Iteratee sending EOF
  54. val iteratee = Done[JsValue,Unit]((),Input.EOF)
  55. // Send an error and close the socket
  56. val enumerator = Enumerator[JsValue](JsObject(Seq("error" -> JsString(error)))).andThen(Enumerator.enumInput(Input.EOF))
  57. (iteratee,enumerator)
  58. }
  59. }
  60. }
  61. class ChatRoom extends Actor {
  62. var members = Map.empty[String, Concurrent.Channel[JsValue]]
  63. val (chatEnumerator, chatChannel) = Concurrent.broadcast[JsValue]
  64. def receive = {
  65. case Join(username) => {
  66. if(members.contains(username)) {
  67. sender ! CannotConnect("This username is already used")
  68. } else {
  69. val (personalEnumerator, personalChannel) = Concurrent.broadcast[JsValue]
  70. members = members + (username -> personalChannel)
  71. sender ! Connected(chatEnumerator.interleave(personalEnumerator))
  72. self ! NotifyJoin(username)
  73. }
  74. }
  75. case NotifyJoin(username) => {
  76. notifyAll("join", username, "has entered the room")
  77. }
  78. case Talk(username, text) => {
  79. notifyAll("talk", username, text)
  80. }
  81. case Tell(username, text, to) => {
  82. notifyOne(to, "talk", username, text)
  83. }
  84. case Quit(username) => {
  85. members = members - username
  86. notifyAll("quit", username, "has left the room")
  87. }
  88. }
  89. def notifyOne(to: String, kind: String, user: String, text: String) {
  90. val msg = JsObject(
  91. Seq(
  92. "kind" -> JsString(kind),
  93. "user" -> JsString(user),
  94. "message" -> JsString(text),
  95. "members" -> JsArray(
  96. members.keys.toList.map(JsString)
  97. )
  98. )
  99. )
  100. members(to).push(msg)
  101. }
  102. def notifyAll(kind: String, user: String, text: String) {
  103. val msg = JsObject(
  104. Seq(
  105. "kind" -> JsString(kind),
  106. "user" -> JsString(user),
  107. "message" -> JsString(text),
  108. "members" -> JsArray(
  109. members.keys.toList.map(JsString)
  110. )
  111. )
  112. )
  113. chatChannel.push(msg)
  114. }
  115. }
  116. case class Join(username: String)
  117. case class Quit(username: String)
  118. case class Talk(username: String, text: String)
  119. case class Tell(username: String, text: String, to: String)
  120. case class NotifyJoin(username: String)
  121. case class Connected(enumerator:Enumerator[JsValue])
  122. case class CannotConnect(msg: String)