📄 messagehelper.java
字号:
/*
* LumaQQ - Java QQ Client
*
* Copyright (C) 2004 luma <stubma@163.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package edu.tsinghua.lumaqq.ui.helper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.swt.graphics.Image;
import edu.tsinghua.lumaqq.IconHolder;
import edu.tsinghua.lumaqq.LumaQQ;
import edu.tsinghua.lumaqq.MessageQueue;
import edu.tsinghua.lumaqq.SoundDaemon;
import edu.tsinghua.lumaqq.models.ClusterModel;
import edu.tsinghua.lumaqq.models.FriendModel;
import edu.tsinghua.lumaqq.models.GroupModel;
import edu.tsinghua.lumaqq.models.IQQNode;
import edu.tsinghua.lumaqq.qq.QQ;
import edu.tsinghua.lumaqq.qq.QQClient;
import edu.tsinghua.lumaqq.qq.Util;
import edu.tsinghua.lumaqq.qq.beans.ClusterIM;
import edu.tsinghua.lumaqq.qq.beans.NormalIM;
import edu.tsinghua.lumaqq.qq.packets.in.ReceiveIMPacket;
import edu.tsinghua.lumaqq.qq.packets.in.SystemNotificationPacket;
import edu.tsinghua.lumaqq.ui.MainShell;
import edu.tsinghua.lumaqq.ui.ReceiveIMWindow;
import edu.tsinghua.lumaqq.ui.SendClusterIMWindow;
import edu.tsinghua.lumaqq.ui.SendIMWindow;
import edu.tsinghua.lumaqq.ui.tool.HeadFactory;
import edu.tsinghua.lumaqq.ui.tool.ShellFactory;
import edu.tsinghua.lumaqq.ui.tool.ShellRegistry;
import edu.tsinghua.lumaqq.utils.OptionUtil;
import edu.tsinghua.lumaqq.utils.ReplyUtil;
import edu.tsinghua.lumaqq.widgets.rich.IRichContent;
import edu.tsinghua.swt.models.ShutterModel;
import edu.tsinghua.swt.widgets.CoolButton;
import edu.tsinghua.swt.widgets.Shutter;
/**
* 消息处理帮助类
*
* @author luma
*/
public class MessageHelper {
private static Log log = LogFactory.getLog(MessageHelper.class);
private MainShell main;
private IconHolder icons;
// 分片缓冲,有的长消息会变成几个分片发送,需要保存起来等待所有分片完成
// key是消息id,value是个Object数组,保存了消息的分片
private Map fragmentCache;
public MessageHelper(MainShell main) {
this.main = main;
icons = IconHolder.getInstance();
fragmentCache = new HashMap();
}
/**
* 把字节数组转换为String,它为我们处理缺省表情的问题
*
* @param b
* 消息字节数组
* @return
* String
*/
public String convertBytes(byte[] b) {
StringBuffer sb = new StringBuffer();
int offset = 0;
int length = 0;
for(int i = 0; i < b.length; i++) {
if(b[i] == QQ.QQ_DEFAULT_FACE_TAG) {
sb.append(Util.getString(b, offset, length));
sb.append((char)b[i]).append((char)(b[i + 1] & 0xFF));
i++;
offset = i + 1;
length = 0;
} else
length++;
}
if(length > 0)
sb.append(Util.getString(b, offset, length));
return sb.toString();
}
/**
* 检查这个消息是完整消息还是分片
*
* @return
* true表示这个消息是分片消息
*/
private boolean isFragment(NormalIM im) {
return im.totalFragments > 1;
}
/**
* 检查这个消息是完整消息还是分片
*
* @return
* true表示这个消息是分片消息
*/
private boolean isFragment(ClusterIM im) {
return im.totalFragments > 1;
}
/**
* 添加一个普通消息分片
*
* @param im
*/
private void addFragment(NormalIM im) {
Integer id = new Integer(im.messageId);
Object[] fragments = (Object[])fragmentCache.get(id);
if(fragments == null || fragments.length != im.totalFragments) {
fragments = new Object[im.totalFragments];
fragmentCache.put(id, fragments);
}
fragments[im.fragmentSequence] = im;
}
/**
* 添加一个群消息分片
*
* @param im
*/
private void addFragment(ClusterIM im) {
Integer id = new Integer(im.messageId);
Object[] fragments = (Object[])fragmentCache.get(id);
if(fragments == null || fragments.length != im.totalFragments) {
fragments = new Object[im.totalFragments];
fragmentCache.put(id, fragments);
}
fragments[im.fragmentSequence] = im;
}
/**
* 得到完整的消息,同时把这个消息从分片缓冲中清楚。调用此方法前,必须先用
* isMessageComplete()判断分片是否都已经收到
*
* @param messageId
* 消息id
* @return
* ClusterIM对象
*/
private ClusterIM getIntegratedClusterIM(char messageId) {
Integer id = new Integer(messageId);
Object[] fragments = (Object[])fragmentCache.remove(id);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
for(int i = 0; i < fragments.length; i++) {
try {
baos.write(((ClusterIM)fragments[i]).messageBytes);
} catch (IOException e) {
}
}
ClusterIM ret = (ClusterIM)fragments[fragments.length - 1];
ret.message = convertBytes(baos.toByteArray());
return ret;
}
/**
* 得到完整的普通消息
*
* @param messageId
* 消息ID
* @return
* NormalIM对象
*/
private NormalIM getIntegratedNormalIM(char messageId) {
Integer id = new Integer(messageId);
Object[] fragments = (Object[])fragmentCache.remove(id);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
for(int i = 0; i < fragments.length; i++) {
try {
baos.write(((NormalIM)fragments[i]).messageBytes);
} catch (IOException e) {
}
}
NormalIM ret = (NormalIM)fragments[0];
ret.message = convertBytes(baos.toByteArray());
return ret;
}
/**
* 检查是否一个长消息的分片都已经收到了
*
* @param messageId
* 消息id
* @return
* true表示已经收到
*/
private boolean isMessageComplete(char messageId) {
Integer id = new Integer(messageId);
if(!fragmentCache.containsKey(id))
return false;
Object[] fragments = (Object[])fragmentCache.get(id);
for(int i = 0; i < fragments.length; i++) {
if(fragments[i] == null)
return false;
}
return true;
}
/**
* 推入一个群通知消息,一个群可以有很多种类型的消息,一般来说,除了普通群消息之外的
* 其他消息,我们都看做是一个群通知,也就是要闪那个小喇叭,而不是把他显示在发送消息
* 的窗口中。这样的群通知消息有申请加入群,退出群,同意加入群等等等等。判断的逻辑基
* 本上也和系统消息差不多,先看小喇叭闪没闪,没闪就闪他,再看是不是第一条消息,是就
* 再闪tray icon
*
* @param packet
* 消息包
*/
public void putClusterNotification(ReceiveIMPacket packet) {
// 保存到聊天记录,群通知消息部分有些特殊(和系统消息一个意思)
// 分成两个域,第一个是消息类型字节,第二个是消息内容,用|分隔,这只
// 是个权宜之计,因为我匆匆定义的聊天记录格式比较土
StringBuffer sb = new StringBuffer();
String sender = String.valueOf(packet.sender);
String clusterId = String.valueOf(packet.header.sender);
sb.append((int)packet.header.type);
sb.append('|');
if(packet.header.type == QQ.QQ_RECV_IM_ADDED_TO_CLUSTER || packet.header.type == QQ.QQ_RECV_IM_CREATE_CLUSTER) {
if(packet.clusterType == QQ.QQ_CLUSTER_TYPE_PERMANENT)
sb.append(LumaQQ.getString("cluster.message.permanent.cluster.created", new Object[] { clusterId, sender }));
else
sb.append(LumaQQ.getString("cluster.message.temporary.cluster.created", new Object[] { clusterId, sender }));
} else if(packet.header.type == QQ.QQ_RECV_IM_DELETED_FROM_CLUSTER) {
if(packet.sender == main.getMyModel().getQQ())
sb.append(LumaQQ.getString("cluster.message.removed", new Object[] { sender }));
else
sb.append(LumaQQ.getString("cluster.message.exit", new Object[] { sender, clusterId } ));
}
else if(packet.header.type == QQ.QQ_RECV_IM_REQUEST_JOIN_CLUSTER)
sb.append(LumaQQ.getString("cluster.message.request", new Object[] { sender, clusterId, packet.message }));
else if(packet.header.type == QQ.QQ_RECV_IM_APPROVE_JOIN_CLUSTER)
sb.append(LumaQQ.getString("cluster.message.approved", new Object[] { sender }));
else if(packet.header.type == QQ.QQ_RECV_IM_REJECT_JOIN_CLUSTER)
sb.append(LumaQQ.getString("cluster.message.rejected", new Object[] { sender, packet.message }));
main.getMessageManager().saveMessage(10000, packet.header.sender, packet.sender, sb.toString(), (int)(System.currentTimeMillis() / 1000));
// 调整动画状态
if(!main.getMessageQueue().hasNext())
main.getUIHelper().startBlinkImage(icons.getImage(IconHolder.icoSysMsg));
if(!main.isSystemMessageIconBlinking())
main.startBlinkSystemMessageIcon();
main.getMessageQueue().putSystemMessage(packet);
// 播放声音
main.getSoundDaemon().play(LumaQQ.SYS_MSG_SOUND);
log.debug("一个系统消息被推入队列");
}
/**
* 处理延迟队列中的消息
*/
public void processPostponedIM() {
ReceiveIMPacket packet;
while((packet = (ReceiveIMPacket)main.getMessageQueue().getPostponedMessage()) != null) {
log.debug("发现一条延迟消息,处理之");
if(packet.header.type == QQ.QQ_RECV_IM_CLUSTER_IM)
putClusterIM(packet);
else
putNormalIM(packet);
}
}
/**
* 推入一条消息并更新各种图标的闪烁状态,这个方法会检查是否好友列表已经
* 得到,如果没有,推入延迟队列
*
* @param packet
* 消息包
*/
public void putNormalIM(ReceiveIMPacket packet) {
// 如果这个消息是分片消息,如果这个消息已经完成,则继续处理,否则推入分片缓冲
if(isFragment(packet.normalIM)) {
addFragment(packet.normalIM);
if(isMessageComplete(packet.normalIM.messageId)) {
packet.normalIM = getIntegratedNormalIM(packet.normalIM.messageId);
} else {
return;
}
} else {
packet.normalIM.message = convertBytes(packet.normalIM.messageBytes);
}
// 得到好友在model中的位置,但是有可能为null,因为也许这是用户的第一次登陆
// 其好友列表还没得到,但是这时候有消息来了,所有也无法闪烁图标了,对于
// 这种情况,需要特殊处理一下,基本的方法是把消息推入延迟处理队列
int[] indices = main.getMVCHelper().getFriendCoordinate(packet.normalHeader.sender);
if((indices == null || indices.length == 0)) {
if(!main.isFriendListFinished()) {
main.getMessageQueue().postponeMessage(packet);
log.debug("发来消息的好友还未出现在好友列表中,延迟处理该消息");
} else {
if(packet.header.type == QQ.QQ_RECV_IM_FROM_STRANGER)
log.debug("收到一个坏人来的消息,忽略");
else
putNormalIM(packet, null, true);
}
} else {
if(packet.header.type == QQ.QQ_RECV_IM_FROM_STRANGER) {
GroupModel g = (GroupModel)main.getModel().getTab(indices[0]);
if(g.isBlackList())
log.debug("收到一个坏人来的消息,忽略");
else
putNormalIM(packet, indices, true);
} else
putNormalIM(packet, indices, false);
}
}
/**
* 推入一条消息并更新各种图标闪烁状态,这个方法接收好友的索引参数
*
* @param packet
* 收到的消息包
* @param indices
* 好友的组索引和组内偏移,如果为null,说明这个用户不是我的好友
*/
public void putNormalIM(ReceiveIMPacket packet, int[] indices, boolean isStranger) {
// 保存到聊天记录中
main.getMessageManager().saveMessage(packet.normalHeader.sender, packet.normalHeader.sender, packet.normalHeader.sender, packet.normalIM.message, (int)(packet.normalHeader.sendTime / 1000));
MVCHelper mvcHelper = main.getMVCHelper();
OptionUtil options = OptionUtil.getInstance();
ShutterModel model = main.getModel();
// 如果设置了拒绝陌生人消息
if(isStranger) {
if(options.isRejectStranger()) {
if(indices == null || indices.length == 0) {
log.debug("设置了拒绝陌生人消息,忽略该消息");
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -