com.irccloud.android.data.EventsDataSource.java Source code

Java tutorial

Introduction

Here is the source code for com.irccloud.android.data.EventsDataSource.java

Source

/*
 * Copyright (c) 2015 IRCCloud, Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.irccloud.android.data;

import android.annotation.SuppressLint;
import android.text.Spanned;
import android.text.TextUtils;

import com.fasterxml.jackson.databind.JsonNode;
import com.irccloud.android.IRCCloudJSONObject;
import com.irccloud.android.Ignore;
import com.irccloud.android.R;
import com.irccloud.android.fragment.MessageViewFragment;

import java.util.HashMap;
import java.util.TimerTask;
import java.util.TreeMap;

@SuppressLint("UseSparseArrays")
public class EventsDataSource {

    public static class Event {
        public int cid;
        public int bid;
        public long eid;
        public String timestamp;
        public String type;
        public String msg;
        public String hostmask;
        public String from;
        public String from_mode;
        public String nick;
        public String old_nick;
        public String server;
        public String diff;
        public String html;
        public String chan;
        public boolean highlight;
        public boolean self;
        public boolean to_chan;
        public boolean to_buffer;
        public int color;
        public int bg_color;
        public JsonNode ops;
        public long group_eid;
        public int row_type;
        public String group_msg;
        public boolean linkify;
        public String target_mode;
        public int reqid;
        public boolean pending;
        public boolean failed;
        public String command;
        public int day;
        public Spanned formatted;
        public String contentDescription;
        public JsonNode entities;
        public TimerTask expiration_timer;

        public String toString() {
            return "{" + "cid: " + cid + " bid: " + bid + " eid: " + eid + " type: " + type + " timestamp: "
                    + timestamp + " from: " + from + " msg: " + msg + " html: " + html + " group_eid: " + group_eid
                    + " group_msg: " + group_msg + " pending: " + pending + "}";
        }

        public synchronized boolean isImportant(String buffer_type) {
            if (self)
                return false;
            if (type == null) {
                return false;
            }

            Ignore ignore = new Ignore();
            ServersDataSource.Server s = ServersDataSource.getInstance().getServer(cid);
            if (s != null) {
                ignore.setIgnores(s.ignores);
                String from = this.from;
                if (from == null || from.length() == 0)
                    from = this.nick;
                if (ignore.match(from + "!" + hostmask))
                    return false;
            }

            if (type.equals("notice") || type.equalsIgnoreCase("channel_invite")) {
                // Notices sent from the server (with no nick sender) aren't important
                // e.g. *** Looking up your hostname...
                if (from == null || from.length() == 0)
                    return false;

                // Notices and invites sent to a buffer shouldn't notify in the server buffer
                if (buffer_type.equalsIgnoreCase("console") && (to_chan || to_buffer))
                    return false;
            }
            return (type.equals("buffer_msg") || type.equals("buffer_me_msg") || type.equals("notice")
                    || type.equals("channel_invite") || type.equals("callerid") || type.equals("wallops"));
        }
    }

    private final HashMap<Integer, TreeMap<Long, Event>> events;
    private static EventsDataSource instance = null;
    public long highest_eid = -1;

    public synchronized static EventsDataSource getInstance() {
        if (instance == null)
            instance = new EventsDataSource();
        return instance;
    }

    public EventsDataSource() {
        events = new HashMap<>(100);
    }

    public void clear() {
        synchronized (events) {
            events.clear();
            highest_eid = -1;
        }
    }

    public void addEvent(Event event) {
        synchronized (events) {
            if (!events.containsKey(event.bid) || events.get(event.bid) == null)
                events.put(event.bid, new TreeMap<Long, Event>());

            events.get(event.bid).put(event.eid, event);
        }
    }

    public interface Formatter {
        public void format(IRCCloudJSONObject event, Event e);
    }

    private HashMap<String, Formatter> formatterMap = new HashMap<String, Formatter>() {
        {
            put("socket_closed", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.from = "";
                    e.row_type = MessageViewFragment.ROW_SOCKETCLOSED;
                    e.color = R.color.timestamp;
                    e.linkify = false;
                    if (event.has("pool_lost"))
                        e.msg = "Connection pool lost";
                    else if (event.has("server_ping_timeout"))
                        e.msg = "Server PING timed out";
                    else if (event.has("reason") && event.getString("reason").length() > 0)
                        e.msg = "Connection lost: " + reason(event.getString("reason"));
                    else if (event.has("abnormal"))
                        e.msg = "Connection closed unexpectedly";
                    else
                        e.msg = "";
                }
            });

            put("user_channel_mode", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.target_mode = event.getString("newmode");
                    e.chan = event.getString("channel");
                }
            });

            put("buffer_me_msg", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.nick = e.from;
                    e.from = "";
                }
            });

            put("nickname_in_use", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.from = event.getString("nick");
                    e.msg = "is already in use";
                    e.bg_color = R.color.error;
                }
            });

            put("unhandled_line", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.from = "";
                    StringBuilder msg = new StringBuilder();
                    if (event.has("command"))
                        msg.append(event.getString("command")).append(" ");
                    if (event.has("raw"))
                        msg.append(event.getString("raw"));
                    else
                        msg.append(event.getString("msg"));
                    e.msg = msg.toString();
                    e.bg_color = R.color.error;
                }
            });
            put("unparsed_line", get("unhandled_line"));

            put("connecting_cancelled", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.from = "";
                    e.msg = "Cancelled";
                    e.bg_color = R.color.error;
                }
            });

            put("connecting_failed", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.row_type = MessageViewFragment.ROW_SOCKETCLOSED;
                    e.color = R.color.timestamp;
                    e.from = "";
                    e.linkify = false;
                    String reason = reason(event.getString("reason"));
                    if (reason != null) {
                        e.msg = "Failed to connect: " + reason;
                    } else {
                        e.msg = "Failed to connect.";
                    }
                }
            });

            put("quit_server", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.from = "";
                    e.msg = "? You disconnected";
                    e.color = R.color.timestamp;
                    e.self = false;
                }
            });

            put("self_details", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.from = "";
                    e.msg = "<pre>Your hostmask: <b>" + event.getString("usermask") + "</b></pre>";
                    e.bg_color = R.color.status_bg;
                    e.linkify = false;
                }
            });

            put("myinfo", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.from = "";
                    StringBuilder msg = new StringBuilder();
                    msg.append("Host: ").append(event.getString("server")).append("\n");
                    msg.append("IRCd: ").append(event.getString("version")).append("\n");
                    msg.append("User modes: ").append(event.getString("user_modes")).append("\n");
                    msg.append("Channel modes: ").append(event.getString("channel_modes")).append("\n");
                    if (event.has("rest") && event.getString("rest").length() > 0)
                        msg.append("Parametric channel modes: ").append(event.getString("rest")).append("\n");
                    e.msg = "<pre>" + TextUtils.htmlEncode(msg.toString()) + "</pre>";
                    e.bg_color = R.color.status_bg;
                    e.linkify = false;
                }
            });

            put("user_mode", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.from = "";
                    e.msg = "<pre>Your user mode is: <b>+" + event.getString("newmode") + "</b></pre>";
                    e.bg_color = R.color.status_bg;
                }
            });

            put("your_unique_id", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.from = "";
                    e.msg = "<pre>Your unique ID is: <b>" + event.getString("unique_id") + "</b></pre>";
                    e.bg_color = R.color.status_bg;
                }
            });

            put("kill", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.from = "";
                    e.msg = "You were killed";
                    if (event.has("from"))
                        e.msg += " by " + event.getString("from");
                    if (event.has("killer_hostmask"))
                        e.msg += " (" + event.getString("killer_hostmask") + ")";
                    if (event.has("reason"))
                        e.msg += ": " + TextUtils.htmlEncode(event.getString("reason"));
                    e.bg_color = R.color.status_bg;
                    e.linkify = false;
                }
            });

            put("banned", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.from = "";
                    e.msg = "You were banned";
                    if (event.has("server"))
                        e.msg += " from " + event.getString("server");
                    if (event.has("reason"))
                        e.msg += ": " + TextUtils.htmlEncode(event.getString("reason"));
                    e.bg_color = R.color.status_bg;
                    e.linkify = false;
                }
            });

            put("channel_topic", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    if (event.has("author"))
                        e.from = event.getString("author");
                    else
                        e.from = event.getString("server");
                    if (event.getString("topic") != null && event.getString("topic").length() > 0)
                        e.msg = "set the topic: " + TextUtils.htmlEncode(event.getString("topic"));
                    else
                        e.msg = "cleared the topic";
                    e.bg_color = R.color.status_bg;
                }
            });

            put("channel_mode", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.nick = e.from;
                    e.from = "";
                    e.msg = "Channel mode set to: <b>" + event.getString("diff") + "</b>";
                    e.bg_color = R.color.status_bg;
                    e.linkify = false;
                    e.self = false;
                }
            });

            put("channel_mode_is", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.from = "";
                    if (event.getString("diff") != null && event.getString("diff").length() > 0)
                        e.msg = "Channel mode is: <b>" + event.getString("diff") + "</b>";
                    else
                        e.msg = "No channel mode";
                    e.bg_color = R.color.status_bg;
                    e.linkify = false;
                }
            });

            put("kicked_channel", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.from = "";
                    e.from_mode = null;
                    e.old_nick = event.getString("nick");
                    e.nick = event.getString("kicker");
                    e.hostmask = event.getString("kicker_hostmask");
                    e.color = R.color.timestamp;
                    e.linkify = false;
                    if (e.self)
                        e.row_type = MessageViewFragment.ROW_SOCKETCLOSED;
                }
            });
            put("you_kicked_channel", get("kicked_channel"));

            put("channel_mode_list_change", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    boolean unknown = true;
                    JsonNode ops = event.getJsonObject("ops");
                    if (ops != null) {
                        JsonNode add = ops.get("add");
                        if (add != null && add.size() > 0) {
                            JsonNode op = add.get(0);
                            if (op.get("mode").asText().equals("b")) {
                                e.nick = e.from;
                                e.from = "";
                                e.msg = "banned <b>" + op.get("param").asText()
                                        + "</b> (<font color=#808080>+b</font>)";
                                unknown = false;
                            } else if (op.get("mode").asText().equals("e")) {
                                e.nick = e.from;
                                e.from = "";
                                e.msg = "exempted <b>" + op.get("param").asText()
                                        + "</b> from bans (<font color=#808080>+e</font>)";
                                unknown = false;
                            } else if (op.get("mode").asText().equals("q")) {
                                if (op.get("param").asText().contains("@")
                                        || op.get("param").asText().contains("$")) {
                                    e.nick = e.from;
                                    e.from = "";
                                    e.msg = "quieted <b>" + op.get("param").asText()
                                            + "</b> (<font color=#808080>+q</font>)";
                                } else {
                                    e.type = "user_channel_mode";
                                    e.chan = event.getString("channel");
                                    e.nick = op.get("param").asText();
                                }
                                unknown = false;
                            } else if (op.get("mode").asText().equals("I")) {
                                e.nick = e.from;
                                e.from = "";
                                e.msg = "added <b>" + op.get("param").asText()
                                        + "</b> to the invite list (<font color=#808080>+I</font>)";
                                unknown = false;
                            }
                        }
                        JsonNode remove = ops.get("remove");
                        if (remove != null && remove.size() > 0) {
                            JsonNode op = remove.get(0);
                            if (op.get("mode").asText().equals("b")) {
                                e.nick = e.from;
                                e.from = "";
                                e.msg = "un-banned <b>" + op.get("param").asText()
                                        + "</b> (<font color=#808080>-b</font>)";
                                unknown = false;
                            } else if (op.get("mode").asText().equals("e")) {
                                e.nick = e.from;
                                e.from = "";
                                e.msg = "un-exempted <b>" + op.get("param").asText()
                                        + "</b> from bans (<font color=#808080>-e</font>)";
                                unknown = false;
                            } else if (op.get("mode").asText().equals("q")) {
                                if (op.get("param").asText().contains("@")
                                        || op.get("param").asText().contains("$")) {
                                    e.nick = e.from;
                                    e.from = "";
                                    e.msg = "un-quieted <b>" + op.get("param").asText()
                                            + "</b> (<font color=#808080>-q</font>)";
                                } else {
                                    e.type = "user_channel_mode";
                                    e.chan = event.getString("channel");
                                    e.nick = op.get("param").asText();
                                }
                                unknown = false;
                            } else if (op.get("mode").asText().equals("I")) {
                                e.nick = e.from;
                                e.from = "";
                                e.msg = "removed <b>" + op.get("param").asText()
                                        + "</b> from the invite list (<font color=#808080>-I</font>)";
                                unknown = false;
                            }
                        }
                    }
                    if (unknown) {
                        e.nick = e.from;
                        e.from = "";
                        e.msg = "set channel mode: <b>" + event.getString("diff") + "</b>";
                    }
                    e.bg_color = R.color.status_bg;
                    e.linkify = false;
                    e.self = false;
                }
            });

            put("motd_response", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    JsonNode lines = event.getJsonNode("lines");
                    e.from = "";
                    if (lines != null) {
                        StringBuilder builder = new StringBuilder("<pre>");
                        if (event.has("start"))
                            builder.append(event.getString("start")).append("<br/>");
                        for (int i = 0; i < lines.size(); i++) {
                            builder.append(TextUtils.htmlEncode(lines.get(i).asText()).replace("  ", " &nbsp;"))
                                    .append("<br/>");
                        }
                        builder.append("</pre>");
                        e.msg = builder.toString();
                    }
                    e.bg_color = R.color.self;
                }
            });
            put("server_motd", get("motd_response"));
            put("info_response", get("motd_response"));

            put("notice", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.chan = event.getString("target");
                    e.msg = "<pre>" + e.msg.replace("  ", " &nbsp;") + "</pre>";
                    e.bg_color = R.color.notice;
                }
            });

            put("newsflash", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.msg = "<pre>" + e.msg + "</pre>";
                    e.bg_color = R.color.notice;
                }
            });

            put("invited", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.from = event.getString("inviter");
                    e.msg = "<pre>invited " + event.getString("invitee") + " to join " + event.getString("channel")
                            + "</pre>";
                    e.bg_color = R.color.notice;
                }
            });

            put("generic_server_info", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.msg = "<pre>" + e.msg + "</pre>";
                    e.bg_color = R.color.notice;
                }
            });

            put("rehashed_config", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.msg = "Rehashed config: " + event.getString("file") + "(" + e.msg + ")";
                    e.msg = "<pre>" + e.msg + "</pre>";
                    e.bg_color = R.color.notice;
                }
            });

            put("knock", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    if (e.nick != null && e.nick.length() > 0) {
                        e.from = e.nick;
                        if (e.hostmask != null && e.hostmask.length() > 0)
                            e.msg += " (" + e.hostmask + ")";
                    } else {
                        e.msg = event.getString("userhost") + " " + e.msg;
                    }
                    e.msg = "<pre>" + e.msg + "</pre>";
                    e.bg_color = R.color.notice;
                }
            });

            put("hidden_host_set", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.bg_color = R.color.status_bg;
                    e.linkify = false;
                    e.from = "";
                    e.msg = "<b>" + event.getString("hidden_host") + "</b> " + e.msg;
                }
            });

            put("inviting_to_channel", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.from = "";
                    e.msg = "<pre>You invited " + event.getString("recipient") + " to join "
                            + event.getString("channel") + "</pre>";
                    e.bg_color = R.color.notice;
                }
            });

            put("channel_invite", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.msg = "<pre>Invite to join " + event.getString("channel") + "</pre>";
                    e.old_nick = event.getString("channel");
                    e.bg_color = R.color.notice;
                }
            });

            put("callerid", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.from = e.nick;
                    e.msg = "<pre>" + e.msg + "</pre>";
                    e.highlight = true;
                    e.linkify = false;
                    e.hostmask = event.getString("usermask");
                }
            });

            put("target_callerid", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.from = event.getString("target_nick");
                    e.msg = "<pre>" + e.msg + "</pre>";
                    e.bg_color = R.color.error;
                }
            });

            put("target_notified", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.from = event.getString("target_nick");
                    e.msg = "<pre>" + e.msg + "</pre>";
                    e.bg_color = R.color.error;
                }
            });

            put("link_channel", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    if (event.has("invalid_chan")) {
                        if (event.has("valid_chan")) {
                            e.msg = event.getString("invalid_chan") + "  " + event.getString("valid_chan") + " "
                                    + e.msg;
                        } else {
                            e.msg = event.getString("invalid_chan") + " " + e.msg;
                        }
                    }
                    e.bg_color = R.color.error;
                }
            });

            String[] statuses = { "server_motdstart", "server_welcome", "server_motd", "server_endofmotd",
                    "server_nomotd", "server_luserclient", "server_luserop", "server_luserconns", "server_luserme",
                    "server_n_local", "server_luserchannels", "server_n_global", "server_yourhost",
                    "server_created", "server_luserunknown", "server_snomask", "starircd_welcome", "zurna_motd",
                    "wait", "logged_in_as", "btn_metadata_set", "sasl_success", "you_are_operator", "codepage",
                    "logged_out", "nick_locked", "text", "admin_info" };
            Formatter statusFormatter = new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.bg_color = R.color.status_bg;
                    e.msg = "<pre>" + e.msg + "</pre>";
                    e.from = "";
                    if (!e.type.equals("server_motd") && !e.type.equals("zurna_motd"))
                        e.linkify = false;
                }
            };
            for (String status : statuses)
                put(status, statusFormatter);

            String[] stats = { "stats", "statslinkinfo", "statscommands", "statscline", "statsnline", "statsiline",
                    "statskline", "statsqline", "statsyline", "statsbline", "statsgline", "statstline",
                    "statseline", "statsvline", "statslline", "statsuptime", "statsoline", "statshline",
                    "statssline", "statsuline", "statsdebug", "endofstats" };
            Formatter statsFormatter = new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.from = "";
                    if (event.has("parts") && event.getString("parts").length() > 0)
                        e.msg = event.getString("parts") + ": " + e.msg;
                    e.bg_color = R.color.status_bg;
                    e.linkify = false;
                    e.msg = "<pre>" + e.msg + "</pre>";
                }
            };
            for (String stat : stats)
                put(stat, statsFormatter);

            String[] caps = { "cap_ls", "cap_req", "cap_ack", "cap_raw" };
            Formatter capsFormatter = new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.from = "";
                    e.bg_color = R.color.status_bg;
                    e.linkify = false;
                    switch (e.type) {
                    case "cap_ls":
                        e.msg = "<b>CAP</b> Server supports: ";
                        break;
                    case "cap_req":
                        e.msg = "<b>CAP</b> Requesting: ";
                        break;
                    case "cap_ack":
                        e.msg = "<b>CAP</b> Acknowledged: ";
                        break;
                    case "cap_raw":
                        e.msg = "<b>CAP</b> " + event.getString("line");
                        break;
                    }
                    JsonNode caps = event.getJsonNode("caps");
                    if (caps != null) {
                        for (int i = 0; i < caps.size(); i++) {
                            if (i > 0)
                                e.msg += " | ";
                            e.msg += caps.get(i).asText();
                        }
                    }
                    e.msg = "<pre>" + e.msg + "</pre>";
                }
            };
            for (String cap : caps)
                put(cap, capsFormatter);

            String[] helps = { "help_topics_start", "help_topics", "help_topics_end", "helphdr", "helpop",
                    "helptlr", "helphlp", "helpfwd", "helpign" };
            Formatter helpsFormatter = new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.bg_color = R.color.status_bg;
                    e.msg = "<pre>" + e.msg + "</pre>";
                    e.from = "";
                }
            };
            for (String help : helps)
                put(help, helpsFormatter);

            String[] errors = { "too_fast", "sasl_fail", "sasl_too_long", "sasl_aborted", "sasl_already", "no_bots",
                    "msg_services", "bad_ping", "not_for_halfops", "ambiguous_error_message", "list_syntax",
                    "who_syntax" };
            Formatter errorFormatter = new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.from = "";
                    e.bg_color = R.color.error;
                }
            };
            for (String error : errors)
                put(error, errorFormatter);

            put("version", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.from = "";
                    e.msg = "<pre><b>" + event.getString("server_version") + "</b> " + event.getString("comments")
                            + "</pre>";
                    e.bg_color = R.color.status_bg;
                    e.linkify = false;
                }
            });

            put("services_down", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.from = event.getString("services_name");
                    e.bg_color = R.color.error;
                }
            });

            put("unknown_umode", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.from = "";
                    if (event.has("flag"))
                        e.msg = "<b>" + event.getString("flag") + "</b> " + e.msg;
                    e.bg_color = R.color.error;
                }
            });

            put("kill_deny", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.from = event.getString("channel");
                    e.bg_color = R.color.error;
                }
            });

            put("chan_own_priv_needed", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.from = event.getString("channel");
                    e.bg_color = R.color.error;
                }
            });

            put("chan_forbidden", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.from = event.getString("channel");
                    e.bg_color = R.color.error;
                }
            });

            put("list_usage", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.msg = "<pre>" + e.msg + "</pre>";
                    e.bg_color = R.color.notice;
                }
            });

            put("time", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.bg_color = R.color.status_bg;
                    e.msg = "<pre>" + event.getString("time_string");
                    if (event.has("time_stamp") && event.getString("time_stamp").length() > 0)
                        e.msg += " (" + event.getString("time_stamp") + ")";
                    e.msg += "  <b>" + event.getString("time_server") + "</b></pre>";
                    e.linkify = false;
                }
            });

            put("watch_status", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.bg_color = R.color.status_bg;
                    e.from = event.getString("watch_nick");
                    e.msg = "<pre>" + e.msg + " (" + event.getString("username") + "@" + event.getString("userhost")
                            + ")</pre>";
                    e.linkify = false;
                }
            });

            put("sqline_nick", new Formatter() {
                @Override
                public void format(IRCCloudJSONObject event, Event e) {
                    e.bg_color = R.color.status_bg;
                    e.from = event.getString("charset");
                    e.msg = "<pre>" + e.msg + "</pre>";
                }
            });

        }
    };

    private String reason(String reason) {
        if (reason != null) {
            if (reason.equalsIgnoreCase("pool_lost")) {
                return "Connection pool failed";
            } else if (reason.equalsIgnoreCase("no_pool")) {
                return "No available connection pools";
            } else if (reason.equalsIgnoreCase("enetdown")) {
                return "Network down";
            } else if (reason.equalsIgnoreCase("etimedout") || reason.equalsIgnoreCase("timeout")) {
                return "Timed out";
            } else if (reason.equalsIgnoreCase("ehostunreach")) {
                return "Host unreachable";
            } else if (reason.equalsIgnoreCase("econnrefused")) {
                return "Connection refused";
            } else if (reason.equalsIgnoreCase("nxdomain")) {
                return "Invalid hostname";
            } else if (reason.equalsIgnoreCase("server_ping_timeout")) {
                return "PING timeout";
            } else if (reason.equalsIgnoreCase("ssl_certificate_error")) {
                return "SSL certificate error";
            } else if (reason.equalsIgnoreCase("ssl_error")) {
                return "SSL error";
            } else if (reason.equalsIgnoreCase("crash")) {
                return "Connection crashed";
            }
        }
        return reason;
    }

    public Event addEvent(IRCCloudJSONObject event) {
        synchronized (events) {
            if (!events.containsKey(event.bid()))
                events.put(event.bid(), new TreeMap<Long, Event>());

            Event e = getEvent(event.eid(), event.bid());
            if (e == null) {
                e = new Event();
                events.get(event.bid()).put(event.eid(), e);
            }
            e.cid = event.cid();
            e.bid = event.bid();
            e.eid = event.eid();
            e.type = event.type();
            e.msg = event.getString("msg");
            e.hostmask = event.getString("hostmask");
            e.from = event.getString("from");
            e.from_mode = event.getString("from_mode");
            e.chan = event.getString("chan");
            if (event.has("newnick"))
                e.nick = event.getString("newnick");
            else if (event.has("nick"))
                e.nick = event.getString("nick");
            else
                e.nick = null;
            e.old_nick = event.getString("oldnick");
            e.server = event.getString("server");
            e.diff = event.getString("diff");
            e.highlight = event.getBoolean("highlight");
            e.self = event.getBoolean("self");
            e.to_chan = event.getBoolean("to_chan");
            e.to_buffer = event.getBoolean("to_buffer");
            e.ops = event.getJsonNode("ops");
            e.color = R.color.row_message_label;
            e.bg_color = R.color.message_bg;
            e.row_type = 0;
            e.html = null;
            e.group_msg = null;
            e.linkify = true;
            e.target_mode = null;
            e.pending = false;
            e.failed = false;
            e.command = null;
            e.day = -1;
            e.entities = event.getJsonNode("entities");

            if (event.has("reqid"))
                e.reqid = event.getInt("reqid");
            else
                e.reqid = -1;

            if (e.from != null)
                e.from = TextUtils.htmlEncode(e.from);

            if (e.msg != null)
                e.msg = TextUtils.htmlEncode(e.msg);

            Formatter f = formatterMap.get(e.type);
            if (f != null)
                f.format(event, e);

            if (event.has("value") && !event.type().startsWith("cap_")) {
                e.msg = "<pre>" + event.getString("value") + " " + e.msg + "</pre>";
            }

            if (e.highlight)
                e.bg_color = R.color.highlight;

            if (e.self)
                e.bg_color = R.color.self;

            if (highest_eid < event.eid())
                highest_eid = event.eid();

            return e;
        }
    }

    public int getSizeOfBuffer(int bid) {
        synchronized (events) {
            if (events.containsKey(bid) && events.get(bid) != null)
                return events.get(bid).size();
        }
        return 0;
    }

    public Event getEvent(long eid, int bid) {
        synchronized (events) {
            if (events.containsKey(bid) && events.get(bid) != null)
                return events.get(bid).get(eid);
        }
        return null;
    }

    public void deleteEvent(long eid, int bid) {
        synchronized (events) {
            if (events.containsKey(bid) && events.get(bid) != null && events.get(bid).containsKey(eid))
                events.get(bid).remove(eid);
        }
    }

    public void deleteEventsForBuffer(int bid) {
        synchronized (events) {
            if (events.containsKey(bid) && events.get(bid) != null)
                events.remove(bid);
        }
    }

    public TreeMap<Long, Event> getEventsForBuffer(int bid) {
        synchronized (events) {
            if (events.containsKey(bid) && events.get(bid) != null) {
                return events.get(bid);
            }
        }
        return null;
    }

    public Long lastEidForBuffer(int bid) {
        synchronized (events) {
            if (events.containsKey(bid) && events.get(bid) != null) {
                Long[] eids = events.get(bid).keySet().toArray(new Long[events.get(bid).keySet().size()]);
                if (eids.length > 0)
                    return eids[eids.length - 1];
            }
        }
        return 0L;
    }

    public void pruneEvents(int bid) {
        synchronized (events) {
            TreeMap<Long, Event> e = events.get(bid);
            while (e != null && e.size() > 50 && e.firstKey() != null) {
                e.remove(e.firstKey());
            }
        }
    }

    public synchronized void clearCaches() {
        synchronized (events) {
            for (int bid : events.keySet()) {
                if (events.containsKey(bid) && events.get(bid) != null) {
                    for (Event e : events.get(bid).values()) {
                        e.timestamp = null;
                        e.html = null;
                        e.formatted = null;
                    }
                }
            }
        }
    }

    public synchronized Event findPendingEventForReqid(int bid, int reqid) {
        synchronized (events) {
            for (Event e : events.get(bid).values()) {
                if (e.reqid == reqid && (e.pending || e.failed))
                    return e;
            }
        }
        return null;
    }

    public synchronized void clearPendingEvents(int bid) {
        Event[] i;
        synchronized (events) {
            i = events.get(bid).values().toArray(new Event[events.get(bid).values().size()]);
        }
        for (Event e : i) {
            if (e.pending || e.failed) {
                if (e.expiration_timer != null) {
                    try {
                        e.expiration_timer.cancel();
                    } catch (Exception e1) {
                        //Timer already cancelled
                    }
                    e.expiration_timer = null;
                }
                deleteEvent(e.eid, e.bid);
            }
        }
    }
}