package edu.washington.cs.oneswarm.ui.gwt.client.newui;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.RunAsyncCallback;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.user.client.Cookies;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.ChangeListener;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.HorizontalSplitPanel;
import com.google.gwt.user.client.ui.Hyperlink;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import edu.washington.cs.oneswarm.ui.gwt.client.OneSwarmDialogBox;
import edu.washington.cs.oneswarm.ui.gwt.client.OneSwarmGWT;
import edu.washington.cs.oneswarm.ui.gwt.client.OneSwarmRPCClient;
import edu.washington.cs.oneswarm.ui.gwt.client.ReportableErrorDialogBox;
import edu.washington.cs.oneswarm.ui.gwt.client.Updateable;
import edu.washington.cs.oneswarm.ui.gwt.client.newui.utils.ResizablePanel;
import edu.washington.cs.oneswarm.ui.gwt.client.newui.utils.ResizablePanelListener;
import edu.washington.cs.oneswarm.ui.gwt.rpc.FriendInfoLite;
import edu.washington.cs.oneswarm.ui.gwt.rpc.OneSwarmConstants;
import edu.washington.cs.oneswarm.ui.gwt.rpc.SerialChatMessage;
import edu.washington.cs.oneswarm.ui.gwt.rpc.TorrentInfo;
import edu.washington.cs.oneswarm.ui.gwt.rpc.UnknownUserException;
public class ChatDialog extends OneSwarmDialogBox implements Updateable, ResizablePanelListener {
public static final int DEFAULT_WIDTH = 540;
public static final int DEFAULT_HEIGHT = 350;
public static final int MAX_LENGTH = 1024;
public static final String CSS_CHAT_DIALOG = "os-chat_dialog";
public static final String CSS_CHAT_OFFLINE = "os-chat_offline";
String[] keys_by_nick = null;
Map<Integer, String> listEntryToKey = new HashMap<Integer, String>();
Map<String, String> keyToNick = new HashMap<String, String>();
Map<String, FriendInfoLite> keyToFriend = new HashMap<String, FriendInfoLite>();
private String mSelectedBase64PublicKey;
private VerticalPanel mChatPanel = new VerticalPanel();
private final ScrollPanel mChatScroll = new ScrollPanel();
final private HelpButton mErrorHelpButton = new HelpButton("Some text here");
final private VerticalPanel mMainRHSVP = new VerticalPanel();
private HorizontalSplitPanel mainPanel;
// private Button mSendButton = new Button("Send");
private final TextBox mTextBox = new TextBox();
private boolean mSending;
private FriendInfoLite[] mFriends;
/**
* When switching among multiple chats, we should keep the display constant
* among them
*/
private final Map<String, VerticalPanel> mKeyToChatPanel = new HashMap<String, VerticalPanel>();
private final Set<String> mShowingFullHistory = new HashSet<String>();
private EntireUIRoot mUIRoot;
private static ChatDialog showing = null;
public static boolean showing() {
return showing != null;
}
public static boolean tryHide() {
if (showing != null) {
if (showing.isWriting()) {
return false;
} else {
showing.hide();
return true;
}
}
return true;
}
public ChatDialog(FriendInfoLite[] friendInfoLites, String inSelectedPublicKey,
EntireUIRoot root) {
mSelectedBase64PublicKey = inSelectedPublicKey;
mFriends = friendInfoLites;
mUIRoot = root;
addStyleName(CSS_CHAT_DIALOG);
setText(msg.swarm_browser_chat());
GWT.runAsync(new RunAsyncCallback() {
public void onFailure(Throwable reason) {
Window.alert("Error loading Chat dialog javascript: " + reason.toString());
}
public void onSuccess() {
// for preloading split code
if (mFriends == null) {
return;
}
ChatDialog dlg = onInitialized();
dlg.show();
dlg.setVisible(false);
dlg.center();
if (Cookies.getCookie("os-chat-position") == null) {
dlg.setPopupPosition(dlg.getPopupLeft(), Window.getScrollTop() + 125);
} else {
try {
String[] toks = Cookies.getCookie("os-chat-position").split("_");
int left = Integer.parseInt(toks[0]), top = Integer.parseInt(toks[1]), width = Integer
.parseInt(toks[2]), height = Integer.parseInt(toks[3]);
if (left < 0 || top < 0) {
throw new Exception("Bad cookie -- negative left/top: " + left + " / "
+ top);
}
if (width > 25 && height > 25) {
dlg.setPopupPosition(left, top);
dlg.resized(width, height);
} else {
throw new Exception("Bad width / height: " + width + " / " + height);
}
} catch (Exception e) {
System.err.println("error parsing chat position cookie: " + e.toString());
e.printStackTrace();
}
}
dlg.setVisible(true);
}
});
}
protected ChatDialog onInitialized() {
for (FriendInfoLite f : mFriends) {
keyToFriend.put(f.getPublicKey(), f);
}
mTextBox.getElement().setId("chatTextBox");
mTextBox.setMaxLength(MAX_LENGTH);
mainPanel = new HorizontalSplitPanel();
createLeftWidget();
mainPanel.setLeftWidget(mUserList);
createChatPanel(false);
mChatScroll.setWidget(mChatPanel);
mChatScroll.setHeight((DEFAULT_HEIGHT - (39 + 16 + 3)) + "px");
mChatScroll.setAlwaysShowScrollBars(false);
HorizontalPanel hp = new HorizontalPanel();
mTextBox.setFocus(true);
mTextBox.addKeyUpHandler(new KeyUpHandler() {
@Override
public void onKeyUp(KeyUpEvent event) {
if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
sendCurrentMessage();
}
}
});
hp.add(mTextBox);
hp.setSpacing(3);
mTextBox.setWidth("96%");
hp.setWidth("100%");
mMainRHSVP.add(mChatScroll);
HorizontalPanel linkAndHelp = new HorizontalPanel();
linkAndHelp.add(mErrorHelpButton);
mErrorHelpButton.setVisible(false);
final Hyperlink linkSwarms = new Hyperlink(msg.chat_link(), "#");
linkAndHelp.add(linkSwarms);
linkAndHelp.setSpacing(3);
linkSwarms.addClickListener(new ClickListener() {
public void onClick(Widget sender) {
TorrentInfo[] selected = mUIRoot.getSelectedSwarms();
if (selected == null) {
return;
}
if (mTextBox.isEnabled() == false) {
return;
}
boolean limited_friend = false, f2f_only = false;
if (keyToFriend.get(mSelectedBase64PublicKey) != null) {
if (keyToFriend.get(mSelectedBase64PublicKey).isCanSeeFileList() == false) {
limited_friend = true;
}
}
/**
* To compensate for the myriad of representations we have of a
* hash. We need to do this on the backend. This is extremely
* frustrating to me.
*/
String[] tids = new String[selected.length];
for (int i = 0; i < tids.length; i++) {
tids[i] = selected[i].getTorrentID();
}
OneSwarmRPCClient.getService().getBase64HashesForOneSwarmHashes(
OneSwarmRPCClient.getSessionID(), tids, new AsyncCallback<String[]>() {
public void onFailure(Throwable caught) {
}
public void onSuccess(String[] result) {
if (result == null) {
System.err.println("null result on base64 conversion");
return;
}
for (int i = 0; i < result.length; i++) {
String id = "id:" + result[i];
mTextBox.setText(mTextBox.getText() + id
+ (i < result.length - 1 ? " " : ""));
if (mTextBox.getText().length() + id.length() > MAX_LENGTH) {
break;
}
}
}
});
for (TorrentInfo t : selected) {
if (t.isF2FOnly()) {
f2f_only = true;
}
}
if (limited_friend || f2f_only) {
StringBuilder errorString = new StringBuilder();
if (limited_friend) {
errorString.append(msg.chat_no_access_yours());
}
if (f2f_only) {
errorString.append(msg.chat_no_access_friends());
}
mErrorHelpButton.setText(errorString.toString());
mErrorHelpButton.setVisible(true);
} else {
mErrorHelpButton.setVisible(false);
}
}
});
mMainRHSVP.add(linkAndHelp);
mMainRHSVP.setCellHorizontalAlignment(linkAndHelp, HorizontalPanel.ALIGN_RIGHT);
mMainRHSVP.add(hp);
mMainRHSVP.setCellWidth(hp, "100%");
mMainRHSVP.setWidth("100%");
mainPanel.setRightWidget(mMainRHSVP);
mainPanel.setSplitPosition("25%");
mainPanel.setWidth(DEFAULT_WIDTH + "px");
mainPanel.setHeight(DEFAULT_HEIGHT + "px");
ResizablePanel rp = new ResizablePanel();
rp.add(mainPanel);
rp.addResizeListener(this);
this.setWidget(rp);
OneSwarmGWT.addToUpdateTask(this);
return this;
}
public void resized(Integer width, Integer height) {
mainPanel.setWidth(width + "px");
mainPanel.setHeight(height + "px");
mUserList.setHeight(height + "px");
mChatScroll.setHeight((height - (39 + 16 + 3)) + "px");
setWidth(width + "px");
setHeight(height + "px");
}
public boolean isWriting() {
return mTextBox.getText().length() > 0;
}
@Override
public void onDetach() {
Cookies.setCookie("os-chat-position", this.getAbsoluteLeft() + "_" + this.getAbsoluteTop()
+ "_" + this.getOffsetWidth() + "_" + this.getOffsetHeight(),
OneSwarmConstants.TEN_YEARS_FROM_NOW);
super.onDetach();
OneSwarmGWT.removeFromUpdateTask(this);
showing = null;
}
@Override
public void onAttach() {
super.onAttach();
showing = this;
}
private void sendCurrentMessage() {
if (mSending) {
return;
}
mSending = true;
// mSendButton.setEnabled(false);
String messageText = mTextBox.getText().trim();
if (messageText.length() == 0) {
mTextBox.setText("");
return;
}
SerialChatMessage serialChatMessage = new SerialChatMessage();
serialChatMessage.setMessage(messageText);
OneSwarmRPCClient.getService().sendChatMessage(OneSwarmRPCClient.getSessionID(),
mSelectedBase64PublicKey, serialChatMessage, new AsyncCallback<Boolean>() {
public void onSuccess(Boolean result) {
mSending = false;
nextRPC = 0; // induce immediate refresh
if (result == false) {
updateFriendStatus(msg.chat_user_offline());
}
}
public void onFailure(Throwable caught) {
try {
throw caught;
} catch (UnknownUserException e) {
Window.alert(msg.chat_unknown());
} catch (Throwable e) {
new ReportableErrorDialogBox(caught.getMessage(), false);
}
mTextBox.setEnabled(false);
}
});
mTextBox.setText("");
}
private void updateFriendStatus(String msg) {
if (msg == null) {
mChatPanel.getWidget(0).setVisible(false);
} else {
mChatPanel.getWidget(0).setVisible(true);
((Label) mChatPanel.getWidget(0)).setText(msg);
}
}
private void createChatPanel(final boolean include_read) {
mLastAdded = WhichAdding.First;
mChatScroll.remove(mChatPanel);
if (mSelectedBase64PublicKey == null) {
return;
}
boolean force = false;
if (include_read && !mShowingFullHistory.contains(mSelectedBase64PublicKey)) {
force = true;
}
if (mKeyToChatPanel.containsKey(mSelectedBase64PublicKey) && !force) {
mChatPanel = mKeyToChatPanel.get(mSelectedBase64PublicKey);
mChatScroll.setWidget(mChatPanel);
mChatScroll.scrollToBottom();
return;
} else {
mChatPanel = new VerticalPanel();
mChatPanel.add(new Label());
mChatPanel.getWidget(0).setVisible(false);
mChatPanel.getWidget(0).addStyleName(CSS_CHAT_OFFLINE);
mChatPanel.setWidth("100%");
mKeyToChatPanel.put(mSelectedBase64PublicKey, mChatPanel);
}
FriendInfoLite f = keyToFriend.get(mSelectedBase64PublicKey);
if (f != null) {
if (f.isConnected() == false) {
updateFriendStatus(msg.chat_user_offline());
} else {
updateFriendStatus(null);
}
} else {
// the friend list only contains online friends.
updateFriendStatus(msg.chat_user_offline());
}
if (include_read) {
mShowingFullHistory.add(mSelectedBase64PublicKey);
}
mTextBox.setEnabled(true);
mChatScroll.setWidget(mChatPanel);
mChatPanel.add(new Label(msg.loading()));
OneSwarmRPCClient.getService().getMessagesForUser(OneSwarmRPCClient.getSessionID(),
mSelectedBase64PublicKey, include_read, 0,
new AsyncCallback<SerialChatMessage[]>() {
public void onFailure(Throwable caught) {
caught.printStackTrace();
}
public void onSuccess(final SerialChatMessage[] initialResult) {
for (int i = 1; i < mChatPanel.getWidgetCount(); i++) {
mChatPanel.remove(i);
}
Hyperlink show = null;
if (!include_read) {
show = new Hyperlink(msg.chat_show_previous(), "");
show.addClickListener(new ClickListener() {
public void onClick(Widget sender) {
createChatPanel(true);
mChatScroll.setWidget(mChatPanel);
}
});
} else {
show = new Hyperlink(msg.chat_clear(), "");
show.addClickListener(new ClickListener() {
public void onClick(Widget sender) {
final String key_shadow = mSelectedBase64PublicKey;
System.out.println("clearing for: " + mSelectedBase64PublicKey
+ " / " + keyToNick.get(mSelectedBase64PublicKey));
OneSwarmRPCClient.getService().clearChatLog(
OneSwarmRPCClient.getSessionID(), key_shadow,
new AsyncCallback<Integer>() {
public void onFailure(Throwable caught) {
caught.printStackTrace();
}
public void onSuccess(Integer result) {
System.out.println("cleared " + result);
/**
* Or else we'll just put up
* this panel again
*/
mKeyToChatPanel.remove(key_shadow);
createChatPanel(true);
}
});
}
});
}
mChatPanel.add(show);
mChatPanel.setCellWidth(show, "100%");
mChatPanel.setCellHorizontalAlignment(show, HorizontalPanel.ALIGN_CENTER);
if (initialResult.length > 4) {
addMessagesToChatPanel(initialResult);
} else if (include_read == false) {
// add a little history.
OneSwarmRPCClient.getService().getMessagesForUser(
OneSwarmRPCClient.getSessionID(), mSelectedBase64PublicKey,
true, 4, new AsyncCallback<SerialChatMessage[]>() {
public void onFailure(Throwable caught) {
caught.printStackTrace();
}
public void onSuccess(SerialChatMessage[] result) {
/**
* If we've re-requested some
* messages which were unread
* before, mark them again as such
* now.
*/
for (SerialChatMessage neu : result) {
for (SerialChatMessage old : initialResult) {
if (old.getUid() == neu.getUid()) {
neu.setUnread(old.isUnread());
}
}
}
addMessagesToChatPanel(result, true);
}
});
}
}
});
}
protected void addMessagesToChatPanel(SerialChatMessage[] result) {
addMessagesToChatPanel(result, false);
}
private enum WhichAdding {
First, Incoming, Outgoing
};
WhichAdding mLastAdded = WhichAdding.First;
protected void addMessagesToChatPanel(SerialChatMessage[] result, boolean old) {
for (SerialChatMessage chat : result) {
HorizontalPanel hp = new HorizontalPanel();
Label chatLabel = new Label(chat.getMessage());
Label timestamp = new Label();
if (chat.isSent() == false) {
chatLabel.setText(chatLabel.getText() + " (" + msg.chat_pending() + ")");
}
chatLabel.setWordWrap(true);
transmorphLinks(chatLabel, "http", "<a href=\"#\" onclick=\"window.open('",
"', '_blank', '')\">", "</a>");
transmorphLinks(chatLabel, "id:", "<a href=\"#" + EntireUIRoot.SEARCH_HISTORY_TOKEN,
"\">", "</a>");
transmorphLinks(chatLabel, "oneswarm:", "<a href=\"", "\">", "</a>");
Date now = new Date();
Date then = new Date(chat.getTimestamp());
String stampStr;
if ((now.getTime() - chat.getTimestamp()) < 86400000 && now.getDate() == then.getDate()) { // 1
// day
stampStr = then.getHours() + ":"
+ (then.getMinutes() < 10 ? "0" + then.getMinutes() : then.getMinutes());
} else {
stampStr = (then.getMonth() + 1) + "/" + then.getDate();
}
timestamp.setText(stampStr);
if (old && !(chat.isUnread()) && chat.isSent()) {
DOM.setStyleAttribute(chatLabel.getElement(), "color", "grey");
System.out.println(chat.getMessage() + " unread: " + chat.isUnread());
}
DOM.setStyleAttribute(timestamp.getElement(), "color", "grey");
DOM.setStyleAttribute(timestamp.getElement(), "fontSize", "80%");
if (chat.isOutgoing()) {
// transition, print our name
if (mLastAdded.equals(WhichAdding.Incoming) || mLastAdded.equals(WhichAdding.First)) {
HTML html = new HTML("<b>" + chat.getNickname() + "</b>");
mChatPanel.add(html);
// if( old && chat.isUnread() == false ) {
DOM.setStyleAttribute(html.getElement(), "color", "grey");
// }
mChatPanel.setCellHorizontalAlignment(html, HorizontalPanel.ALIGN_RIGHT);
}
mLastAdded = WhichAdding.Outgoing;
hp.add(chatLabel);
hp.add(timestamp);
hp.setCellHorizontalAlignment(timestamp, HorizontalPanel.ALIGN_RIGHT);
mChatPanel.add(hp);
mChatPanel.setCellHorizontalAlignment(hp, HorizontalPanel.ALIGN_RIGHT);
} else {
// transition, print their name
if (mLastAdded.equals(WhichAdding.Outgoing) || mLastAdded.equals(WhichAdding.First)) {
HTML html = new HTML("<b>" + chat.getNickname() + "</b>");
mChatPanel.add(html);
// if( old && chat.isUnread() == false ) {
DOM.setStyleAttribute(html.getElement(), "color", "grey");
// }
mChatPanel.setCellHorizontalAlignment(html, HorizontalPanel.ALIGN_LEFT);
}
mLastAdded = WhichAdding.Incoming;
hp.add(timestamp);
hp.setCellHorizontalAlignment(timestamp, HorizontalPanel.ALIGN_LEFT);
hp.add(chatLabel);
mChatPanel.add(hp);
mChatPanel.setCellHorizontalAlignment(hp, HorizontalPanel.ALIGN_LEFT);
}
hp.setCellVerticalAlignment(timestamp, VerticalPanel.ALIGN_BOTTOM);
hp.setCellVerticalAlignment(hp, VerticalPanel.ALIGN_BOTTOM);
hp.setSpacing(2);
hp.setCellWidth(timestamp, "35px");
}
mChatScroll.scrollToBottom();
}
private void transmorphLinks(Label chatLabel, String tag, String prefix, String suffix,
String close) {
try {
String innerHTML = chatLabel.getElement().getInnerHTML();
System.out.println("inner html is: " + innerHTML);
StringBuilder out = new StringBuilder();
int curr = 0;
boolean done = false;
while (!done) {
curr = innerHTML.indexOf(tag, curr);
if (curr == -1) {
done = true;
break;
}
out.append(innerHTML.substring(0, curr) + prefix);
int end = innerHTML.indexOf(' ', curr);
if (end == -1) {
end = innerHTML.length();
}
out.append(innerHTML.subSequence(curr, end) + suffix
+ innerHTML.subSequence(curr, end) + close);
curr = out.length();
out.append(innerHTML.subSequence(end, innerHTML.length()));
innerHTML = out.toString();
}
// System.out.println("transmorphed: " + innerHTML);
chatLabel.getElement().setInnerHTML(innerHTML);
} catch (Exception e) {
// eat exception, do nothing to chat label itself.
e.printStackTrace();
}
}
final ListBox mUserList = new ListBox();
ChangeListener userChangeListener = null;
private void createLeftWidget() {
mUserList.setVisibleItemCount(10);
mUserList.setHeight(DEFAULT_HEIGHT + "px");
mUserList.setWidth("100%");
userChangeListener = new ChangeListener() {
public void onChange(Widget sender) {
System.out.println("change listener " + mUserList.getSelectedIndex() + " / ");
if (mUserList.getSelectedIndex() == -1) {
mSelectedBase64PublicKey = null;
createChatPanel(false);
ChatDialog.this.setText(msg.swarm_browser_chat());
return;
}
mSelectedBase64PublicKey = keys_by_nick[mUserList.getSelectedIndex()];
ChatDialog.this.setText(msg.swarm_browser_chat() + ": "
+ keyToNick.get(mSelectedBase64PublicKey));
mUserList.setItemText(mUserList.getSelectedIndex(),
keyToNick.get(mSelectedBase64PublicKey));
System.out.println(mSelectedBase64PublicKey);
createChatPanel(false);
}
};
mUserList.addChangeListener(userChangeListener);
mUserList.addItem("Loading...");
OneSwarmRPCClient.getService().getUsersWithMessages(OneSwarmRPCClient.getSessionID(),
new AsyncCallback<HashMap<String, String[]>>() {
public void onFailure(Throwable caught) {
caught.printStackTrace();
}
public void onSuccess(final HashMap<String, String[]> result) {
mUserList.clear();
/**
* Result is base64Key -> String[]{name, unread
* messages}
*/
/**
* Build up the list of users with which we 1) have
* chatted (i.e., with history) or 2) could chat (those
* that have chat capability). We get 1) from the friend
* list passed to the constructor and 2) from an RPC to
* the backend DB.
*/
java.util.Set<String> userSet = new HashSet<String>();
userSet.addAll(result.keySet());
for (FriendInfoLite f : mFriends) {
if (f.isSupportsChat() && f.isAllowChat()) {
userSet.add(f.getPublicKey());
}
}
for (String key : userSet) {
if (result.containsKey(key)) {
keyToNick.put(key, result.get(key)[0]);
} else {
for (FriendInfoLite f : mFriends) {
if (f.getPublicKey().equals(key)) {
keyToNick.put(key, f.getName());
}
}
}
}
keys_by_nick = keyToNick.keySet().toArray(new String[0]);
Arrays.sort(keys_by_nick, new Comparator<String>() {
public int compare(String o1, String o2) {
return keyToNick.get(o1).compareTo(keyToNick.get(o2));
}
});
mUserList.setVisibleItemCount(Math.max(keys_by_nick.length, 10));
boolean picked = false;
for (String key : keys_by_nick) {
String unreadStr = "";
if (result.containsKey(key)) {
if (result.get(key)[1].equals("0") == false) {
unreadStr = " - " + result.get(key)[1];
}
}
mUserList.addItem(keyToNick.get(key) + unreadStr);
if (mSelectedBase64PublicKey != null) {
if (mSelectedBase64PublicKey.equals(key)) {
mUserList.setSelectedIndex(mUserList.getItemCount() - 1);
ChatDialog.this.setText(msg.swarm_browser_chat() + ": "
+ keyToNick.get(mSelectedBase64PublicKey));
mUserList.setItemText(mUserList.getSelectedIndex(),
keyToNick.get(mSelectedBase64PublicKey));
}
} else if (unreadStr.equals("") == false && !picked) {
/**
* If nothing selected, at least prefer
* something that has unread messages
*/
picked = true;
mUserList.setSelectedIndex(mUserList.getItemCount() - 1);
userChangeListener.onChange(mUserList);
}
}
}
}); // getUsersWithMessages RPC
}
long nextRPC = 0;
public void update(int count) {
if (nextRPC < System.currentTimeMillis()) {
nextRPC = System.currentTimeMillis() + 5 * 1000;
OneSwarmRPCClient.getService().getMessagesForUser(OneSwarmRPCClient.getSessionID(),
mSelectedBase64PublicKey, false, 0, new AsyncCallback<SerialChatMessage[]>() {
public void onFailure(Throwable caught) {
caught.printStackTrace();
}
public void onSuccess(SerialChatMessage[] result) {
if (result.length > 0) {
addMessagesToChatPanel(result);
}
nextRPC = System.currentTimeMillis() + 1000;
}
});
}
}
}