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:
Michael Bayne
2010-10-22 21:12:29 +00:00
parent 555b865bbf
commit 9d2ca42eac
434 changed files with 163 additions and 208 deletions
@@ -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;
}