diff --git a/src/main/java/com/threerings/crowd/chat/server/ChatChannelManager.java b/src/main/java/com/threerings/crowd/chat/server/ChatChannelManager.java index ab3bd52ab..ee346dd7e 100644 --- a/src/main/java/com/threerings/crowd/chat/server/ChatChannelManager.java +++ b/src/main/java/com/threerings/crowd/chat/server/ChatChannelManager.java @@ -57,7 +57,6 @@ import com.threerings.crowd.chat.data.ChannelSpeakMarshaller; import com.threerings.crowd.chat.data.ChatChannel; import com.threerings.crowd.chat.data.ChatCodes; import com.threerings.crowd.chat.data.UserMessage; -import com.threerings.crowd.chat.server.SpeakUtil.ChatHistoryEntry; import com.threerings.crowd.data.BodyObject; import com.threerings.crowd.data.CrowdCodes; import com.threerings.crowd.peer.data.CrowdClientInfo; @@ -83,7 +82,7 @@ public abstract class ChatChannelManager /** The things in the user's chat history, aggregated from all nodes and sorted by * timestamp. */ - public List history; + public List history; } /** @@ -114,9 +113,9 @@ public abstract class ChatChannelManager @AnyThread public void collectChatHistory (Name user, final ResultListener lner) { - NodeRequestsListener> listener = - new NodeRequestsListener>() { - public void requestsProcessed (NodeRequestsResult> rRes) { + NodeRequestsListener> listener = + new NodeRequestsListener>() { + public void requestsProcessed (NodeRequestsResult> rRes) { ChatHistoryResult chRes = new ChatHistoryResult(); chRes.failedNodes = rRes.getNodeErrors().keySet(); chRes.history = Lists.newArrayList( @@ -295,7 +294,7 @@ public abstract class ChatChannelManager for (int bodyId : bodyIds) { BodyObject bobj = getBodyObject(bodyId); if (bobj != null && shouldDeliverSpeak(channel, message, bobj)) { - SpeakUtil.recordToChatHistory(channel, message, bobj.getVisibleName()); + _chatHistory.record(channel, message, bobj.getVisibleName()); bobj.postMessage(ChatCodes.CHAT_CHANNEL_NOTIFICATION, channel, message); } } @@ -420,22 +419,23 @@ public abstract class ChatChannelManager { // find all the UserMessages for the given user and send them back listener.requestProcessed(Lists.newArrayList(Iterables.filter( - SpeakUtil.getChatHistory(_user), IS_USER_MESSAGE))); + _chatHistory.get(_user), IS_USER_MESSAGE))); } protected Name _user; + @Inject ChatHistory _chatHistory; } - protected static final Predicate IS_USER_MESSAGE = - new Predicate() { - public boolean apply (ChatHistoryEntry entry) { + protected static final Predicate IS_USER_MESSAGE = + new Predicate() { + public boolean apply (ChatHistory.Entry entry) { return entry.message instanceof UserMessage; } }; - protected static final Comparator SORT_BY_TIMESTAMP = - new Comparator() { - public int compare (ChatHistoryEntry e1, ChatHistoryEntry e2) { + protected static final Comparator SORT_BY_TIMESTAMP = + new Comparator() { + public int compare (ChatHistory.Entry e1, ChatHistory.Entry e2) { return Longs.compare(e1.message.timestamp, e2.message.timestamp); } }; @@ -500,6 +500,9 @@ public abstract class ChatChannelManager /** Used for acquiring BodyObject references from Names and ClientObjects. */ @Inject protected BodyLocator _locator; + /** Used for recording chat history. */ + @Inject protected ChatHistory _chatHistory; + /** The period on which we check for idle channels. */ protected static final long IDLE_CHANNEL_CHECK_PERIOD = 5 * 1000L; diff --git a/src/main/java/com/threerings/crowd/chat/server/ChatHistory.java b/src/main/java/com/threerings/crowd/chat/server/ChatHistory.java new file mode 100644 index 000000000..28eb8de28 --- /dev/null +++ b/src/main/java/com/threerings/crowd/chat/server/ChatHistory.java @@ -0,0 +1,174 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2011 Three Rings Design, Inc., All Rights Reserved +// http://code.google.com/p/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.crowd.chat.server; + +import java.util.List; +import java.util.Map; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.inject.Singleton; + +import com.threerings.util.Name; + +import com.threerings.crowd.chat.data.ChatChannel; +import com.threerings.crowd.chat.data.ChatMessage; +import com.threerings.crowd.chat.data.KeepNoHistory; +import com.threerings.crowd.chat.data.UserMessage; +import com.threerings.io.Streamable; + +/** + * Provides a server-wide history of chat messages. + */ +@Singleton +public class ChatHistory +{ + /** + * Recorded parcel of chat for historical purposes, maintained by + * {@link #record(ChatChannel, UserMessage, Name...)}, + * {@link #get(Name)}, and {@link #clear(Name)}. + */ + public static class Entry + implements Streamable + { + /** The channel on which the message was sent, of null if the channel manager was not + * used. */ + public ChatChannel channel; + + /** The message sent. */ + public ChatMessage message; + + /** For deserialization. */ + public Entry () + { + } + + /** + * Creates a new history entry. + */ + public Entry (ChatChannel channel, ChatMessage message) + { + this.channel = channel; + this.message = message; + } + } + + /** + * Creates a new chat history, automatically registering a message observer with + * {@link SpeakUtil}. + */ + public ChatHistory () + { + SpeakUtil.registerMessageObserver(new SpeakUtil.MessageObserver() { + public void messageDelivered (Name hearer, UserMessage message) { + record(null, message, hearer); + } + }); + } + + /** + * Returns a list of {@link Entry} objects, one for each message to which this user has been + * privy in the recent past. If the given name implements {@link KeepNoHistory}, null is + * returned. + */ + public List get (Name username) + { + List history = getList(username); + if (history != null) { + prune(System.currentTimeMillis(), history); + } + return history; + } + + /** + * Clears the chat history for the specified user. + */ + public void clear (Name username) + { + // Log.info("Clearing history for " + username + "."); + _histories.remove(username); + } + + /** + * Records the specified channel and message to the specified users' chat histories. If {@link + * ChatMessage#timestamp} is not already filled in, it will be. + */ + public void record (ChatChannel channel, UserMessage msg, Name ...usernames) + { + // fill in the message's time stamp if necessary + if (msg.timestamp == 0L) { + msg.timestamp = System.currentTimeMillis(); + } + + for (Name username : usernames) { + // add the message to this user's chat history + List history = getList(username); + if (history == null) { + continue; + } + history.add(new Entry(channel, msg)); + + // if the history is big enough, potentially prune it (we always prune when asked for + // the history, so this is just to balance memory usage with CPU expense) + if (history.size() > 15) { + prune(msg.timestamp, history); + } + } + } + + /** + * Returns this user's chat history, creating one if necessary. If the given name implements + * {@link KeepNoHistory}, null is returned. + */ + protected List getList (Name username) + { + if (username instanceof KeepNoHistory) { + return null; + } + List history = _histories.get(username); + if (history == null) { + _histories.put(username, history = Lists.newArrayList()); + } + return history; + } + + /** + * Prunes all messages from this history which are expired. + */ + protected void prune (long now, List history) + { + int prunepos = 0; + for (int ll = history.size(); prunepos < ll; prunepos++) { + Entry entry = history.get(prunepos); + if (now - entry.message.timestamp < HISTORY_EXPIRATION) { + break; // stop when we get to the first valid message + } + } + history.subList(0, prunepos).clear(); + } + + /** Recent chat history for the server. */ + protected Map> _histories = Maps.newHashMap(); + + /** The amount of time before chat history becomes... history. */ + protected static final long HISTORY_EXPIRATION = 5L * 60L * 1000L; +} diff --git a/src/main/java/com/threerings/crowd/chat/server/SpeakUtil.java b/src/main/java/com/threerings/crowd/chat/server/SpeakUtil.java index 7f76833e0..7d83898d4 100644 --- a/src/main/java/com/threerings/crowd/chat/server/SpeakUtil.java +++ b/src/main/java/com/threerings/crowd/chat/server/SpeakUtil.java @@ -21,12 +21,6 @@ package com.threerings.crowd.chat.server; -import java.util.List; -import java.util.Map; - -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; - import com.samskivert.util.ObserverList; import com.threerings.util.Name; @@ -34,15 +28,12 @@ import com.threerings.util.Name; import com.threerings.presents.dobj.DObject; import com.threerings.presents.dobj.RootDObjectManager; -import com.threerings.crowd.chat.data.ChatChannel; import com.threerings.crowd.chat.data.ChatCodes; import com.threerings.crowd.chat.data.ChatMessage; -import com.threerings.crowd.chat.data.KeepNoHistory; import com.threerings.crowd.chat.data.SpeakObject; import com.threerings.crowd.chat.data.SystemMessage; import com.threerings.crowd.chat.data.UserMessage; import com.threerings.crowd.data.BodyObject; -import com.threerings.io.Streamable; import static com.threerings.crowd.Log.log; @@ -63,36 +54,6 @@ public class SpeakUtil void messageDelivered (Name hearer, UserMessage message); } - /** - * Recorded parcel of chat for historical purposes, maintained by - * {@link #recordToChatHistory(ChatChannel, UserMessage, Name...)}, - * {@link #getChatHistory(Name)}, and {@link #clearHistory(Name)}. - */ - public static class ChatHistoryEntry - implements Streamable - { - /** The channel on which the message was sent, of null if the channel manager was not - * used. */ - public ChatChannel channel; - - /** The message sent. */ - public ChatMessage message; - - /** For deserialization. */ - public ChatHistoryEntry () - { - } - - /** - * Creates a new history entry. - */ - public ChatHistoryEntry (ChatChannel channel, ChatMessage message) - { - this.channel = channel; - this.message = message; - } - } - /** * Registers a {@link MessageObserver} to be notified whenever a user-originated chat message * is heard by another user. @@ -231,56 +192,6 @@ public class SpeakUtil } } - /** - * Returns a list of {@link ChatMessage} objects to which this user has been privy in the - * recent past. If the given name implements {@link KeepNoHistory}, null is returned. - */ - public static List getChatHistory (Name username) - { - List history = getHistoryList(username); - if (history != null) { - pruneHistory(System.currentTimeMillis(), history); - } - return history; - } - - /** - * Called to clear the chat history for the specified user. - */ - public static void clearHistory (Name username) - { - // Log.info("Clearing history for " + username + "."); - _histories.remove(username); - } - - /** - * Records the specified channel and message to the specified users' chat histories. If {@link - * ChatMessage#timestamp} is not already filled in, it will be. - */ - public static void recordToChatHistory ( - ChatChannel channel, UserMessage msg, Name... usernames) - { - // fill in the message's time stamp if necessary - if (msg.timestamp == 0L) { - msg.timestamp = System.currentTimeMillis(); - } - - for (Name username : usernames) { - // add the message to this user's chat history - List history = getHistoryList(username); - if (history == null) { - continue; - } - history.add(new ChatHistoryEntry(channel, msg)); - - // if the history is big enough, potentially prune it (we always prune when asked for - // the history, so this is just to balance memory usage with CPU expense) - if (history.size() > 15) { - pruneHistory(msg.timestamp, history); - } - } - } - /** * Notes that the specified user was privy to the specified message. If {@link * ChatMessage#timestamp} is not already filled in, it will be. @@ -292,7 +203,6 @@ public class SpeakUtil msg.timestamp = System.currentTimeMillis(); } - recordToChatHistory(null, msg, username); // Log.info("Noted that " + username + " heard " + msg + "."); // notify any message observers @@ -308,37 +218,6 @@ public class SpeakUtil sendMessage(speakObj, new SystemMessage(message, bundle, level)); } - /** - * Returns this user's chat history, creating one if necessary. If the given name implements - * {@link KeepNoHistory}, null is returned. - */ - protected static List getHistoryList (Name username) - { - if (username instanceof KeepNoHistory) { - return null; - } - List history = _histories.get(username); - if (history == null) { - _histories.put(username, history = Lists.newArrayList()); - } - return history; - } - - /** - * Prunes all messages from this history which are expired. - */ - protected static void pruneHistory (long now, List history) - { - int prunepos = 0; - for (int ll = history.size(); prunepos < ll; prunepos++) { - ChatHistoryEntry entry = history.get(prunepos); - if (now - entry.message.timestamp < HISTORY_EXPIRATION) { - break; // stop when we get to the first valid message - } - } - history.subList(0, prunepos).clear(); - } - /** Used to note the recipients of a chat message. */ protected static class MessageMapper implements SpeakObject.ListenerOp { @@ -375,9 +254,6 @@ public class SpeakUtil protected UserMessage _message; } - /** Recent chat history for the server. */ - protected static Map> _histories = Maps.newHashMap(); - /** Used to note the recipients of a chat message. */ protected static MessageMapper _messageMapper = new MessageMapper(); @@ -386,7 +262,4 @@ public class SpeakUtil /** Used to notify our {@link MessageObserver}s. */ protected static MessageObserverOp _messageOp = new MessageObserverOp(); - - /** The amount of time before chat history becomes... history. */ - protected static final long HISTORY_EXPIRATION = 5L * 60L * 1000L; } diff --git a/src/main/java/com/threerings/crowd/server/CrowdSession.java b/src/main/java/com/threerings/crowd/server/CrowdSession.java index 5bf512313..f3cbe16bd 100644 --- a/src/main/java/com/threerings/crowd/server/CrowdSession.java +++ b/src/main/java/com/threerings/crowd/server/CrowdSession.java @@ -25,6 +25,7 @@ import com.google.inject.Inject; import com.threerings.presents.server.PresentsSession; +import com.threerings.crowd.chat.server.ChatHistory; import com.threerings.crowd.chat.server.SpeakUtil; import com.threerings.crowd.data.BodyObject; import com.threerings.crowd.data.OccupantInfo; @@ -80,7 +81,7 @@ public class CrowdSession extends PresentsSession _bodyman.updateOccupantStatus(body, OccupantInfo.ACTIVE); // clear our chat history - SpeakUtil.clearHistory(body.getVisibleName()); + _chatHistory.clear(body.getVisibleName()); } /** @@ -97,4 +98,5 @@ public class CrowdSession extends PresentsSession @Inject protected BodyLocator _locator; @Inject protected BodyManager _bodyman; @Inject protected LocationManager _locman; + @Inject protected ChatHistory _chatHistory; }