RetroShare/libresapi/src/api/ChatHandler.cpp
2016-06-05 11:26:29 -04:00

1129 lines
41 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "ChatHandler.h"
#include "Pagination.h"
#include "Operators.h"
#include "GxsResponseTask.h"
#include <retroshare/rspeers.h>
#include <retroshare/rsidentity.h>
#include <algorithm>
#include <limits>
#include <sstream>
#include <time.h>
namespace resource_api
{
std::string id(const ChatHandler::Msg& m)
{
std::stringstream ss;
ss << m.id;
return ss.str();
}
StreamBase& operator << (StreamBase& left, ChatHandler::Msg& m)
{
left << makeKeyValueReference("incoming", m.incoming)
<< makeKeyValueReference("was_send", m.was_send)
<< makeKeyValueReference("author_id", m.author_id)
<< makeKeyValueReference("author_name", m.author_name)
<< makeKeyValueReference("msg", m.msg)
<< makeKeyValueReference("recv_time", m.recv_time)
<< makeKeyValueReference("send_time", m.send_time)
<< makeKeyValueReference("id", m.id);
StreamBase& ls = left.getStreamToMember("links");
ls.getStreamToMember();
for(std::vector<ChatHandler::Triple>::iterator vit = m.links.begin(); vit != m.links.end(); ++vit)
{
ls.getStreamToMember() << makeKeyValueReference("first", vit->first)
<< makeKeyValueReference("second", vit->second)
<< makeKeyValueReference("third", vit->third);
}
return left;
}
bool compare_lobby_id(const ChatHandler::Lobby& l1, const ChatHandler::Lobby& l2)
{
if(l1.auto_subscribe && !l2.auto_subscribe) return true;
if(!l1.auto_subscribe && l2.auto_subscribe) return false;
if(l1.is_private && !l2.is_private) return true;
if(!l1.is_private && l2.is_private) return false;
if(l1.subscribed && !l2.subscribed) return true;
if(!l1.subscribed && l2.subscribed) return false;
return l1.id < l2.id;
}
StreamBase& operator <<(StreamBase& left, KeyValueReference<ChatLobbyId> kv)
{
if(left.serialise())
{
std::stringstream ss;
ss << kv.value;
left << makeKeyValue(kv.key, ss.str());
}
else
{
std::string val;
left << makeKeyValueReference(kv.key, val);
std::stringstream ss(val);
ss >> kv.value;
}
return left;
}
StreamBase& operator << (StreamBase& left, ChatHandler::Lobby& l)
{
ChatId chatId(l.id);
if (l.is_broadcast)
chatId = ChatId::makeBroadcastId();
left << makeKeyValueReference("id", l.id)
<< makeKeyValue("chat_id", chatId.toStdString())
<< makeKeyValueReference("name",l.name)
<< makeKeyValueReference("topic", l.topic)
<< makeKeyValueReference("subscribed", l.subscribed)
<< makeKeyValueReference("auto_subscribe", l.auto_subscribe)
<< makeKeyValueReference("is_private", l.is_private)
<< makeKeyValueReference("is_broadcast", l.is_broadcast)
<< makeKeyValueReference("gxs_id", l.gxs_id);
return left;
}
StreamBase& operator << (StreamBase& left, ChatHandler::ChatInfo& info)
{
left << makeKeyValueReference("remote_author_id", info.remote_author_id)
<< makeKeyValueReference("remote_author_name", info.remote_author_name)
<< makeKeyValueReference("is_broadcast", info.is_broadcast)
<< makeKeyValueReference("is_distant_chat_id", info.is_distant_chat_id)
<< makeKeyValueReference("is_lobby", info.is_lobby)
<< makeKeyValueReference("is_peer", info.is_peer);
return left;
}
class SendLobbyParticipantsTask: public GxsResponseTask
{
public:
SendLobbyParticipantsTask(RsIdentity* idservice, ChatHandler::LobbyParticipantsInfo pi):
GxsResponseTask(idservice, 0), mParticipantsInfo(pi)
{
const std::map<RsGxsId, time_t>& map = mParticipantsInfo.participants;
for(std::map<RsGxsId, time_t>::const_iterator mit = map.begin(); mit != map.end(); ++mit)
{
requestGxsId(mit->first);
}
}
private:
ChatHandler::LobbyParticipantsInfo mParticipantsInfo;
protected:
virtual void gxsDoWork(Request &/*req*/, Response &resp)
{
resp.mDataStream.getStreamToMember();
const std::map<RsGxsId, time_t>& map = mParticipantsInfo.participants;
for(std::map<RsGxsId, time_t>::const_iterator mit = map.begin(); mit != map.end(); ++mit)
{
StreamBase& stream = resp.mDataStream.getStreamToMember();
double last_active = mit->second;
stream << makeKeyValueReference("last_active", last_active);
streamGxsId(mit->first, stream.getStreamToMember("identity"));
}
resp.mStateToken = mParticipantsInfo.state_token;
resp.setOk();
done();
}
};
ChatHandler::ChatHandler(StateTokenServer *sts, RsNotify *notify, RsMsgs *msgs, RsPeers* peers, RsIdentity* identity, UnreadMsgNotify* unread):
mStateTokenServer(sts), mNotify(notify), mRsMsgs(msgs), mRsPeers(peers), mRsIdentity(identity), mUnreadMsgNotify(unread), mMtx("ChatHandler::mMtx")
{
mNotify->registerNotifyClient(this);
mStateTokenServer->registerTickClient(this);
mMsgStateToken = mStateTokenServer->getNewToken();
mLobbiesStateToken = mStateTokenServer->getNewToken();
mUnreadMsgsStateToken = mStateTokenServer->getNewToken();
addResourceHandler("*", this, &ChatHandler::handleWildcard);
addResourceHandler("lobbies", this, &ChatHandler::handleLobbies);
addResourceHandler("subscribe_lobby", this, &ChatHandler::handleSubscribeLobby);
addResourceHandler("unsubscribe_lobby", this, &ChatHandler::handleUnsubscribeLobby);
addResourceHandler("clear_lobby", this, &ChatHandler::handleClearLobby);
addResourceHandler("lobby_participants", this, &ChatHandler::handleLobbyParticipants);
addResourceHandler("messages", this, &ChatHandler::handleMessages);
addResourceHandler("send_message", this, &ChatHandler::handleSendMessage);
addResourceHandler("mark_chat_as_read", this, &ChatHandler::handleMarkChatAsRead);
addResourceHandler("info", this, &ChatHandler::handleInfo);
addResourceHandler("receive_status", this, &ChatHandler::handleReceiveStatus);
addResourceHandler("send_status", this, &ChatHandler::handleSendStatus);
addResourceHandler("unread_msgs", this, &ChatHandler::handleUnreadMsgs);
}
ChatHandler::~ChatHandler()
{
mNotify->unregisterNotifyClient(this);
mStateTokenServer->unregisterTickClient(this);
}
void ChatHandler::notifyChatMessage(const ChatMessage &msg)
{
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
mRawMsgs.push_back(msg);
}
void ChatHandler::notifyChatCleared(const ChatId &chat_id)
{
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
//Remove processed messages
std::list<Msg>& msgs = mMsgs[chat_id];
msgs.clear();
//Remove unprocessed messages
for(std::list<ChatMessage>::iterator lit = mRawMsgs.begin(); lit != mRawMsgs.end();)
{
ChatMessage& msg = *lit;
if (msg.chat_id == chat_id)
{
lit = mRawMsgs.erase(lit);
} else {
++lit;
}
}
}
void ChatHandler::notifyChatStatus(const ChatId &chat_id, const std::string &status)
{
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
locked_storeTypingInfo(chat_id, status);
}
void ChatHandler::notifyChatLobbyEvent(uint64_t lobby_id, uint32_t event_type,
const RsGxsId &nickname, const std::string& any_string)
{
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
if(event_type == RS_CHAT_LOBBY_EVENT_PEER_STATUS)
{
locked_storeTypingInfo(ChatId(lobby_id), any_string, nickname);
}
}
void ChatHandler::tick()
{
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
// first fetch lobbies
std::vector<Lobby> lobbies;
std::list<ChatLobbyId> subscribed_ids;
mRsMsgs->getChatLobbyList(subscribed_ids);
for(std::list<ChatLobbyId>::iterator lit = subscribed_ids.begin(); lit != subscribed_ids.end(); ++lit)
{
ChatLobbyInfo info;
if(mRsMsgs->getChatLobbyInfo(*lit,info))
{
Lobby l;
l.id = *lit;
l.name = info.lobby_name;
l.topic = info.lobby_topic;
l.subscribed = true;
l.auto_subscribe = info.lobby_flags & RS_CHAT_LOBBY_FLAGS_AUTO_SUBSCRIBE;
l.is_private = !(info.lobby_flags & RS_CHAT_LOBBY_FLAGS_PUBLIC);
l.is_broadcast = false;
l.gxs_id = info.gxs_id;
lobbies.push_back(l);
// update the lobby participants list
// maybe it causes to much traffic to do this in every tick,
// because the client would get the whole list every time a message was received
// we could reduce the checking frequency
std::map<ChatLobbyId, LobbyParticipantsInfo>::iterator mit = mLobbyParticipantsInfos.find(*lit);
if(mit == mLobbyParticipantsInfos.end())
{
mLobbyParticipantsInfos[*lit].participants = info.gxs_ids;
mLobbyParticipantsInfos[*lit].state_token = mStateTokenServer->getNewToken();
}
else
{
LobbyParticipantsInfo& pi = mit->second;
if(!std::equal(pi.participants.begin(), pi.participants.end(), info.gxs_ids.begin()))
{
pi.participants = info.gxs_ids;
mStateTokenServer->replaceToken(pi.state_token);
}
}
}
}
// remove participants info of old lobbies
std::vector<ChatLobbyId> participants_info_to_delete;
for(std::map<ChatLobbyId, LobbyParticipantsInfo>::iterator mit = mLobbyParticipantsInfos.begin();
mit != mLobbyParticipantsInfos.end(); ++mit)
{
if(std::find(subscribed_ids.begin(), subscribed_ids.end(), mit->first) == subscribed_ids.end())
{
participants_info_to_delete.push_back(mit->first);
}
}
for(std::vector<ChatLobbyId>::iterator vit = participants_info_to_delete.begin(); vit != participants_info_to_delete.end(); ++vit)
{
LobbyParticipantsInfo& pi = mLobbyParticipantsInfos[*vit];
mStateTokenServer->discardToken(pi.state_token);
mLobbyParticipantsInfos.erase(*vit);
}
{
Lobby l;
l.name = "BroadCast";
l.topic = "Retroshare broadcast chat: messages are sent to all connected friends.";
l.subscribed = true;
l.auto_subscribe = false;
l.is_private = false;
l.is_broadcast = true;
lobbies.push_back(l);
}
std::vector<VisibleChatLobbyRecord> unsubscribed_lobbies;
mRsMsgs->getListOfNearbyChatLobbies(unsubscribed_lobbies);
for(std::vector<VisibleChatLobbyRecord>::iterator vit = unsubscribed_lobbies.begin(); vit != unsubscribed_lobbies.end(); ++vit)
{
const VisibleChatLobbyRecord& info = *vit;
if(std::find(subscribed_ids.begin(), subscribed_ids.end(), info.lobby_id) == subscribed_ids.end())
{
Lobby l;
l.id = info.lobby_id;
l.name = info.lobby_name;
l.topic = info.lobby_topic;
l.subscribed = false;
l.auto_subscribe = info.lobby_flags & RS_CHAT_LOBBY_FLAGS_AUTO_SUBSCRIBE;
l.is_private = !(info.lobby_flags & RS_CHAT_LOBBY_FLAGS_PUBLIC);
l.is_broadcast = false;
l.gxs_id = RsGxsId();
lobbies.push_back(l);
}
}
// process new messages
bool changed = false;
bool lobby_unread_count_changed = false;
std::vector<std::list<ChatMessage>::iterator> done;
std::vector<RsPeerId> peers_changed;
bool gxs_id_failed = false; // to prevent asking for multiple failing gxs ids in one tick, to not flush the cache
for(std::list<ChatMessage>::iterator lit = mRawMsgs.begin(); lit != mRawMsgs.end(); ++lit)
{
ChatMessage& msg = *lit;
std::string author_id;
std::string author_name;
if(msg.chat_id.isBroadcast())
{
author_id = msg.broadcast_peer_id.toStdString();
author_name = mRsPeers->getPeerName(msg.broadcast_peer_id);
}
else if(msg.chat_id.isDistantChatId())
{
DistantChatPeerInfo dcpinfo ;
if(!rsMsgs->getDistantChatStatus(msg.chat_id.toDistantChatId(),dcpinfo))
{
std::cerr << "(EE) cannot get info for distant chat peer " << msg.chat_id.toDistantChatId() << std::endl;
continue ;
}
RsIdentityDetails details;
if(!gxs_id_failed && mRsIdentity->getIdDetails(msg.incoming? dcpinfo.to_id: dcpinfo.own_id, details))
{
author_id = details.mId.toStdString();
author_name = details.mNickname;
}
else
{
gxs_id_failed = true;
continue;
}
}
else if(msg.chat_id.isLobbyId())
{
author_id = msg.lobby_peer_gxs_id.toStdString();
RsIdentityDetails details;
if(!gxs_id_failed && mRsIdentity->getIdDetails(msg.lobby_peer_gxs_id, details))
{
author_name = details.mNickname;
lobby_unread_count_changed = true;
}
else
{
gxs_id_failed = true;
continue;
}
}
else if(msg.chat_id.isPeerId())
{
RsPeerId id;
if(msg.incoming)
id = msg.chat_id.toPeerId();
else
id = mRsPeers->getOwnId();
author_id = id.toStdString();
author_name = mRsPeers->getPeerName(id);
if(std::find(peers_changed.begin(), peers_changed.end(), msg.chat_id.toPeerId()) == peers_changed.end())
peers_changed.push_back(msg.chat_id.toPeerId());
}
else
{
std::cerr << "Error in ChatHandler::tick(): unhandled chat_id=" << msg.chat_id.toStdString() << std::endl;
// remove from queue, so msgs with wrong ids to not pile up
done.push_back(lit);
continue;
}
if(mChatInfo.find(msg.chat_id) == mChatInfo.end())
{
ChatInfo info;
info.is_broadcast = msg.chat_id.isBroadcast();
info.is_distant_chat_id = msg.chat_id.isDistantChatId();
info.is_lobby = msg.chat_id.isLobbyId();
info.is_peer = msg.chat_id.isPeerId();
if(msg.chat_id.isLobbyId())
{
for(std::vector<Lobby>::iterator vit = mLobbies.begin(); vit != mLobbies.end(); ++vit)
{
if(vit->id == msg.chat_id.toLobbyId())
info.remote_author_name = vit->name;
}
}
else if(msg.chat_id.isDistantChatId())
{
RsIdentityDetails details;
DistantChatPeerInfo dcpinfo ;
if(!gxs_id_failed && rsMsgs->getDistantChatStatus(msg.chat_id.toDistantChatId(),dcpinfo)
&& mRsIdentity->getIdDetails(msg.incoming? dcpinfo.to_id: dcpinfo.own_id, details))
{
info.remote_author_id = details.mId.toStdString();
info.remote_author_name = details.mNickname;
}
else
{
gxs_id_failed = true;
continue;
}
}
else if(msg.chat_id.isPeerId())
{
info.remote_author_id = msg.chat_id.toPeerId().toStdString();
info.remote_author_name = mRsPeers->getPeerName(msg.chat_id.toPeerId());
}
mChatInfo[msg.chat_id] = info;
}
Msg m;
m.read = !msg.incoming;
m.incoming = msg.incoming;
m.was_send = msg.online;
m.author_id = author_id;
m.author_name = author_name;
// remove html tags from chat message
// extract links form href
getPlainText(msg.msg, m.msg, m.links);
m.recv_time = msg.recvTime;
m.send_time = msg.sendTime;
m.id = RSRandom::random_u32();
mMsgs[msg.chat_id].push_back(m);
done.push_back(lit);
changed = true;
}
for(std::vector<std::list<ChatMessage>::iterator>::iterator vit = done.begin(); vit != done.end(); ++vit)
mRawMsgs.erase(*vit);
// send changes
if(changed)
{
mStateTokenServer->replaceToken(mMsgStateToken);
mStateTokenServer->replaceToken(mUnreadMsgsStateToken);
}
for(std::vector<RsPeerId>::iterator vit = peers_changed.begin(); vit != peers_changed.end(); ++vit)
{
const std::list<Msg>& msgs = mMsgs[ChatId(*vit)];
uint32_t count = 0;
for(std::list<Msg>::const_iterator lit = msgs.begin(); lit != msgs.end(); ++lit)
if(!lit->read)
count++;
if(mUnreadMsgNotify)
mUnreadMsgNotify->notifyUnreadMsgCountChanged(*vit, count);
}
std::sort(lobbies.begin(), lobbies.end(), &compare_lobby_id);
if(lobby_unread_count_changed || mLobbies != lobbies)
{
mStateTokenServer->replaceToken(mLobbiesStateToken);
mLobbies = lobbies;
}
}
static void writeUTF8 ( std::ostream & Out, unsigned int Ch )
/* writes Ch in UTF-8 encoding to Out. Note this version only deals
with characters up to 16 bits.
From: http://www.codecodex.com/wiki/Unescape_HTML_special_characters_from_a_String*/
{
if (Ch >= 0x800)
{
Out.put(0xE0 | ((Ch >> 12) & 0x0F));
Out.put(0x80 | ((Ch >> 6) & 0x3F));
Out.put(0x80 | ((Ch ) & 0x3F));
}
else if (Ch >= 0x80)
{
Out.put(0xC0 | ((Ch >> 6) & 0x1F));
Out.put(0x80 | ((Ch ) & 0x3F));
}
else
{
Out.put(Ch);
}
}
static unsigned int stringToDecUInt ( std::string str)
{
unsigned int out = 0;
unsigned int max = std::numeric_limits<int>::max() / 10;
int lenght = str.length();
for (int curs = 0; curs < lenght; ++curs) {
char c = str[curs];
if ( (c >= '0')
&& (c <= '9')
&& (out < max)
) {
out *= 10;
out += (int)( c - '0');
} else return 0;
}
return out;
}
void ChatHandler::getPlainText(const std::string& in, std::string &out, std::vector<Triple> &links)
{
if (in.size() == 0)
return;
if (in[0] != '<' || in[in.size() - 1] != '>')
{
// It's a plain text message without HTML
out = in;
return;
}
bool ignore = false;
bool keep_link = false;
std::string last_six_chars;
unsigned int tag_start_index = 0;
Triple current_link;
bool onEscapeChar = false;
unsigned int escapeCharIndexStart = -1;
for(unsigned int i = 0; i < in.size(); ++i)
{
if(keep_link && in[i] == '"')
{
keep_link = false;
current_link.second = out.size();
}
if(last_six_chars == "href=\"")
{
keep_link = true;
current_link.first = out.size();
}
// "rising edge" sets mode to ignore
if(in[i] == '<')
{
tag_start_index = i;
ignore = true;
}
if(!ignore || keep_link)
out += in[i];
// "falling edge" resets mode to keep
if(in[i] == '>' && ignore) {
// leave ignore mode on, if it's a style tag
if (tag_start_index == 0 || tag_start_index + 6 > i || in.substr(tag_start_index, 6) != "<style") {
ignore = false;
tag_start_index = 0;
}
}
last_six_chars += in[i];
if(last_six_chars.size() > 6)
last_six_chars = last_six_chars.substr(1);
std::string a = "</a>";
if( current_link.first != -1
&& last_six_chars.size() >= a.size()
&& last_six_chars.substr(last_six_chars.size()-a.size()) == a)
{
// only allow these protocols
// we don't want for example javascript:alert(0)
std::string http = "http://";
std::string https = "https://";
std::string retroshare = "retroshare://";
if( out.substr(current_link.first, http.size()) == http
|| out.substr(current_link.first, https.size()) == https
|| out.substr(current_link.first, retroshare.size()) == retroshare)
{
current_link.third = out.size();
links.push_back(current_link);
}
current_link = Triple();
}
std::string br = "<br/>";
if(last_six_chars.size() >= br.size()
&& last_six_chars.substr(last_six_chars.size()-br.size()) == br)
{
out += "\n";
}
if (in[i] == '&') {
onEscapeChar = true;
escapeCharIndexStart = out.length();
}
if (ignore || keep_link) {
onEscapeChar = false;
escapeCharIndexStart = -1;
}
if ((in[i] == ';') && onEscapeChar) {
onEscapeChar = false;
bool escapeFound = true;
std::string escapeReplace = "";
//Keep only escape value to replace it
std::string escapeCharValue = out.substr(escapeCharIndexStart,
out.length() - escapeCharIndexStart - 1);
if (escapeCharValue[0] == '#') {
int escapedCharUTF8 = stringToDecUInt(escapeCharValue.substr(1));
std::ostringstream escapedSStream;
writeUTF8( escapedSStream, escapedCharUTF8);
escapeReplace = escapedSStream.str();
} else if (escapeCharValue == "euro") {
escapeReplace = "";
} else if (escapeCharValue == "nbsp") {
escapeReplace = " ";
} else if (escapeCharValue == "quot") {
escapeReplace = "\"";
} else if (escapeCharValue == "amp") {
escapeReplace = "&";
} else if (escapeCharValue == "lt") {
escapeReplace = "<";
} else if (escapeCharValue == "gt") {
escapeReplace = ">";
} else if (escapeCharValue == "iexcl") {
escapeReplace = "¡";
} else if (escapeCharValue == "cent") {
escapeReplace = "¢";
} else if (escapeCharValue == "pound") {
escapeReplace = "£";
} else if (escapeCharValue == "curren") {
escapeReplace = "¤";
} else if (escapeCharValue == "yen") {
escapeReplace = "¥";
} else if (escapeCharValue == "brvbar") {
escapeReplace = "¦";
} else if (escapeCharValue == "sect") {
escapeReplace = "§";
} else if (escapeCharValue == "uml") {
escapeReplace = "¨";
} else if (escapeCharValue == "copy") {
escapeReplace = "©";
} else if (escapeCharValue == "ordf") {
escapeReplace = "ª";
} else if (escapeCharValue == "not") {
escapeReplace = "¬";
} else if (escapeCharValue == "shy") {
escapeReplace = " ";//?
} else if (escapeCharValue == "reg") {
escapeReplace = "®";
} else if (escapeCharValue == "macr") {
escapeReplace = "¯";
} else if (escapeCharValue == "deg") {
escapeReplace = "°";
} else if (escapeCharValue == "plusmn") {
escapeReplace = "±";
} else if (escapeCharValue == "sup2") {
escapeReplace = "²";
} else if (escapeCharValue == "sup3") {
escapeReplace = "³";
} else if (escapeCharValue == "acute") {
escapeReplace = "´";
} else if (escapeCharValue == "micro") {
escapeReplace = "µ";
} else if (escapeCharValue == "para") {
escapeReplace = "";
} else if (escapeCharValue == "middot") {
escapeReplace = "·";
} else if (escapeCharValue == "cedil") {
escapeReplace = "¸";
} else if (escapeCharValue == "sup1") {
escapeReplace = "¹";
} else if (escapeCharValue == "ordm") {
escapeReplace = "º";
} else if (escapeCharValue == "raquo") {
escapeReplace = "»";
} else if (escapeCharValue == "frac14") {
escapeReplace = "¼";
} else if (escapeCharValue == "frac12") {
escapeReplace = "½";
} else if (escapeCharValue == "frac34") {
escapeReplace = "¾";
} else if (escapeCharValue == "iquest") {
escapeReplace = "¿";
} else if (escapeCharValue == "Agrave") {
escapeReplace = "À";
} else if (escapeCharValue == "Aacute") {
escapeReplace = "Á";
} else if (escapeCharValue == "Acirc") {
escapeReplace = "Â";
} else if (escapeCharValue == "Atilde") {
escapeReplace = "Ã";
} else if (escapeCharValue == "Auml") {
escapeReplace = "Ä";
} else if (escapeCharValue == "Aring") {
escapeReplace = "Å";
} else if (escapeCharValue == "AElig") {
escapeReplace = "Æ";
} else if (escapeCharValue == "Ccedil") {
escapeReplace = "Ç";
} else if (escapeCharValue == "Egrave") {
escapeReplace = "È";
} else if (escapeCharValue == "Eacute") {
escapeReplace = "É";
} else if (escapeCharValue == "Ecirc") {
escapeReplace = "Ê";
} else if (escapeCharValue == "Euml") {
escapeReplace = "Ë";
} else if (escapeCharValue == "Igrave") {
escapeReplace = "Ì";
} else if (escapeCharValue == "Iacute") {
escapeReplace = "Í";
} else if (escapeCharValue == "Icirc") {
escapeReplace = "Î";
} else if (escapeCharValue == "Iuml") {
escapeReplace = "Ï";
} else if (escapeCharValue == "ETH") {
escapeReplace = "Ð";
} else if (escapeCharValue == "Ntilde") {
escapeReplace = "Ñ";
} else if (escapeCharValue == "Ograve") {
escapeReplace = "Ò";
} else if (escapeCharValue == "Oacute") {
escapeReplace = "Ó";
} else if (escapeCharValue == "Ocirc") {
escapeReplace = "Ô";
} else if (escapeCharValue == "Otilde") {
escapeReplace = "Õ";
} else if (escapeCharValue == "Ouml") {
escapeReplace = "Ö";
} else if (escapeCharValue == "times") {
escapeReplace = "×";
} else if (escapeCharValue == "Oslash") {
escapeReplace = "Ø";
} else if (escapeCharValue == "Ugrave") {
escapeReplace = "Ù";
} else if (escapeCharValue == "Uacute") {
escapeReplace = "Ú";
} else if (escapeCharValue == "Ucirc") {
escapeReplace = "Û";
} else if (escapeCharValue == "Uuml") {
escapeReplace = "Ü";
} else if (escapeCharValue == "Yacute") {
escapeReplace = "Ý";
} else if (escapeCharValue == "THORN") {
escapeReplace = "Þ";
} else if (escapeCharValue == "szlig") {
escapeReplace = "ß";
} else if (escapeCharValue == "agrave") {
escapeReplace = "à";
} else if (escapeCharValue == "aacute") {
escapeReplace = "á";
} else if (escapeCharValue == "acirc") {
escapeReplace = "â";
} else if (escapeCharValue == "atilde") {
escapeReplace = "ã";
} else if (escapeCharValue == "auml") {
escapeReplace = "ä";
} else if (escapeCharValue == "aring") {
escapeReplace = "å";
} else if (escapeCharValue == "aelig") {
escapeReplace = "æ";
} else if (escapeCharValue == "ccedil") {
escapeReplace = "ç";
} else if (escapeCharValue == "egrave") {
escapeReplace = "è";
} else if (escapeCharValue == "eacute") {
escapeReplace = "é";
} else if (escapeCharValue == "ecirc") {
escapeReplace = "ê";
} else if (escapeCharValue == "euml") {
escapeReplace = "ë";
} else if (escapeCharValue == "igrave") {
escapeReplace = "ì";
} else if (escapeCharValue == "iacute") {
escapeReplace = "í";
} else if (escapeCharValue == "icirc") {
escapeReplace = "î";
} else if (escapeCharValue == "iuml") {
escapeReplace = "ï";
} else if (escapeCharValue == "eth") {
escapeReplace = "ð";
} else if (escapeCharValue == "ntilde") {
escapeReplace = "ñ";
} else if (escapeCharValue == "ograve") {
escapeReplace = "ò";
} else if (escapeCharValue == "oacute") {
escapeReplace = "ó";
} else if (escapeCharValue == "ocirc") {
escapeReplace = "ô";
} else if (escapeCharValue == "otilde") {
escapeReplace = "õ";
} else if (escapeCharValue == "ouml") {
escapeReplace = "ö";
} else if (escapeCharValue == "divide") {
escapeReplace = "÷";
} else if (escapeCharValue == "oslash") {
escapeReplace = "ø";
} else if (escapeCharValue == "ugrave") {
escapeReplace = "ù";
} else if (escapeCharValue == "uacute") {
escapeReplace = "ú";
} else if (escapeCharValue == "ucirc") {
escapeReplace = "û";
} else if (escapeCharValue == "uuml") {
escapeReplace = "ü";
} else if (escapeCharValue == "yacute") {
escapeReplace = "ý";
} else if (escapeCharValue == "thorn") {
escapeReplace = "þ";
} else {
escapeFound = false;
}
if (escapeFound) {
out = out.substr(0, escapeCharIndexStart-1);
out += escapeReplace;
}
}
}
}
void ChatHandler::locked_storeTypingInfo(const ChatId &chat_id, std::string status, RsGxsId lobby_gxs_id)
{
TypingLabelInfo& info = mTypingLabelInfo[chat_id];
info.timestamp = time(0);
info.status = status;
mStateTokenServer->replaceToken(info.state_token);
info.author_id = lobby_gxs_id;
}
void ChatHandler::handleWildcard(Request &/*req*/, Response &resp)
{
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
resp.mDataStream.getStreamToMember();
for(std::map<ChatId, std::list<Msg> >::iterator mit = mMsgs.begin(); mit != mMsgs.end(); ++mit)
{
resp.mDataStream.getStreamToMember() << makeValue(mit->first.toStdString());
}
resp.setOk();
}
void ChatHandler::handleLobbies(Request &/*req*/, Response &resp)
{
tick();
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
resp.mDataStream.getStreamToMember();
for(std::vector<Lobby>::iterator vit = mLobbies.begin(); vit != mLobbies.end(); ++vit)
{
uint32_t unread_msgs = 0;
ChatId chat_id(vit->id);
std::map<ChatId, std::list<Msg> >::iterator mit = mMsgs.find(chat_id);
if(mit != mMsgs.end())
{
std::list<Msg>& msgs = mit->second;
for(std::list<Msg>::iterator lit = msgs.begin(); lit != msgs.end(); ++lit)
if(!lit->read)
unread_msgs++;
}
resp.mDataStream.getStreamToMember() << *vit << makeKeyValueReference("unread_msg_count", unread_msgs);
}
resp.mStateToken = mLobbiesStateToken;
resp.setOk();
}
void ChatHandler::handleSubscribeLobby(Request &req, Response &resp)
{
ChatLobbyId id = 0;
RsGxsId gxs_id;
req.mStream << makeKeyValueReference("id", id) << makeKeyValueReference("gxs_id", gxs_id);
if(id == 0)
{
resp.setFail("Error: id must not be null");
return;
}
if(gxs_id.isNull())
{
resp.setFail("Error: gxs_id must not be null");
return;
}
if(mRsMsgs->joinVisibleChatLobby(id, gxs_id))
resp.setOk();
else
resp.setFail("lobby join failed. (See console for more info)");
}
void ChatHandler::handleUnsubscribeLobby(Request &req, Response &resp)
{
ChatLobbyId id = 0;
req.mStream << makeKeyValueReference("id", id);
mRsMsgs->unsubscribeChatLobby(id);
resp.setOk();
}
void ChatHandler::handleClearLobby(Request &req, Response &resp)
{
ChatLobbyId id = 0;
req.mStream << makeKeyValueReference("id", id);
if (id !=0) {
notifyChatCleared(ChatId(id));
} else {
//Is BroadCast
notifyChatCleared(ChatId("B"));
}
resp.setOk();
}
ResponseTask* ChatHandler::handleLobbyParticipants(Request &req, Response &resp)
{
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
ChatId id(req.mPath.top());
if(!id.isLobbyId())
{
resp.setFail("Path element \""+req.mPath.top()+"\" is not a ChatLobbyId.");
return 0;
}
std::map<ChatLobbyId, LobbyParticipantsInfo>::const_iterator mit = mLobbyParticipantsInfos.find(id.toLobbyId());
if(mit == mLobbyParticipantsInfos.end())
{
resp.setFail("lobby not found");
return 0;
}
return new SendLobbyParticipantsTask(mRsIdentity, mit->second);
}
void ChatHandler::handleMessages(Request &req, Response &resp)
{
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
ChatId id(req.mPath.top());
// make response a list
resp.mDataStream.getStreamToMember();
if(id.isNotSet())
{
resp.setFail("\""+req.mPath.top()+"\" is not a valid chat id");
return;
}
std::map<ChatId, std::list<Msg> >::iterator mit = mMsgs.find(id);
if(mit == mMsgs.end())
{
resp.mStateToken = mMsgStateToken; // even set state token, if not found yet, maybe later messages arrive and then the chat id will be found
resp.setFail("chat with id=\""+req.mPath.top()+"\" not found");
return;
}
resp.mStateToken = mMsgStateToken;
handlePaginationRequest(req, resp, mit->second);
}
void ChatHandler::handleSendMessage(Request &req, Response &resp)
{
std::string chat_id;
std::string msg;
req.mStream << makeKeyValueReference("chat_id", chat_id)
<< makeKeyValueReference("msg", msg);
ChatId id(chat_id);
if(id.isNotSet())
{
resp.setFail("chat_id is invalid");
return;
}
if(mRsMsgs->sendChat(id, msg))
resp.setOk();
else
resp.setFail("failed to send message");
}
void ChatHandler::handleMarkChatAsRead(Request &req, Response &resp)
{
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
ChatId id(req.mPath.top());
if(id.isNotSet())
{
resp.setFail("\""+req.mPath.top()+"\" is not a valid chat id");
return;
}
std::map<ChatId, std::list<Msg> >::iterator mit = mMsgs.find(id);
if(mit == mMsgs.end())
{
resp.setFail("chat not found. Maybe this chat does not have messages yet?");
return;
}
std::list<Msg>& msgs = mit->second;
for(std::list<Msg>::iterator lit = msgs.begin(); lit != msgs.end(); ++lit)
{
lit->read = true;
}
// lobby list contains unread msgs, so update it
if(id.isLobbyId())
mStateTokenServer->replaceToken(mLobbiesStateToken);
if(id.isPeerId() && mUnreadMsgNotify)
mUnreadMsgNotify->notifyUnreadMsgCountChanged(id.toPeerId(), 0);
mStateTokenServer->replaceToken(mUnreadMsgsStateToken);
}
void ChatHandler::handleInfo(Request &req, Response &resp)
{
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
ChatId id(req.mPath.top());
if(id.isNotSet())
{
resp.setFail("\""+req.mPath.top()+"\" is not a valid chat id");
return;
}
std::map<ChatId, ChatInfo>::iterator mit = mChatInfo.find(id);
if(mit == mChatInfo.end())
{
resp.setFail("chat not found.");
return;
}
resp.mDataStream << mit->second;
resp.setOk();
}
class SendTypingLabelInfo: public GxsResponseTask
{
public:
SendTypingLabelInfo(RsIdentity* identity, RsPeers* peers, ChatId id, const ChatHandler::TypingLabelInfo& info):
GxsResponseTask(identity), mState(BEGIN), mPeers(peers),mId(id), mInfo(info) {}
private:
enum State {BEGIN, WAITING_ID};
State mState;
RsPeers* mPeers;
ChatId mId;
ChatHandler::TypingLabelInfo mInfo;
protected:
void gxsDoWork(Request& /*req*/, Response& resp)
{
if(mState == BEGIN)
{
// lobby and distant require to fetch a gxs_id
if(mId.isLobbyId())
{
requestGxsId(mInfo.author_id);
}
else if(mId.isDistantChatId())
{
DistantChatPeerInfo dcpinfo ;
rsMsgs->getDistantChatStatus(mId.toDistantChatId(), dcpinfo);
requestGxsId(dcpinfo.to_id);
}
mState = WAITING_ID;
}
else
{
std::string name = "BUG: case not handled in SendTypingLabelInfo";
if(mId.isPeerId())
{
name = mPeers->getPeerName(mId.toPeerId());
}
else if(mId.isDistantChatId())
{
DistantChatPeerInfo dcpinfo ;
rsMsgs->getDistantChatStatus(mId.toDistantChatId(), dcpinfo);
name = getName(dcpinfo.to_id);
}
else if(mId.isLobbyId())
{
name = getName(mInfo.author_id);
}
else if(mId.isBroadcast())
{
name = mPeers->getPeerName(mId.broadcast_status_peer_id);
}
uint32_t ts = mInfo.timestamp;
resp.mDataStream << makeKeyValueReference("author_name", name)
<< makeKeyValueReference("timestamp", ts)
<< makeKeyValueReference("status_string", mInfo.status);
resp.mStateToken = mInfo.state_token;
resp.setOk();
done();
}
}
};
ResponseTask* ChatHandler::handleReceiveStatus(Request &req, Response &resp)
{
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
ChatId id(req.mPath.top());
if(id.isNotSet())
{
resp.setFail("\""+req.mPath.top()+"\" is not a valid chat id");
return 0;
}
std::map<ChatId, TypingLabelInfo>::iterator mit = mTypingLabelInfo.find(id);
if(mit == mTypingLabelInfo.end())
{
locked_storeTypingInfo(id, "");
mit = mTypingLabelInfo.find(id);
}
return new SendTypingLabelInfo(mRsIdentity, mRsPeers, id, mit->second);
}
void ChatHandler::handleSendStatus(Request &req, Response &resp)
{
std::string chat_id;
std::string status;
req.mStream << makeKeyValueReference("chat_id", chat_id)
<< makeKeyValueReference("status", status);
ChatId id(chat_id);
if(id.isNotSet())
{
resp.setFail("chat_id is invalid");
return;
}
mRsMsgs->sendStatusString(id, status);
resp.setOk();
}
void ChatHandler::handleUnreadMsgs(Request &/*req*/, Response &resp)
{
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
resp.mDataStream.getStreamToMember();
for(std::map<ChatId, std::list<Msg> >::const_iterator mit = mMsgs.begin(); mit != mMsgs.end(); ++mit)
{
uint32_t count = 0;
for(std::list<Msg>::const_iterator lit = mit->second.begin(); lit != mit->second.end(); ++lit)
if(!lit->read)
count++;
std::map<ChatId, ChatInfo>::iterator mit2 = mChatInfo.find(mit->first);
if(mit2 == mChatInfo.end())
std::cerr << "Error in ChatHandler::handleUnreadMsgs(): ChatInfo not found. It is weird if this happens. Normally it should not happen." << std::endl;
if(count && (mit2 != mChatInfo.end()))
{
resp.mDataStream.getStreamToMember()
<< makeKeyValue("id", mit->first.toStdString())
<< makeKeyValueReference("unread_count", count)
<< mit2->second;
}
}
resp.mStateToken = mUnreadMsgsStateToken;
}
} // namespace resource_api