Convert Narya (most of the way) over to a Maven Ant task based build. The
ActionScript bits remain belligerent, but the Java stuff is mostly shipshape. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@6222 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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.client;
|
||||
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.client.InvocationService;
|
||||
|
||||
import com.threerings.crowd.chat.data.ChatChannel;
|
||||
|
||||
/**
|
||||
* Provides a way for clients to speak on chat channels.
|
||||
*/
|
||||
public interface ChannelSpeakService extends InvocationService
|
||||
{
|
||||
/**
|
||||
* Requests to speak the supplied message on the specified channel.
|
||||
*/
|
||||
public void speak (Client client, ChatChannel channel, String message, byte mode);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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.client;
|
||||
|
||||
import com.threerings.crowd.chat.data.ChatMessage;
|
||||
|
||||
/**
|
||||
* A chat display provides a means by which chat messages can be
|
||||
* displayed. The chat display will be notified when chat messages of
|
||||
* various sorts have been received by the client.
|
||||
*/
|
||||
public interface ChatDisplay
|
||||
{
|
||||
/**
|
||||
* Called to clear the chat display.
|
||||
*/
|
||||
void clear ();
|
||||
|
||||
/**
|
||||
* Called to display a chat message.
|
||||
*
|
||||
* @param alreadyDisplayed true if a previous chat display in the list has
|
||||
* already displayed this message, false otherwise.
|
||||
*
|
||||
* @return true if the message was displayed, false if not.
|
||||
*/
|
||||
boolean displayMessage (ChatMessage msg, boolean alreadyDisplayed);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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.client;
|
||||
|
||||
import com.threerings.util.Name;
|
||||
|
||||
/**
|
||||
* Filters messages chat messages to or from the server.
|
||||
*/
|
||||
public interface ChatFilter
|
||||
{
|
||||
/**
|
||||
* Filter a chat message.
|
||||
* @param msg the message text to be filtered.
|
||||
* @param otherUser an optional argument that represents the target or the speaker, depending
|
||||
* on 'outgoing', and can be considered in filtering if it is provided.
|
||||
* @param outgoing true if the message is going out to the server.
|
||||
*
|
||||
* @return the filtered message, or null to block it completely.
|
||||
*/
|
||||
String filter (String msg, Name otherUser, boolean outgoing);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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.client;
|
||||
|
||||
import com.threerings.util.Name;
|
||||
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.client.InvocationService;
|
||||
|
||||
/**
|
||||
* The chat services provide a mechanism by which the client can broadcast chat messages to all
|
||||
* clients that are subscribed to a particular place object or directly to a particular client.
|
||||
* These services should not be used directly, but instead should be accessed via the
|
||||
* {@link ChatDirector}.
|
||||
*/
|
||||
public interface ChatService extends InvocationService
|
||||
{
|
||||
/**
|
||||
* Used to communicate the response to a {@link ChatService#tell} request.
|
||||
*/
|
||||
public static interface TellListener extends InvocationListener
|
||||
{
|
||||
/**
|
||||
* Communicates the response to a {@link ChatService#tell} request.
|
||||
*
|
||||
* @param idleTime the number of ms the tellee has been idle or 0L if they are not idle.
|
||||
* @param awayMessage the away message configured by the told player or null if they have
|
||||
* no away message.
|
||||
*/
|
||||
void tellSucceeded (long idleTime, String awayMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that a tell message be delivered to the user with username equal to
|
||||
* <code>target</code>.
|
||||
*
|
||||
* @param client a connected, operational client instance.
|
||||
* @param target the username of the user to which the tell message should be delivered.
|
||||
* @param message the contents of the message.
|
||||
* @param listener the reference that will receive the tell response.
|
||||
*/
|
||||
void tell (Client client, Name target, String message, TellListener listener);
|
||||
|
||||
/**
|
||||
* Requests that a message be broadcast to all users in the system.
|
||||
*
|
||||
* @param client a connected, operational client instance.
|
||||
* @param message the contents of the message.
|
||||
* @param listener the reference that will receive a failure response.
|
||||
*/
|
||||
void broadcast (Client client, String message, InvocationListener listener);
|
||||
|
||||
/**
|
||||
* Sets this client's away message. If the message is null or the empty string, the away
|
||||
* message will be cleared.
|
||||
*/
|
||||
void away (Client client, String message);
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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.client;
|
||||
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.samskivert.util.RandomUtil;
|
||||
import com.threerings.util.Name;
|
||||
|
||||
import static com.threerings.crowd.Log.log;
|
||||
|
||||
/**
|
||||
* A chat filter that can filter out curse words from user chat.
|
||||
*/
|
||||
public abstract class CurseFilter implements ChatFilter
|
||||
{
|
||||
/** Indicates how messages should be handled. */
|
||||
public enum Mode { DROP, COMIC, VERNACULAR, UNFILTERED; }
|
||||
|
||||
/**
|
||||
* Creates a curse filter. The curse words should be a string in the following format:
|
||||
*
|
||||
* <pre>
|
||||
* *penis*=John_Thomas shit*=barnacle muff=britches
|
||||
* </pre>
|
||||
*
|
||||
* The key/value pairs are separated by spaces, * matches word characters and the value after
|
||||
* the = is the string into which to convert the text when converting to the vernacular.
|
||||
* Underscores in the target string will be turned into spaces.
|
||||
*
|
||||
* <p> And stopWords should be in the following format:
|
||||
*
|
||||
* <pre>
|
||||
* *faggot* rape rapes raped raping
|
||||
* </pre>
|
||||
*
|
||||
* Words are separated by spaces and * matches any other word characters.
|
||||
*/
|
||||
public CurseFilter (String curseWords, String stopWords)
|
||||
{
|
||||
configureCurseWords(curseWords);
|
||||
configureStopWords(stopWords);
|
||||
}
|
||||
|
||||
/**
|
||||
* The client will need to provide a way to look up our current chat filter mode.
|
||||
*/
|
||||
public abstract Mode getFilterMode ();
|
||||
|
||||
// from interface ChatFilter
|
||||
public String filter (String msg, Name otherUser, boolean outgoing)
|
||||
{
|
||||
// first, check against the drop-always list
|
||||
_stopMatcher.reset(msg);
|
||||
if (_stopMatcher.find()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// then see what kind of curse filtering the user has configured
|
||||
Mode level = getFilterMode();
|
||||
if (level == Mode.UNFILTERED) {
|
||||
return msg;
|
||||
}
|
||||
|
||||
StringBuffer inbuf = new StringBuffer(msg);
|
||||
StringBuffer outbuf = new StringBuffer(msg.length());
|
||||
for (int ii=0, nn=_matchers.length; ii < nn; ii++) {
|
||||
Matcher m = _matchers[ii];
|
||||
m.reset(inbuf);
|
||||
while (m.find()) {
|
||||
switch (level) {
|
||||
case DROP:
|
||||
return null;
|
||||
|
||||
case COMIC:
|
||||
m.appendReplacement(outbuf,
|
||||
_replacements[ii].replace(" ", comicChars(_comicLength[ii])));
|
||||
break;
|
||||
|
||||
case VERNACULAR:
|
||||
String vernacular = _vernacular[ii];
|
||||
if (Character.isUpperCase(m.group(2).codePointAt(0))) {
|
||||
int firstCharLen = Character.charCount(vernacular.codePointAt(0));
|
||||
vernacular = vernacular.substring(0, firstCharLen).toUpperCase() +
|
||||
vernacular.substring(firstCharLen);
|
||||
}
|
||||
m.appendReplacement(outbuf, _replacements[ii].replace(" ", vernacular));
|
||||
break;
|
||||
|
||||
case UNFILTERED:
|
||||
// We returned the msg unadulterated above in this case, so it should be
|
||||
// impossible to wind up here, but let's enumerate it so we can let the compiler
|
||||
// scream about missing enum values in a switch
|
||||
log.warning("Omg? We're trying to filter chat even though we're unfiltered?");
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
if (outbuf.length() == 0) {
|
||||
// optimization: if we didn't find a match, jump to the next
|
||||
// pattern without doing any StringBuilder jimmying
|
||||
continue;
|
||||
}
|
||||
m.appendTail(outbuf);
|
||||
|
||||
// swap the buffers around and clear the output
|
||||
StringBuffer temp = inbuf;
|
||||
inbuf = outbuf;
|
||||
outbuf = temp;
|
||||
outbuf.setLength(0);
|
||||
}
|
||||
|
||||
return inbuf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the curse word portion of our filtering.
|
||||
*/
|
||||
protected void configureCurseWords (String curseWords)
|
||||
{
|
||||
StringTokenizer st = new StringTokenizer(curseWords);
|
||||
int numWords = st.countTokens();
|
||||
|
||||
_matchers = new Matcher[numWords];
|
||||
_replacements = new String[numWords];
|
||||
_vernacular = new String[numWords];
|
||||
_comicLength = new int[numWords];
|
||||
|
||||
for (int ii=0; ii < numWords; ii++) {
|
||||
String mapping = st.nextToken();
|
||||
StringTokenizer st2 = new StringTokenizer(mapping, "=");
|
||||
if (st2.countTokens() != 2) {
|
||||
log.warning("Something looks wrong in the x.cursewords properties (" +
|
||||
mapping + "), skipping.");
|
||||
continue;
|
||||
}
|
||||
String curse = st2.nextToken();
|
||||
|
||||
String s = "";
|
||||
String p = "";
|
||||
if (curse.startsWith("*")) {
|
||||
curse = curse.substring(1);
|
||||
p += "([a-zA-Z]*)";
|
||||
s += "$1";
|
||||
} else {
|
||||
p += "()";
|
||||
}
|
||||
s += " ";
|
||||
p += " ";
|
||||
if (curse.endsWith("*")) {
|
||||
curse = curse.substring(0, curse.length() - 1);
|
||||
p += "([a-zA-Z]*)";
|
||||
s += "$3";
|
||||
}
|
||||
|
||||
String pattern = "\\b" + p.replace(" ", "(" + curse + ")") + "\\b";
|
||||
Pattern pat = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
|
||||
_matchers[ii] = pat.matcher("");
|
||||
_replacements[ii] = s;
|
||||
_vernacular[ii] = st2.nextToken().replace('_', ' ');
|
||||
_comicLength[ii] = curse.codePointCount(0, curse.length());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the words that will stop.
|
||||
*/
|
||||
protected void configureStopWords (String stopWords)
|
||||
{
|
||||
StringTokenizer st = new StringTokenizer(stopWords);
|
||||
|
||||
String pattern = "";
|
||||
while (st.hasMoreTokens()) {
|
||||
if ("".equals(pattern)) {
|
||||
pattern += "(";
|
||||
} else {
|
||||
pattern += "|";
|
||||
}
|
||||
pattern += getStopWordRegexp(st.nextToken());
|
||||
}
|
||||
pattern += ")";
|
||||
|
||||
setStopPattern(pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets our stop word matcher to one for the given regular expression.
|
||||
*/
|
||||
protected void setStopPattern (String pattern)
|
||||
{
|
||||
_stopMatcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns a naughty word into a regular expression to catch it.
|
||||
*/
|
||||
protected String getStopWordRegexp (String word)
|
||||
{
|
||||
return "\\b" + word.replace("*", "[A-Za-z]*") + "\\b";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a comicy replacement of the specified length.
|
||||
*/
|
||||
protected String comicChars (int length)
|
||||
{
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (int ii=0; ii < length; ii++) {
|
||||
buf.append(RandomUtil.pickRandom(COMIC_CHARS));
|
||||
}
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/** A matcher that will always cause a message to be dropped if it matches. */
|
||||
protected Matcher _stopMatcher;
|
||||
|
||||
/** Matchers for each curseword. */
|
||||
protected Matcher[] _matchers;
|
||||
|
||||
/** Length of comic-y replacements for each curseword. */
|
||||
protected int[] _comicLength;
|
||||
|
||||
/** Replacements. */
|
||||
protected String[] _replacements;
|
||||
|
||||
/** Replacements for each curseword "in the vernacular". */
|
||||
protected String[] _vernacular;
|
||||
|
||||
/** Comic replacement characters. */
|
||||
protected static final String[] COMIC_CHARS = { "!", "@", "#", "%", "&", "*" };
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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.client;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.samskivert.util.ObserverList;
|
||||
|
||||
import com.threerings.crowd.chat.data.ChatMessage;
|
||||
|
||||
/**
|
||||
* Stores chat history.
|
||||
*/
|
||||
public class HistoryList extends ArrayList<ChatMessage>
|
||||
implements ChatDisplay
|
||||
{
|
||||
/** An interface for chat history observers. */
|
||||
public interface Observer {
|
||||
/** Called when messages have been added or removed from the chat history.
|
||||
* @param adjustment the number of messages that have been added (+) or removed (-). */
|
||||
void historyUpdated (int adjustment);
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public boolean displayMessage (ChatMessage msg, boolean alreadyDisplayed)
|
||||
{
|
||||
// see if we're full, and if so, clear out a bunch of old stuff
|
||||
int adjusted;
|
||||
if (size() == MAX_HISTORY) {
|
||||
removeRange(0, PRUNE_HISTORY);
|
||||
adjusted = PRUNE_HISTORY;
|
||||
|
||||
} else {
|
||||
adjusted = 0;
|
||||
}
|
||||
|
||||
// add the message to the history
|
||||
add(msg);
|
||||
|
||||
// notify observers that something changed
|
||||
notify(adjusted);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear ()
|
||||
{
|
||||
// see how many entries we're clearing out..
|
||||
int adjusted = size();
|
||||
super.clear();
|
||||
|
||||
// and notify the chat displays of that fact
|
||||
notify(adjusted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an {@link Observer} that wants to know about changes to the history.
|
||||
*/
|
||||
public void addObserver (Observer obs)
|
||||
{
|
||||
_obs.add(obs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a {@link Observer} from hearing about changes to the history.
|
||||
*/
|
||||
public void removeObserver (Observer obs)
|
||||
{
|
||||
_obs.remove(obs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies listening {@link Observer}s that there has been a change to this history.
|
||||
*/
|
||||
protected void notify (int adjustment)
|
||||
{
|
||||
_historyUpdatedOp.setAdjustment(adjustment);
|
||||
_obs.apply(_historyUpdatedOp);
|
||||
}
|
||||
|
||||
protected static class HistoryUpdatedOp
|
||||
implements ObserverList.ObserverOp<Observer>
|
||||
{
|
||||
public void setAdjustment (int adjustment) {
|
||||
_adjustment = adjustment;
|
||||
}
|
||||
|
||||
public boolean apply (Observer obs) {
|
||||
obs.historyUpdated(_adjustment);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected int _adjustment;
|
||||
}
|
||||
|
||||
/** A list of {@link Observer}s interested in history changes. */
|
||||
protected ObserverList<Observer> _obs = ObserverList.newFastUnsafe();
|
||||
|
||||
/** An operation used to notify observers of history updates. */
|
||||
protected HistoryList.HistoryUpdatedOp _historyUpdatedOp = new HistoryUpdatedOp();
|
||||
|
||||
/** The maximum number of history entries we'll keep. */
|
||||
protected static final int MAX_HISTORY = 2000;
|
||||
|
||||
/** The number of history entries we'll prune when we hit the max. */
|
||||
protected static final int PRUNE_HISTORY = 200;
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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.client;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import com.samskivert.util.ObserverList;
|
||||
|
||||
import com.threerings.util.MessageBundle;
|
||||
import com.threerings.util.Name;
|
||||
|
||||
import com.threerings.presents.client.BasicDirector;
|
||||
|
||||
import com.threerings.crowd.util.CrowdContext;
|
||||
|
||||
/**
|
||||
* Manages the mutelist.
|
||||
*
|
||||
* TODO: This class right now is pretty much just a placeholder.
|
||||
*/
|
||||
public class MuteDirector extends BasicDirector
|
||||
implements ChatFilter
|
||||
{
|
||||
/**
|
||||
* An interface that can be registered with the MuteDirector to
|
||||
* receive notifications to the mutelist.
|
||||
*/
|
||||
public static interface MuteObserver
|
||||
{
|
||||
/**
|
||||
* The specified player was added or removed from the mutelist.
|
||||
*/
|
||||
void muteChanged (Name playername, boolean nowMuted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be instantiated after the ChatDirector.
|
||||
*/
|
||||
public MuteDirector (CrowdContext ctx)
|
||||
{
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the mute director with the specified list of initial mutees.
|
||||
*/
|
||||
public MuteDirector (CrowdContext ctx, Name[] list)
|
||||
{
|
||||
this(ctx);
|
||||
|
||||
Collections.addAll(_mutelist, list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to shut down the mute director.
|
||||
*/
|
||||
public void shutdown ()
|
||||
{
|
||||
if (_chatdir != null) {
|
||||
_chatdir.removeChatFilter(this);
|
||||
_chatdir = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the required ChatDirector.
|
||||
*/
|
||||
public void setChatDirector (ChatDirector chatdir)
|
||||
{
|
||||
if (_chatdir == null) {
|
||||
_chatdir = chatdir;
|
||||
_chatdir.addChatFilter(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the specified mutelist observer.
|
||||
*/
|
||||
public void addMuteObserver (MuteObserver obs)
|
||||
{
|
||||
_observers.add(obs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified mutelist observer.
|
||||
*/
|
||||
public void removeMuteObserver (MuteObserver obs)
|
||||
{
|
||||
_observers.remove(obs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if the specified user is muted.
|
||||
*/
|
||||
public boolean isMuted (Name username)
|
||||
{
|
||||
return _mutelist.contains(username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mute or unmute the specified user.
|
||||
*/
|
||||
public void setMuted (Name username, boolean mute)
|
||||
{
|
||||
boolean changed = mute ? _mutelist.add(username) : _mutelist.remove(username);
|
||||
String feedback;
|
||||
if (mute) {
|
||||
feedback = "m.muted";
|
||||
} else {
|
||||
feedback = changed ? "m.unmuted" : "m.notmuted";
|
||||
}
|
||||
|
||||
// always give some feedback to the user
|
||||
_chatdir.displayFeedback(null, MessageBundle.tcompose(feedback, username));
|
||||
|
||||
// if the mutelist actually changed, notify observers
|
||||
if (changed) {
|
||||
notifyObservers(username, mute);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a list of the currently muted players.
|
||||
*
|
||||
* This list may be out of date immediately upon returning from this method.
|
||||
*/
|
||||
public Name[] getMuted ()
|
||||
{
|
||||
return _mutelist.toArray(new Name[_mutelist.size()]);
|
||||
}
|
||||
|
||||
// documentation inherited from interface ChatFilter
|
||||
public String filter (String msg, Name otherUser, boolean outgoing)
|
||||
{
|
||||
// we are only concerned with filtering things going to or coming
|
||||
// from muted users
|
||||
if ((otherUser != null) && isMuted(otherUser)) {
|
||||
// if it was outgoing, explain the dropped message, otherwise
|
||||
// silently drop
|
||||
if (outgoing) {
|
||||
_chatdir.displayFeedback(null, "m.no_tell_mute");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify our observers of a change in the mutelist.
|
||||
*/
|
||||
protected void notifyObservers (final Name username, final boolean muted)
|
||||
{
|
||||
_observers.apply(new ObserverList.ObserverOp<MuteObserver>() {
|
||||
public boolean apply (MuteObserver observer) {
|
||||
observer.muteChanged(username, muted);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** The chat director that we're working hard for. */
|
||||
protected ChatDirector _chatdir;
|
||||
|
||||
/** The mutelist. */
|
||||
protected HashSet<Name> _mutelist = Sets.newHashSet();
|
||||
|
||||
/** List of mutelist observers. */
|
||||
protected ObserverList<MuteObserver> _observers =
|
||||
new ObserverList<MuteObserver>(ObserverList.FAST_UNSAFE_NOTIFY);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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.client;
|
||||
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.client.InvocationService;
|
||||
|
||||
/**
|
||||
* Provides a means by which "speaking" can be allowed among subscribers
|
||||
* of a particular distributed object.
|
||||
*/
|
||||
public interface SpeakService extends InvocationService
|
||||
{
|
||||
/**
|
||||
* Issues a request to speak "on" the distributed object via which
|
||||
* this speak service was provided.
|
||||
*
|
||||
* @param message the message to be spoken.
|
||||
* @param mode the "mode" of the message. This is an opaque value that
|
||||
* will be passed back down via the {@link ChatDirector} to the {@link
|
||||
* ChatDisplay} implementations which can interpret it in an
|
||||
* application specific manner. It's useful for differentiating
|
||||
* between regular speech, emotes, etc.
|
||||
*/
|
||||
void speak (Client client, String message, byte mode);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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.data;
|
||||
|
||||
import javax.annotation.Generated;
|
||||
|
||||
import com.threerings.crowd.chat.client.ChannelSpeakService;
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.data.InvocationMarshaller;
|
||||
|
||||
/**
|
||||
* Provides the implementation of the {@link ChannelSpeakService} interface
|
||||
* that marshalls the arguments and delivers the request to the provider
|
||||
* on the server. Also provides an implementation of the response listener
|
||||
* interfaces that marshall the response arguments and deliver them back
|
||||
* to the requesting client.
|
||||
*/
|
||||
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
|
||||
comments="Derived from ChannelSpeakService.java.")
|
||||
public class ChannelSpeakMarshaller extends InvocationMarshaller
|
||||
implements ChannelSpeakService
|
||||
{
|
||||
/** The method id used to dispatch {@link #speak} requests. */
|
||||
public static final int SPEAK = 1;
|
||||
|
||||
// from interface ChannelSpeakService
|
||||
public void speak (Client arg1, ChatChannel arg2, String arg3, byte arg4)
|
||||
{
|
||||
sendRequest(arg1, SPEAK, new Object[] {
|
||||
arg2, arg3, Byte.valueOf(arg4)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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.data;
|
||||
|
||||
import com.threerings.io.SimpleStreamableObject;
|
||||
|
||||
import com.threerings.presents.dobj.DSet;
|
||||
|
||||
/**
|
||||
* Represents a chat channel.
|
||||
*/
|
||||
public abstract class ChatChannel extends SimpleStreamableObject
|
||||
implements Comparable<ChatChannel>, DSet.Entry
|
||||
{
|
||||
// from interface Comparable<ChatChannel>
|
||||
public abstract int compareTo (ChatChannel other);
|
||||
|
||||
/**
|
||||
* Converts this channel into a unique name that can be used as the name of the distributed
|
||||
* lock used when resolving the channel.
|
||||
*/
|
||||
public abstract String getLockName ();
|
||||
|
||||
// from interface DSet.Entry
|
||||
public Comparable<?> getKey ()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals (Object other)
|
||||
{
|
||||
return compareTo((ChatChannel)other) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract int hashCode ();
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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.data;
|
||||
|
||||
import com.threerings.presents.data.InvocationCodes;
|
||||
import com.threerings.presents.data.Permission;
|
||||
|
||||
import com.threerings.crowd.chat.client.ChatDirector;
|
||||
import com.threerings.crowd.chat.client.SpeakService;
|
||||
|
||||
/**
|
||||
* Contains codes used by the chat invocation services.
|
||||
*/
|
||||
public interface ChatCodes extends InvocationCodes
|
||||
{
|
||||
/** A return value used by the ChatDirector and possibly other entities to indicate successful
|
||||
* processing of chat. */
|
||||
public static final String SUCCESS = "success";
|
||||
|
||||
/** The message identifier for a chat notification message. */
|
||||
public static final String CHAT_NOTIFICATION = "crowd.chat";
|
||||
|
||||
/** The message identifier for a chat channel notification message. */
|
||||
public static final String CHAT_CHANNEL_NOTIFICATION = "crowd.chat.channel";
|
||||
|
||||
/** The access control identifier for normal chat privileges. */
|
||||
public static final Permission CHAT_ACCESS = new Permission();
|
||||
|
||||
/** The access control identifier for broadcast chat privileges. */
|
||||
public static final Permission BROADCAST_ACCESS = new Permission();
|
||||
|
||||
/** The configuration key for idle time. */
|
||||
public static final String IDLE_TIME_KEY = "narya.chat.idle_time";
|
||||
|
||||
/** The default time after which a player is assumed idle. */
|
||||
public static final long DEFAULT_IDLE_TIME = 3 * 60 * 1000L;
|
||||
|
||||
/** The chat localtype code for chat messages delivered on the place object currently occupied
|
||||
* by the client. This is the only type of chat message that will be delivered unless the chat
|
||||
* director is explicitly provided with other chat message sources via {@link
|
||||
* ChatDirector#addAuxiliarySource}. */
|
||||
public static final String PLACE_CHAT_TYPE = "placeChat";
|
||||
|
||||
/** The chat localtype for messages received on the user object. */
|
||||
public static final String USER_CHAT_TYPE = "userChat";
|
||||
|
||||
/** The default mode used by {@link SpeakService#speak} requests. */
|
||||
public static final byte DEFAULT_MODE = 0;
|
||||
|
||||
/** A {@link SpeakService#speak} mode to indicate that the user is thinking what they're
|
||||
* saying, or is it that they're saying what they're thinking? */
|
||||
public static final byte THINK_MODE = 1;
|
||||
|
||||
/** A {@link SpeakService#speak} mode to indicate that a speak is actually an emote. */
|
||||
public static final byte EMOTE_MODE = 2;
|
||||
|
||||
/** A {@link SpeakService#speak} mode to indicate that a speak is actually a shout. */
|
||||
public static final byte SHOUT_MODE = 3;
|
||||
|
||||
/** A {@link SpeakService#speak} mode to indicate that a speak is actually a server-wide
|
||||
* broadcast. */
|
||||
public static final byte BROADCAST_MODE = 4;
|
||||
|
||||
/** The last chat mode defined in the interface. */
|
||||
public static final byte LAST_MODE = BROADCAST_MODE;
|
||||
|
||||
/** String translations for the various chat modes. */
|
||||
public static final String[] XLATE_MODES = {
|
||||
"default", "think", "emote", "shout", "broadcast"
|
||||
};
|
||||
|
||||
/** An error code delivered when the user targeted for a tell notification is not online. */
|
||||
public static final String USER_NOT_ONLINE = "m.user_not_online";
|
||||
|
||||
/** An error code delivered when the user targeted for a tell notification is disconnected. */
|
||||
public static final String USER_DISCONNECTED = "m.user_disconnected";
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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.data;
|
||||
|
||||
import javax.annotation.Generated;
|
||||
|
||||
import com.threerings.crowd.chat.client.ChatService;
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.client.InvocationService;
|
||||
import com.threerings.presents.data.InvocationMarshaller;
|
||||
import com.threerings.presents.dobj.InvocationResponseEvent;
|
||||
import com.threerings.util.Name;
|
||||
|
||||
/**
|
||||
* Provides the implementation of the {@link ChatService} interface
|
||||
* that marshalls the arguments and delivers the request to the provider
|
||||
* on the server. Also provides an implementation of the response listener
|
||||
* interfaces that marshall the response arguments and deliver them back
|
||||
* to the requesting client.
|
||||
*/
|
||||
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
|
||||
comments="Derived from ChatService.java.")
|
||||
public class ChatMarshaller extends InvocationMarshaller
|
||||
implements ChatService
|
||||
{
|
||||
/**
|
||||
* Marshalls results to implementations of {@link ChatService.TellListener}.
|
||||
*/
|
||||
public static class TellMarshaller extends ListenerMarshaller
|
||||
implements TellListener
|
||||
{
|
||||
/** The method id used to dispatch {@link #tellSucceeded}
|
||||
* responses. */
|
||||
public static final int TELL_SUCCEEDED = 1;
|
||||
|
||||
// from interface TellMarshaller
|
||||
public void tellSucceeded (long arg1, String arg2)
|
||||
{
|
||||
_invId = null;
|
||||
omgr.postEvent(new InvocationResponseEvent(
|
||||
callerOid, requestId, TELL_SUCCEEDED,
|
||||
new Object[] { Long.valueOf(arg1), arg2 }, transport));
|
||||
}
|
||||
|
||||
@Override // from InvocationMarshaller
|
||||
public void dispatchResponse (int methodId, Object[] args)
|
||||
{
|
||||
switch (methodId) {
|
||||
case TELL_SUCCEEDED:
|
||||
((TellListener)listener).tellSucceeded(
|
||||
((Long)args[0]).longValue(), (String)args[1]);
|
||||
return;
|
||||
|
||||
default:
|
||||
super.dispatchResponse(methodId, args);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The method id used to dispatch {@link #away} requests. */
|
||||
public static final int AWAY = 1;
|
||||
|
||||
// from interface ChatService
|
||||
public void away (Client arg1, String arg2)
|
||||
{
|
||||
sendRequest(arg1, AWAY, new Object[] {
|
||||
arg2
|
||||
});
|
||||
}
|
||||
|
||||
/** The method id used to dispatch {@link #broadcast} requests. */
|
||||
public static final int BROADCAST = 2;
|
||||
|
||||
// from interface ChatService
|
||||
public void broadcast (Client arg1, String arg2, InvocationService.InvocationListener arg3)
|
||||
{
|
||||
ListenerMarshaller listener3 = new ListenerMarshaller();
|
||||
listener3.listener = arg3;
|
||||
sendRequest(arg1, BROADCAST, new Object[] {
|
||||
arg2, listener3
|
||||
});
|
||||
}
|
||||
|
||||
/** The method id used to dispatch {@link #tell} requests. */
|
||||
public static final int TELL = 3;
|
||||
|
||||
// from interface ChatService
|
||||
public void tell (Client arg1, Name arg2, String arg3, ChatService.TellListener arg4)
|
||||
{
|
||||
ChatMarshaller.TellMarshaller listener4 = new ChatMarshaller.TellMarshaller();
|
||||
listener4.listener = arg4;
|
||||
sendRequest(arg1, TELL, new Object[] {
|
||||
arg2, arg3, listener4
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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.data;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.io.Streamable;
|
||||
|
||||
import com.threerings.util.ActionScript;
|
||||
|
||||
/**
|
||||
* The abstract base class of all the client-side ChatMessage objects.
|
||||
*/
|
||||
public abstract class ChatMessage
|
||||
implements Streamable
|
||||
{
|
||||
/** The actual text of the message. */
|
||||
public String message;
|
||||
|
||||
/** The bundle to use when translating this message. */
|
||||
public String bundle;
|
||||
|
||||
/** The client side 'localtype' of this chat, set to the type registered with an auxiliary
|
||||
* source in the ChatDirector. */
|
||||
public transient String localtype;
|
||||
|
||||
/** The client time that this message was created. */
|
||||
@ActionScript(type="int")
|
||||
public transient long timestamp;
|
||||
|
||||
/**
|
||||
* For all your unserialization needs.
|
||||
*/
|
||||
public ChatMessage ()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a ChatMessage.
|
||||
*/
|
||||
public ChatMessage (String message, String bundle)
|
||||
{
|
||||
this.message = message;
|
||||
this.bundle = bundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Once this message reaches the client, the information contained within is changed around a
|
||||
* bit.
|
||||
*/
|
||||
public void setClientInfo (String msg, String ltype)
|
||||
{
|
||||
message = msg;
|
||||
localtype = ltype;
|
||||
bundle = null;
|
||||
timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate message format for this message.
|
||||
*/
|
||||
public String getFormat ()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString ()
|
||||
{
|
||||
return StringUtil.shortClassName(this) + StringUtil.fieldsToString(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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.data;
|
||||
|
||||
import com.threerings.crowd.chat.server.SpeakUtil;
|
||||
import com.threerings.util.Name;
|
||||
|
||||
/**
|
||||
* Marks a {@link Name} as disinterested in chat history such that {@link SpeakUtil} will keep no
|
||||
* messages sent to it.
|
||||
*/
|
||||
public interface KeepNoHistory
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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.data;
|
||||
|
||||
import javax.annotation.Generated;
|
||||
|
||||
import com.threerings.crowd.chat.client.SpeakService;
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.data.InvocationMarshaller;
|
||||
|
||||
/**
|
||||
* Provides the implementation of the {@link SpeakService} interface
|
||||
* that marshalls the arguments and delivers the request to the provider
|
||||
* on the server. Also provides an implementation of the response listener
|
||||
* interfaces that marshall the response arguments and deliver them back
|
||||
* to the requesting client.
|
||||
*/
|
||||
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
|
||||
comments="Derived from SpeakService.java.")
|
||||
public class SpeakMarshaller extends InvocationMarshaller
|
||||
implements SpeakService
|
||||
{
|
||||
/** The method id used to dispatch {@link #speak} requests. */
|
||||
public static final int SPEAK = 1;
|
||||
|
||||
// from interface SpeakService
|
||||
public void speak (Client arg1, String arg2, byte arg3)
|
||||
{
|
||||
sendRequest(arg1, SPEAK, new Object[] {
|
||||
arg2, Byte.valueOf(arg3)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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.data;
|
||||
|
||||
import com.threerings.util.Name;
|
||||
|
||||
/**
|
||||
* Provides a mechanism by which the speak service can identify chat listeners so as to maintain a
|
||||
* recent history of all chat traffic on the server.
|
||||
*/
|
||||
public interface SpeakObject
|
||||
{
|
||||
/** Used in conjunction with {@link SpeakObject#applyToListeners}. */
|
||||
public static interface ListenerOp
|
||||
{
|
||||
/** Call this method if you only have access to body oids. */
|
||||
void apply (int bodyOid);
|
||||
|
||||
/** Call this method if you can provide usernames directly. */
|
||||
void apply (Name username);
|
||||
}
|
||||
|
||||
/**
|
||||
* The speak service will call this every time a chat message is delivered on this speak object
|
||||
* to note the listeners that received the message.
|
||||
*/
|
||||
void applyToListeners (ListenerOp op);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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.data;
|
||||
|
||||
/**
|
||||
* A ChatMessage that represents a message that came from the server and did not result from direct
|
||||
* user action.
|
||||
*/
|
||||
public class SystemMessage extends ChatMessage
|
||||
{
|
||||
/** Attention level constant to indicate that this message is merely providing the user with
|
||||
* information. */
|
||||
public static final byte INFO = 0;
|
||||
|
||||
/** Attention level constant to indicate that this message is the result of a user action. */
|
||||
public static final byte FEEDBACK = 1;
|
||||
|
||||
/** Attention level constant to indicate that some action is required. */
|
||||
public static final byte ATTENTION = 2;
|
||||
|
||||
/** The attention level of this message. */
|
||||
public byte attentionLevel;
|
||||
|
||||
// documentation inherited
|
||||
public SystemMessage ()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a SystemMessage.
|
||||
*/
|
||||
public SystemMessage (String message, String bundle, byte attentionLevel)
|
||||
{
|
||||
super(message, bundle);
|
||||
this.attentionLevel = attentionLevel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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.data;
|
||||
|
||||
import com.threerings.util.Name;
|
||||
|
||||
/**
|
||||
* A feedback message to indicate that a tell succeeded.
|
||||
*/
|
||||
public class TellFeedbackMessage extends UserMessage
|
||||
{
|
||||
/**
|
||||
* A tell feedback message is only composed on the client.
|
||||
*/
|
||||
public TellFeedbackMessage (Name target, String message, boolean failure)
|
||||
{
|
||||
super(target, null, message, ChatCodes.DEFAULT_MODE);
|
||||
_failure = failure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this is a failure feedback, false if it is successful tell feedback.
|
||||
*/
|
||||
public boolean isFailure ()
|
||||
{
|
||||
return _failure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormat ()
|
||||
{
|
||||
return _failure ? null : "m.told_format";
|
||||
}
|
||||
|
||||
protected boolean _failure;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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.data;
|
||||
|
||||
import com.threerings.util.Name;
|
||||
|
||||
/**
|
||||
* A ChatMessage representing a message that came from another user.
|
||||
*/
|
||||
public class UserMessage extends ChatMessage
|
||||
{
|
||||
/** The user that the message came from. */
|
||||
public Name speaker;
|
||||
|
||||
/** The mode of the message. @see ChatCodes.DEFAULT_MODE */
|
||||
public byte mode;
|
||||
|
||||
/**
|
||||
* For unserialization.
|
||||
*/
|
||||
public UserMessage ()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a user message.
|
||||
*/
|
||||
public UserMessage (Name speaker, String bundle, String message, byte mode)
|
||||
{
|
||||
super(message, bundle);
|
||||
this.speaker = speaker;
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a user message for a player originated tell (which has no bundle and is in the
|
||||
* default mode).
|
||||
*/
|
||||
public UserMessage (Name speaker, String message)
|
||||
{
|
||||
super(message, null);
|
||||
this.speaker = speaker;
|
||||
this.mode = ChatCodes.DEFAULT_MODE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name to display for the speaker. Some types of messages may wish to not use the
|
||||
* canonical name for the speaker and should thus override this function.
|
||||
*/
|
||||
public Name getSpeakerDisplayName ()
|
||||
{
|
||||
return speaker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormat ()
|
||||
{
|
||||
switch (mode) {
|
||||
case ChatCodes.THINK_MODE: return "m.think_format";
|
||||
case ChatCodes.EMOTE_MODE: return "m.emote_format";
|
||||
case ChatCodes.SHOUT_MODE: return "m.shout_format";
|
||||
case ChatCodes.BROADCAST_MODE: return "m.broadcast_format";
|
||||
default: // fall through
|
||||
}
|
||||
|
||||
if (ChatCodes.USER_CHAT_TYPE.equals(localtype)) {
|
||||
return "m.tell_format";
|
||||
}
|
||||
return "m.speak_format";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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.data;
|
||||
|
||||
import com.threerings.util.Name;
|
||||
|
||||
/**
|
||||
* A system message triggered by the activity of another user. If the user is muted we can suppress
|
||||
* this message, unlike a normal system message.
|
||||
*/
|
||||
public class UserSystemMessage extends SystemMessage
|
||||
{
|
||||
/** The "speaker" of this message, the user that triggered that this message be sent to us. */
|
||||
public Name speaker;
|
||||
|
||||
/** Suitable for unserialization. */
|
||||
public UserSystemMessage ()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a INFO-level UserSystemMessage.
|
||||
*/
|
||||
public UserSystemMessage (Name sender, String message, String bundle)
|
||||
{
|
||||
this(sender, message, bundle, INFO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a UserSystemMessage.
|
||||
*/
|
||||
public UserSystemMessage (Name sender, String message, String bundle, byte attentionLevel)
|
||||
{
|
||||
super(message, bundle, attentionLevel);
|
||||
this.speaker = sender;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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 javax.annotation.Generated;
|
||||
|
||||
import com.threerings.crowd.chat.data.ChannelSpeakMarshaller;
|
||||
import com.threerings.crowd.chat.data.ChatChannel;
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.server.InvocationDispatcher;
|
||||
import com.threerings.presents.server.InvocationException;
|
||||
|
||||
/**
|
||||
* Dispatches requests to the {@link ChannelSpeakProvider}.
|
||||
*/
|
||||
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
|
||||
comments="Derived from ChannelSpeakService.java.")
|
||||
public class ChannelSpeakDispatcher extends InvocationDispatcher<ChannelSpeakMarshaller>
|
||||
{
|
||||
/**
|
||||
* Creates a dispatcher that may be registered to dispatch invocation
|
||||
* service requests for the specified provider.
|
||||
*/
|
||||
public ChannelSpeakDispatcher (ChannelSpeakProvider provider)
|
||||
{
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelSpeakMarshaller createMarshaller ()
|
||||
{
|
||||
return new ChannelSpeakMarshaller();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchRequest (
|
||||
ClientObject source, int methodId, Object[] args)
|
||||
throws InvocationException
|
||||
{
|
||||
switch (methodId) {
|
||||
case ChannelSpeakMarshaller.SPEAK:
|
||||
((ChannelSpeakProvider)provider).speak(
|
||||
source, (ChatChannel)args[0], (String)args[1], ((Byte)args[2]).byteValue()
|
||||
);
|
||||
return;
|
||||
|
||||
default:
|
||||
super.dispatchRequest(source, methodId, args);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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 javax.annotation.Generated;
|
||||
|
||||
import com.threerings.crowd.chat.client.ChannelSpeakService;
|
||||
import com.threerings.crowd.chat.data.ChatChannel;
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.server.InvocationProvider;
|
||||
|
||||
/**
|
||||
* Defines the server-side of the {@link ChannelSpeakService}.
|
||||
*/
|
||||
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
|
||||
comments="Derived from ChannelSpeakService.java.")
|
||||
public interface ChannelSpeakProvider extends InvocationProvider
|
||||
{
|
||||
/**
|
||||
* Handles a {@link ChannelSpeakService#speak} request.
|
||||
*/
|
||||
void speak (ClientObject caller, ChatChannel arg1, String arg2, byte arg3);
|
||||
}
|
||||
@@ -0,0 +1,503 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.primitives.Longs;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import com.samskivert.util.ArrayIntSet;
|
||||
import com.samskivert.util.ResultListener;
|
||||
|
||||
import com.threerings.util.Name;
|
||||
|
||||
import com.threerings.presents.annotation.AnyThread;
|
||||
import com.threerings.presents.client.InvocationService;
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.server.InvocationException;
|
||||
import com.threerings.presents.server.InvocationManager;
|
||||
import com.threerings.presents.server.PresentsDObjectMgr;
|
||||
|
||||
import com.threerings.presents.peer.data.ClientInfo;
|
||||
import com.threerings.presents.peer.data.NodeObject;
|
||||
import com.threerings.presents.peer.server.PeerManager;
|
||||
import com.threerings.presents.peer.server.PeerManager.NodeRequest;
|
||||
import com.threerings.presents.peer.server.NodeRequestsListener;
|
||||
|
||||
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;
|
||||
import com.threerings.crowd.peer.data.CrowdNodeObject;
|
||||
import com.threerings.crowd.peer.server.CrowdPeerManager;
|
||||
|
||||
import static com.threerings.crowd.Log.log;
|
||||
|
||||
/**
|
||||
* Handles chat channel services.
|
||||
*/
|
||||
public abstract class ChatChannelManager
|
||||
implements ChannelSpeakProvider
|
||||
{
|
||||
/**
|
||||
* Value asynchronously returned by {@link #collectChatHistory} after polling all peer nodes.
|
||||
*/
|
||||
public static class ChatHistoryResult
|
||||
{
|
||||
/** The set of nodes that either did not reply within the timeout, or had a failure. */
|
||||
public Set<String> failedNodes;
|
||||
|
||||
/** The things in the user's chat history, aggregated from all nodes and sorted by
|
||||
* timestamp. */
|
||||
public List<ChatHistoryEntry> history;
|
||||
}
|
||||
|
||||
/**
|
||||
* When a body becomes a member of a channel, this method should be called so that any server
|
||||
* that happens to be hosting that channel can be told that the body in question is now a
|
||||
* participant.
|
||||
*/
|
||||
@AnyThread
|
||||
public void bodyAddedToChannel (ChatChannel channel, int bodyId)
|
||||
{
|
||||
_peerMan.invokeNodeAction(new ParticipantChanged(channel, bodyId, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* When a body loses channel membership, this method should be called so that any server that
|
||||
* happens to be hosting that channel can be told that the body in question is now a
|
||||
* participant.
|
||||
*/
|
||||
@AnyThread
|
||||
public void bodyRemovedFromChannel (ChatChannel channel, int bodyId)
|
||||
{
|
||||
_peerMan.invokeNodeAction(new ParticipantChanged(channel, bodyId, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects all chat messages heard by the given user on all peers.
|
||||
*/
|
||||
@AnyThread
|
||||
public void collectChatHistory (Name user, final ResultListener<ChatHistoryResult> lner)
|
||||
{
|
||||
NodeRequestsListener<List<ChatHistoryEntry>> listener =
|
||||
new NodeRequestsListener<List<ChatHistoryEntry>>() {
|
||||
public void requestsProcessed (NodeRequestsResult<List<ChatHistoryEntry>> rRes) {
|
||||
ChatHistoryResult chRes = new ChatHistoryResult();
|
||||
chRes.failedNodes = rRes.getNodeErrors().keySet();
|
||||
chRes.history = Lists.newArrayList(
|
||||
Iterables.concat(rRes.getNodeResults().values()));
|
||||
Collections.sort(chRes.history, SORT_BY_TIMESTAMP);
|
||||
lner.requestCompleted(chRes);
|
||||
}
|
||||
public void requestFailed (String cause) {
|
||||
lner.requestFailed(new InvocationException(cause));
|
||||
}
|
||||
};
|
||||
_peerMan.invokeNodeRequest(new ChatCollectionRequest(user), listener);
|
||||
}
|
||||
|
||||
// from interface ChannelSpeakProvider
|
||||
public void speak (ClientObject caller, final ChatChannel channel, String message, byte mode)
|
||||
{
|
||||
final UserMessage umsg = new UserMessage(
|
||||
((BodyObject)caller).getVisibleName(), null, message, mode);
|
||||
|
||||
// if we're hosting this channel, dispatch it directly
|
||||
if (_channels.containsKey(channel)) {
|
||||
dispatchSpeak(channel, umsg);
|
||||
return;
|
||||
}
|
||||
|
||||
// if we're resolving this channel, queue up our message for momentary deliver
|
||||
List<UserMessage> msgs = _resolving.get(channel);
|
||||
if (msgs != null) {
|
||||
msgs.add(umsg);
|
||||
return;
|
||||
}
|
||||
|
||||
// forward the speak request to the server that hosts the channel in question
|
||||
_peerMan.invokeNodeAction(new ForwardChannelSpeak(channel, umsg), new Runnable() {
|
||||
public void run () {
|
||||
_resolving.put(channel, Lists.newArrayList(umsg));
|
||||
resolveAndDispatch(channel);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates our singleton manager and registers our invocation service.
|
||||
*/
|
||||
@Inject protected ChatChannelManager (PresentsDObjectMgr omgr, InvocationManager invmgr)
|
||||
{
|
||||
invmgr.registerDispatcher(new ChannelSpeakDispatcher(this), CrowdCodes.CROWD_GROUP);
|
||||
|
||||
// create and start our idle channel closer; this will run as long as omgr is alive
|
||||
omgr.newInterval(new Runnable() {
|
||||
public void run () {
|
||||
closeIdleChannels();
|
||||
}
|
||||
}).schedule(IDLE_CHANNEL_CHECK_PERIOD, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the channel specified in the supplied action and then dispatches it.
|
||||
*/
|
||||
protected void resolveAndDispatch (final ChatChannel channel)
|
||||
{
|
||||
NodeObject.Lock lock = new NodeObject.Lock("ChatChannel", channel.getLockName());
|
||||
_peerMan.performWithLock(lock, new PeerManager.LockedOperation() {
|
||||
public void run () {
|
||||
((CrowdNodeObject)_peerMan.getNodeObject()).addToHostedChannels(channel);
|
||||
finishResolveAndDispatch(channel);
|
||||
}
|
||||
public void fail (String peerName) {
|
||||
List<UserMessage> msgs = _resolving.remove(channel);
|
||||
if (peerName == null) {
|
||||
log.warning("Failed to resolve chat channel due to lock failure",
|
||||
"channel", channel);
|
||||
} else {
|
||||
// some other peer resolved this channel first, so forward any queued messages
|
||||
// directly to that node
|
||||
for (UserMessage msg : msgs) {
|
||||
_peerMan.invokeNodeAction(peerName, new ForwardChannelSpeak(channel, msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the participant set for the specified chat channel and dispatches all pending
|
||||
* messages to the channel. End users of the chat channel system should override this method
|
||||
* and do what is necessary to resolve the channel's participant set and call {@link
|
||||
* #resolutionComplete} or {@link #resolutionFailed}.
|
||||
*/
|
||||
protected void finishResolveAndDispatch (ChatChannel channel)
|
||||
{
|
||||
resolutionComplete(channel, new ArrayIntSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* This should be called when a channel's participant set has been resolved.
|
||||
*/
|
||||
protected void resolutionComplete (ChatChannel channel, Set<Integer> parts)
|
||||
{
|
||||
// map the participants of our now resolved channel
|
||||
ChannelInfo info = new ChannelInfo();
|
||||
info.channel = channel;
|
||||
info.participants = parts;
|
||||
_channels.put(channel, info);
|
||||
|
||||
// dispatch any pending messages now that we know where they go
|
||||
for (UserMessage msg : _resolving.remove(channel)) {
|
||||
dispatchSpeak(channel, msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This should be called if channel resolution fails.
|
||||
*/
|
||||
protected void resolutionFailed (ChatChannel channel, Exception cause)
|
||||
{
|
||||
log.warning("Failed to resolve chat channel", "channel", channel, cause);
|
||||
|
||||
// alas, we just drop all pending messages because we're hosed
|
||||
_resolving.remove(channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that we dispatch the supplied message to all participants of the specified chat
|
||||
* channel. The speaker will be validated prior to dispatching the message as the originating
|
||||
* server does not have the information it needs to validate the speaker and must leave that to
|
||||
* us, the channel hosting server.
|
||||
*/
|
||||
protected void dispatchSpeak (ChatChannel channel, UserMessage message)
|
||||
{
|
||||
final ChannelInfo info = _channels.get(channel);
|
||||
if (info == null) {
|
||||
// TODO: maybe we should just reresolve the channel...
|
||||
log.warning("Requested to dispatch speak on unhosted channel", "channel", channel,
|
||||
"msg", message);
|
||||
return;
|
||||
}
|
||||
|
||||
// validate the speaker
|
||||
if (!info.participants.contains(getBodyId(message.speaker))) {
|
||||
log.warning("Dropping channel chat message from non-speaker", "channel", channel,
|
||||
"message", message);
|
||||
return;
|
||||
}
|
||||
|
||||
// note that we're dispatching a message on this channel
|
||||
info.lastMessage = System.currentTimeMillis();
|
||||
|
||||
// generate a mapping from node name to an array of body ids for the participants that are
|
||||
// currently on the node in question
|
||||
final Map<String,int[]> partMap = Maps.newHashMap();
|
||||
for (NodeObject nodeobj : _peerMan.getNodeObjects()) {
|
||||
ArrayIntSet nodeBodyIds = new ArrayIntSet();
|
||||
for (ClientInfo clinfo : nodeobj.clients) {
|
||||
int bodyId = getBodyId(((CrowdClientInfo)clinfo).visibleName);
|
||||
if (info.participants.contains(bodyId)) {
|
||||
nodeBodyIds.add(bodyId);
|
||||
}
|
||||
}
|
||||
partMap.put(nodeobj.nodeName, nodeBodyIds.toIntArray());
|
||||
}
|
||||
|
||||
for (Map.Entry<String,int[]> entry : partMap.entrySet()) {
|
||||
_peerMan.invokeNodeAction(
|
||||
entry.getKey(), new DispatchChannelSpeak(channel, message, entry.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delivers the supplied chat channel message to the specified bodies.
|
||||
*/
|
||||
protected void deliverSpeak (ChatChannel channel, UserMessage message, int[] bodyIds)
|
||||
{
|
||||
channel = intern(channel);
|
||||
for (int bodyId : bodyIds) {
|
||||
BodyObject bobj = getBodyObject(bodyId);
|
||||
if (bobj != null && shouldDeliverSpeak(channel, message, bobj)) {
|
||||
SpeakUtil.recordToChatHistory(channel, message, bobj.getVisibleName());
|
||||
bobj.postMessage(ChatCodes.CHAT_CHANNEL_NOTIFICATION, channel, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called periodically to check for and close any channels that have been idle too long.
|
||||
*/
|
||||
protected void closeIdleChannels ()
|
||||
{
|
||||
long now = System.currentTimeMillis();
|
||||
Iterator<Map.Entry<ChatChannel, ChannelInfo>> iter = _channels.entrySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
Map.Entry<ChatChannel, ChannelInfo> entry = iter.next();
|
||||
if (now - entry.getValue().lastMessage > IDLE_CHANNEL_CLOSE_TIME) {
|
||||
((CrowdNodeObject)_peerMan.getNodeObject()).removeFromHostedChannels(
|
||||
entry.getKey());
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ratifies the delivery of the supplied chat channel message to the specified body. Derived
|
||||
* classes can override this method to implement channel disabling, mute lists or any other
|
||||
* suppression they might need.
|
||||
*/
|
||||
protected boolean shouldDeliverSpeak (ChatChannel channel, UserMessage message, BodyObject body)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a widely referenced instance equivalent to the given channel, if one is available.
|
||||
* This reduces memory usage since clients send new channel instances with each message.
|
||||
*/
|
||||
protected ChatChannel intern (ChatChannel channel)
|
||||
{
|
||||
ChannelInfo chinfo = _channels.get(channel);
|
||||
if (chinfo != null) {
|
||||
return chinfo.channel;
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a speaker's visible name into a unique integer id. This is not the oid for this
|
||||
* speaker but rather a persistent integer identifier that can be passed between servers and
|
||||
* used to look up the speaker on the target server via a call to {@link #getBodyObject}. We
|
||||
* use this rather than names to avoid having to send (large) {@link Name} objects for every
|
||||
* channel participant to each individual peer that will be forwarding messages.
|
||||
*/
|
||||
protected abstract int getBodyId (Name speaker);
|
||||
|
||||
/**
|
||||
* Locates a body object from the given unique id. May return null.
|
||||
*/
|
||||
protected abstract BodyObject getBodyObject (int bodyId);
|
||||
|
||||
/** Forwards a channel speak request from the server hosting the message originator to the
|
||||
* server that is hosting the channel. */
|
||||
protected abstract static class ChannelAction extends PeerManager.NodeAction
|
||||
{
|
||||
public ChannelAction (ChatChannel channel) {
|
||||
_channel = channel;
|
||||
}
|
||||
public ChannelAction () {
|
||||
}
|
||||
@Override public boolean isApplicable (NodeObject nodeobj) {
|
||||
return ((CrowdNodeObject)nodeobj).hostedChannels.contains(_channel);
|
||||
}
|
||||
protected ChatChannel _channel;
|
||||
@Inject protected transient ChatChannelManager _channelMan;
|
||||
}
|
||||
|
||||
/** Informs the server hosting a channel that a body has been added to or removed from the
|
||||
* channel's participants set. */
|
||||
protected static class ParticipantChanged extends ChannelAction
|
||||
{
|
||||
public ParticipantChanged (ChatChannel channel, int bodyId, boolean added) {
|
||||
super(channel);
|
||||
_bodyId = bodyId;
|
||||
_added = added;
|
||||
}
|
||||
public ParticipantChanged () {
|
||||
}
|
||||
@Override protected void execute () {
|
||||
ChannelInfo info = _channelMan._channels.get(_channel);
|
||||
if (info != null) {
|
||||
if (_added) {
|
||||
info.participants.add(_bodyId);
|
||||
} else {
|
||||
info.participants.remove(_bodyId);
|
||||
}
|
||||
} else if (_channelMan._resolving.containsKey(_channel)) {
|
||||
log.warning("Oh for fuck's sake, distributed systems are complicated",
|
||||
"channel", _channel);
|
||||
}
|
||||
}
|
||||
protected int _bodyId;
|
||||
protected boolean _added;
|
||||
}
|
||||
|
||||
protected static class ChatCollectionRequest extends NodeRequest
|
||||
{
|
||||
public ChatCollectionRequest (Name user)
|
||||
{
|
||||
_user = user;
|
||||
}
|
||||
|
||||
public ChatCollectionRequest ()
|
||||
{
|
||||
}
|
||||
|
||||
@Override public boolean isApplicable (NodeObject nodeobj)
|
||||
{
|
||||
// poll all nodes
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override protected void execute (InvocationService.ResultListener listener)
|
||||
{
|
||||
// find all the UserMessages for the given user and send them back
|
||||
listener.requestProcessed(Lists.newArrayList(Iterables.filter(
|
||||
SpeakUtil.getChatHistory(_user), IS_USER_MESSAGE)));
|
||||
}
|
||||
|
||||
protected Name _user;
|
||||
}
|
||||
|
||||
protected static final Predicate<ChatHistoryEntry> IS_USER_MESSAGE =
|
||||
new Predicate<ChatHistoryEntry>() {
|
||||
public boolean apply (ChatHistoryEntry entry) {
|
||||
return entry.message instanceof UserMessage;
|
||||
}
|
||||
};
|
||||
|
||||
protected static final Comparator<ChatHistoryEntry> SORT_BY_TIMESTAMP =
|
||||
new Comparator<ChatHistoryEntry>() {
|
||||
public int compare (ChatHistoryEntry e1, ChatHistoryEntry e2) {
|
||||
return Longs.compare(e1.message.timestamp, e2.message.timestamp);
|
||||
}
|
||||
};
|
||||
|
||||
/** Forwards a channel speak request from the server hosting the message originator to the
|
||||
* server that is hosting the channel. */
|
||||
protected static class ForwardChannelSpeak extends ChannelAction
|
||||
{
|
||||
public ForwardChannelSpeak (ChatChannel channel, UserMessage message) {
|
||||
super(channel);
|
||||
_message = message;
|
||||
}
|
||||
public ForwardChannelSpeak () {
|
||||
}
|
||||
@Override protected void execute () {
|
||||
_channelMan.dispatchSpeak(_channel, _message);
|
||||
}
|
||||
protected UserMessage _message;
|
||||
}
|
||||
|
||||
/** Forwards a chat channel message to the server to which some subset of the channel
|
||||
* participants are connected so that it can dispatch the message on their body objects. */
|
||||
protected static class DispatchChannelSpeak extends ForwardChannelSpeak
|
||||
{
|
||||
public DispatchChannelSpeak (ChatChannel channel, UserMessage message, int[] bodyIds) {
|
||||
super(channel, message);
|
||||
_bodyIds = bodyIds;
|
||||
}
|
||||
public DispatchChannelSpeak () {
|
||||
}
|
||||
@Override public boolean isApplicable (NodeObject nodeobj) {
|
||||
return true; // not used
|
||||
}
|
||||
@Override protected void execute () {
|
||||
_channelMan.deliverSpeak(_channel, _message, _bodyIds);
|
||||
}
|
||||
protected int[] _bodyIds;
|
||||
}
|
||||
|
||||
/** Contains metadata for a particular channel. */
|
||||
protected static class ChannelInfo
|
||||
{
|
||||
/** The channel this info is for. */
|
||||
public ChatChannel channel;
|
||||
|
||||
/** The body ids of the participants of this channel. */
|
||||
public Set<Integer> participants;
|
||||
|
||||
/** The time at which a message was last dispatched on this channel. */
|
||||
public long lastMessage;
|
||||
}
|
||||
|
||||
/** Contains pending messages for all channels currently being resolved. */
|
||||
protected Map<ChatChannel,List<UserMessage>> _resolving = Maps.newHashMap();
|
||||
|
||||
/** A map of resolved channels to metadata records. */
|
||||
protected Map<ChatChannel,ChannelInfo> _channels = Maps.newHashMap();
|
||||
|
||||
/** Provides peer services. */
|
||||
@Inject protected CrowdPeerManager _peerMan;
|
||||
|
||||
/** The period on which we check for idle channels. */
|
||||
protected static final long IDLE_CHANNEL_CHECK_PERIOD = 5 * 1000L;
|
||||
|
||||
/** The amount of idle time (in milliseconds) after which we close a channel. */
|
||||
protected static final long IDLE_CHANNEL_CLOSE_TIME = 5 * 60 * 1000L;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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 javax.annotation.Generated;
|
||||
|
||||
import com.threerings.crowd.chat.client.ChatService;
|
||||
import com.threerings.crowd.chat.data.ChatMarshaller;
|
||||
import com.threerings.presents.client.InvocationService;
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.server.InvocationDispatcher;
|
||||
import com.threerings.presents.server.InvocationException;
|
||||
import com.threerings.util.Name;
|
||||
|
||||
/**
|
||||
* Dispatches requests to the {@link ChatProvider}.
|
||||
*/
|
||||
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
|
||||
comments="Derived from ChatService.java.")
|
||||
public class ChatDispatcher extends InvocationDispatcher<ChatMarshaller>
|
||||
{
|
||||
/**
|
||||
* Creates a dispatcher that may be registered to dispatch invocation
|
||||
* service requests for the specified provider.
|
||||
*/
|
||||
public ChatDispatcher (ChatProvider provider)
|
||||
{
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatMarshaller createMarshaller ()
|
||||
{
|
||||
return new ChatMarshaller();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchRequest (
|
||||
ClientObject source, int methodId, Object[] args)
|
||||
throws InvocationException
|
||||
{
|
||||
switch (methodId) {
|
||||
case ChatMarshaller.AWAY:
|
||||
((ChatProvider)provider).away(
|
||||
source, (String)args[0]
|
||||
);
|
||||
return;
|
||||
|
||||
case ChatMarshaller.BROADCAST:
|
||||
((ChatProvider)provider).broadcast(
|
||||
source, (String)args[0], (InvocationService.InvocationListener)args[1]
|
||||
);
|
||||
return;
|
||||
|
||||
case ChatMarshaller.TELL:
|
||||
((ChatProvider)provider).tell(
|
||||
source, (Name)args[0], (String)args[1], (ChatService.TellListener)args[2]
|
||||
);
|
||||
return;
|
||||
|
||||
default:
|
||||
super.dispatchRequest(source, methodId, args);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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.Iterator;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.util.MessageBundle;
|
||||
import com.threerings.util.Name;
|
||||
import com.threerings.util.TimeUtil;
|
||||
|
||||
import com.threerings.presents.client.InvocationService.InvocationListener;
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.dobj.DObject;
|
||||
import com.threerings.presents.server.InvocationException;
|
||||
import com.threerings.presents.server.InvocationManager;
|
||||
import com.threerings.presents.server.InvocationProvider;
|
||||
|
||||
import com.threerings.crowd.chat.client.ChatService.TellListener;
|
||||
import com.threerings.crowd.chat.client.ChatService;
|
||||
import com.threerings.crowd.chat.data.ChatCodes;
|
||||
import com.threerings.crowd.chat.data.SystemMessage;
|
||||
import com.threerings.crowd.chat.data.UserMessage;
|
||||
import com.threerings.crowd.data.BodyObject;
|
||||
import com.threerings.crowd.data.CrowdCodes;
|
||||
import com.threerings.crowd.data.OccupantInfo;
|
||||
import com.threerings.crowd.data.PlaceObject;
|
||||
import com.threerings.crowd.server.BodyLocal;
|
||||
import com.threerings.crowd.server.BodyLocator;
|
||||
import com.threerings.crowd.server.PlaceRegistry;
|
||||
|
||||
/**
|
||||
* The chat provider handles the server side of the chat-related invocation services.
|
||||
*/
|
||||
@Singleton
|
||||
public class ChatProvider
|
||||
implements InvocationProvider
|
||||
{
|
||||
/** Interface to allow an auto response to a tell message. */
|
||||
public static interface TellAutoResponder
|
||||
{
|
||||
/**
|
||||
* Called following the delivery of <code>message</code> from <code>teller</code> to
|
||||
* <code>tellee</code>.
|
||||
*/
|
||||
void sentTell (BodyObject teller, BodyObject tellee, String message);
|
||||
}
|
||||
|
||||
/** Used to forward certain types of chat messages between servers in a multi-server setup. */
|
||||
public static interface ChatForwarder
|
||||
{
|
||||
/**
|
||||
* Requests that the supplied tell message be delivered to the appropriate destination.
|
||||
*
|
||||
* @return true if the tell was delivered, false otherwise.
|
||||
*/
|
||||
boolean forwardTell (UserMessage message, Name target, TellListener listener);
|
||||
|
||||
/**
|
||||
* Requests that the supplied broadcast message be delivered on other servers.
|
||||
*/
|
||||
void forwardBroadcast (Name from, byte levelOrMode, String bundle, String msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and registers this chat provider.
|
||||
*/
|
||||
@Inject public ChatProvider (InvocationManager invmgr)
|
||||
{
|
||||
// register a chat provider with the invocation manager
|
||||
invmgr.registerDispatcher(new ChatDispatcher(this), CrowdCodes.CROWD_GROUP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an object to which all broadcasts should be sent, rather than iterating over the place
|
||||
* objects and sending to each of them.
|
||||
*
|
||||
* @param object an object to send all broadcasts, or null to send to each place object
|
||||
* instead.
|
||||
*/
|
||||
public void setAlternateBroadcastObject (DObject object)
|
||||
{
|
||||
_broadcastObject = object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the auto tell responder for the chat provider. Only one auto responder is allowed.
|
||||
* <em>Note:</em> this only works for same-server tells. If the tell is forwarded to another
|
||||
* server, no auto-response opportunity is provided (because we never have both body objects in
|
||||
* the same place).
|
||||
*/
|
||||
public void setTellAutoResponder (TellAutoResponder autoRespond)
|
||||
{
|
||||
_autoRespond = autoRespond;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the chat forwarder. This is used by the Crowd peer services to forward messages
|
||||
* between servers in a multi-server cluster.
|
||||
*/
|
||||
public void setChatForwarder (ChatForwarder forwarder)
|
||||
{
|
||||
_chatForwarder = forwarder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a {@link ChatService#tell} request.
|
||||
*/
|
||||
public void tell (ClientObject caller, Name target, String message, TellListener listener)
|
||||
throws InvocationException
|
||||
{
|
||||
// ensure that the caller has normal chat privileges
|
||||
InvocationException.requireAccess(caller, ChatCodes.CHAT_ACCESS);
|
||||
|
||||
// deliver the tell message to the target
|
||||
BodyObject source = (BodyObject)caller;
|
||||
deliverTell(createTellMessage(source, message), target, listener);
|
||||
|
||||
// inform the auto-responder if needed
|
||||
BodyObject targobj;
|
||||
if (_autoRespond != null && (targobj = _locator.lookupBody(target)) != null) {
|
||||
_autoRespond.sentTell(source, targobj, message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a {@link ChatService#broadcast} request.
|
||||
*/
|
||||
public void broadcast (ClientObject caller, String message, InvocationListener listener)
|
||||
throws InvocationException
|
||||
{
|
||||
// make sure the requesting user has broadcast privileges
|
||||
InvocationException.requireAccess(caller, ChatCodes.BROADCAST_ACCESS);
|
||||
BodyObject body = (BodyObject)caller;
|
||||
broadcast(body.getVisibleName(), null, message, false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a {@link ChatService#away} request.
|
||||
*/
|
||||
public void away (ClientObject caller, String message)
|
||||
{
|
||||
BodyObject body = (BodyObject)caller;
|
||||
// we modify this field via an invocation service request because a body object is not
|
||||
// modifiable by the client
|
||||
body.setAwayMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the specified message to all place objects in the system.
|
||||
*
|
||||
* @param from the user the broadcast is from, or null to send the message as a system message.
|
||||
* @param bundle the bundle, or null if the message needs no translation.
|
||||
* @param msg the content of the message to broadcast.
|
||||
* @param attention if true, the message is sent as ATTENTION level, otherwise as INFO. Ignored
|
||||
* if from is non-null.
|
||||
* @param forward if true, forward this broadcast on to any registered chat forwarder, if
|
||||
* false, deliver it only locally on this server.
|
||||
*/
|
||||
public void broadcast (Name from, String bundle, String msg, boolean attention, boolean forward)
|
||||
{
|
||||
byte levelOrMode = (from != null) ? ChatCodes.BROADCAST_MODE
|
||||
: (attention ? SystemMessage.ATTENTION : SystemMessage.INFO);
|
||||
broadcast(from, levelOrMode, bundle, msg, forward);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast with support for a customizable level or mode.
|
||||
* @param levelOrMode if from is null, it's an attentionLevel, else it's a mode code.
|
||||
*/
|
||||
public void broadcast (Name from, byte levelOrMode, String bundle, String msg, boolean forward)
|
||||
{
|
||||
if (_broadcastObject != null) {
|
||||
broadcastTo(_broadcastObject, from, levelOrMode, bundle, msg);
|
||||
|
||||
} else {
|
||||
for (Iterator<PlaceObject> iter = _plreg.enumeratePlaces(); iter.hasNext(); ) {
|
||||
PlaceObject plobj = iter.next();
|
||||
if (plobj.shouldBroadcast()) {
|
||||
broadcastTo(plobj, from, levelOrMode, bundle, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (forward && _chatForwarder != null) {
|
||||
_chatForwarder.forwardBroadcast(from, levelOrMode, bundle, msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delivers a tell message to the specified target and notifies the supplied listener of the
|
||||
* result. It is assumed that the teller has already been permissions checked.
|
||||
*/
|
||||
public void deliverTell (UserMessage message, Name target, TellListener listener)
|
||||
throws InvocationException
|
||||
{
|
||||
// make sure the target user is online
|
||||
BodyObject tobj = _locator.lookupBody(target);
|
||||
if (tobj == null) {
|
||||
// if we have a forwarder configured, try forwarding the tell
|
||||
if (_chatForwarder != null && _chatForwarder.forwardTell(message, target, listener)) {
|
||||
return;
|
||||
}
|
||||
throw new InvocationException(ChatCodes.USER_NOT_ONLINE);
|
||||
}
|
||||
|
||||
if (tobj.status == OccupantInfo.DISCONNECTED) {
|
||||
String errmsg = MessageBundle.compose(
|
||||
ChatCodes.USER_DISCONNECTED, TimeUtil.getTimeOrderString(
|
||||
System.currentTimeMillis() - tobj.getLocal(BodyLocal.class).statusTime,
|
||||
TimeUtil.SECOND));
|
||||
throw new InvocationException(errmsg);
|
||||
}
|
||||
|
||||
// deliver a tell notification to the target player
|
||||
deliverTell(tobj, message);
|
||||
|
||||
// let the teller know it went ok
|
||||
long idle = 0L;
|
||||
if (tobj.status == OccupantInfo.IDLE) {
|
||||
idle = System.currentTimeMillis() - tobj.getLocal(BodyLocal.class).statusTime;
|
||||
}
|
||||
String awayMessage = null;
|
||||
if (!StringUtil.isBlank(tobj.awayMessage)) {
|
||||
awayMessage = tobj.awayMessage;
|
||||
}
|
||||
listener.tellSucceeded(idle, awayMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delivers a tell notification to the specified target player. It is assumed that the message
|
||||
* is coming from some server entity and need not be permissions checked or notified of the
|
||||
* result.
|
||||
*/
|
||||
public void deliverTell (BodyObject target, UserMessage message)
|
||||
{
|
||||
SpeakUtil.sendMessage(target, message);
|
||||
|
||||
// note that the teller "heard" what they said
|
||||
SpeakUtil.noteMessage(message.speaker, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to create a {@link UserMessage} for the supplied sender.
|
||||
*/
|
||||
protected UserMessage createTellMessage (BodyObject source, String message)
|
||||
{
|
||||
return new UserMessage(source.getVisibleName(), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Direct a broadcast to the specified object.
|
||||
*/
|
||||
protected void broadcastTo (
|
||||
DObject object, Name from, byte levelOrMode, String bundle, String msg)
|
||||
{
|
||||
if (from == null) {
|
||||
SpeakUtil.sendSystem(object, bundle, msg, levelOrMode /* level */);
|
||||
|
||||
} else {
|
||||
SpeakUtil.sendSpeak(object, from, bundle, msg, levelOrMode /* mode */);
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides access to place managers. */
|
||||
@Inject protected PlaceRegistry _plreg;
|
||||
|
||||
/** Used to look up body objects by name. */
|
||||
@Inject protected BodyLocator _locator;
|
||||
|
||||
/** Generates auto-responses to tells. May be null. */
|
||||
protected TellAutoResponder _autoRespond;
|
||||
|
||||
/** Forwards chat between servers. May be null. */
|
||||
protected ChatForwarder _chatForwarder;
|
||||
|
||||
/** An alternative object to which broadcasts should be sent. */
|
||||
protected DObject _broadcastObject;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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 javax.annotation.Generated;
|
||||
|
||||
import com.threerings.crowd.chat.data.SpeakMarshaller;
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.server.InvocationDispatcher;
|
||||
import com.threerings.presents.server.InvocationException;
|
||||
|
||||
/**
|
||||
* Dispatches requests to the {@link SpeakProvider}.
|
||||
*/
|
||||
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
|
||||
comments="Derived from SpeakService.java.")
|
||||
public class SpeakDispatcher extends InvocationDispatcher<SpeakMarshaller>
|
||||
{
|
||||
/**
|
||||
* Creates a dispatcher that may be registered to dispatch invocation
|
||||
* service requests for the specified provider.
|
||||
*/
|
||||
public SpeakDispatcher (SpeakProvider provider)
|
||||
{
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpeakMarshaller createMarshaller ()
|
||||
{
|
||||
return new SpeakMarshaller();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchRequest (
|
||||
ClientObject source, int methodId, Object[] args)
|
||||
throws InvocationException
|
||||
{
|
||||
switch (methodId) {
|
||||
case SpeakMarshaller.SPEAK:
|
||||
((SpeakProvider)provider).speak(
|
||||
source, (String)args[0], ((Byte)args[1]).byteValue()
|
||||
);
|
||||
return;
|
||||
|
||||
default:
|
||||
super.dispatchRequest(source, methodId, args);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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 com.threerings.util.MessageManager;
|
||||
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.dobj.DObject;
|
||||
import com.threerings.presents.server.InvocationManager;
|
||||
|
||||
import com.threerings.crowd.chat.client.SpeakService;
|
||||
import com.threerings.crowd.chat.data.ChatCodes;
|
||||
import com.threerings.crowd.data.BodyObject;
|
||||
|
||||
import static com.threerings.crowd.Log.log;
|
||||
|
||||
/**
|
||||
* Wires up the {@link SpeakService} to a particular distributed object. A server entity can make
|
||||
* "speech" available among the subscribers of a particular distributed object by constructing a
|
||||
* speak handler and registering it with the {@link InvocationManager}, then placing the resulting
|
||||
* marshaller into the distributed object in question so that subscribers to that object can use it
|
||||
* to generate "speak" requests on that object.
|
||||
*/
|
||||
public class SpeakHandler
|
||||
implements SpeakProvider
|
||||
{
|
||||
/**
|
||||
* Used to prevent abitrary users from issuing speak requests.
|
||||
*/
|
||||
public static interface SpeakerValidator
|
||||
{
|
||||
/**
|
||||
* Should return true if the supplied speaker is allowed to speak via the speak provider
|
||||
* with which this validator was registered.
|
||||
*/
|
||||
boolean isValidSpeaker (DObject speakObj, ClientObject speaker, byte mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a handler that will provide speech on the supplied distributed object.
|
||||
*
|
||||
* @param speakObj the object for which speech requests will be processed.
|
||||
* @param validator an optional validator that can be used to prevent arbitrary users from
|
||||
* using the speech services on this object.
|
||||
*/
|
||||
public SpeakHandler (DObject speakObj, SpeakerValidator validator)
|
||||
{
|
||||
_speakObj = speakObj;
|
||||
_validator = validator;
|
||||
}
|
||||
|
||||
// from interface SpeakProvider
|
||||
public void speak (ClientObject caller, String message, byte mode)
|
||||
{
|
||||
// ensure that the caller has normal chat privileges
|
||||
BodyObject source = (BodyObject)caller;
|
||||
String errmsg = source.checkAccess(ChatCodes.CHAT_ACCESS, null);
|
||||
if (errmsg != null) {
|
||||
// we normally don't listen for responses to speak messages so we can't just throw an
|
||||
// InvocationException we have to specifically communicate the error to the user
|
||||
SpeakUtil.sendFeedback(source, MessageManager.GLOBAL_BUNDLE, errmsg);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: broadcast should be handled more like a system message rather than as a mode for a
|
||||
// user message so that we don't have to do this validation here. Or not.
|
||||
|
||||
// ensure that the speaker is valid
|
||||
if ((mode == ChatCodes.BROADCAST_MODE) ||
|
||||
(_validator != null && !_validator.isValidSpeaker(_speakObj, caller, mode))) {
|
||||
log.warning("Refusing invalid speak request", "caller", caller.who(),
|
||||
"speakObj", _speakObj.which(), "message", message, "mode", mode);
|
||||
|
||||
} else {
|
||||
// issue the speak message on our speak object
|
||||
SpeakUtil.sendSpeak(_speakObj, source.getVisibleName(), null, message, mode);
|
||||
}
|
||||
}
|
||||
|
||||
/** Our speech object. */
|
||||
protected DObject _speakObj;
|
||||
|
||||
/** The entity that will validate our speakers. */
|
||||
protected SpeakerValidator _validator;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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 javax.annotation.Generated;
|
||||
|
||||
import com.threerings.crowd.chat.client.SpeakService;
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.server.InvocationProvider;
|
||||
|
||||
/**
|
||||
* Defines the server-side of the {@link SpeakService}.
|
||||
*/
|
||||
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
|
||||
comments="Derived from SpeakService.java.")
|
||||
public interface SpeakProvider extends InvocationProvider
|
||||
{
|
||||
/**
|
||||
* Handles a {@link SpeakService#speak} request.
|
||||
*/
|
||||
void speak (ClientObject caller, String arg1, byte arg2);
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 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.samskivert.util.ObserverList;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Provides the back-end of the chat speaking facilities.
|
||||
*/
|
||||
public class SpeakUtil
|
||||
{
|
||||
/**
|
||||
* An interface used to notify external systems whenever a chat message is spoken by one user
|
||||
* and heard by another.
|
||||
*/
|
||||
public static interface MessageObserver
|
||||
{
|
||||
/**
|
||||
* Called for each player that hears a particular chat message.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public static void registerMessageObserver (MessageObserver obs)
|
||||
{
|
||||
_messageObs.add(obs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a registration made previously with {@link #registerMessageObserver}.
|
||||
*/
|
||||
public static void removeMessageObserver (MessageObserver obs)
|
||||
{
|
||||
_messageObs.remove(obs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a speak notification to the specified place object originating with the specified
|
||||
* speaker (the speaker optionally being a server entity that wishes to fake a "speak" message)
|
||||
* and with the supplied message content.
|
||||
*
|
||||
* @param speakObj the object on which to generate the speak message.
|
||||
* @param speaker the username of the user that generated the message (or some special speaker
|
||||
* name for server messages).
|
||||
* @param bundle null when the message originates from a real human, the bundle identifier that
|
||||
* will be used by the client to translate the message text when the message originates from a
|
||||
* server entity "faking" a chat message.
|
||||
* @param message the text of the speak message.
|
||||
*/
|
||||
public static void sendSpeak (DObject speakObj, Name speaker, String bundle, String message)
|
||||
{
|
||||
sendSpeak(speakObj, speaker, bundle, message, ChatCodes.DEFAULT_MODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a speak notification to the specified place object originating with the specified
|
||||
* speaker (the speaker optionally being a server entity that wishes to fake a "speak" message)
|
||||
* and with the supplied message content.
|
||||
*
|
||||
* @param speakObj the object on which to generate the speak message.
|
||||
* @param speaker the username of the user that generated the message (or some special speaker
|
||||
* name for server messages).
|
||||
* @param bundle null when the message originates from a real human, the bundle identifier that
|
||||
* will be used by the client to translate the message text when the message originates from a
|
||||
* server entity "faking" a chat message.
|
||||
* @param message the text of the speak message.
|
||||
* @param mode the mode of the message, see {@link ChatCodes#DEFAULT_MODE}.
|
||||
*/
|
||||
public static void sendSpeak (DObject speakObj, Name speaker, String bundle, String message,
|
||||
byte mode)
|
||||
{
|
||||
sendMessage(speakObj, new UserMessage(speaker, bundle, message, mode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a system INFO message notification to the specified object with the supplied message
|
||||
* content. A system message is one that will be rendered where the speak messages are
|
||||
* rendered, but in a way that makes it clear that it is a message from the server.
|
||||
*
|
||||
* Info messages are sent when something happens that was neither directly triggered by the
|
||||
* user, nor requires direct action.
|
||||
*
|
||||
* @param speakObj the object on which to deliver the message.
|
||||
* @param bundle the name of the localization bundle that should be used to translate this
|
||||
* system message prior to displaying it to the client.
|
||||
* @param message the text of the message.
|
||||
*/
|
||||
public static void sendInfo (DObject speakObj, String bundle, String message)
|
||||
{
|
||||
sendSystem(speakObj, bundle, message, SystemMessage.INFO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a system FEEDBACK message notification to the specified object with the supplied
|
||||
* message content. A system message is one that will be rendered where the speak messages are
|
||||
* rendered, but in a way that makes it clear that it is a message from the server.
|
||||
*
|
||||
* Feedback messages are sent in direct response to a user action, usually to indicate success
|
||||
* or failure of the user's action.
|
||||
*
|
||||
* @param speakObj the object on which to deliver the message.
|
||||
* @param bundle the name of the localization bundle that should be used to translate this
|
||||
* system message prior to displaying it to the client.
|
||||
* @param message the text of the message.
|
||||
*/
|
||||
public static void sendFeedback (DObject speakObj, String bundle, String message)
|
||||
{
|
||||
sendSystem(speakObj, bundle, message, SystemMessage.FEEDBACK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a system ATTENTION message notification to the specified object with the supplied
|
||||
* message content. A system message is one that will be rendered where the speak messages are
|
||||
* rendered, but in a way that makes it clear that it is a message from the server.
|
||||
*
|
||||
* Attention messages are sent when something requires user action that did not result from
|
||||
* direct action by the user.
|
||||
*
|
||||
* @param speakObj the object on which to deliver the message.
|
||||
* @param bundle the name of the localization bundle that should be used to translate this
|
||||
* system message prior to displaying it to the client.
|
||||
* @param message the text of the message.
|
||||
*/
|
||||
public static void sendAttention (DObject speakObj, String bundle, String message)
|
||||
{
|
||||
sendSystem(speakObj, bundle, message, SystemMessage.ATTENTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the specified message on the specified object.
|
||||
*/
|
||||
public static void sendMessage (DObject speakObj, ChatMessage msg)
|
||||
{
|
||||
if (speakObj == null) {
|
||||
log.warning("Dropping speak message, no speak obj '" + msg + "'.", new Exception());
|
||||
return;
|
||||
}
|
||||
|
||||
// post the message to the relevant object
|
||||
speakObj.postMessage(ChatCodes.CHAT_NOTIFICATION, new Object[] { msg });
|
||||
|
||||
// if this is a user message; add it to the heard history of all users that can "hear" it
|
||||
if (!(msg instanceof UserMessage)) {
|
||||
return;
|
||||
|
||||
} else if (speakObj instanceof SpeakObject) {
|
||||
_messageMapper.omgr = (RootDObjectManager)speakObj.getManager();
|
||||
_messageMapper.message = (UserMessage)msg;
|
||||
((SpeakObject)speakObj).applyToListeners(_messageMapper);
|
||||
_messageMapper.omgr = null;
|
||||
_messageMapper.message = null;
|
||||
|
||||
} else {
|
||||
log.info("Unable to note listeners", "dclass", speakObj.getClass(), "msg", msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<ChatHistoryEntry> getChatHistory (Name username)
|
||||
{
|
||||
List<ChatHistoryEntry> 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<ChatHistoryEntry> 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.
|
||||
*/
|
||||
protected static void noteMessage (Name username, UserMessage msg)
|
||||
{
|
||||
// fill in the message's time stamp if necessary
|
||||
if (msg.timestamp == 0L) {
|
||||
msg.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
recordToChatHistory(null, msg, username);
|
||||
// Log.info("Noted that " + username + " heard " + msg + ".");
|
||||
|
||||
// notify any message observers
|
||||
_messageOp.init(username, msg);
|
||||
_messageObs.apply(_messageOp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the specified system message on the specified dobj.
|
||||
*/
|
||||
protected static void sendSystem (DObject speakObj, String bundle, String message, byte level)
|
||||
{
|
||||
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<ChatHistoryEntry> getHistoryList (Name username)
|
||||
{
|
||||
if (username instanceof KeepNoHistory) {
|
||||
return null;
|
||||
}
|
||||
List<ChatHistoryEntry> 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<ChatHistoryEntry> 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
|
||||
{
|
||||
public RootDObjectManager omgr;
|
||||
public UserMessage message;
|
||||
|
||||
public void apply (int bodyOid) {
|
||||
DObject dobj = omgr.getObject(bodyOid);
|
||||
if (dobj != null && dobj instanceof BodyObject) {
|
||||
noteMessage(((BodyObject)dobj).getVisibleName(), message);
|
||||
}
|
||||
}
|
||||
|
||||
public void apply (Name username) {
|
||||
noteMessage(username, message);
|
||||
}
|
||||
}
|
||||
|
||||
/** Used to notify our {@link MessageObserver}s. */
|
||||
protected static class MessageObserverOp
|
||||
implements ObserverList.ObserverOp<MessageObserver>
|
||||
{
|
||||
public void init (Name hearer, UserMessage message) {
|
||||
_hearer = hearer;
|
||||
_message = message;
|
||||
}
|
||||
|
||||
public boolean apply (MessageObserver observer) {
|
||||
observer.messageDelivered(_hearer, _message);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected Name _hearer;
|
||||
protected UserMessage _message;
|
||||
}
|
||||
|
||||
/** Recent chat history for the server. */
|
||||
protected static Map<Name, List<ChatHistoryEntry>> _histories = Maps.newHashMap();
|
||||
|
||||
/** Used to note the recipients of a chat message. */
|
||||
protected static MessageMapper _messageMapper = new MessageMapper();
|
||||
|
||||
/** A list of {@link MessageObserver}s. */
|
||||
protected static ObserverList<MessageObserver> _messageObs = ObserverList.newFastUnsafe();
|
||||
|
||||
/** 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;
|
||||
}
|
||||
Reference in New Issue
Block a user