You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@esme.apache.org by dp...@apache.org on 2009/01/23 01:26:47 UTC

svn commit: r736864 [1/3] - in /incubator/esme/trunk/server: ./ src/main/scala/bootstrap/liftweb/ src/main/scala/org/ src/main/scala/org/apache/ src/main/scala/org/apache/esme/ src/main/scala/org/apache/esme/actor/ src/main/scala/org/apache/esme/api/ s...

Author: dpp
Date: Thu Jan 22 16:26:46 2009
New Revision: 736864

URL: http://svn.apache.org/viewvc?rev=736864&view=rev
Log:
Moved to org.apache package space

Added:
    incubator/esme/trunk/server/src/main/scala/org/
    incubator/esme/trunk/server/src/main/scala/org/apache/
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/Distributor.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/GroupActor.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/HttpSender.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/MessagePullActor.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/SchedulerActor.scala   (with props)
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/UserActor.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/api/
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/api/RestAPI.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/comet/
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/comet/.keep
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/comet/PublicTimeline.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/comet/TagCloud.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/comet/Timeline.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/external/
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/external/AtomFeed.scala   (with props)
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/external/Feed.scala   (with props)
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/external/RssFeed.scala   (with props)
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/external/TwitterFeed.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/lib/
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/lib/MsgFormat.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/lib/MsgParser.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/lib/TagUtils.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/.keep
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Action.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/AuthToken.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/DidPerform.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/ExtSession.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Group.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Mailbox.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Message.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/MessageTag.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Relationship.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Tag.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/Tracking.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/UrlStore.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/model/User.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/snippet/
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/snippet/.keep
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/snippet/Style.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/snippet/UserSnip.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/view/
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/view/.keep
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/view/ActionView.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/view/Auth.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/view/Track.scala
    incubator/esme/trunk/server/src/main/scala/org/apache/esme/view/UserView.scala
    incubator/esme/trunk/server/src/test/scala/org/
    incubator/esme/trunk/server/src/test/scala/org/apache/
    incubator/esme/trunk/server/src/test/scala/org/apache/esme/
    incubator/esme/trunk/server/src/test/scala/org/apache/esme/AppTest.scala
    incubator/esme/trunk/server/src/test/scala/org/apache/esme/lib/
    incubator/esme/trunk/server/src/test/scala/org/apache/esme/lib/MsgParseTest.scala
Removed:
    incubator/esme/trunk/server/src/main/scala/us/
    incubator/esme/trunk/server/src/test/scala/us/
Modified:
    incubator/esme/trunk/server/pom.xml
    incubator/esme/trunk/server/src/main/scala/bootstrap/liftweb/Boot.scala

Modified: incubator/esme/trunk/server/pom.xml
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/pom.xml?rev=736864&r1=736863&r2=736864&view=diff
==============================================================================
--- incubator/esme/trunk/server/pom.xml (original)
+++ incubator/esme/trunk/server/pom.xml Thu Jan 22 16:26:46 2009
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
   <modelVersion>4.0.0</modelVersion>
-  <groupId>us.esme</groupId>
+  <groupId>org.apache</groupId>
   <artifactId>esme</artifactId>
   <version>0.2.3-SNAPSHOT</version>
   <packaging>war</packaging>

Modified: incubator/esme/trunk/server/src/main/scala/bootstrap/liftweb/Boot.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/bootstrap/liftweb/Boot.scala?rev=736864&r1=736863&r2=736864&view=diff
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/bootstrap/liftweb/Boot.scala (original)
+++ incubator/esme/trunk/server/src/main/scala/bootstrap/liftweb/Boot.scala Thu Jan 22 16:26:46 2009
@@ -1,19 +1,24 @@
 package bootstrap.liftweb
 
-/*
- * Copyright 2008 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
+/**
+ * Copyright 2008-2009 WorldWide Conferencing, LLC
+ * 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ * 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.
+ * 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.
  */
 
 import net.liftweb.util._
@@ -23,7 +28,7 @@
 import Helpers._
 import net.liftweb.mapper.{DB, ConnectionManager, Schemifier, DefaultConnectionIdentifier, ConnectionIdentifier}
 import java.sql.{Connection, DriverManager}
-import us.esme._
+import org.apache.esme._
 import model._
 import actor._
 import lib._
@@ -45,7 +50,7 @@
     
     if (!DB.jndiJdbcConnAvailable_?) DB.defineConnectionManager(DefaultConnectionIdentifier, DBVendor)
     // where to search snippet
-    LiftRules.addToPackages("us.esme")
+    LiftRules.addToPackages("org.apache.esme")
     
     if (Props.mode == Props.RunModes.Test) {
       Schemifier.destroyTables_!!(Log.infoF _, User, ExtSession,

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/Distributor.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/Distributor.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/Distributor.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/Distributor.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,148 @@
+/**
+ * Copyright 2008-2009 WorldWide Conferencing, LLC
+ * 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+/*
+ * Distributor.scala
+ *
+ * To change this template, choose Tools | Template Manager
+ * and open the template in the editor.
+ */
+
+package org.apache.esme.actor
+
+import scala.actors.Actor
+import Actor._
+
+import net.liftweb._
+import http._
+import util._
+
+import org.apache.esme._
+import model._
+
+import scala.xml.{Elem}
+
+object Distributor extends Actor {
+  def act = loop {
+    react {
+      case StartMeUp =>
+        link(ActorWatcher)
+        User.findAll.map(_.id.is).foreach(findOrCreateUser)
+        Group.findAll.map(_.id.is).foreach(findOrCreateGroup)
+
+      case UserCreatedMessage(user, text, tags, when, 
+                              metaData,
+                              source,
+                              inReplyTo) =>
+        findOrCreateUser(user) ! 
+        UserActor.CreateMessage(text, tags,
+                                when, metaData, source, inReplyTo)
+
+      case AddMessageToMailbox(user, message, reason) =>
+        findOrCreateUser(user) ! UserActor.AddToMailbox(message, reason)
+  
+      case Listen(user, who) =>
+        findOrCreateUser(user) ! UserActor.Listen(who)
+
+      case Unlisten(user, who) =>
+        findOrCreateUser(user) ! UserActor.Unlisten(who)
+
+      case LatestMessages(user, cnt) =>
+        findOrCreateUser(user).forward(UserActor.LatestMessages(cnt))
+
+      case m @ UserUpdated(id) =>
+        users.get(id).foreach(_ ! m)
+
+      case nm @ NewMessage(msg) =>
+        val toSend = UserActor.TestForTracking(msg)
+        users.values.foreach(_ ! toSend)
+        listeners.foreach(_ ! nm)
+
+      case UpdateTrackingFor(userId, ttype) =>
+        for (user <- users.get(userId))
+        user ! UserActor.UpdateTracking(ttype)
+          
+      case PublicTimelineListeners(who) =>
+        listeners = who :: listeners
+
+      case PublicTimelineUnlisteners(who) =>
+        listeners = listeners.filter(_ ne who)
+
+      case _ =>
+    }
+  }
+
+  private case object StartMeUp
+
+  case class UserCreatedMessage(user: Long, text: String, tags: List[String],
+                                when: Long,
+                                metaData: Box[Elem],
+                                source: String,
+                                replyTo: Box[Long])
+  case class AddMessageToMailbox(user: Long, message: Message, reason: MailboxReason)
+  case class Listen(user: Long, who: Actor)
+  case class Unlisten(user: Long, who: Actor)
+  case class LatestMessages(user: Long, cnt: Int)
+  case class NewMessage(msg: Message)
+  case class UpdateTrackingFor(user: Long, which: TrackingType)
+  case class UserUpdated(userId: Long)
+  case class PublicTimelineListeners(who: Actor)
+  case class PublicTimelineUnlisteners(who: Actor)
+  sealed trait TrackingType
+  case object PerformTrackingType extends TrackingType
+  case object TrackTrackingType extends TrackingType
+
+  start
+  this ! StartMeUp
+
+  // do nothing
+  def touch {
+
+  }
+
+  private var users: Map[Long, UserActor] = Map.empty
+  private var groups: Map[Long, GroupActor] = Map.empty
+  private var listeners: List[Actor] = Nil
+
+  private def findOrCreateUser(user: Long): UserActor = {
+    users.get(user) match {
+      case Some(ret) => ret
+      case _ =>
+        val ret = new UserActor
+        ret.start
+        ret ! UserActor.StartMeUp(user)
+        users = users + (user -> ret)
+        ret
+    }
+  }
+
+  private def findOrCreateGroup(group: Long) {
+    groups.get(group) match {
+      case Some(ret) => ret
+      case _ =>
+        val ret = new GroupActor
+        ret.start
+        ret ! GroupActor.StartMeUp(group)
+        groups = groups + (group -> ret)
+        ret
+    }
+  }
+}

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/GroupActor.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/GroupActor.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/GroupActor.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/GroupActor.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,47 @@
+/**
+ * Copyright 2008-2009 WorldWide Conferencing, LLC
+ * 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.esme.actor
+
+import scala.actors.Actor
+import Actor._
+
+
+import net.liftweb._
+import http._
+
+import org.apache.esme._
+import model._
+
+object GroupActor {
+  case class StartMeUp(group: Long)
+}
+
+class GroupActor extends Actor {
+  import GroupActor._
+  
+  def act = loop {
+    react {
+      case StartMeUp =>
+        link(ActorWatcher)
+    }
+  } 
+}

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/HttpSender.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/HttpSender.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/HttpSender.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/HttpSender.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,147 @@
+/**
+ * Copyright 2008-2009 WorldWide Conferencing, LLC
+ * 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.esme.actor
+
+import scala.actors.Actor
+import Actor._
+
+import net.liftweb._
+import http._
+import util._
+import net.liftweb.http.testing._
+
+import org.apache.esme._
+import model._
+import lib._
+
+import org.apache.commons.httpclient._
+import org.apache.commons.httpclient.auth._
+import methods._
+import java.io.OutputStream
+
+object HttpSender extends Actor with GetPoster {
+  def act = loop {
+    react {
+      case StartMeUp =>
+        link(ActorWatcher)
+        
+
+      case SendAMessage(action, msg, user, reason, token) =>
+        send(action, msg, user, reason, token)
+
+      case _ =>
+    }
+  }
+
+  private case object StartMeUp
+  case class SendAMessage(action: Performances, msg: Message, user: User, reason: MailboxReason, token: String)
+
+  private def send(action: Performances, msg: Message, user: User, reason: MailboxReason, token: String) {
+    import Mailer._
+    
+    action match {
+      case MailTo(who, text) =>
+        val body = text match {
+          case None => XHTMLMailBodyType(msg.digestedXHTML)
+          case Some(t) => PlainMailBodyType(expandText(t, msg, user, reason))
+        }
+        Mailer.sendMail(From("i@esme.us"), Subject("msg"),
+                        To(who), body)
+          
+      case HttpTo(url, username, password, headers, data) =>
+        val load = data match {
+          case None => ""
+          case Some(d) => expandText(d, msg, user, reason)
+        }
+        post(url, httpClient,
+             ("X-ESME-Token" -> token) :: headers,
+             username, password,
+             load)
+        
+      case PerformResend | PerformFilter => // IGNORE
+      
+    }
+  }
+  
+  private def expandText(text: String, msg: Message, user: User, reason: MailboxReason) = {
+    val followerId = reason match {
+      case FollowedReason(followerId) => Some(followerId)
+      case UnfollowedReason(followerId) => Some(followerId)
+      case _ => None
+    }
+    
+    val followerName = followerId match {
+      case Some(followerId) => User.find(followerId).map[String](_ nickname).openOr("N/A")
+      case None => "N/A"
+    }
+    
+    text.replace("%u", user.nickname).
+    replace("%f", followerName).
+    replace("%i", user.imageUrl).
+    replace("%w", user.wholeName).
+    replace("%s", msg.getText)
+  }
+
+  // Overloaded method from GetPoster
+  private def post(url: String, httpClient: HttpClient,
+       headers: List[(String, String)],
+       username: String, password: String,
+       body: String) {
+    val poster = new PostMethod(baseUrl + url)
+    for ((name, value) <- headers) poster.setRequestHeader(name, value)
+    poster.setRequestEntity(new RequestEntity {
+      private val bytes = body.toString.getBytes("UTF-8")
+
+      def getContentLength() = bytes.length
+      def getContentType() = "application/x-www-form-urlencoded"
+      def isRepeatable() = true
+      def writeRequest(out: OutputStream) {
+    out.write(bytes)
+      }
+    })
+
+    httpClient.getState().setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password))
+    poster.setDoAuthentication(true)
+
+    try {
+      httpClient.executeMethod(poster)
+      Log.info(poster.getStatusText)
+      Log.info(poster.getResponseBodyAsString)
+    } finally {
+      poster.releaseConnection
+    }
+  }
+  
+  def httpClient = {
+    val ret = new HttpClient(new SimpleHttpConnectionManager(false))
+
+    for (host <- Props.get("http.proxyHost"))
+    ret.getHostConfiguration.setProxy(host,
+                                      Props.getInt("http.proxyPort",80))
+    ret
+  }
+
+  start
+  this ! StartMeUp
+
+  def baseUrl = ""
+}

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/MessagePullActor.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/MessagePullActor.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/MessagePullActor.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/MessagePullActor.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,111 @@
+package org.apache.esme.actor
+
+/**
+ * Copyright 2008-2009 WorldWide Conferencing, LLC
+ * 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import scala.actors.Actor
+import scala.actors.Actor._
+import net.liftweb.http.ActorWatcher
+import org.apache.esme.actor.Distributor.{UserCreatedMessage=>Msg}
+
+class MessagePullActor(val messageProcessor: Actor, private var lastMessage: Option[Msg], val messageSource: UniqueMessageSource) extends Actor {
+
+  import MessagePullActor._
+
+  def act {
+    loop {
+      react {
+        case StartUp => {
+          link(ActorWatcher)
+        }
+        case ByeBye => {
+          unlink(ActorWatcher)
+          self.exit()
+        }
+        case (msgs: List[Msg]) => {
+          val lastMessages = messageSource.getLastSortedMessages(msgs, lastMessage)
+          for (message <- lastMessages) {
+            messageProcessor ! message
+            lastMessage = Some(message)
+          }
+        }
+        case FetchMessages => actor {
+          // "this" used to reference invoking actor
+          this ! messageSource()
+        }
+      }
+    }
+  }
+  
+}
+
+object MessagePullActor extends Actor {
+  
+  private var messagePullActors: Map[Any, Actor] = Map()
+  
+  def act = loop {
+    react {
+      case StartPullActor(obj, lastMessage, messageSource) => {
+        if (!messagePullActors.contains(obj)) {
+          val pullActor = new MessagePullActor(Distributor, lastMessage, messageSource)
+          messagePullActors += (obj -> pullActor)
+          pullActor.start
+          pullActor ! StartUp
+        }
+      }
+      case StopPullActor(obj) => {
+        if (messagePullActors.contains(obj)) {
+          messagePullActors(obj) ! ByeBye
+          messagePullActors -= obj
+        }
+      }
+      case Fetch(obj) => {
+        if (messagePullActors.contains(obj)) {
+          messagePullActors(obj) ! FetchMessages
+        }
+      }
+    }
+  }
+
+  start
+
+  // do nothing
+  def touch {
+  }
+  
+  private case object StartUp
+  private case object ByeBye
+  private case object FetchMessages
+  case class StartPullActor(any: Any, lastMessage: Option[Msg], messageSource: UniqueMessageSource)
+  case class StopPullActor(any: Any)
+  case class Fetch(any: Any)
+}
+
+trait UniqueMessageSource extends (() => List[Msg]) {
+  def messageSorter(a: Msg, b: Msg) = a.when < b.when
+
+  def getLastSortedMessages(msgs: List[Msg], lastMessage: Option[Msg]): List[Msg] = {
+    lastMessage match {
+      case Some(message: Msg) => msgs.filter{messageSorter(message, _)}
+      case None => msgs
+    }
+  }.sort(messageSorter)
+}

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/SchedulerActor.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/SchedulerActor.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/SchedulerActor.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/SchedulerActor.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,108 @@
+package org.apache.esme.actor
+
+/**
+ * Copyright 2008-2009 WorldWide Conferencing, LLC
+ * 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import scala.actors.Actor
+import scala.actors.TIMEOUT
+import scala.actors.Actor._
+import org.apache.esme.actor.Distributor.AddMessageToMailbox
+import org.apache.esme.model._
+import net.liftweb.http.ActorWatcher
+import net.liftweb.util.{Full,Empty,TimeHelpers}
+
+class SchedulerActor(val messageProcessor: Actor, val user: Long, val everySeconds: Int, val reason: MailboxReason) extends Actor {
+
+  import SchedulerActor._
+  
+  def act {
+    sendMessage()
+    loop {
+      reactWithin (everySeconds * 1000) {
+        case StartUp => {
+          link(ActorWatcher)
+        }
+        case ByeBye => {
+          unlink(ActorWatcher)
+          self.exit()
+        }
+        case TIMEOUT => {
+          sendMessage()
+        }
+      }
+    }
+  }
+  
+  def sendMessage() {
+    val now = System.currentTimeMillis
+    val dateString = TimeHelpers.toInternetDate(now)
+    Message.create.author(user).
+                   when(now).
+                   source("every").
+                   setTextAndTags("Regularly scheduled action: " + dateString, Nil, Empty).
+                   foreach{ msg =>
+                     // Noisy & can't be rejected
+                     // if (msg.save) {
+                       messageProcessor ! Distributor.AddMessageToMailbox(user, msg, reason)
+                     // }
+                   }
+  }
+  
+}
+
+object SchedulerActor extends Actor{
+  
+  private var regularsPerAction: Map[Long, List[Actor]] = Map()
+  
+  def act = loop {
+    react {
+      case StartRegular(action, everySeconds) => {
+        val id = action.id.is
+        val regularActor = new SchedulerActor(Distributor, action.user, everySeconds, RegularReason(id))
+        if (!regularsPerAction.contains(id)) {
+          regularsPerAction += (id -> List(regularActor))
+        } else {
+          regularsPerAction(id) += regularActor
+        }
+        regularActor.start
+        regularActor ! StartUp
+      }
+
+      case StopRegular(id) => {
+        if (regularsPerAction.contains(id)) {
+          for(regularActor <- regularsPerAction(id)) regularActor ! ByeBye
+          regularsPerAction -= id
+        }
+      }
+    }
+  }
+
+  start
+
+  // do nothing
+  def touch {
+  }
+  
+  case class StartRegular(action: Action, everySeconds: Int)
+  case class StopRegular(action: Long)
+  private case object StartUp
+  private case object ByeBye
+}

Propchange: incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/SchedulerActor.scala
------------------------------------------------------------------------------
    svn:executable = *

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/UserActor.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/UserActor.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/UserActor.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/actor/UserActor.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,228 @@
+/**
+ * Copyright 2008-2009 WorldWide Conferencing, LLC
+ * 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.esme.actor
+
+import scala.actors.Actor
+import Actor._
+
+import net.liftweb._
+import http._
+import util._
+import mapper._
+
+import org.apache.esme._
+import model._
+import lib._
+
+import java.util.logging._
+import java.util.{TimeZone, Calendar}
+import scala.xml.{Elem}
+
+object UserActor {
+  private[actor] case class StartMeUp(user: Long)
+  private[actor] case class CreateMessage(text: String, tags: List[String],
+                                          when: Long, metaData: Box[Elem],
+                                          source: String,
+                                          replyTo: Box[Long])
+  private[actor] case class AddToMailbox(msg: Message, reason: MailboxReason)
+  private[actor] case class Listen(who: Actor)
+  private[actor] case class Unlisten(who: Actor)
+  private[actor] case class LatestMessages(cnt: Int)
+  private[actor] case class TestForTracking(msg: Message)
+  private[actor] case class UpdateTracking(ttype: Distributor.TrackingType)
+  
+  case class MessageReceived(msg: Message, reason: MailboxReason)
+  
+  val logger: Logger = Logger.getLogger("org.apache.esme.actor")
+}
+
+class UserActor extends Actor {
+  import UserActor._
+  
+  private var userId: Long = 0
+
+  private var userTimezone: TimeZone = _
+  
+  private var listeners: List[Actor] = Nil
+  
+  private var tracking: List[TrackingMatcher] = Nil
+
+  private var perform: List[PerformMatcher] = Nil
+  
+  private var _mailbox: Array[Long] = Array()
+
+  private def followers: List[Long] = User.followerIdsForUserId(userId)
+
+  private case class RunFunc(f: () => Unit)
+
+  def act = loop {
+    react {
+      case m @ Distributor.UserUpdated(_) =>
+        User.find(userId).
+        foreach(u => userTimezone = TimeZone.getTimeZone(u.timezone))
+        listeners.foreach(_ ! m)
+
+      case StartMeUp(user) =>
+        link(ActorWatcher)
+        userId = user
+        User.find(userId).
+        foreach(u => userTimezone = TimeZone.getTimeZone(u.timezone))
+
+        _mailbox = Mailbox.mostRecentMessagesFor(userId, 500).
+        map(_._1.id.is).toArray
+
+        this ! UpdateTracking(Distributor.TrackTrackingType)
+        this ! UpdateTracking(Distributor.PerformTrackingType)
+
+      case RunFunc(f) => f()
+        
+      case CreateMessage(text, tags, when, metaData, source, replyTo) =>
+        val tagLst = tags.removeDuplicates.map(Tag.findOrCreate)
+
+        Message.create.author(userId).when(when).
+        source(source).
+        setTextAndTags(text, tagLst, metaData).map{msg =>
+          // do some security... only reply to messages
+          // that are in our mailbox
+          for (rt <- replyTo;
+               mb <- Mailbox.find(By(Mailbox.message, rt),
+                                  By(Mailbox.user, userId))) msg.replyTo(rt)
+
+          msg.saveMe
+
+          Distributor ! Distributor.AddMessageToMailbox(userId, msg, NoReason)
+        
+          for (id <- followers)
+          Distributor ! Distributor.AddMessageToMailbox(id, msg, NoReason)
+        
+          for (id <- msg.sentToIds)
+          Distributor ! Distributor.AddMessageToMailbox(id, msg,
+                                                        DirectReason(userId))
+
+          for (convId <- msg.conversation.can ;
+               val msgId = Message.findMap(By(Message.conversation, convId))
+               (m => Full(m.id.is));
+               userId <- (Mailbox.findMap(InRaw(Mailbox.message, msgId.mkString(", "),
+                                                IHaveValidatedThisSQL("dpp", "Aug 27. 2008")))
+                          (mb => Full(mb.user.is))).removeDuplicates)
+          Distributor ! Distributor.AddMessageToMailbox(userId, msg, ConversationReason(convId))
+
+          Distributor ! Distributor.NewMessage(msg)
+        }
+
+      case AddToMailbox(msg, reason) => 
+        addToMailbox(msg, reason)
+        
+        
+      case TestForTracking(msg) =>
+        for (t <- tracking.find(_.doesMatch_?(msg)))
+        this ! AddToMailbox(msg, TrackReason(t.trackId))
+         
+          
+      case UpdateTracking(ttype) =>
+        ttype match {
+          case Distributor.TrackTrackingType =>
+            tracking = Tracking.findAll(By(Tracking.user, userId),
+                                        By(Tracking.disabled, false),
+                                        By(Tracking.removed, false)).
+            flatMap(_.matcher)
+        
+          case Distributor.PerformTrackingType =>
+            perform = Action.findAll(By(Action.user, userId),
+                                     By(Action.disabled, false),
+                                     By(Action.removed, false)).
+            map(_.matcher)
+        }
+  
+      case Listen(who) => listeners = who :: listeners
+    
+      case Unlisten(who) => listeners = listeners.filter(_ ne who)
+    
+      case LatestMessages(cnt) => reply(_mailbox.take(cnt).toList)
+    }
+  }
+
+  def buildCalendar = userTimezone match {
+    case null => Calendar.getInstance()
+    case tz => Calendar.getInstance(tz)
+  }
+  
+  private def addToMailbox(msg: Message, reason: MailboxReason) {
+    // if the message is not in my mailbox
+    if (Mailbox.find(By(Mailbox.message, msg),
+                     By(Mailbox.user, userId)).isEmpty) {
+      
+      // get all the performance things
+      val cal = buildCalendar
+      val toDo = perform.filter(_.func(msg, userId, cal, reason))
+
+      // is one of those reasons rejection of the message
+      val reject = toDo.exists(_.filter_?)
+
+      // if we're not rejecting the message
+      if (!reject) {
+        val mb = Mailbox.create.user(userId).message(msg)
+        reason match {
+          case TrackReason(trackId) => mb.viaTrack(trackId)
+          case DirectReason(fromId) => mb.directlyFrom(fromId)
+          case ConversationReason(convId) => mb.conversation(convId)
+          case ResendReason(resender) => mb.resentBy(resender)
+          case LoginReason(loggedId) => mb.login(loggedId)
+          case FollowedReason(followerId) => mb.followed(followerId)
+          case UnfollowedReason(unfollowerId) => mb.unfollowed(unfollowerId)
+          case ProfileReason(moduserId) => mb.profile(moduserId)
+          case RegularReason(actionId) => mb.regular(actionId)
+          case NoReason =>
+        }
+        mb.saveMe
+          
+        _mailbox = (msg.id.is :: _mailbox.toList).take(500).toArray
+
+        listeners.foreach(_ ! MessageReceived(msg, reason))
+            
+        toDo.foreach {
+          td =>
+
+          td.whatToDo match {
+            case m @ MailTo(_, _) =>
+              User.find(userId).foreach( u =>
+                HttpSender ! HttpSender.SendAMessage(m, msg, u, reason, td.uniqueId))
+             
+            case h @ HttpTo(_, _, _, _, _) =>
+              User.find(userId).foreach( u =>
+                HttpSender ! HttpSender.SendAMessage(h, msg, u, reason, td.uniqueId))
+
+            case PerformResend =>
+              if (! msg.saved_?) msg.save
+              for (id <- followers)
+              Distributor !
+              Distributor.AddMessageToMailbox(id, msg, ResendReason(userId))
+
+            case FetchFeed(url) => MessagePullActor ! MessagePullActor.Fetch(td.performId)
+
+            case PerformFilter => // IGNORE
+          }
+        }
+      }
+    }
+  }
+}

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/api/RestAPI.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/api/RestAPI.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/api/RestAPI.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/api/RestAPI.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,408 @@
+/**
+ * Copyright 2008-2009 WorldWide Conferencing, LLC
+ * 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+/*
+ * RestAPI.scala
+ *
+ * To change this template, choose Tools | Template Manager
+ * and open the template in the editor.
+ */
+
+package org.apache.esme.api
+
+import net.liftweb._
+import http._
+import rest._
+import util._
+import mapper._
+import Helpers._
+
+import org.apache.esme._
+import model._
+import actor._
+
+import scala.xml.{NodeSeq, Text, Elem, XML}
+import scala.actors.Actor
+import Actor._
+
+import scala.collection.mutable.ListBuffer
+import java.util.logging._
+
+object RestAPI extends XMLApiHelper {
+  val logger: Logger = Logger.getLogger("org.apache.esme.api")
+
+  def dispatch: LiftRules.DispatchPF = {
+    case Req("api" :: "status" :: Nil, "", GetRequest) => status
+    case Req("api" :: "login" :: Nil, "", PostRequest) => login
+    case Req("api" :: "logout" :: Nil, "", GetRequest) => logout
+    case Req("api" :: "get_msgs" :: Nil, "", GetRequest) => getMsgs
+    case Req("api" :: "wait_for_msgs" :: Nil, "", GetRequest) =>
+      waitForMsgs
+      
+    case Req("api" :: "send_msg" :: Nil, "", PostRequest) => 
+      () => sendMsg(User.currentUser.map(_.id.is), S)
+
+    case Req("api" :: "get_following" :: Nil, _, GetRequest) =>
+      following(calcUser)
+    case Req("api" :: "get_followers" :: Nil, _, GetRequest) =>
+      followers(calcUser)
+    case Req("api" :: "follow" :: Nil, _, PostRequest) =>
+      performFollow(S.param("user"))
+    case Req("api" :: "unfollow" :: Nil, _, PostRequest) => 
+      performUnfollow(S.param("user"))
+    case Req("api" :: "all_users" ::
+                      Nil, _, GetRequest) => allUsers _
+    case Req("api" :: "get_tagcloud" :: Nil, "", GetRequest) =>
+      () => getTagCloud(S)
+
+      // DPP to document
+    case Req("api" :: "get_tracking" :: Nil, "", GetRequest) =>
+      getTracking
+
+    case Req("api" :: "add_tracking" :: Nil, "", PostRequest) =>
+      addTracking
+
+    case Req("api" :: "remove_tracking" :: Nil, "", PostRequest) =>
+      removeTracking
+
+    case Req("api" :: "get_conversation" :: Nil, "", GetRequest) =>
+      getConversation
+      
+    case Req("api" :: "get_actions" :: Nil, "", GetRequest) =>
+      getActions _
+        
+    case Req("api" :: "add_action" :: Nil, "", PostRequest) =>
+      addAction _
+        
+    case Req("api" :: "enable_action" :: Nil, "", PostRequest) =>
+      enableAction _
+        
+    case Req("api" :: "delete_action" :: Nil, "", PostRequest) =>
+      deleteAction _
+  }
+
+  def findAction: Box[Action] =
+  for (user <- User.currentUser ?~ "Not Logged In";
+       id <- S.param("actionid") ?~ "id param not supplied";
+       action <- Action.find(By(Action.user, user),
+                             By(Action.id, id.toLong),
+                             By(Action.removed, false))) yield action
+
+  def addAction(): LiftResponse = {
+    val ret: Box[NodeSeq] =
+    for (user <- User.currentUser ?~ "Not logged in";
+         name <- S.param("name") ?~ "'name' param not supplied";
+         test <- S.param("test") ?~ "'test' param not supplied";
+         action <- S.param("action") ?~ "'action' param not supplied";
+         val a = Action.create.user(user).name(name);
+         a2 <- a.setTest(test);
+         a3 <- a.setAction(action)) yield a3.saveMe.toXml
+
+    ret
+  }
+
+  def getActions(): LiftResponse = {
+    val ret: Box[NodeSeq] =
+    for (user <- User.currentUser ?~ "Not logged in")
+    yield user.performing.flatMap(_.toXml)
+
+    ret
+  }
+
+  def enableAction(): LiftResponse = {
+    val ret: Box[Boolean] =
+    for (action <- findAction;
+         enabled <- S.param("enabled").map(toBoolean) ?~ "enabled param not supplied")
+    yield action.disabled(!enabled).save
+  
+    ret
+  }
+
+  def deleteAction(): LiftResponse = {
+    val ret: Box[Boolean] =
+    for (action <- findAction) 
+    yield action.removed(true).save
+    
+    ret
+  }
+  
+  private def calcUser: Box[User] =
+  S.param("user").flatMap(User.findFromWeb) or
+  User.currentUser
+  
+  def getTracking(): LiftResponse = {
+    val ret: Box[NodeSeq] =
+    for (user <- User.currentUser ?~ "Not logged in")
+    yield Tracking.findAll(By(Tracking.user, user)).flatMap(_.toXml)
+    ret
+  }
+
+  def getConversation(): LiftResponse = {
+    val ret: Box[NodeSeq] =
+    for (user <- User.currentUser ?~ "Not logged in";
+         id <- S.param("conversationid").map(toLong) ?~ "id param missing"
+    ) yield <conversation id={id.toString}>{
+        Message.findAndPrime(By(Message.conversation, id),
+                             OrderBy(Message.id, Ascending)).map(_.toXml)
+      }</conversation>
+
+    ret
+  }
+
+  def removeTracking(): LiftResponse = {
+    val ret: Box[Boolean] =
+    for (user <- User.currentUser ?~ "Not logged in";
+         id <- S.param("trackid") ?~ "id param missing";
+         track <- Tracking.find(By(Tracking.id, id.toLong),
+                                By(Tracking.user, user)) ?~ "Couldn't find tracking item"
+    ) yield track.removed(true).save
+
+    ret
+  }
+
+  def addTracking(): LiftResponse = {
+    val ret: Box[Boolean] =
+    for (user <- User.currentUser ?~ "Not logged in";
+         toTrack <- (S.param("track") ?~ "No track param") if toTrack.trim.length > 0)
+    yield
+    Tracking.create.user(user).regex(toTrack).save
+
+    ret
+  }
+
+  def status(): LiftResponse =
+  {
+    val ret: Box[NodeSeq] = User.currentUser.map(_.toXml)
+    ret
+  }
+
+  def allUsers(): LiftResponse = 
+  for (user <- User.findAll) yield user.toXml
+
+
+  def following(muser: Box[User])(): LiftResponse = {
+    val r: Box[NodeSeq] = for (user <- muser) yield
+    user.following().map(_.toXml)
+    
+    r
+  }
+  
+  def followers(muser: Box[User])(): LiftResponse = {
+    val r: Box[NodeSeq] = for (user <- muser) yield
+    user.followers().map(_.toXml)
+    
+    r
+  }
+  
+  def performFollow(userName: Box[String])(): LiftResponse = {
+    val r: Box[Boolean] =
+    for (user <- User.currentUser;
+         userName <- userName;
+         other <- User.findFromWeb(userName)
+    ) yield user.follow(other)
+    
+    r
+  }
+  
+  def performUnfollow(userName: Box[String])(): LiftResponse = {
+    val r: Box[Boolean] =
+    for (user <- User.currentUser;
+         userName <- userName;
+         other <- User.findFromWeb(userName)
+    ) yield user.unfollow(other)
+    
+    r
+  }
+
+  def login(): LiftResponse = {
+    val res: Box[Boolean] = if (User.loggedIn_?) Empty else
+    for (token <- S.param("token") ?~ "No 'token' param";
+         auth <- AuthToken.find(By(AuthToken.uniqueId, token))
+         ?~ "Token not found";
+         user <- auth.user.obj;
+         session <- S.session
+    ) yield {
+      User.logUserIn(user)
+      val myActor = buildActor(user.id)
+      restActor(Full(myActor))
+      true
+    }
+
+    res
+  }
+
+  def logout(): LiftResponse = {
+    User.logUserOut()
+    true
+  }
+
+  def waitForMsgs(): LiftResponse = {
+    val seq: Long = Helpers.nextNum
+
+    def waitForAnswer: Box[List[(Message, MailboxReason)]] = 
+    receiveWithin(6L * 60L * 1000L) {
+      case (s2, ret: List[(Message, MailboxReason)]) if s2 == seq =>
+        Full(ret)
+      case (s2, _, _) if s2 != seq => waitForAnswer
+      case _ => Empty
+    }
+
+    var r: Box[NodeSeq] = 
+    for (act <- restActor.is ?~ "No REST actor";
+         val ignore = act ! ListenFor(self, 5 minutes, seq);
+         answer <- waitForAnswer ?~ "Didn't get an answer")
+    yield answer.flatMap{ case (msg, reason) => msg.toXml % reason.attr}
+
+    r
+  }
+
+  def sendMsgWithToken(req: Req): Box[LiftResponse] = {
+    for (token <- req.param("token");
+         auth <- AuthToken.find(By(AuthToken.uniqueId, token));
+         userId <- auth.user.can;
+         ret <- sendMsg(Full(userId), req))  yield ret
+  }
+  
+  def sendMsg(theUser: Box[Long], params: HasParams): LiftResponse = {
+    val r: Box[Boolean] =
+    for (user <- theUser ?~ "User not found";
+         msg <- params.param("message") ?~ "Message not included")
+    yield {
+      val from: String = params.param("via") openOr "api"
+
+      val xml: Box[Elem] = params.param("metadata").flatMap(md =>
+        tryo(XML.loadString(md)))
+
+      Distributor !
+      Distributor.UserCreatedMessage(user, msg,
+                                     Tag.split(params.param("tags")
+                                               openOr ""),
+                                     millis,
+                                     xml,
+                                     from,
+                                     params.param("replyto").map(toLong))
+      true
+    }
+    r
+  }
+
+  def getMsgs(): LiftResponse = {
+    val t: Box[NodeSeq] =
+    for (tagName <- S.param("tag");
+         tag <- Tag.find(By(Tag.name, tagName)))
+    yield tag.findMessages.map(_.toXml)
+        
+    val r: Box[NodeSeq] = 
+    t or (for (user <- calcUser ?~ "User not found";
+               val lst = Mailbox.mostRecentMessagesFor(user.id, 40))
+          yield lst.flatMap{ case (msg, why) => msg.toXml % why.attr})
+    
+    r
+  }
+
+  def getTagCloud(params: HasParams): LiftResponse = {
+    // By default, get the normalised word frequencies from the messages
+    val numTags = (params.param("numTags") openOr "20").toInt
+    val numMsgs = 40 //(params.param("numMsgs") openOr "40").toInt
+
+    val r: Box[NodeSeq] = 
+    for (user <- calcUser ?~ "User not found";
+         val lst = Mailbox.mostRecentMessagesFor(user.id, numMsgs).map(_._1))
+    yield
+    <tag_cloud>
+      {
+        for (t <- Tag.centreWeightedTopNTagFreqs(lst, numTags)) yield <tag name={t._1} weight={t._2.toString} />
+      }
+      {
+        for (t <- Message.centreWeightedTopNWordFreqs(lst, numTags)) yield <word name={t._1} weight={t._2.toString} />
+      }
+    </tag_cloud>
+     
+     
+    r
+  }
+
+  def createTag(in: NodeSeq) = <esme_api>{in}</esme_api>
+
+  
+  private def buildActor(userId: Long): RestActor = {
+    val ret = new RestActor
+    ret.start
+    ret ! StartUp(userId)
+    ret
+  }
+
+  object restActor extends SessionVar[Box[RestActor]](Empty) {
+    override def cleanupFunc: Box[() => Unit] = Full(() => this.is.map(_ ! ByeBye))
+  }
+  
+
+  class RestActor extends Actor {
+    private var userId: Long = _
+    private var msgs: List[(Message, MailboxReason)] = Nil
+    private var listener: Box[(Actor, Long)] = Empty
+    
+    def act = loop {
+      react {
+        case StartUp(userId) =>
+          link(ActorWatcher)
+          this.userId = userId
+          Distributor ! Distributor.Listen(userId, this)
+
+        case ByeBye =>
+          unlink(ActorWatcher)
+          Distributor ! Distributor.Unlisten(userId, this)
+          self.exit()
+          
+        case UserActor.MessageReceived(msg, reason) =>
+          msgs = (msg, reason) :: msgs
+          listener.foreach {
+            who =>
+            who._1 ! (who._2, msgs)
+            listener = Empty
+            msgs = Nil
+          }
+        
+        case ReleaseListener =>
+          listener.foreach(l => l._1 ! (l._2, Nil))
+          listener = Empty
+
+        case ListenFor(who, len, seq) =>
+          msgs match {
+            case Nil =>
+              listener.foreach(l => l._1 ! (l._2, Nil))
+              listener = Full((who, seq))
+              ActorPing.schedule(this, ReleaseListener, len)
+             
+            case xs =>
+              who ! (seq, xs)
+              msgs = Nil
+              listener = Empty
+          }
+      }
+    }
+  }
+
+  private case class StartUp(userId: Long)
+  private case object ByeBye
+  private case class ListenFor(who: Actor, howLong: TimeSpan, seq: Long)
+  private case object ReleaseListener
+}

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/comet/.keep
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/comet/.keep?rev=736864&view=auto
==============================================================================
    (empty)

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/comet/PublicTimeline.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/comet/PublicTimeline.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/comet/PublicTimeline.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/comet/PublicTimeline.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,86 @@
+/**
+ * Copyright 2008-2009 WorldWide Conferencing, LLC
+ * 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.esme.comet
+
+import net.liftweb.http._
+import net.liftweb.mapper._
+import net.liftweb.util._
+import net.liftweb.util.Helpers._
+import scala.xml._
+import js._
+import JsCmds._
+import JE._
+
+import org.apache.esme._
+import actor._
+import model._
+import lib._
+
+import java.text._
+
+class PublicTimeline extends CometActor {
+  private var messages: List[Long] = Nil
+  private var lastRender = millis
+  private var scheduled = false
+
+  override def localSetup() {
+    super.localSetup()
+    Distributor ! Distributor.PublicTimelineListeners(this) 
+    messages = Message.findAll(OrderBy(Message.id, Descending), 
+                               MaxRows(40)).map(_.id.is)
+  }
+  
+  override def localShutdown() {
+    super.localShutdown()
+    Distributor ! Distributor.PublicTimelineUnlisteners(this)
+  }
+  
+  def render = {
+    lastRender = millis
+    scheduled = false
+    val msgMap = Message.findMessages(messages)
+    val toDisplay = messages.flatMap(msgMap.get)
+    val jsId = "public_timeline_messages";
+
+    OnLoad(JsCrVar(jsId, JsArray(
+        toDisplay.map(_.asJs) :_*)) &
+    JsFunc("displayMessages", JsVar(jsId), jsId).cmd)
+  }
+
+  override def lowPriority = {
+    case ForceRender =>
+      reRender(false)
+
+    case Distributor.NewMessage(msg) =>
+      messages = (msg.id.is :: messages).take(40)
+
+      if ((millis - lastRender) < 30000L) {
+        if (!scheduled) {
+          scheduled = true    
+          ActorPing.schedule(this, ForceRender, 30000L)
+        }
+      }
+      else reRender(false)
+  }
+}
+
+case object ForceRender

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/comet/TagCloud.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/comet/TagCloud.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/comet/TagCloud.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/comet/TagCloud.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,98 @@
+/**
+ * Copyright 2008-2009 WorldWide Conferencing, LLC
+ * 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.esme.comet
+
+import net.liftweb.http._
+import net.liftweb.util._
+import net.liftweb.util.Helpers._
+import scala.xml._
+import js._
+import JsCmds._
+import JE._
+
+import org.apache.esme._
+import actor._
+import model._
+import lib._
+
+import java.text._
+
+class TagCloud extends CometActor /* with MsgFormat*/ {
+
+  private var messages: List[Long] = Nil
+  
+  override def localSetup() {
+    super.localSetup()
+    for (user <- User.currentUser) {
+      Distributor ! Distributor.Listen(user.id, this)
+      Distributor !? (2000, Distributor.LatestMessages(user.id, 40)) match {
+        case Some(msg: List[Long]) => messages = msg
+        case x =>
+      }
+    }
+  }
+  
+  override def localShutdown() {
+    super.localShutdown()
+    for (user <- User.currentUser) {
+      Distributor ! Distributor.Unlisten(user.id, this)
+    }
+  }
+
+  private def lookupMessages(): List[Message] = {
+    val mm = Message.findMessages(messages)
+    messages.flatMap(mm.get)
+  }
+
+  def render = {
+    val messages = lookupMessages()
+    //Sort the tags & words to put the most prominent in the middle
+    <div id="tagcloud">
+      <p id="tag-para">Tags</p>
+      <p>
+        {
+          for ((name, weight) <- Tag.centreWeightedTopNTagFreqs(messages, 20))
+          yield <xml:group><a href={"/tag/" + name}
+              style={"font-size: "+(0.5F + weight)+"em; text-decoration: none;"}>{
+                name}</a> </xml:group>
+        }
+      </p>
+      <p/>
+      <p id="word-para">Words</p>
+      <p>
+        {
+          for ((name, weight) <- Message.centreWeightedTopNWordFreqs(messages, 20))
+          yield <xml:group><a href={"/search/" + name}
+              style={"font-size: "+(0.5F + weight)+"em; text-decoration: none;"}>{
+                name}</a> </xml:group>
+        }
+      </p>
+    </div>
+  }
+
+
+  override def lowPriority = {
+    case UserActor.MessageReceived(msg, _) =>
+      messages = (msg.id.is :: messages).take(40)
+      reRender(false)
+  }
+}

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/comet/Timeline.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/comet/Timeline.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/comet/Timeline.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/comet/Timeline.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,78 @@
+/**
+ * Copyright 2008-2009 WorldWide Conferencing, LLC
+ * 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.esme.comet
+
+import net.liftweb.http._
+import net.liftweb.util._
+import net.liftweb.util.Helpers._
+import scala.xml._
+import js._
+import JsCmds._
+import JE._
+
+import org.apache.esme._
+import actor._
+import model._
+import lib._
+
+import java.text._
+
+class Timeline extends CometActor {
+  private var messages: List[Long] = Nil
+  
+  override def localSetup() {
+    super.localSetup()
+    for (user <- User.currentUser) {
+      Distributor ! Distributor.Listen(user.id, this)
+      Distributor !? (200, Distributor.LatestMessages(user.id, 40)) match {
+        case Some(msg: List[Long]) => messages = msg
+        case x =>
+      }
+    }
+  }
+  
+  override def localShutdown() {
+    super.localShutdown()
+    for (user <- User.currentUser) {
+      Distributor ! Distributor.Unlisten(user.id, this)
+    }
+  }
+  
+  def render = {
+    val msgMap = Message.findMessages(messages)
+    val toDisplay = messages.flatMap(msgMap.get)
+    val jsId = "timeline_messages";
+
+    OnLoad(JsCrVar(jsId, JsArray(
+        toDisplay.map(_.asJs) :_*)) &
+    JsFunc("displayMessages", JsVar(jsId), jsId).cmd)
+  }
+  
+  override def lowPriority = {
+    case UserActor.MessageReceived(msg, _) =>
+      messages = (msg.id.is :: messages).take(40)
+      reRender(false)
+      
+    case Distributor.UserUpdated(_) =>
+      reRender(false)
+  }
+}

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/external/AtomFeed.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/external/AtomFeed.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/external/AtomFeed.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/external/AtomFeed.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,71 @@
+package org.apache.esme.external
+
+/**
+ * Copyright 2008-2009 WorldWide Conferencing, LLC
+ * 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import java.text._
+import java.util.Locale.US
+
+import org.apache.esme.model.User
+
+object AtomFeed{
+  val dateFormats = List(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", US),
+                                  new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", US),
+                                  new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", US),
+                                  new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", US))
+}
+
+class AtomFeed(user: User, atomURL: String, source: String, truncateChars: Int, tags: List[String])
+  extends Feed(user, atomURL, source, truncateChars, tags) {
+  import scala.xml._
+  
+  override def dateFormats = AtomFeed.dateFormats
+  
+  override def getEntries(xml: Elem) = xml \ "entry"
+  
+  override def getText(node: Node) = {
+    // a title element is mandatory
+    node \ "title" text
+  }
+  
+  override def getLink(node: Node) = {
+    // there must be either a link with @rel="alternate"
+    // or a link without @rel or content
+    val link = node \ "link"
+    if (link isEmpty)
+      node \ "content" text
+    else {
+      val alternate = link find(_ \ "@rel" == "alternate")
+      val anyLink = alternate getOrElse((link find(_ \ "@rel" == Nil)).get)
+      anyLink \ "@href" text
+    }
+  }
+  
+  override def getDate(node: Node) = {
+    // parseInternetDate(node \ "published" text).getTime
+    val published = node \ "published"
+    val date = if (published isEmpty)
+      node \ "updated"
+      else published
+    parseInternetDate(date text).getTime
+  }
+}
+

Propchange: incubator/esme/trunk/server/src/main/scala/org/apache/esme/external/AtomFeed.scala
------------------------------------------------------------------------------
    svn:executable = *

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/external/Feed.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/external/Feed.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/external/Feed.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/external/Feed.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,104 @@
+package org.apache.esme.external
+
+/**
+ * Copyright 2008-2009 WorldWide Conferencing, LLC
+ * 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import java.io.IOException
+import java.text._
+import java.util.Date
+
+
+import org.apache.commons.httpclient.methods.GetMethod
+
+import org.apache.esme.actor._
+import org.apache.esme.model.User
+
+import org.apache.esme.actor.Distributor.UserCreatedMessage
+
+import net.liftweb.util._
+
+abstract class Feed(val user: User, val url: String, val source: String, val truncateChars: Int, val tags: List[String]) extends UniqueMessageSource {
+  // import java.net.{URLConnection, URL}
+  import scala.xml._
+
+  protected def dateFormats: List[SimpleDateFormat]
+  var dateFormat: SimpleDateFormat = _
+
+  override def apply() = {
+    
+    ( for (node <- (getEntries(XML.loadString(responseString))))
+        yield UserCreatedMessage(
+          if (user != null) {user.id} else 0,
+          getText(node) + " " + getLink(node),
+          tags,
+          getDate(node),
+          Empty,
+          source,
+          Empty
+        )
+    ).toList
+  }
+  
+  protected def getEntries(xml: Elem): NodeSeq
+  
+  protected def getText(xml: Node): String
+  
+  protected def getLink(xml: Node): String
+  
+  protected def getDate(xml: Node): Long
+  
+  protected def responseString() = {
+    // url.openConnection
+    val httpClient = HttpSender.httpClient
+    val method = new GetMethod(url)
+    try {
+      httpClient.executeMethod(method)
+      method.getResponseBodyAsString
+    } catch {
+      case ioe: IOException => ""
+    } finally {
+      method.releaseConnection
+    }
+  }
+  
+  protected def parseInternetDate(dateString: String): Date = {
+    val fixedDateString = fixDateString(dateString)
+    if (dateFormat == null) dateFormats.find { df =>
+      try {
+        dateFormat = df
+        return df.parse(fixedDateString)
+        // true
+      } catch {
+        case pe: ParseException => false
+      }
+    }
+    dateFormat.parse(fixedDateString)
+  }
+  
+  protected def fixDateString(dateString: String) = {
+    val l = dateString.length
+    if (dateString.charAt(l - 3) == ':')
+      dateString.substring(0,l-3) + dateString.substring(l - 2, l)
+    else dateString
+  }
+
+}
+

Propchange: incubator/esme/trunk/server/src/main/scala/org/apache/esme/external/Feed.scala
------------------------------------------------------------------------------
    svn:executable = *

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/external/RssFeed.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/external/RssFeed.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/external/RssFeed.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/external/RssFeed.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,88 @@
+package org.apache.esme.external
+
+/**
+ * Copyright 2008-2009 WorldWide Conferencing, LLC
+ * 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import java.text._
+import java.util.Locale.US
+
+import org.apache.esme.model.User
+import org.apache.esme.model.Message
+import org.apache.esme.actor.Distributor.{UserCreatedMessage=>Msg}
+
+import net.liftweb.util.Empty
+
+object RssFeed {
+  val dateFormats = List(new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", US))
+}
+
+class RssFeed(user: User, rssURL: String, source: String, truncateChars: Int, tags: List[String])
+  extends Feed(user, rssURL, source, truncateChars, tags) {
+  import scala.xml._
+  
+  override def dateFormats = RssFeed.dateFormats
+  
+  override def getEntries(xml: Elem) = xml \\ "item"
+  
+  override def getText(node: Node) = {
+    // if there's no title, get description
+    val title = node \ "title"
+    if (title isEmpty)
+      node \ "description" text
+    else
+      title text
+  }
+  
+  override def getLink(node: Node) = {
+    // a link is optional
+    val link = node \ "link"
+    if (link isEmpty)
+      ""
+    else
+      link text
+  }
+  
+  override def getDate(node: Node) = {
+    // if there's no published date, take current time
+    val date = node \ "pubDate"
+    if (date isEmpty)
+      System.currentTimeMillis
+    else
+      parseInternetDate(date text).getTime
+  }
+  
+  // need to compare by text since a pubDate is not mandatory and indeed, often is missing
+  override def getLastSortedMessages(msgs: List[Msg], lastMessage: Option[Msg]): List[Msg] = {
+    lastMessage match {
+      case Some(message: Msg) =>
+        // a hack to format text identically- difference in urls & trailing whitespace
+        val lastMessageText = 
+          Message.create.setTextAndTags(message.text, Nil, Empty).
+            get.getText.trim
+        msgs.takeWhile{ msg =>
+          Message.create.setTextAndTags(msg.text, Nil, Empty).
+            get.getText.trim != lastMessageText
+        }
+      case None => msgs
+    }
+  }.reverse
+}
+

Propchange: incubator/esme/trunk/server/src/main/scala/org/apache/esme/external/RssFeed.scala
------------------------------------------------------------------------------
    svn:executable = *

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/external/TwitterFeed.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/external/TwitterFeed.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/external/TwitterFeed.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/external/TwitterFeed.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,61 @@
+package org.apache.esme.external
+
+/**
+ * Copyright 2008-2009 WorldWide Conferencing, LLC
+ * 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.esme.actor.UniqueMessageSource
+import org.apache.esme.actor.Distributor.UserCreatedMessage
+import org.apache.esme.model.User
+
+import net.liftweb._
+import util._
+
+object TwitterFeed {
+  val UserTimelineUrl = "http://twitter.com/statuses/user_timeline/"
+  val Protocol = "xml"
+  val Params = ""
+  val tf = new java.text.SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy", java.util.Locale.US)
+  
+}
+
+class TwitterFeed(val user: User, val twitterUser: String) extends UniqueMessageSource {
+  import java.net.{URLConnection, URL}
+  import scala.xml._
+  import TwitterFeed._
+
+  override def apply() = {
+    val url = new URL(UserTimelineUrl + twitterUser + "." + Protocol + Params)
+    val conn = url.openConnection()
+
+    
+    (for (node <- (XML.load(conn.getInputStream) \ "status")) yield UserCreatedMessage(
+      user.id,
+      node \ "text" text,
+      List("twitter"),
+      tf.parse(node \ "created_at" text).getTime,
+      Empty,
+      "twiiter",
+      Empty
+    )).toList
+  }
+  
+}
+

Added: incubator/esme/trunk/server/src/main/scala/org/apache/esme/lib/MsgFormat.scala
URL: http://svn.apache.org/viewvc/incubator/esme/trunk/server/src/main/scala/org/apache/esme/lib/MsgFormat.scala?rev=736864&view=auto
==============================================================================
--- incubator/esme/trunk/server/src/main/scala/org/apache/esme/lib/MsgFormat.scala (added)
+++ incubator/esme/trunk/server/src/main/scala/org/apache/esme/lib/MsgFormat.scala Thu Jan 22 16:26:46 2009
@@ -0,0 +1,117 @@
+/**
+ * Copyright 2008-2009 WorldWide Conferencing, LLC
+ * 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.esme.lib
+
+import org.apache.esme._
+import model._
+
+import scala.xml.{NodeSeq, Node, Text}
+
+import net.liftweb._
+import util._
+import http._
+import Helpers._
+
+import java.text.SimpleDateFormat
+
+object IsIE7 extends SessionVar[Boolean]({
+    // "Firefox/3"
+    // "MSIE 7"
+    def findUserAgent(in: List[(String, String)]): Box[String] =
+    in.filter(_._1.equalsIgnoreCase("User-Agent")).map(_._2).firstOption
+
+  val r: Box[Boolean] =
+  for (session <- S.session;
+       agent <- findUserAgent(session.initialHeaders)) yield
+  agent.indexOf("MSIE 7") >= 0
+
+  r openOr false
+})
+
+/*
+trait MsgFormat {
+  
+  def formatMsg(in: Message, showReply: Boolean, showConv: Boolean): NodeSeq =
+  formatMsg(in, showReply, showConv, Empty)
+
+  def formatMsg(in: Message, showReply: Boolean, showConv: Boolean,
+                inside: Box[() => NodeSeq]): NodeSeq =
+  <div class="b-list">
+    <table>
+      <tr>
+        <td class="image">{
+            val r: Box[NodeSeq] =
+            for (user <- in.author.obj;
+                 image <- user.image if !IsIE7.is) yield <img src={image} alt={user.niceName}/>
+
+            r openOr <xml:group>&nbsp;</xml:group>
+          }</td>
+        <td class="message">
+          <div class="outer">
+            <div class="inner clear">
+              <p class="text">{in.digestedXHTML}</p>
+            </div>
+            <p class="date">{in.author.obj.map(u => <a href={"/user/"+urlEncode(u.nickname)}>{u.niceName}</a>).openOr("")}: 
+              { dateFormat(in.when)} {
+                if (showReply) <a href={"javascript:setReplyTo("+in.id+","+
+                                        in.getText.encJs+
+                                        ")"}>reply</a>
+                else Text("")
+
+              }
+              {
+                in.conversation.can.toList.map(cid => <a href={"/conversation/"+cid}>conversation</a>)
+              }
+            </p>
+          </div>
+        </td>
+        <td class="tag">
+          {
+            in.tags.map(tag => <p><a href={"/tag/"+urlEncode(tag)}>{tag}</a></p>)
+          }
+        </td>
+      </tr>
+    </table>
+    {
+      inside.map(_()).openOr(Text(""))
+    }
+  </div>
+
+  private def dateFormat(in: Long): String = {
+    val delta = ((millis - in) / 1000L).toInt
+
+    delta match {
+      case 1 => "1 Second ago"
+      case n if n < 60 => ""+n+" Seconds ago"
+      case n if n >= 60 && n < 120 => "1 Minute ago"
+      case n if n < 3600 => ""+(n / 60)+" Minutes ago"
+      case n =>
+        (n / 3600) match {
+          case 1 => "1 Hour ago"
+          case n if n < 24 => ""+n+" Hours ago"
+          case _ => val df = new SimpleDateFormat("MM/dd HH:mm zzz")
+            dateFormatter.format(new java.util.Date(in))
+        }
+    }
+  }
+}
+*/



Re: svn commit: r736864 [1/3] - in /incubator/esme/trunk/server: ./ src/main/scala/bootstrap/liftweb/ src/main/scala/org/ src/main/scala/org/apache/ src/main/scala/org/apache/esme/ src/main/scala/org/apache/esme/actor/ src/main/scala/org/apache/esme/api/ s...

Posted by Daniel Kulp <dk...@apache.org>.
> ===========================================================================
>=== --- incubator/esme/trunk/server/pom.xml (original)
> +++ incubator/esme/trunk/server/pom.xml Thu Jan 22 16:26:46 2009
> @@ -1,7 +1,7 @@
>  <?xml version="1.0" encoding="UTF-8"?>
>  <project xmlns="http://maven.apache.org/POM/4.0.0"
> xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
> http://maven.apache.org/maven-v4_0_0.xsd">
> <modelVersion>4.0.0</modelVersion>
> -  <groupId>us.esme</groupId>
> +  <groupId>org.apache</groupId>
>    <artifactId>esme</artifactId>
>    <version>0.2.3-SNAPSHOT</version>
>    <packaging>war</packaging>

The group id should be org.apache.esme to make all artifacts (when you get to 
a point where you have multiple artifacts) go into sub dirs of the esme 
stuff.



-- 
Daniel Kulp
dkulp@apache.org
http://dankulp.com/blog