diff options
Diffstat (limited to 'src/main/java/se/lublin/humla/protocol/ModelHandler.java')
-rw-r--r-- | src/main/java/se/lublin/humla/protocol/ModelHandler.java | 474 |
1 files changed, 474 insertions, 0 deletions
diff --git a/src/main/java/se/lublin/humla/protocol/ModelHandler.java b/src/main/java/se/lublin/humla/protocol/ModelHandler.java new file mode 100644 index 0000000..abe8fdc --- /dev/null +++ b/src/main/java/se/lublin/humla/protocol/ModelHandler.java @@ -0,0 +1,474 @@ +/* + * Copyright (C) 2014 Andrew Comminos + * + * 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 3 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, see <http://www.gnu.org/licenses/>. + */ + +package se.lublin.humla.protocol; + +import android.content.Context; +import android.util.Log; + +import se.lublin.humla.Constants; +import se.lublin.humla.R; +import se.lublin.humla.model.Channel; +import se.lublin.humla.model.Message; +import se.lublin.humla.model.User; +import se.lublin.humla.protobuf.Mumble; +import se.lublin.humla.util.IHumlaObserver; +import se.lublin.humla.util.HumlaLogger; +import se.lublin.humla.util.MessageFormatter; + +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Handles network messages related to the user-channel tree model. + * This includes channels, users, messages, and permissions. + * Created by andrew on 18/07/13. + */ +public class ModelHandler extends HumlaTCPMessageListener.Stub { + private final Context mContext; + private final Map<Integer, Channel> mChannels; + private final Map<Integer, User> mUsers; + private final List<Integer> mLocalMuteHistory; + private final List<Integer> mLocalIgnoreHistory; + private final IHumlaObserver mObserver; + private final HumlaLogger mLogger; + private int mPermissions; + private int mSession; + + public ModelHandler(Context context, IHumlaObserver observer, HumlaLogger logger, + @Nullable List<Integer> localMuteHistory, + @Nullable List<Integer> localIgnoreHistory) { + mContext = context; + mChannels = new HashMap<Integer, Channel>(); + mUsers = new HashMap<Integer, User>(); + mLocalMuteHistory = localMuteHistory; + mLocalIgnoreHistory = localIgnoreHistory; + mObserver = observer; + mLogger = logger; + } + + public Channel getChannel(int id) { + return mChannels.get(id); + } + + public User getUser(int session) { + return mUsers.get(session); + } + + /** + * Creates a stub channel with the given ID. + * Useful for keeping user references when we get a UserState message before a ChannelState. + * @param id The channel ID. + * @return The newly created stub channel. + */ + private Channel createStubChannel(int id) { + Channel channel = new Channel(id, false); + mChannels.put(id, channel); + return channel; + } + + public Map<Integer, Channel> getChannels() { + return Collections.unmodifiableMap(mChannels); + } + + public Map<Integer, User> getUsers() { + return Collections.unmodifiableMap(mUsers); + } + + /** + * Returns the current user's permissions. + * @return The server-wide permissions. + */ + public int getPermissions() { + return mPermissions; + } + + public void clear() { + mChannels.clear(); + mUsers.clear(); + } + + @Override + public void messageChannelState(Mumble.ChannelState msg) { + if(!msg.hasChannelId()) + return; + + Channel channel = mChannels.get(msg.getChannelId()); + Channel parent = mChannels.get(msg.getParent()); + + final boolean newChannel = channel == null; + + if(channel == null) { + channel = new Channel(msg.getChannelId(), msg.getTemporary()); + mChannels.put(msg.getChannelId(), channel); + } + + if(msg.hasName()) + channel.setName(msg.getName()); + + if(msg.hasPosition()) + channel.setPosition(msg.getPosition()); + + if(msg.hasParent()) { + Channel oldParent = channel.getParent(); + channel.setParent(parent); + parent.addSubchannel(channel); + if(oldParent != null) { + oldParent.removeSubchannel(channel); + } + } + + if(msg.hasDescriptionHash()) + channel.setDescriptionHash(msg.getDescriptionHash().toByteArray()); + + if(msg.hasDescription()) + channel.setDescription(msg.getDescription()); + + if(msg.getLinksCount() > 0) { + channel.clearLinks(); + for(int link : msg.getLinksList()) { + Channel linked = mChannels.get(link); + channel.addLink(linked); + // Don't add this channel to the other channel's link list- this update occurs on + // server synchronization, and we will get a message for the other channels' links + // laster. + } + } + + if(msg.getLinksRemoveCount() > 0) { + for(int link : msg.getLinksRemoveList()) { + Channel linked = mChannels.get(link); + channel.removeLink(linked); + linked.removeLink(channel); + } + } + + if(msg.getLinksAddCount() > 0) { + for(int link : msg.getLinksAddList()) { + Channel linked = mChannels.get(link); + channel.addLink(linked); + linked.addLink(channel); + } + } + + if(newChannel) + mObserver.onChannelAdded(channel); + else + mObserver.onChannelStateUpdated(channel); + } + + @Override + public void messageChannelRemove(Mumble.ChannelRemove msg) { + final Channel channel = mChannels.get(msg.getChannelId()); + if(channel != null && channel.getId() != 0) { + mChannels.remove(channel.getId()); + Channel parent = channel.getParent(); + if(parent != null) { + parent.removeSubchannel(channel); + } + mObserver.onChannelRemoved(channel); + } + } + + @Override + public void messagePermissionQuery(Mumble.PermissionQuery msg) { + if(msg.getFlush()) + for(Channel channel : mChannels.values()) + channel.setPermissions(0); + + final Channel channel = mChannels.get(msg.getChannelId()); + if(channel != null) { + channel.setPermissions(msg.getPermissions()); + if(msg.getChannelId() == 0) // If we're provided permissions for the root channel, we'll apply these as our server permissions. + mPermissions = channel.getPermissions(); + mObserver.onChannelPermissionsUpdated(channel); + } + } + + @Override + public void messageUserState(Mumble.UserState msg) { + User user = mUsers.get(msg.getSession()); + boolean newUser = false; + + User self = mUsers.get(mSession); + + if(user == null) { + if(msg.hasName()) { + user = new User(msg.getSession(), msg.getName()); + mUsers.put(msg.getSession(), user); + newUser = true; + // Add user to root channel by default. This works because for some reason, we don't get a channel ID when the user joins into root. + Channel root = mChannels.get(0); + if(root == null) root = createStubChannel(0); + user.setChannel(root); + } + else + return; + } + + User actor = null; + if(msg.hasActor()) + actor = getUser(msg.getActor()); + + final User finalUser = user; + + if(msg.hasUserId()) { + user.setUserId(msg.getUserId()); + // Restore local mute and ignore from history + if (mLocalMuteHistory != null && mLocalMuteHistory.contains(user.getUserId())) { + user.setLocalMuted(true); + } + if (mLocalIgnoreHistory != null && mLocalIgnoreHistory.contains(user.getUserId())) { + user.setLocalIgnored(true); + } + } + + if(msg.hasHash()) { + user.setHash(msg.getHash()); + + /* + * TODO: + * - Check if user is local muted in database, if so re-mute them here + * - Check if user is friend, if so indicate + */ + } + + if(newUser) + mLogger.logInfo(mContext.getString(R.string.chat_notify_connected, MessageFormatter.highlightString(user.getName()))); + + if(msg.hasSelfDeaf() || msg.hasSelfMute()) { + if(msg.hasSelfMute()) + user.setSelfMuted(msg.getSelfMute()); + if(msg.hasSelfDeaf()) + user.setSelfDeafened(msg.getSelfDeaf()); + + if(self != null && user.getSession() != self.getSession() && user.getChannel().equals(self.getChannel())) { + if(user.isSelfMuted() && user.isSelfDeafened()) + mLogger.logInfo(mContext.getString(R.string.chat_notify_now_muted_deafened, MessageFormatter.highlightString(user.getName()))); + else if(user.isSelfMuted()) + mLogger.logInfo(mContext.getString(R.string.chat_notify_now_muted, MessageFormatter.highlightString(user.getName()))); + else + mLogger.logInfo(mContext.getString(R.string.chat_notify_now_unmuted, MessageFormatter.highlightString(user.getName()))); + } else if(self != null && user.getSession() == self.getSession()) { + if(user.isSelfMuted() && user.isSelfDeafened()) + mLogger.logInfo(mContext.getString(R.string.chat_notify_muted_deafened, MessageFormatter.highlightString(user.getName()))); + else if(user.isSelfMuted()) + mLogger.logInfo(mContext.getString(R.string.chat_notify_muted, MessageFormatter.highlightString(user.getName()))); + else + mLogger.logInfo(mContext.getString(R.string.chat_notify_unmuted, MessageFormatter.highlightString(user.getName()))); + } + } + + if(msg.hasRecording()) { + user.setRecording(msg.getRecording()); + + if(self != null) { + if(user.getSession() == self.getSession()) { + if(user.isRecording()) + mLogger.logInfo(mContext.getString(R.string.chat_notify_self_recording_started)); + else + mLogger.logInfo(mContext.getString(R.string.chat_notify_self_recording_stopped)); + } else { + Channel selfChannel = self.getChannel(); + // If in a linked channel OR the same channel as the current user, notify the user about recording + if(selfChannel != null && (selfChannel.getLinks().contains(selfChannel) || selfChannel.equals(user.getChannel()))) { + if(user.isRecording()) + mLogger.logInfo(mContext.getString(R.string.chat_notify_user_recording_started, MessageFormatter.highlightString(user.getName()))); + else + mLogger.logInfo(mContext.getString(R.string.chat_notify_user_recording_stopped, MessageFormatter.highlightString(user.getName()))); + } + } + } + } + + if(msg.hasDeaf() || msg.hasMute() || msg.hasSuppress() || msg.hasPrioritySpeaker()) { + if(msg.hasDeaf()) + user.setDeafened(msg.getDeaf()); + if(msg.hasMute()) + user.setMuted(msg.getMute()); + if(msg.hasSuppress()) + user.setSuppressed(msg.getSuppress()); + if(msg.hasPrioritySpeaker()) + user.setPrioritySpeaker(msg.getPrioritySpeaker()); + +// if(self != null && ((user.getChannelId() == self.getChannelId()) || (actor.getSessionId() == self.getSessionId()))) { +// if(user.getSessionId() == self.getSessionId()) { +// if(msg.hasMute() && msg.hasDeaf() && user.isMuted() && user.isDeafened()) { +// mLogger.logInfo(); +// } +// } +// } + + /* + * TODO: logging + * Base this off of Messages.cpp:353 + */ + } + + if(msg.hasChannelId()) { + final Channel channel = mChannels.get(msg.getChannelId()); + if(channel == null) { + Log.e(Constants.TAG, "Invalid channel for user!"); + return; // TODO handle better + } + final Channel old = user.getChannel(); + + user.setChannel(channel); + + if(!newUser) { + mObserver.onUserJoinedChannel(finalUser, channel, old); + } + + Channel sessionChannel = self != null ? self.getChannel() : null; + + // Notify the user of other users' current channel changes + if (self != null && sessionChannel != null && old != null && !self.equals(user)) { + // TODO add logic for other user moving self + String actorString = actor != null ? MessageFormatter.highlightString(actor.getName()) : mContext.getString(R.string.the_server); + if(!sessionChannel.equals(channel) && sessionChannel.equals(old)) { + // User moved out of self's channel + if(actor != null && actor.getSession() == user.getSession()) { + // By themselves + mLogger.logInfo(mContext.getString(R.string.chat_notify_user_left_channel, MessageFormatter.highlightString(user.getName()), MessageFormatter.highlightString(channel.getName()))); + } else { + // By external actor + mLogger.logInfo(mContext.getString(R.string.chat_notify_user_left_channel_by, MessageFormatter.highlightString(user.getName()), MessageFormatter.highlightString(channel.getName()), actorString)); + } + } else if(sessionChannel.equals(channel)) { + // User moved into self's channel + if(actor != null && actor.getSession() == user.getSession()) { + // By themselves + mLogger.logInfo(mContext.getString(R.string.chat_notify_user_joined_channel, MessageFormatter.highlightString(user.getName()))); + } else { + // By external actor + mLogger.logInfo(mContext.getString(R.string.chat_notify_user_joined_channel_by, MessageFormatter.highlightString(user.getName()), MessageFormatter.highlightString(old.getName()), actorString)); + } + } + } + + /* + * TODO: logging + * Base this off of Messages.cpp:454 + */ + } + + if(msg.hasName()) + user.setName(msg.getName()); + + if (msg.hasTextureHash()) { + user.setTextureHash(msg.getTextureHash()); + user.setTexture(null); // clear cached texture when we receive a new hash + } + + if (msg.hasTexture()) { + // FIXME: is it reasonable to create a bitmap here? How expensive? + user.setTexture(msg.getTexture()); + } + + if(msg.hasCommentHash()) + user.setCommentHash(msg.getCommentHash()); + + if(msg.hasComment()) + user.setComment(msg.getComment()); + + if (newUser) + mObserver.onUserConnected(user); + else + mObserver.onUserStateUpdated(user); + } + + @Override + public void messageUserRemove(Mumble.UserRemove msg) { + final User user = mUsers.get(msg.getSession()); + final User actor = mUsers.get(msg.getActor()); + final String reason = msg.getReason(); + + if(msg.getSession() == mSession) + mLogger.logWarning(mContext.getString(msg.getBan() ? R.string.chat_notify_kick_ban_self : R.string.chat_notify_kick_self, MessageFormatter.highlightString(actor.getName()), reason)); + else if(actor != null) + mLogger.logWarning(mContext.getString(msg.getBan() ? R.string.chat_notify_kick_ban : R.string.chat_notify_kick, MessageFormatter.highlightString(actor.getName()), reason, MessageFormatter.highlightString(user.getName()))); + else + mLogger.logInfo(mContext.getString(R.string.chat_notify_disconnected, MessageFormatter.highlightString(user.getName()))); + + user.setChannel(null); + mObserver.onUserRemoved(user, reason); + } + + @Override + public void messagePermissionDenied(final Mumble.PermissionDenied msg) { + final String reason; + switch (msg.getType()) { + case ChannelName: + reason = mContext.getString(R.string.deny_reason_channel_name); + break; + case TextTooLong: + reason = mContext.getString(R.string.deny_reason_text_too_long); + break; + case TemporaryChannel: + reason = mContext.getString(R.string.deny_reason_no_operation_temp); + break; + case MissingCertificate: + reason = mContext.getString(R.string.deny_reason_no_certificate); + break; + case UserName: + reason = mContext.getString(R.string.deny_reason_invalid_username); + break; + case ChannelFull: + reason = mContext.getString(R.string.deny_reason_channel_full); + break; + case NestingLimit: + reason = mContext.getString(R.string.deny_reason_channel_nesting); + break; + default: + if(msg.hasReason()) reason = mContext.getString(R.string.deny_reason_other, msg.getReason()); + else reason = mContext.getString(R.string.perm_denied); + + } + mObserver.onPermissionDenied(reason); + } + + @Override + public void messageTextMessage(Mumble.TextMessage msg) { + User sender = mUsers.get(msg.getActor()); + + if(sender != null && sender.isLocalIgnored()) + return; + + List<Channel> channels = new ArrayList<Channel>(msg.getChannelIdCount()); + for(int channelId : msg.getChannelIdList()) channels.add(mChannels.get(channelId)); + List<Channel> trees = new ArrayList<Channel>(msg.getTreeIdCount()); + for(int treeId : msg.getTreeIdList()) trees.add(mChannels.get(treeId)); + List<User> users = new ArrayList<User>(msg.getSessionCount()); + for(int userId : msg.getSessionList()) users.add(mUsers.get(userId)); + + String actorName = sender != null ? sender.getName() : mContext.getString(R.string.server); + + Message message = new Message(msg.getActor(), actorName, channels, trees, users, msg.getMessage()); + mObserver.onMessageLogged(message); + } + + @Override + public void messageServerSync(Mumble.ServerSync msg) { + mSession = msg.getSession(); + mLogger.logInfo(msg.getWelcomeText()); + } +} |