Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

* Fixed #1251 - Add LiftNamedComet to Lift-webkit

  • Loading branch information...
commit d59a033ce18af3696c9f5ae8d546b8424c54aba7 1 parent 54a023e
Diego Medina fmpwizard authored
57 web/webkit/src/main/scala/net/liftweb/http/NamedCometActorSnippet.scala
View
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2007-2012 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package http
+
+import common.Full
+import xml.NodeSeq
+
+/**
+ * This trait adds a named comet actor on the page. *
+ */
+trait NamedCometActorSnippet {
+
+ /**
+ * This is your Comet Class
+ */
+ def cometClass: String
+
+ /**
+ * This is how you are naming your comet actors.
+ * It can be as simple as
+ *
+ * <pre name="code" class="scala"><code>
+ * def name= S.param(p).openOr("A")
+ * </code></pre>
+ *
+ * This causes every comet actor for class @cometClass with the same @name
+ * to include the same Comet Actor
+ *
+ */
+ def name: String
+
+ /**
+ * The render method that inserts the <lift:comet> tag
+ * to add the comet actor to the page
+ */
+ final def render(xhtml: NodeSeq): NodeSeq = {
+ for (sess <- S.session) sess.sendCometActorMessage(
+ cometClass, Full(name), CometName(name)
+ )
+ <lift:comet type={cometClass} name={name}>{xhtml}</lift:comet>
+ }
+}
50 web/webkit/src/main/scala/net/liftweb/http/NamedCometActorTrait.scala
View
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2007-2012 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package http
+
+import util.Helpers._
+import common.{Loggable, Full}
+
+
+trait NamedCometActorTrait extends CometActor with Loggable {
+
+ /**
+ * First thing we do is registering this comet actor
+ * for the "name" key
+ */
+ override def localSetup = {
+ NamedCometListener.getOrAddDispatchersFor(name).foreach(
+ dispatcher=> dispatcher ! registerCometActor(this, name)
+ )
+ super.localSetup()
+ }
+
+ /**
+ * We remove the CometActor from the map of registered actors
+ */
+ override def localShutdown = {
+ NamedCometListener.getOrAddDispatchersFor(name).foreach(
+ dispatcher=> dispatcher ! unregisterCometActor(this)
+ )
+ super.localShutdown()
+ }
+
+ // time out the comet actor if it hasn't been on a page for 2 minutes
+ override def lifespan = Full(120 seconds)
+
+}
95 web/webkit/src/main/scala/net/liftweb/http/NamedCometDispatcher.scala
View
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2007-2012 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package http
+
+import common.{Box, Full, Loggable}
+import actor.LiftActor
+
+/**
+ * This class keeps a list of comet actors that need to update the UI
+ */
+class NamedCometDispatcher(name: Box[String]) extends LiftActor with Loggable {
+
+ logger.debug("DispatcherActor got name: %s".format(name))
+
+ private var cometActorsToUpdate: Vector[CometActor]= Vector()
Diego Medina Owner

I wasn't sure if there was a better data structure than Vector for this variable.
The operations I'm doing here are:
1- Append
2- To remove dead actors I use .fildetrNot(...)
3- Iterate through the whole Vector to send message to each CometActor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ override def messageHandler = {
+ /**
+ * if we do not have this actor in the list, add it (register it)
+ */
+ case registerCometActor(actor, Full(name)) => {
+ if(cometActorsToUpdate.contains(actor) == false){
+ logger.debug("We are adding actor: %s to the list".format(actor))
+ cometActorsToUpdate= cometActorsToUpdate :+ actor
+ } else {
+ logger.debug("The list so far is %s".format(cometActorsToUpdate))
+ }
+ }
+ case unregisterCometActor(actor) => {
+ logger.debug("before %s".format(cometActorsToUpdate))
+ cometActorsToUpdate= cometActorsToUpdate.filterNot(_ == actor)
+ logger.debug("after %s".format(cometActorsToUpdate))
+ }
+
+ //Catch the dummy message we send on comet creation
+ case CometName(name) =>
+
+ /**
+ * Go through the list of actors and send them a message
+ */
+ case msg => {
+ cometActorsToUpdate.par.foreach{ x => {
Diego Medina Owner

See comments on line 65 explaining how come this compiles on 2.8.x.
But I also wonder if you think this is an ok practice or not.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ x ! msg
+ logger.debug("We will update this comet actor: %s showing name: %s".format(x, name))
+ }
+ }
+ }
+ }
+
+ /**
+ * Vector.par was introduced in 2.9.x , so to be able to use it in Lift builds for scala 2.8.x
+ * I am using this implicit conversion to avoid a compiler error.
+ * I fake the par method to return a simple Vector
+ *
+ */
+ implicit def fakeParFor2_8_x(v: Vector[CometActor]): VectorPar2_8_x = {
+ new VectorPar2_8_x(v)
+ }
+}
+
+/**
+ * Vector.par was introduced in 2.9.x , so to be able to use it in Lift builds for scala 2.8.x
+ * I am using this implicit conversion to avoid a compiler error.
+ * I fake the par method to return a simple Vector
+ *
+ * TODO: Remove after we no longer build for 2.8.x
+ */
+class VectorPar2_8_x(v: Vector[CometActor]) {
+ def par = v
+}
+
+
+/**
+ * These are the message we pass around to
+ * register each named comet actor with a dispatcher that
+ * only updates the specific version it monitors
+ */
+case class registerCometActor(actor: CometActor, name: Box[String])
+case class unregisterCometActor(actor: CometActor)
+case class CometName(name: String)
94 web/webkit/src/main/scala/net/liftweb/http/NamedCometListener.scala
View
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2007-2012 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package http
+
+import util.Schedule
+import util.Helpers._
+import actor.{LAFuture, LiftActor}
+import common.{Empty, Full, Box, Loggable}
+
+/**
+ * Maintain a Map[Value the actor monitors -> Ref to the Actor Dispatcher]
+ *
+ * For a url like: http://hostnbame/index/?p=icecream
+ * If you name your actor based on the value of p
+ * For each flavor that users have on their urls,
+ * the map would be like:
+ * chocolate -> code.comet.CometClassNames@ea5e9e7 ,
+ * vanilla -> code.comet.CometClassNames@wv9i7o3, etc
+ *
+ * If we have the actor already on the Map, just return it,
+ * because it has to update the UI.
+ * If wee do not have this actor on our Map. create a new
+ * Dispatcher that will monitor this value, add it to our Map
+ * and return the Ref to this new dispatcher so it updates the UI
+ *
+ *
+ */
+object NamedCometListener extends Loggable {
+
+ /**
+ * The map of key -> Dispatchers
+ */
+ private var disptchers: Map[String, LiftActor] = Map()
Diego Medina Owner

Same question about data structure.
Actions:
1- Append
2- Lookup at random placed based on key

Something I don't do right now is remove LiftActors (the dispatchers) when the last cometActor is removed from the dispatch. Any suggestions on best way to do this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ /**
+ * Either returns or creates a dispatcher for the @str key
+ */
+ def getOrAddDispatchersFor(str: Box[String]): LAFuture[LiftActor] = synchronized {
+ val name= str.getOrElse("")
+ val liftActor: LAFuture[LiftActor] = new LAFuture()
+ Schedule{() => {
Diego Medina Owner

Did I get the use of Futures right?

From our user's point, they will use this like this:

NamedCometListener.getOrAddDispatchersFor(name).foreach(
  dispatcher=> dispatcher ! registerCometActor(this, name)
)

I wasn't too sure if just by calling .foreach , there was a new thread being used, or if I had to then use Schedule, as I did here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ val ret= disptchers.get(name ) match {
+ case Some(actor) => actor
+ case None => {
+ val ret = new NamedCometDispatcher(str)
+ disptchers += name -> ret
+ logger.debug("Our map of NamedCometDispatchers is: %s".format(disptchers));
+ ret
+ }
+ }
+ liftActor.satisfy(ret)
+ }
+ }
+ liftActor
+ }
+
+ /**
+ * Returns a Future containing a Full dispatcher or None for the @str key
+ *
+ * A common use case for this method is if you are sending updates to comet actors from a rest endpoint,
+ * you do not want to create dispatchers if no comet is presently monitoring the @str key
+ *
+ */
+ def getDispatchersFor(str: Box[String]): LAFuture[Box[LiftActor]] = synchronized {
+ val name= str.getOrElse("")
+ val liftActor: LAFuture[Box[LiftActor]] = new LAFuture()
+ Schedule{() => {
+ val ret= disptchers.get(name ) match {
+ case Some(actor) => Full(actor)
+ case None => Empty
+ }
+ liftActor.satisfy(ret)
+ }
+ }
+ liftActor
+ }
+
+
+}
63 web/webkit/src/test/scala/net/liftweb/http/NamedCometPerTabSpec.scala
View
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2010-2012 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package http
+
+import js.JsCmds
+import xml._
+import org.specs.Specification
+
+import common._
+
+/**
+ * System under specification for NamedComet* files.
+ */
+object NamedCometPerTabSpec extends Specification("NamedCometPerTabSpec Specification") {
+
+ class CometA extends NamedCometActorTrait{
+ override def lowPriority = {
+ case msg => JsCmds.Noop
+ }
+ def render = {
+ "nada" #> Text("nada")
+ }
+ }
+
+ "A NamedCometDispatcher" should {
+ doBefore {
+ val cometA= new CometA{override def name= Full("1")}
+ cometA.localSetup
+ }
+ "be created for a comet" in {
+ NamedCometListener.getDispatchersFor(Full("1")).foreach(
+ actor => actor.open_!.toString must startWith("net.liftweb.http.NamedCometDispatcher")
+ )
+ }
+ "be created even if no comet is present when calling getOrAddDispatchersFor" in {
+ NamedCometListener.getOrAddDispatchersFor(Full("3")).foreach(
+ actor => actor.toString must startWith("net.liftweb.http.NamedCometDispatcher")
+ )
+ }
+ "not be created for a non existing key" in {
+ NamedCometListener.getDispatchersFor(Full("2")).foreach(
+ actor => actor must_== Empty
+ )
+ }
+ }
+
+
+}
Diego Medina

I wasn't sure if there was a better data structure than Vector for this variable.
The operations I'm doing here are:
1- Append
2- To remove dead actors I use .fildetrNot(...)
3- Iterate through the whole Vector to send message to each CometActor

Diego Medina

See comments on line 65 explaining how come this compiles on 2.8.x.
But I also wonder if you think this is an ok practice or not.

Diego Medina

Same question about data structure.
Actions:
1- Append
2- Lookup at random placed based on key

Something I don't do right now is remove LiftActors (the dispatchers) when the last cometActor is removed from the dispatch. Any suggestions on best way to do this?

Diego Medina

Did I get the use of Futures right?

From our user's point, they will use this like this:

NamedCometListener.getOrAddDispatchersFor(name).foreach(
  dispatcher=> dispatcher ! registerCometActor(this, name)
)

I wasn't too sure if just by calling .foreach , there was a new thread being used, or if I had to then use Schedule, as I did here.

Please sign in to comment.
Something went wrong with that request. Please try again.