phoenix-firestorm/indra/newview/llimview.cpp

4981 lines
162 KiB
C++

/**
* @file LLIMMgr.cpp
* @brief Container for Instant Messaging
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "llviewerprecompiledheaders.h"
#include "llimview.h"
#include "llavatarnamecache.h" // IDEVO
#include "llavataractions.h"
#include "llfloaterconversationlog.h"
#include "llfloaterreg.h"
#include "llfontgl.h"
#include "llgl.h"
#include "llrect.h"
#include "llerror.h"
#include "llbutton.h"
#include "llsdutil_math.h"
#include "llstring.h"
#include "lltextutil.h"
#include "lltrans.h"
#include "lltranslate.h"
#include "lluictrlfactory.h"
#include "llfloaterimsessiontab.h"
#include "llagent.h"
#include "llagentui.h"
#include "llappviewer.h"
#include "llavatariconctrl.h"
#include "llcallingcard.h"
#include "llchat.h"
// <FS:Ansariel> [FS communication UI]
//#include "llfloaterimsession.h"
//#include "llfloaterimcontainer.h"
#include "fsfloaterim.h"
#include "fsfloaterimcontainer.h"
// </FS:Ansariel> [FS communication UI]
#include "llgroupiconctrl.h"
#include "llmd5.h"
#include "llmutelist.h"
#include "llrecentpeople.h"
#include "llviewermessage.h"
#include "llviewerwindow.h"
#include "llnotifications.h"
#include "llnotificationsutil.h"
// <FS:Ansariel> [FS communication UI]
//#include "llfloaterimnearbychat.h"
#include "fsfloaternearbychat.h"
// </FS:Ansariel> [FS communication UI]
#include "llspeakers.h" //for LLIMSpeakerMgr
#include "lltextbox.h"
#include "lltoolbarview.h"
#include "llviewercontrol.h"
#include "llviewerparcelmgr.h"
#include "llconversationlog.h"
#include "message.h"
#include "llviewerregion.h"
#include "llcorehttputil.h"
#include "lluiusage.h"
// [RLVa:KB] - Checked: 2013-05-10 (RLVa-1.4.9)
#include "rlvactions.h"
#include "rlvcommon.h"
// [/RLVa:KB]
#include "exogroupmutelist.h"
#include "fsconsoleutils.h"
#include "fscommon.h"
#include "fsfloaternearbychat.h"
#include "llslurl.h"
#ifdef OPENSIM
#include "llviewernetwork.h"
#endif // OPENSIM
#include "llgiveinventory.h"
#include "llinventoryfunctions.h"
const static std::string ADHOC_NAME_SUFFIX(" Conference");
const static std::string NEARBY_P2P_BY_OTHER("nearby_P2P_by_other");
const static std::string NEARBY_P2P_BY_AGENT("nearby_P2P_by_agent");
// Markers inserted around translated part of chat text
const static std::string XL8_START_TAG(" (");
const static std::string XL8_END_TAG(")");
const S32 XL8_PADDING = 3; // XL8_START_TAG.size() + XL8_END_TAG.size()
/** Timeout of outgoing session initialization (in seconds) */
const static U32 SESSION_INITIALIZATION_TIMEOUT = 30;
void startConfrenceCoro(std::string url, LLUUID tempSessionId, LLUUID creatorId, LLUUID otherParticipantId, LLSD agents);
void chatterBoxInvitationCoro(std::string url, LLUUID sessionId, LLIMMgr::EInvitationType invitationType);
void chatterBoxHistoryCoro(std::string url, LLUUID sessionId, std::string from, std::string message, U32 timestamp);
void start_deprecated_conference_chat(const LLUUID& temp_session_id, const LLUUID& creator_id, const LLUUID& other_participant_id, const LLSD& agents_to_invite);
const LLUUID LLOutgoingCallDialog::OCD_KEY = LLUUID("7CF78E11-0CFE-498D-ADB9-1417BF03DDB4");
//
// Globals
//
LLIMMgr* gIMMgr = NULL;
BOOL LLSessionTimeoutTimer::tick()
{
if (mSessionId.isNull()) return TRUE;
LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(mSessionId);
if (session && !session->mSessionInitialized)
{
gIMMgr->showSessionStartError("session_initialization_timed_out_error", mSessionId);
}
return TRUE;
}
void notify_of_message(const LLSD& msg, bool is_dnd_msg);
void process_dnd_im(const LLSD& notification)
{
// <FS:Ansariel> [FS communication UI] CHUI will call this after returning
// from DnD mode to highlight missed IMs in their
// conversations floater; we don't need this as our IM tabs
// will already be highlighted.
return;
// </FS:Ansariel> [FS communication UI]
// <FS:LO> Hide unreachable code to prevent it from breaking compiles
#if 0
LLSD data = notification["substitutions"];
LLUUID sessionID = data["SESSION_ID"].asUUID();
LLUUID fromID = data["FROM_ID"].asUUID();
//re-create the IM session if needed
//(when coming out of DND mode upon app restart)
if(!gIMMgr->hasSession(sessionID))
{
//reconstruct session using data from the notification
std::string name = data["FROM"];
LLAvatarName av_name;
if (LLAvatarNameCache::get(data["FROM_ID"], &av_name))
{
name = av_name.getDisplayName();
}
LLIMModel::getInstance()->newSession(sessionID,
name,
IM_NOTHING_SPECIAL,
fromID,
false,
false); //will need slight refactor to retrieve whether offline message or not (assume online for now)
}
notify_of_message(data, true);
#endif
// </FS:LO>
}
static void on_avatar_name_cache_toast(const LLUUID& agent_id,
const LLAvatarName& av_name,
LLSD msg)
{
LLSD args;
args["MESSAGE"] = msg["message"];
args["TIME"] = msg["time"];
// *TODO: Can this ever be an object name or group name?
args["FROM"] = av_name.getCompleteName();
args["FROM_ID"] = msg["from_id"];
args["SESSION_ID"] = msg["session_id"];
args["SESSION_TYPE"] = msg["session_type"];
// <FS:Ansariel> [FS communication UI]
//LLNotificationsUtil::add("IMToast", args, args, boost::bind(&LLFloaterIMContainer::showConversation, LLFloaterIMContainer::getInstance(), msg["session_id"].asUUID()));
if (gSavedSettings.getS32("NotificationToastLifeTime") > 0 || gSavedSettings.getS32("ToastFadingTime") > 0) // Ansa: only create toast if it should be visible at all
{
LLNotificationsUtil::add("IMToast", args, LLSD(), boost::bind(&FSFloaterIM::show, msg["session_id"].asUUID()));
}
// </FS:Ansariel> [FS communication UI]
}
void notify_of_message(const LLSD& msg, bool is_dnd_msg)
{
// [CHUI Merge] Commented out for now. Need to see if/how we can/want to wire it up
#if 0
std::string user_preferences;
LLUUID participant_id = msg[is_dnd_msg ? "FROM_ID" : "from_id"].asUUID();
LLUUID session_id = msg[is_dnd_msg ? "SESSION_ID" : "session_id"].asUUID();
LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(session_id);
// do not show notification which goes from agent
if (gAgent.getID() == participant_id)
{
return;
}
// determine state of conversations floater
enum {CLOSED, NOT_ON_TOP, ON_TOP, ON_TOP_AND_ITEM_IS_SELECTED} conversations_floater_status;
LLFloaterIMContainer* im_box = LLFloaterReg::getTypedInstance<LLFloaterIMContainer>("im_container");
LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::getConversation(session_id);
bool store_dnd_message = false; // flag storage of a dnd message
bool is_session_focused = session_floater->isTornOff() && session_floater->hasFocus();
if (!LLFloater::isVisible(im_box) || im_box->isMinimized())
{
conversations_floater_status = CLOSED;
}
else if (!im_box->hasFocus() &&
!(session_floater && LLFloater::isVisible(session_floater)
&& !session_floater->isMinimized() && session_floater->hasFocus()))
{
conversations_floater_status = NOT_ON_TOP;
}
else if (im_box->getSelectedSession() != session_id)
{
conversations_floater_status = ON_TOP;
}
else
{
conversations_floater_status = ON_TOP_AND_ITEM_IS_SELECTED;
}
// determine user prefs for this session
if (session_id.isNull())
{
if (msg["source_type"].asInteger() == CHAT_SOURCE_OBJECT)
{
user_preferences = gSavedSettings.getString("NotificationObjectIMOptions");
if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundObjectIM") == TRUE))
{
make_ui_sound("UISndNewIncomingIMSession");
}
}
else
{
user_preferences = gSavedSettings.getString("NotificationNearbyChatOptions");
if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNearbyChatIM") == TRUE))
{
make_ui_sound("UISndNewIncomingIMSession");
}
}
}
else if(session->isP2PSessionType())
{
if (LLAvatarTracker::instance().isBuddy(participant_id))
{
user_preferences = gSavedSettings.getString("NotificationFriendIMOptions");
if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundFriendIM") == TRUE))
{
make_ui_sound("UISndNewIncomingIMSession");
}
}
else
{
user_preferences = gSavedSettings.getString("NotificationNonFriendIMOptions");
if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNonFriendIM") == TRUE))
{
make_ui_sound("UISndNewIncomingIMSession");
}
}
}
else if(session->isAdHocSessionType())
{
user_preferences = gSavedSettings.getString("NotificationConferenceIMOptions");
if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundConferenceIM") == TRUE))
{
make_ui_sound("UISndNewIncomingIMSession");
}
}
else if(session->isGroupSessionType())
{
user_preferences = gSavedSettings.getString("NotificationGroupChatOptions");
if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundGroupChatIM") == TRUE))
{
make_ui_sound("UISndNewIncomingIMSession");
}
}
// actions:
// 0. nothing - exit
if (("noaction" == user_preferences ||
ON_TOP_AND_ITEM_IS_SELECTED == conversations_floater_status)
&& session_floater->isMessagePaneExpanded())
{
return;
}
// 1. open floater and [optional] surface it
if ("openconversations" == user_preferences &&
(CLOSED == conversations_floater_status
|| NOT_ON_TOP == conversations_floater_status))
{
if(!gAgent.isDoNotDisturb())
{
if(!LLAppViewer::instance()->quitRequested() && !LLFloater::isVisible(im_box))
{
// Open conversations floater
LLFloaterReg::showInstance("im_container");
}
im_box->collapseMessagesPane(false);
if (session_floater)
{
if (session_floater->getHost())
{
if (NULL != im_box && im_box->isMinimized())
{
LLFloater::onClickMinimize(im_box);
}
}
else
{
if (session_floater->isMinimized())
{
LLFloater::onClickMinimize(session_floater);
}
}
}
}
else
{
store_dnd_message = true;
}
}
// 2. Flash line item
if ("openconversations" == user_preferences
|| ON_TOP == conversations_floater_status
|| ("toast" == user_preferences && ON_TOP != conversations_floater_status)
|| ("flash" == user_preferences && (CLOSED == conversations_floater_status
|| NOT_ON_TOP == conversations_floater_status))
|| is_dnd_msg)
{
if(!LLMuteList::getInstance()->isMuted(participant_id))
{
if(gAgent.isDoNotDisturb())
{
store_dnd_message = true;
}
else
{
if (is_dnd_msg && (ON_TOP == conversations_floater_status ||
NOT_ON_TOP == conversations_floater_status ||
CLOSED == conversations_floater_status))
{
im_box->highlightConversationItemWidget(session_id, true);
}
else
{
im_box->flashConversationItemWidget(session_id, true);
}
}
}
}
// 3. Flash FUI button
if (("toast" == user_preferences || "flash" == user_preferences) &&
(CLOSED == conversations_floater_status
|| NOT_ON_TOP == conversations_floater_status)
&& !is_session_focused
&& !is_dnd_msg) //prevent flashing FUI button because the conversation floater will have already opened
{
if(!LLMuteList::getInstance()->isMuted(participant_id))
{
if(!gAgent.isDoNotDisturb())
{
gToolBarView->flashCommand(LLCommandId("chat"), true, im_box->isMinimized());
}
else
{
store_dnd_message = true;
}
}
}
// 4. Toast
if ((("toast" == user_preferences) &&
(ON_TOP_AND_ITEM_IS_SELECTED != conversations_floater_status) &&
(!session_floater->isTornOff() || !LLFloater::isVisible(session_floater)))
|| !session_floater->isMessagePaneExpanded())
{
//Show IM toasts (upper right toasts)
// Skip toasting for system messages and for nearby chat
if(session_id.notNull() && participant_id.notNull())
{
if(!is_dnd_msg)
{
if(gAgent.isDoNotDisturb())
{
store_dnd_message = true;
}
else
{
LLAvatarNameCache::get(participant_id, boost::bind(&on_avatar_name_cache_toast, _1, _2, msg));
}
}
}
}
if (store_dnd_message)
{
// If in DND mode, allow notification to be stored so upon DND exit
// the user will be notified with some limitations (see 'is_dnd_msg' flag checks)
if(session_id.notNull()
&& participant_id.notNull()
&& !session_floater->isShown())
{
LLAvatarNameCache::get(participant_id, boost::bind(&on_avatar_name_cache_toast, _1, _2, msg));
}
}
#endif
// [CHUI Merge]
// <FS:Ansariel> [FS communication UI] Use old toast handling code for now
LLUUID participant_id = msg["from_id"].asUUID();
LLUUID session_id = msg["session_id"].asUUID();
// do not show toast in busy mode or it goes from agent
if (gAgent.isDoNotDisturb() || gAgent.getID() == participant_id)
{
return;
}
// Ansa: CHUI routes nearby chat through here with session id = null uuid!
if (session_id.isNull())
{
FSFloaterIMContainer* im_container = FSFloaterIMContainer::getInstance();
FSFloaterNearbyChat* nearby_chat_instance = FSFloaterNearbyChat::findInstance();
if (!im_container->getVisible() && nearby_chat_instance && im_container->hasFloater(nearby_chat_instance)
&& gSavedSettings.getBOOL("FSNotifyNearbyChatFlash"))
{
im_container->addFlashingSession(session_id);
gToolBarView->flashCommand(LLCommandId("chat"), true, im_container->isMinimized());
}
return;
}
// <FS:Ansariel> Don't toast if the message is an announcement
if (msg["is_announcement"].asBoolean())
{
return;
}
// </FS:Ansariel> Don't toast if the message is an announcement
// Skip toasting for system messages
if (participant_id.isNull())
{
return;
}
FSFloaterIMContainer* im_container = FSFloaterIMContainer::getInstance();
FSFloaterIM* im_instance = FSFloaterIM::findInstance(session_id);
if (!im_container->getVisible() && im_instance && im_container->hasFloater(im_instance)
&& gSavedSettings.getBOOL("FSNotifyIMFlash"))
{
im_container->addFlashingSession(session_id);
gToolBarView->flashCommand(LLCommandId("chat"), true, im_container->isMinimized());
}
// <FS:Ansariel> (Group-)IMs in chat console
if (FSConsoleUtils::ProcessInstantMessage(session_id, participant_id, msg["message"].asString()))
{
return;
}
// </FS:Ansariel> (Group-)IMs in chat console
// check whether incoming IM belongs to an active session or not
if (LLIMModel::getInstance()->getActiveSessionID().notNull()
&& LLIMModel::getInstance()->getActiveSessionID() == session_id)
{
return;
}
// *NOTE Skip toasting if the user disable it in preferences/debug settings ~Alexandrea
LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(session_id);
if (!gSavedSettings.getBOOL("EnableGroupChatPopups") && session->isGroupSessionType())
{
return;
}
if (!gSavedSettings.getBOOL("EnableIMChatPopups") && !session->isGroupSessionType())
{
return;
}
// Skip toasting if we have open window of IM with this session id
FSFloaterIM* open_im_floater = FSFloaterIM::findInstance(session_id);
if (open_im_floater && open_im_floater->getVisible())
{
return;
}
LLAvatarNameCache::get(participant_id, boost::bind(&on_avatar_name_cache_toast, _1, _2, msg));
// </FS:Ansariel> [FS communication UI]
}
// <FS:Ansariel> [FS communication UI] Re-added to not toast if our IM floater is active
void LLIMModel::setActiveSessionID(const LLUUID& session_id)
{
// check if such an ID really exists
if (!findIMSession(session_id))
{
LL_WARNS() << "Trying to set as active a non-existent session!" << LL_ENDL;
return;
}
mActiveSessionID = session_id;
}
// </FS:Ansariel> [FS communication UI]
void on_new_message(const LLSD& msg)
{
notify_of_message(msg, false);
}
void startConfrenceCoro(std::string url,
LLUUID tempSessionId, LLUUID creatorId, LLUUID otherParticipantId, LLSD agents)
{
LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("ConferenceChatStart", httpPolicy));
LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
LLSD postData;
postData["method"] = "start conference";
postData["session-id"] = tempSessionId;
postData["params"] = agents;
LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData);
LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
if (!status)
{
LL_WARNS("LLIMModel") << "Failed to start conference" << LL_ENDL;
//try an "old school" way.
// *TODO: What about other error status codes? 4xx 5xx?
if (status == LLCore::HttpStatus(HTTP_BAD_REQUEST))
{
start_deprecated_conference_chat(
tempSessionId,
creatorId,
otherParticipantId,
agents);
}
//else throw an error back to the client?
//in theory we should have just have these error strings
//etc. set up in this file as opposed to the IMMgr,
//but the error string were unneeded here previously
//and it is not worth the effort switching over all
//the possible different language translations
}
}
void chatterBoxInvitationCoro(std::string url, LLUUID sessionId, LLIMMgr::EInvitationType invitationType)
{
LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("ConferenceInviteStart", httpPolicy));
LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
LLSD postData;
postData["method"] = "accept invitation";
postData["session-id"] = sessionId;
LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData);
LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
if (!gIMMgr)
{
LL_WARNS("") << "Global IM Manager is NULL" << LL_ENDL;
return;
}
if (!status)
{
LL_WARNS("LLIMModel") << "Bad HTTP response in chatterBoxInvitationCoro" << LL_ENDL;
//throw something back to the viewer here?
gIMMgr->clearPendingAgentListUpdates(sessionId);
gIMMgr->clearPendingInvitation(sessionId);
if (status == LLCore::HttpStatus(HTTP_NOT_FOUND))
{
static const std::string error_string("session_does_not_exist_error");
gIMMgr->showSessionStartError(error_string, sessionId);
}
return;
}
result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS);
LLIMSpeakerMgr* speakerMgr = LLIMModel::getInstance()->getSpeakerManager(sessionId);
if (speakerMgr)
{
//we've accepted our invitation
//and received a list of agents that were
//currently in the session when the reply was sent
//to us. Now, it is possible that there were some agents
//to slip in/out between when that message was sent to us
//and now.
//the agent list updates we've received have been
//accurate from the time we were added to the session
//but unfortunately, our base that we are receiving here
//may not be the most up to date. It was accurate at
//some point in time though.
speakerMgr->setSpeakers(result);
//we now have our base of users in the session
//that was accurate at some point, but maybe not now
//so now we apply all of the updates we've received
//in case of race conditions
speakerMgr->updateSpeakers(gIMMgr->getPendingAgentListUpdates(sessionId));
}
if (LLIMMgr::INVITATION_TYPE_VOICE == invitationType)
{
gIMMgr->startCall(sessionId, LLVoiceChannel::INCOMING_CALL);
}
if ((invitationType == LLIMMgr::INVITATION_TYPE_VOICE
|| invitationType == LLIMMgr::INVITATION_TYPE_IMMEDIATE)
&& LLIMModel::getInstance()->findIMSession(sessionId))
{
// TODO remove in 2010, for voice calls we do not open an IM window
//LLFloaterIMSession::show(mSessionID);
}
gIMMgr->clearPendingAgentListUpdates(sessionId);
gIMMgr->clearPendingInvitation(sessionId);
}
void translateSuccess(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, const std::string& utf8_text,
U64 time_n_flags, std::string originalMsg, std::string expectLang, std::string translation, const std::string detected_language)
{
std::string message_txt(utf8_text);
// filter out non-interesting responses
if (!translation.empty()
&& ((detected_language.empty()) || (expectLang != detected_language))
&& (LLStringUtil::compareInsensitive(translation, originalMsg) != 0))
{ // Note - if this format changes, also fix code in addMessagesFromServerHistory()
message_txt += XL8_START_TAG + LLTranslate::removeNoTranslateTags(translation) + XL8_END_TAG;
}
// Extract info packed in time_n_flags
bool log2file = (bool)(time_n_flags & (1LL << 32));
bool is_region_msg = (bool)(time_n_flags & (1LL << 33));
U32 time_stamp = (U32)(time_n_flags & 0x00000000ffffffff);
LLIMModel::getInstance()->processAddingMessage(session_id, from, from_id, message_txt, log2file, is_region_msg, time_stamp, false);
}
void translateFailure(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, const std::string& utf8_text,
U64 time_n_flags, int status, const std::string err_msg)
{
std::string message_txt(utf8_text);
std::string msg = LLTrans::getString("TranslationFailed", LLSD().with("[REASON]", err_msg));
LLStringUtil::replaceString(msg, "\n", " "); // we want one-line error messages
message_txt += XL8_START_TAG + msg + XL8_END_TAG;
// Extract info packed in time_n_flags
bool log2file = (bool)(time_n_flags & (1LL << 32));
bool is_region_msg = (bool)(time_n_flags & (1LL << 33));
U32 time_stamp = (U32)(time_n_flags & 0x00000000ffffffff);
LLIMModel::getInstance()->processAddingMessage(session_id, from, from_id, message_txt, log2file, is_region_msg, time_stamp, false);
}
void chatterBoxHistoryCoro(std::string url, LLUUID sessionId, std::string from, std::string message, U32 timestamp)
{ // if parameters from, message and timestamp have values, they are a message that opened chat
LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("ChatHistory", httpPolicy));
LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
LLSD postData;
postData["method"] = "fetch history";
postData["session-id"] = sessionId;
LL_DEBUGS("ChatHistory") << sessionId << ": Chat history posting " << postData << " to " << url
<< ", from " << from << ", message " << message << ", timestamp " << (S32)timestamp << LL_ENDL;
LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData);
LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
if (!status)
{
LL_WARNS("ChatHistory") << sessionId << ": Bad HTTP response in chatterBoxHistoryCoro"
<< ", results: " << httpResults << LL_ENDL;
return;
}
// Add history to IM session
LLSD history = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_CONTENT];
LL_DEBUGS("ChatHistory") << sessionId << ": Chat server history fetch returned " << history << LL_ENDL;
try
{
LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(sessionId);
if (session && history.isArray())
{ // Result array is sorted oldest to newest
if (history.size() > 0)
{ // History from the chat server has an integer 'time' value timestamp. Create 'datetime' string which will match
// what we have from the local history cache
for (LLSD::array_iterator cur_server_hist = history.beginArray(), endLists = history.endArray();
cur_server_hist != endLists;
cur_server_hist++)
{
if ((*cur_server_hist).isMap())
{ // Take the 'time' value from the server and make the date-time string that will be in local cache log files
// {'from_id':u7aa8c222-8a81-450e-b3d1-9c28491ef717,'message':'Can you hear me now?','from':'Chat Tester','num':i86,'time':r1.66501e+09}
U32 timestamp = (U32)((*cur_server_hist)[LL_IM_TIME].asInteger());
(*cur_server_hist)[LL_IM_DATE_TIME] = LLLogChat::timestamp2LogString(timestamp, true);
}
}
session->addMessagesFromServerHistory(history, from, message, timestamp);
// Display the newly added messages
// <FS:Ansariel> [FS communication UI]
//LLFloaterIMSession* floater = LLFloaterReg::findTypedInstance<LLFloaterIMSession>("impanel", sessionId);
FSFloaterIM* floater = LLFloaterReg::findTypedInstance<FSFloaterIM>("fs_impanel", sessionId);
// </FS:Ansariel> [FS communication UI]
if (floater && floater->isInVisibleChain())
{
floater->updateMessages();
}
}
else
{
LL_DEBUGS("ChatHistory") << sessionId << ": Empty history from chat server, nothing to add" << LL_ENDL;
}
}
else if (session && !history.isArray())
{
LL_WARNS("ChatHistory") << sessionId << ": Bad array data fetching chat history" << LL_ENDL;
}
else
{
LL_WARNS("ChatHistory") << sessionId << ": Unable to find session fetching chat history" << LL_ENDL;
}
}
catch (...)
{
LOG_UNHANDLED_EXCEPTION("chatterBoxHistoryCoro");
LL_WARNS("ChatHistory") << "chatterBoxHistoryCoro unhandled exception while processing data for session " << sessionId << LL_ENDL;
}
}
LLIMModel::LLIMModel()
{
// <FS:Ansariel> [FS communication UI]
//addNewMsgCallback(boost::bind(&LLFloaterIMSession::newIMCallback, _1));
addNewMsgCallback(boost::bind(&FSFloaterIM::newIMCallback, _1));
// </FS:Ansariel> [FS communication UI]
addNewMsgCallback(boost::bind(&on_new_message, _1));
LLCallDialogManager::instance();
}
LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id, const uuid_vec_t& ids, bool voice, bool has_offline_msg)
: mSessionID(session_id),
mName(name),
mType(type),
mHasOfflineMessage(has_offline_msg),
// [SL:KB] - Patch: Chat-GroupSnooze | Checked: 2012-08-01 (Catznip-3.3)
mCloseAction(CLOSE_DEFAULT),
mSnoozeTime(-1),
// [/SL:KB]
mParticipantUnreadMessageCount(0),
mNumUnread(0),
mOtherParticipantID(other_participant_id),
mInitialTargetIDs(ids),
mVoiceChannel(NULL),
mSpeakers(NULL),
mSessionInitialized(false),
mCallBackEnabled(true),
mTextIMPossible(true),
mStartCallOnInitialize(false),
mStartedAsIMCall(voice),
mIsDNDsend(false),
mAvatarNameCacheConnection()
{
// set P2P type by default
mSessionType = P2P_SESSION;
if (IM_NOTHING_SPECIAL == mType || IM_SESSION_P2P_INVITE == mType)
{
mVoiceChannel = new LLVoiceChannelP2P(session_id, name, other_participant_id);
}
else
{
mVoiceChannel = new LLVoiceChannelGroup(session_id, name);
// determine whether it is group or conference session
if (gAgent.isInGroup(mSessionID))
{
mSessionType = GROUP_SESSION;
}
else
{
mSessionType = ADHOC_SESSION;
}
}
if(mVoiceChannel)
{
mVoiceChannelStateChangeConnection = mVoiceChannel->setStateChangedCallback(boost::bind(&LLIMSession::onVoiceChannelStateChanged, this, _1, _2, _3));
}
mSpeakers = new LLIMSpeakerMgr(mVoiceChannel);
// All participants will be added to the list of people we've recently interacted with.
// we need to add only _active_ speakers...so comment this.
// may delete this later on cleanup
//mSpeakers->addListener(&LLRecentPeople::instance(), "add");
//we need to wait for session initialization for outgoing ad-hoc and group chat session
//correct session id for initiated ad-hoc chat will be received from the server
if (!LLIMModel::getInstance()->sendStartSession(mSessionID, mOtherParticipantID,
mInitialTargetIDs, mType))
{
//we don't need to wait for any responses
//so we're already initialized
mSessionInitialized = true;
}
else
{
//tick returns TRUE - timer will be deleted after the tick
new LLSessionTimeoutTimer(mSessionID, SESSION_INITIALIZATION_TIMEOUT);
}
if (IM_NOTHING_SPECIAL == mType)
{
mCallBackEnabled = LLVoiceClient::getInstance()->isSessionCallBackPossible(mSessionID);
mTextIMPossible = LLVoiceClient::getInstance()->isSessionTextIMPossible(mSessionID);
}
buildHistoryFileName();
loadHistory();
// Localizing name of ad-hoc session. STORM-153
// Changing name should happen here- after the history file was created, so that
// history files have consistent (English) names in different locales.
if (isAdHocSessionType() && IM_SESSION_INVITE == mType)
{
mAvatarNameCacheConnection = LLAvatarNameCache::get(mOtherParticipantID,boost::bind(&LLIMModel::LLIMSession::onAdHocNameCache,this, _2));
}
}
void LLIMModel::LLIMSession::onAdHocNameCache(const LLAvatarName& av_name)
{
mAvatarNameCacheConnection.disconnect();
if (!av_name.isValidName())
{
S32 separator_index = mName.rfind(" ");
std::string name = mName.substr(0, separator_index);
++separator_index;
std::string conference_word = mName.substr(separator_index, mName.length());
// additional check that session name is what we expected
if ("Conference" == conference_word)
{
LLStringUtil::format_map_t args;
args["[AGENT_NAME]"] = name;
LLTrans::findString(mName, "conference-title-incoming", args);
}
}
else
{
LLStringUtil::format_map_t args;
args["[AGENT_NAME]"] = av_name.getCompleteName();
LLTrans::findString(mName, "conference-title-incoming", args);
}
}
void LLIMModel::LLIMSession::onVoiceChannelStateChanged(const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state, const LLVoiceChannel::EDirection& direction)
{
std::string you_joined_call = LLTrans::getString("you_joined_call");
std::string you_started_call = LLTrans::getString("you_started_call");
std::string other_avatar_name = "";
LLAvatarName av_name;
std::string message;
switch(mSessionType)
{
case P2P_SESSION:
LLAvatarNameCache::get(mOtherParticipantID, &av_name);
other_avatar_name = av_name.getUserName();
if(direction == LLVoiceChannel::INCOMING_CALL)
{
switch(new_state)
{
case LLVoiceChannel::STATE_CALL_STARTED :
{
LLStringUtil::format_map_t string_args;
string_args["[NAME]"] = other_avatar_name;
message = LLTrans::getString("name_started_call", string_args);
LLIMModel::getInstance()->addMessage(mSessionID, SYSTEM_FROM, LLUUID::null, message);
break;
}
case LLVoiceChannel::STATE_CONNECTED :
LLIMModel::getInstance()->addMessage(mSessionID, SYSTEM_FROM, LLUUID::null, you_joined_call);
default:
break;
}
}
else // outgoing call
{
switch(new_state)
{
case LLVoiceChannel::STATE_CALL_STARTED :
LLIMModel::getInstance()->addMessage(mSessionID, SYSTEM_FROM, LLUUID::null, you_started_call);
break;
case LLVoiceChannel::STATE_CONNECTED :
message = LLTrans::getString("answered_call");
LLIMModel::getInstance()->addMessage(mSessionID, SYSTEM_FROM, LLUUID::null, message);
default:
break;
}
}
break;
case GROUP_SESSION:
case ADHOC_SESSION:
if(direction == LLVoiceChannel::INCOMING_CALL)
{
switch(new_state)
{
case LLVoiceChannel::STATE_CONNECTED :
LLIMModel::getInstance()->addMessage(mSessionID, SYSTEM_FROM, LLUUID::null, you_joined_call);
default:
break;
}
}
else // outgoing call
{
switch(new_state)
{
case LLVoiceChannel::STATE_CALL_STARTED :
LLIMModel::getInstance()->addMessage(mSessionID, SYSTEM_FROM, LLUUID::null, you_started_call);
break;
default:
break;
}
}
default:
break;
}
// Update speakers list when connected
if (LLVoiceChannel::STATE_CONNECTED == new_state)
{
mSpeakers->update(true);
}
}
LLIMModel::LLIMSession::~LLIMSession()
{
if (mAvatarNameCacheConnection.connected())
{
mAvatarNameCacheConnection.disconnect();
}
delete mSpeakers;
mSpeakers = NULL;
// End the text IM session if necessary
if(LLVoiceClient::getInstance() && mOtherParticipantID.notNull())
{
switch(mType)
{
case IM_NOTHING_SPECIAL:
case IM_SESSION_P2P_INVITE:
LLVoiceClient::getInstance()->endUserIMSession(mOtherParticipantID);
break;
default:
// Appease the linux compiler
break;
}
}
mVoiceChannelStateChangeConnection.disconnect();
// HAVE to do this here -- if it happens in the LLVoiceChannel destructor it will call the wrong version (since the object's partially deconstructed at that point).
mVoiceChannel->deactivate();
delete mVoiceChannel;
mVoiceChannel = NULL;
}
void LLIMModel::LLIMSession::sessionInitReplyReceived(const LLUUID& new_session_id)
{
mSessionInitialized = true;
if (new_session_id != mSessionID)
{
mSessionID = new_session_id;
mVoiceChannel->updateSessionID(new_session_id);
}
}
void LLIMModel::LLIMSession::addMessage(const std::string& from,
const LLUUID& from_id,
const std::string& utf8_text,
const std::string& time,
const bool is_history, // comes from a history file or chat server
const bool is_region_msg,
const U32 timestamp) // may be zero
{
LLSD message;
message["from"] = from;
message["from_id"] = from_id;
message["message"] = utf8_text;
message["time"] = time; // string used in display, may be full data YYYY/MM/DD HH:MM or just HH:MM
message["timestamp"] = (S32)timestamp; // use string? LLLogChat::timestamp2LogString(timestamp, true);
message["index"] = (LLSD::Integer)mMsgs.size();
message["is_history"] = is_history;
message["is_region_msg"] = is_region_msg;
LL_DEBUGS("UIUsage") << "addMessage " << " from " << from << " from_id " << from_id << " utf8_text " << utf8_text << " time " << time << " is_history " << is_history << " session mType " << mType << LL_ENDL;
if (from_id == gAgent.getID())
{
if (mType == IM_SESSION_GROUP_START)
{
LLUIUsage::instance().logCommand("Chat.SendGroup");
}
else if (mType == IM_NOTHING_SPECIAL)
{
LLUIUsage::instance().logCommand("Chat.SendIM");
}
else
{
LLUIUsage::instance().logCommand("Chat.SendOther");
}
}
mMsgs.push_front(message); // Add most recent messages to the front of mMsgs
if (mSpeakers && from_id.notNull())
{
mSpeakers->speakerChatted(from_id);
mSpeakers->setSpeakerTyping(from_id, FALSE);
}
}
void LLIMModel::LLIMSession::addMessagesFromHistoryCache(const chat_message_list_t& history)
{
// Add the messages from the local cached chat history to the session window
for (const auto& msg : history)
{
std::string from = msg[LL_IM_FROM];
LLUUID from_id;
if (msg[LL_IM_FROM_ID].isDefined())
{
from_id = msg[LL_IM_FROM_ID].asUUID();
}
else
{ // convert it to a legacy name if we have a complete name
std::string legacy_name = gCacheName->buildLegacyName(from);
from_id = LLAvatarNameCache::getInstance()->findIdByName(legacy_name);
}
// Save the last minute of messages so we can merge with the chat server history.
// Really would be nice to have a numeric timestamp in the local cached chat file
const std::string & msg_time_str = msg[LL_IM_DATE_TIME].asString();
if (mLastHistoryCacheDateTime != msg_time_str)
{
mLastHistoryCacheDateTime = msg_time_str; // Reset to the new time
mLastHistoryCacheMsgs.clear();
}
mLastHistoryCacheMsgs.push_front(msg);
LL_DEBUGS("ChatHistory") << mSessionID << ": Adding history cache message: " << msg << LL_ENDL;
// Add message from history cache to the display
addMessage(from, from_id, msg[LL_IM_TEXT], msg[LL_IM_TIME], true, false, 0); // from history data, not region message, no timestamp
}
}
void LLIMModel::LLIMSession::addMessagesFromServerHistory(const LLSD& history, // Array of chat messages from chat server
const std::string& target_from, // Sender of message that opened chat
const std::string& target_message, // Message text that opened chat
U32 timestamp) // timestamp of message that opened chat
{ // Add messages from history returned by the chat server.
// The session mMsgs may contain chat messages from the local history cache file, and possibly one or more newly
// arrived chat messages. If the chat window was manually opened, these will be empty and history can
// more easily merged. The history from the server, however, may overlap what is in the file and those must also be merged.
// At this point, the session mMsgs can have
// no messages
// nothing from history file cache, but one or more very recently arrived messages,
// messages from history file cache, no recent chat
// messages from history file cache, one or more very recent messages
//
// The chat history from server can possibly contain:
// no messages
// messages that start back before anything in the local file (obscure case, but possible)
// messages that match messages from the history file cache
// messages from the last hour, new to the viewer
// one or more messages that match most recently received chat (the one that opened the window)
// In other words:
// messages from chat server may or may not match what we already have in mMsgs
// We can drop anything that is during the time span covered by the local cache file
// To keep things simple, drop any chat data older than the local cache file
if (!history.isArray())
{
LL_WARNS("ChatHistory") << mSessionID << ": Unexpected history data not array, type " << (S32)history.type() << LL_ENDL;
return;
}
if (history.size() == 0)
{ // If history is empty
LL_DEBUGS("ChatHistory") << mSessionID << ": addMessagesFromServerHistory() has empty history, nothing to merge" << LL_ENDL;
return;
}
if (history.size() == 1 && // Server chat history has one entry,
target_from.length() > 0 && // and we have a chat message that just arrived
mMsgs.size() > 0) // and we have some data in the window - assume the history message is there.
{ // This is the common case where a group chat is silent for a while, and then one message is sent.
LL_DEBUGS("ChatHistory") << mSessionID << ": addMessagesFromServerHistory() only has chat message just received." << LL_ENDL;
return;
}
LL_DEBUGS("ChatHistory") << mSessionID << ": addMessagesFromServerHistory() starting with mMsg.size() " << mMsgs.size()
<< " adding history with " << history.size() << " messages"
<< ", target_from: " << target_from
<< ", target_message: " << target_message
<< ", timestamp: " << (S32)timestamp << LL_ENDL;
// At start of merging, mMsgs is either empty, has some chat messages read from a local cache file, and may have
// one or more messages that just arrived from the server.
U32 match_timestamp = 0;
chat_message_list_t shift_msgs;
if (mMsgs.size() > 0 &&
target_from.length() > 0
&& target_message.length() > 0)
{ // Find where to insert the history messages by popping off a few in the session.
// The most common case is one duplciate message, the one that opens a chat session
while (mMsgs.size() > 0)
{
// The "time" value from mMsgs is a string, either just time HH:MM or a full date and time
LLSD cur_msg = mMsgs.front(); // Get most recent message from the chat display (front of mMsgs list)
if (cur_msg.isMap())
{
LL_DEBUGS("ChatHistoryCompare") << mSessionID << ": Finding insertion point, looking at cur_msg: " << cur_msg << LL_ENDL;
match_timestamp = cur_msg["timestamp"].asInteger(); // get timestamp of message in the session, may be zero
if ((S32)timestamp > match_timestamp)
{
LL_DEBUGS("ChatHistory") << mSessionID << ": found older chat message: " << cur_msg
<< ", timestamp " << (S32)timestamp
<< " vs. match_timestamp " << match_timestamp
<< ", shift_msgs size is " << shift_msgs.size() << LL_ENDL;
break;
}
// Have the matching message or one more recent: these need to be at the end
shift_msgs.push_front(cur_msg); // Move chat message to temp list.
mMsgs.pop_front(); // Normally this is just one message
LL_DEBUGS("ChatHistory") << mSessionID << ": shifting chat message " << cur_msg
<< " to be inserted at end, shift_msgs size is " << shift_msgs.size()
<< ", match_timestamp " << match_timestamp
<< ", timestamp " << (S32)timestamp << LL_ENDL;
}
else
{
LL_DEBUGS("ChatHistory") << mSessionID << ": Unexpected non-map entry in session messages: " << cur_msg << LL_ENDL;
return;
}
}
}
// Now merge messages from server history data into the session display. The history data
// from the local file may overlap with the chat messages from the server.
// Drop any messages from the chat server history that are before the latest one from the local history file.
// Unfortunately, messages from the local file don't have timestamps - just datetime strings
LLSD::array_const_iterator cur_history_iter = history.beginArray();
while (cur_history_iter != history.endArray())
{
const LLSD &cur_server_hist = *cur_history_iter;
cur_history_iter++;
if (cur_server_hist.isMap())
{ // Each server history entry looks like
// { 'from':'Laggy Avatar', 'from_id' : u72345678 - 744f - 43b9 - 98af - b06f1c76ddda, 'index' : i24, 'is_history' : 1, 'message' : 'That was slow', 'time' : '02/13/2023 10:03', 'timestamp' : i1676311419 }
// If we reach the message that opened our window, stop adding messages
U32 history_msg_timestamp = (U32)cur_server_hist[LL_IM_TIME].asInteger();
if ((match_timestamp > 0 && match_timestamp <= history_msg_timestamp) ||
(timestamp > 0 && timestamp <= history_msg_timestamp))
{ // we found the message we matched, so stop inserting from chat server history
LL_DEBUGS("ChatHistoryCompare") << "Found end of chat history insertion with match_timestamp " << (S32)match_timestamp
<< " vs. history_msg_timestamp " << (S32)history_msg_timestamp
<< " vs. timestamp " << (S32)timestamp
<< LL_ENDL;
break;
}
LL_DEBUGS("ChatHistoryCompare") << "Compared match_timestamp " << (S32)match_timestamp
<< " vs. history_msg_timestamp " << (S32)history_msg_timestamp << LL_ENDL;
bool add_chat_to_conversation = true;
if (!mLastHistoryCacheDateTime.empty())
{ // Skip past the any from server that are older than what we already read from the history file.
std::string history_datetime = cur_server_hist[LL_IM_DATE_TIME].asString();
if (history_datetime.empty())
{
history_datetime = cur_server_hist[LL_IM_TIME].asString();
}
if (history_datetime < mLastHistoryCacheDateTime)
{
LL_DEBUGS("ChatHistoryCompare") << "Skipping message from chat server history since it's older than messages the session already has."
<< history_datetime << " vs " << mLastHistoryCacheDateTime << LL_ENDL;
add_chat_to_conversation = false;
}
else if (history_datetime > mLastHistoryCacheDateTime)
{ // The message from the chat server is more recent than the last one from the local cache file. Add it
LL_DEBUGS("ChatHistoryCompare") << "Found message dated "
<< history_datetime << " vs " << mLastHistoryCacheDateTime
<< ", adding new message from chat server history " << cur_server_hist << LL_ENDL;
}
else // (history_datetime == mLastHistoryCacheDateTime)
{ // Messages are in the same minute as the last from the cache log file.
const std::string & history_msg_text = cur_server_hist[LL_IM_TEXT];
// Look in the saved messages from the history file that have the same time
for (const auto& scan_msg : mLastHistoryCacheMsgs)
{
LL_DEBUGS("ChatHistoryCompare") << "comparing messages " << scan_msg[LL_IM_TEXT]
<< " with " << cur_server_hist << LL_ENDL;
if (scan_msg.size() > 0)
{ // Extra work ... the history_msg_text value may have been translated, i.e. "I am confused (je suis confus)"
// while the server history will only have the first part "I am confused"
std::string target_compare(scan_msg[LL_IM_TEXT]);
if (target_compare.size() > history_msg_text.size() + XL8_PADDING &&
target_compare.substr(history_msg_text.size(), XL8_START_TAG.size()) == XL8_START_TAG &&
target_compare.substr(target_compare.size() - XL8_END_TAG.size()) == XL8_END_TAG)
{ // This really looks like a "translated string (cadena traducida)" so just compare the source part
LL_DEBUGS("ChatHistory") << mSessionID << ": Found translated chat " << target_compare
<< " when comparing to history " << history_msg_text
<< ", will truncate" << LL_ENDL;
target_compare = target_compare.substr(0, history_msg_text.size());
}
if (history_msg_text == target_compare)
{ // Found a match, so don't add a duplicate chat message to the window
LL_DEBUGS("ChatHistory") << mSessionID << ": Found duplicate message text " << history_msg_text
<< " : " << (S32)history_msg_timestamp << ", matching datetime " << history_datetime << LL_ENDL;
add_chat_to_conversation = false;
break;
}
}
}
}
}
LLUUID sender_id = cur_server_hist[LL_IM_FROM_ID].asUUID();
if (add_chat_to_conversation)
{ // Check if they're muted
if (LLMuteList::getInstance()->isMuted(sender_id, LLMute::flagTextChat))
{
add_chat_to_conversation = false;
LL_DEBUGS("ChatHistory") << mSessionID << ": Skipped adding chat from " << sender_id
<< " as muted, message: " << cur_server_hist
<< LL_ENDL;
}
}
if (add_chat_to_conversation)
{ // Finally add message to the chat session
std::string chat_time_str = LLConversation::createTimestamp((U64Seconds)history_msg_timestamp);
std::string sender_name = cur_server_hist[LL_IM_FROM].asString();
std::string history_msg_text = cur_server_hist[LL_IM_TEXT].asString();
LLSD message;
message["from"] = sender_name;
message["from_id"] = sender_id;
message["message"] = history_msg_text;
message["time"] = chat_time_str;
message["timestamp"] = (S32)history_msg_timestamp;
message["index"] = (LLSD::Integer)mMsgs.size();
message["is_history"] = true;
mMsgs.push_front(message);
LL_DEBUGS("ChatHistory") << mSessionID << ": push_front() adding group chat history message " << message << LL_ENDL;
// Add chat history messages to the local cache file, only in the case where we opened the chat window
// Need to solve the logic around messages that arrive and open chat - at this point, they've already been added to the
// local history cache file. If we append messages here, it will be out of order.
if (target_from.empty() && target_message.empty())
{
LLIMModel::getInstance()->logToFile(LLIMModel::getInstance()->getHistoryFileName(mSessionID),
sender_name, sender_id, history_msg_text);
}
}
}
}
S32 shifted_size = shift_msgs.size();
while (shift_msgs.size() > 0)
{ // Finally add back any new messages, and tweak the index value to be correct.
LLSD newer_message = shift_msgs.front();
shift_msgs.pop_front();
S32 old_index = newer_message["index"];
newer_message["index"] = (LLSD::Integer)mMsgs.size(); // Update the index to match the new position in the conversation
LL_DEBUGS("ChatHistory") << mSessionID << ": Re-adding newest group chat history messages from " << newer_message["from"]
<< ", text: " << newer_message["message"]
<< " old index " << old_index << ", new index " << newer_message["index"] << LL_ENDL;
mMsgs.push_front(newer_message);
}
LL_DEBUGS("ChatHistory") << mSessionID << ": addMessagesFromServerHistory() exiting with mMsg.size() " << mMsgs.size()
<< ", shifted " << shifted_size << " messages" << LL_ENDL;
mLastHistoryCacheDateTime.clear(); // Don't need this data
mLastHistoryCacheMsgs.clear();
}
void LLIMModel::LLIMSession::chatFromLogFile(LLLogChat::ELogLineType type, const LLSD& msg, void* userdata)
{
if (!userdata) return;
LLIMSession* self = (LLIMSession*) userdata;
if (type == LLLogChat::LOG_LINE)
{
LL_DEBUGS("ChatHistory") << "chatFromLogFile() adding LOG_LINE message from " << msg << LL_ENDL;
self->addMessage("", LLSD(), msg["message"].asString(), "", true, false, 0); // from history data, not region message, no timestamp
}
else if (type == LLLogChat::LOG_LLSD)
{
LL_DEBUGS("ChatHistory") << "chatFromLogFile() adding LOG_LLSD message from " << msg << LL_ENDL;
self->addMessage(msg["from"].asString(), msg["from_id"].asUUID(), msg["message"].asString(), msg["time"].asString(), true, false, 0); // from history data, not region message, no timestamp
}
}
void LLIMModel::LLIMSession::loadHistory()
{
mMsgs.clear();
mLastHistoryCacheMsgs.clear();
mLastHistoryCacheDateTime.clear();
if ( gSavedPerAccountSettings.getBOOL("LogShowHistory") )
{
// read and parse chat history from local file
chat_message_list_t chat_history;
LLLogChat::loadChatHistory(mHistoryFileName, chat_history, LLSD(), isGroupChat());
addMessagesFromHistoryCache(chat_history);
}
}
LLIMModel::LLIMSession* LLIMModel::findIMSession(const LLUUID& session_id) const
{
return get_if_there(mId2SessionMap, session_id, (LLIMModel::LLIMSession*) NULL);
}
//*TODO consider switching to using std::set instead of std::list for holding LLUUIDs across the whole code
LLIMModel::LLIMSession* LLIMModel::findAdHocIMSession(const uuid_vec_t& ids)
{
S32 num = ids.size();
if (!num) return NULL;
if (mId2SessionMap.empty()) return NULL;
std::map<LLUUID, LLIMSession*>::const_iterator it = mId2SessionMap.begin();
for (; it != mId2SessionMap.end(); ++it)
{
LLIMSession* session = (*it).second;
if (!session->isAdHoc()) continue;
if (session->mInitialTargetIDs.size() != num) continue;
std::list<LLUUID> tmp_list(session->mInitialTargetIDs.begin(), session->mInitialTargetIDs.end());
uuid_vec_t::const_iterator iter = ids.begin();
while (iter != ids.end())
{
tmp_list.remove(*iter);
++iter;
if (tmp_list.empty())
{
break;
}
}
if (tmp_list.empty() && iter == ids.end())
{
return session;
}
}
return NULL;
}
bool LLIMModel::LLIMSession::isOutgoingAdHoc() const
{
return IM_SESSION_CONFERENCE_START == mType;
}
bool LLIMModel::LLIMSession::isAdHoc()
{
return IM_SESSION_CONFERENCE_START == mType || (IM_SESSION_INVITE == mType && !gAgent.isInGroup(mSessionID, TRUE));
}
bool LLIMModel::LLIMSession::isP2P()
{
return IM_NOTHING_SPECIAL == mType;
}
bool LLIMModel::LLIMSession::isGroupChat()
{
return IM_SESSION_GROUP_START == mType || (IM_SESSION_INVITE == mType && gAgent.isInGroup(mSessionID, TRUE));
}
LLUUID LLIMModel::LLIMSession::generateOutgoingAdHocHash() const
{
LLUUID hash = LLUUID::null;
if (mInitialTargetIDs.size())
{
std::set<LLUUID> sorted_uuids(mInitialTargetIDs.begin(), mInitialTargetIDs.end());
hash = generateHash(sorted_uuids);
}
return hash;
}
void LLIMModel::LLIMSession::buildHistoryFileName()
{
mHistoryFileName = mName;
//ad-hoc requires sophisticated chat history saving schemes
if (isAdHoc())
{
/* in case of outgoing ad-hoc sessions we need to make specilized names
* if this naming system is ever changed then the filtering definitions in
* lllogchat.cpp need to be change acordingly so that the filtering for the
* date stamp code introduced in STORM-102 will work properly and not add
* a date stamp to the Ad-hoc conferences.
*/
if (mInitialTargetIDs.size())
{
std::set<LLUUID> sorted_uuids(mInitialTargetIDs.begin(), mInitialTargetIDs.end());
mHistoryFileName = mName + " hash" + generateHash(sorted_uuids).asString();
}
else
{
//in case of incoming ad-hoc sessions
mHistoryFileName = mName + " " + LLLogChat::timestamp2LogString(0, true) + " " + mSessionID.asString().substr(0, 4);
}
}
else if (isP2P()) // look up username to use as the log name
{
LLAvatarName av_name;
// For outgoing sessions we already have a cached name
// so no need for a callback in LLAvatarNameCache::get()
if (LLAvatarNameCache::get(mOtherParticipantID, &av_name))
{
// <FS:Ansariel> [Legacy IM logfile names]
//mHistoryFileName = LLCacheName::buildUsername(av_name.getUserName());
if (gSavedSettings.getBOOL("UseLegacyIMLogNames"))
{
std::string user_name = av_name.getUserName();
mHistoryFileName = user_name.substr(0, user_name.find(" Resident"));;
}
else
{
mHistoryFileName = LLCacheName::buildUsername(av_name.getUserName());
}
// </FS:Ansariel> [Legacy IM logfile names]
}
else
{
// Incoming P2P sessions include a name that we can use to build a history file name
// <FS:Ansariel> [Legacy IM logfile names]
//mHistoryFileName = LLCacheName::buildUsername(mName);
if (gSavedSettings.getBOOL("UseLegacyIMLogNames"))
{
mHistoryFileName = mName.substr(0, mName.find(" Resident"));;
}
else
{
mHistoryFileName = LLCacheName::buildUsername(mName);
}
// </FS:Ansariel> [Legacy IM logfile names]
}
// user's account name can change, but filenames and session names are account name based
LLConversationLog::getInstance()->verifyFilename(mSessionID, mHistoryFileName, av_name.getCompleteName());
}
else if (isGroupChat())
{
mHistoryFileName = mName + GROUP_CHAT_SUFFIX;
}
}
//static
LLUUID LLIMModel::LLIMSession::generateHash(const std::set<LLUUID>& sorted_uuids)
{
LLMD5 md5_uuid;
std::set<LLUUID>::const_iterator it = sorted_uuids.begin();
while (it != sorted_uuids.end())
{
md5_uuid.update((unsigned char*)(*it).mData, 16);
it++;
}
md5_uuid.finalize();
LLUUID participants_md5_hash;
md5_uuid.raw_digest((unsigned char*) participants_md5_hash.mData);
return participants_md5_hash;
}
void LLIMModel::processSessionInitializedReply(const LLUUID& old_session_id, const LLUUID& new_session_id)
{
LLIMSession* session = findIMSession(old_session_id);
if (session)
{
session->sessionInitReplyReceived(new_session_id);
if (old_session_id != new_session_id)
{
mId2SessionMap.erase(old_session_id);
mId2SessionMap[new_session_id] = session;
}
// <FS:Ansariel> [FS communication UI]
//LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(old_session_id);
FSFloaterIM* im_floater = FSFloaterIM::findInstance(old_session_id);
// </FS:Ansariel> [FS communication UI]
if (im_floater)
{
im_floater->sessionInitReplyReceived(new_session_id);
}
if (old_session_id != new_session_id)
{
gIMMgr->notifyObserverSessionIDUpdated(old_session_id, new_session_id);
}
// auto-start the call on session initialization?
if (session->mStartCallOnInitialize)
{
gIMMgr->startCall(new_session_id);
}
}
}
void LLIMModel::testMessages()
{
LLUUID bot1_id("d0426ec6-6535-4c11-a5d9-526bb0c654d9");
LLUUID bot1_session_id;
std::string from = "IM Tester";
bot1_session_id = LLIMMgr::computeSessionID(IM_NOTHING_SPECIAL, bot1_id);
newSession(bot1_session_id, from, IM_NOTHING_SPECIAL, bot1_id);
addMessage(bot1_session_id, from, bot1_id, "Test Message: Hi from testerbot land!");
LLUUID bot2_id;
std::string firstname[] = {"Roflcopter", "Joe"};
std::string lastname[] = {"Linden", "Tester", "Resident", "Schmoe"};
S32 rand1 = ll_rand(sizeof firstname)/(sizeof firstname[0]);
S32 rand2 = ll_rand(sizeof lastname)/(sizeof lastname[0]);
from = firstname[rand1] + " " + lastname[rand2];
bot2_id.generate(from);
LLUUID bot2_session_id = LLIMMgr::computeSessionID(IM_NOTHING_SPECIAL, bot2_id);
newSession(bot2_session_id, from, IM_NOTHING_SPECIAL, bot2_id);
addMessage(bot2_session_id, from, bot2_id, "Test Message: Hello there, I have a question. Can I bother you for a second? ");
addMessage(bot2_session_id, from, bot2_id, "Test Message: OMGWTFBBQ.");
}
//session name should not be empty
bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type,
const LLUUID& other_participant_id, const uuid_vec_t& ids, bool voice, bool has_offline_msg)
{
if (name.empty())
{
LL_WARNS() << "Attempt to create a new session with empty name; id = " << session_id << LL_ENDL;
return false;
}
if (findIMSession(session_id))
{
LL_WARNS() << "IM Session " << session_id << " already exists" << LL_ENDL;
return false;
}
LLIMSession* session = new LLIMSession(session_id, name, type, other_participant_id, ids, voice, has_offline_msg);
mId2SessionMap[session_id] = session;
// When notifying observer, name of session is used instead of "name", because they may not be the
// same if it is an adhoc session (in this case name is localized in LLIMSession constructor).
std::string session_name = LLIMModel::getInstance()->getName(session_id);
LLIMMgr::getInstance()->notifyObserverSessionAdded(session_id, session_name, other_participant_id,has_offline_msg);
return true;
}
bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id, bool voice, bool has_offline_msg)
{
uuid_vec_t ids;
ids.push_back(other_participant_id);
return newSession(session_id, name, type, other_participant_id, ids, voice, has_offline_msg);
}
bool LLIMModel::clearSession(const LLUUID& session_id)
{
if (mId2SessionMap.find(session_id) == mId2SessionMap.end()) return false;
delete (mId2SessionMap[session_id]);
mId2SessionMap.erase(session_id);
return true;
}
void LLIMModel::getMessages(const LLUUID& session_id, chat_message_list_t& messages, int start_index, const bool sendNoUnreadMsgs)
{
getMessagesSilently(session_id, messages, start_index);
if (sendNoUnreadMsgs)
{
sendNoUnreadMessages(session_id);
}
}
void LLIMModel::getMessagesSilently(const LLUUID& session_id, chat_message_list_t& messages, int start_index)
{
LLIMSession* session = findIMSession(session_id);
if (!session)
{
LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL;
return;
}
int i = session->mMsgs.size() - start_index;
for (chat_message_list_t::iterator iter = session->mMsgs.begin();
iter != session->mMsgs.end() && i > 0;
iter++)
{
LLSD msg;
msg = *iter;
messages.push_back(*iter);
i--;
}
}
void LLIMModel::sendNoUnreadMessages(const LLUUID& session_id)
{
LLIMSession* session = findIMSession(session_id);
if (!session)
{
LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL;
return;
}
session->mNumUnread = 0;
session->mParticipantUnreadMessageCount = 0;
LLSD arg;
arg["session_id"] = session_id;
arg["num_unread"] = 0;
arg["participant_unread"] = session->mParticipantUnreadMessageCount;
mNoUnreadMsgsSignal(arg);
}
bool LLIMModel::addToHistory(const LLUUID& session_id,
const std::string& from,
const LLUUID& from_id,
const std::string& utf8_text,
bool is_region_msg,
U32 timestamp,
// <FS:Ansariel> Added is_announcement parameter
bool is_announcement)
{
LLIMSession* session = findIMSession(session_id);
if (!session)
{
LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL;
return false;
}
// <FS:Ansariel> Forward IM to nearby chat if wanted
std::string timestr = LLLogChat::timestamp2LogString(timestamp, false);
session->addMessage(from, from_id, utf8_text, timestr, false, is_region_msg, timestamp); //might want to add date separately
static LLCachedControl<bool> show_im_in_chat(gSavedSettings, "FSShowIMInChatHistory");
if (show_im_in_chat && !is_announcement)
{
LLChat chat;
chat.mChatStyle = CHAT_STYLE_NORMAL;
// FS:LO FIRE-5230 - Chat Console Improvement: Replacing the "IM" in front of group chat messages with the actual group name
chat.mChatType = CHAT_TYPE_IM;
chat.mFromID = from_id;
//chat.mFromName = from;
static LLCachedControl<S32> group_name_length(gSavedSettings, "FSShowGroupNameLength");
if(group_name_length != 0 && session->isGroupSessionType())
{
chat.mChatType = CHAT_TYPE_IM_GROUP;
chat.mFromNameGroup = "[" + session->mName.substr(0, group_name_length) + "] ";
chat.mFromName = from;
}
else
{
chat.mChatType = CHAT_TYPE_IM;
chat.mFromName = from;
}
// FS:LO FIRE-5230 - Chat Console Improvement: Replacing the "IM" in front of group chat messages with the actual group name
chat.mSourceType = CHAT_SOURCE_AGENT;
chat.mText = utf8_text;
chat.mTimeStr = timestr;
// <FS:Ansariel> [FS communication UI]
//LLFloaterNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance<LLFloaterNearbyChat>("nearby_chat", LLSD());
FSFloaterNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance<FSFloaterNearbyChat>("fs_nearby_chat", LLSD());
// </FS:Ansariel> [FS communication UI]
nearby_chat->addMessage(chat, true, LLSD());
}
// </FS:Ansariel>
return true;
}
bool LLIMModel::logToFile(const std::string& file_name, const std::string& from, const LLUUID& from_id, const std::string& utf8_text)
{
if (gSavedPerAccountSettings.getS32("KeepConversationLogTranscripts") > 1)
{
std::string from_name = from;
LLAvatarName av_name;
if (!from_id.isNull() &&
LLAvatarNameCache::get(from_id, &av_name) &&
!av_name.isDisplayNameDefault())
{
from_name = av_name.getCompleteName();
}
LLLogChat::saveHistory(file_name, from_name, from_id, utf8_text);
LLConversationLog::instance().cache(); // update the conversation log too
return true;
}
else
{
return false;
}
}
void LLIMModel::proccessOnlineOfflineNotification(
const LLUUID& session_id,
const std::string& utf8_text)
{
// Add system message to history
addMessage(session_id, SYSTEM_FROM, LLUUID::null, utf8_text);
}
// <FS:Ansariel> Added is_announcement parameter
//void LLIMModel::addMessage(const LLUUID& session_id, const std::string& from, const LLUUID& from_id,
// const std::string& utf8_text, bool log2file /* = true */, bool is_region_msg, /* = false */ U32 time_stamp /* = 0 */)
void LLIMModel::addMessage(const LLUUID& session_id, const std::string& from, const LLUUID& from_id,
const std::string& utf8_text, bool log2file /* = true */, bool is_region_msg /* = false */, U32 time_stamp /* = 0 */, bool is_announcement /* = false */, bool keyword_alert_performed /* = false */)
{
//if (gSavedSettings.getBOOL("TranslateChat") && (from != SYSTEM_FROM))
if (gSavedSettings.getBOOL("TranslateChat") && (from != SYSTEM_FROM) && !is_announcement && !keyword_alert_performed)
{
const std::string from_lang = ""; // leave empty to trigger autodetect
const std::string to_lang = LLTranslate::getTranslateLanguage();
U64 time_n_flags = ((U64) time_stamp) | (log2file ? (1LL << 32) : 0) | (is_region_msg ? (1LL << 33) : 0); // boost::bind has limited parameters
LLTranslate::translateMessage(from_lang, to_lang, utf8_text,
boost::bind(&translateSuccess, session_id, from, from_id, utf8_text, time_n_flags, utf8_text, from_lang, _1, _2),
boost::bind(&translateFailure, session_id, from, from_id, utf8_text, time_n_flags, _1, _2));
}
else
{
processAddingMessage(session_id, from, from_id, utf8_text, log2file, is_region_msg, time_stamp, is_announcement, keyword_alert_performed);
}
}
// <FS:Ansariel> Added is_announcement parameter
//void LLIMModel::processAddingMessage(const LLUUID& session_id, const std::string& from, const LLUUID& from_id,
// const std::string& utf8_text, bool log2file, bool is_region_msg, U32 time_stamp)
void LLIMModel::processAddingMessage(const LLUUID& session_id, const std::string& from, const LLUUID& from_id,
const std::string& utf8_text, bool log2file, bool is_region_msg, U32 time_stamp, bool is_announcement, bool keyword_alert_performed)
{
LLIMSession* session = addMessageSilently(session_id, from, from_id, utf8_text, log2file, is_region_msg, time_stamp, is_announcement);
if (!session)
return;
//good place to add some1 to recent list
//other places may be called from message history.
if( !from_id.isNull() &&
( session->isP2PSessionType() || session->isAdHocSessionType() ) )
LLRecentPeople::instance().add(from_id);
// notify listeners
LLSD arg;
arg["session_id"] = session_id;
arg["num_unread"] = session->mNumUnread;
arg["participant_unread"] = session->mParticipantUnreadMessageCount;
arg["message"] = utf8_text;
arg["from"] = from;
arg["from_id"] = from_id;
arg["time"] = LLLogChat::timestamp2LogString(time_stamp, true);
arg["session_type"] = session->mSessionType;
arg["is_region_msg"] = is_region_msg;
arg["is_announcement"] = is_announcement; // <FS:Ansariel> Indicator if it's an announcement
arg["keyword_alert_performed"] = keyword_alert_performed; // <FS:Ansariel> Pass info if keyword alert has been performed
mNewMsgSignal(arg);
}
// <FS:Ansariel> Added is_announcement parameter
//LLIMModel::LLIMSession* LLIMModel::addMessageSilently(const LLUUID& session_id, const std::string& from, const LLUUID& from_id,
// const std::string& utf8_text, bool log2file /* = true */, bool is_region_msg, /* false */
// U32 timestamp /* = 0 */)
LLIMModel::LLIMSession* LLIMModel::addMessageSilently(const LLUUID& session_id, const std::string& from, const LLUUID& from_id,
const std::string& utf8_text, bool log2file /* = true */, bool is_region_msg /* = false */,
U32 timestamp /* = 0 */, bool is_announcement /* = false */)
{
LLIMSession* session = findIMSession(session_id);
if (!session)
{
return NULL;
}
// replace interactive system message marker with correct from string value
std::string from_name = from;
if (INTERACTIVE_SYSTEM_FROM == from)
{
from_name = SYSTEM_FROM;
}
addToHistory(session_id, from_name, from_id, utf8_text, is_region_msg, timestamp, is_announcement);
if (log2file && !is_announcement)
{
logToFile(getHistoryFileName(session_id), from_name, from_id, utf8_text);
}
session->mNumUnread++;
//update count of unread messages from real participant
if (!(from_id.isNull() || from_id == gAgentID || SYSTEM_FROM == from)
// we should increment counter for interactive system messages()
|| INTERACTIVE_SYSTEM_FROM == from)
{
++(session->mParticipantUnreadMessageCount);
}
return session;
}
const std::string LLIMModel::getName(const LLUUID& session_id) const
{
LLIMSession* session = findIMSession(session_id);
if (!session)
{
LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL;
return LLTrans::getString("no_session_message");
}
return session->mName;
}
const S32 LLIMModel::getNumUnread(const LLUUID& session_id) const
{
LLIMSession* session = findIMSession(session_id);
if (!session)
{
LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL;
return -1;
}
return session->mNumUnread;
}
const LLUUID& LLIMModel::getOtherParticipantID(const LLUUID& session_id) const
{
LLIMSession* session = findIMSession(session_id);
if (!session)
{
LL_WARNS() << "session " << session_id << " does not exist " << LL_ENDL;
return LLUUID::null;
}
return session->mOtherParticipantID;
}
EInstantMessage LLIMModel::getType(const LLUUID& session_id) const
{
LLIMSession* session = findIMSession(session_id);
if (!session)
{
LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL;
return IM_COUNT;
}
return session->mType;
}
LLVoiceChannel* LLIMModel::getVoiceChannel( const LLUUID& session_id ) const
{
LLIMSession* session = findIMSession(session_id);
if (!session)
{
LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL;
return NULL;
}
return session->mVoiceChannel;
}
LLIMSpeakerMgr* LLIMModel::getSpeakerManager( const LLUUID& session_id ) const
{
LLIMSession* session = findIMSession(session_id);
if (!session)
{
LL_WARNS() << "session " << session_id << " does not exist " << LL_ENDL;
return NULL;
}
return session->mSpeakers;
}
const std::string& LLIMModel::getHistoryFileName(const LLUUID& session_id) const
{
LLIMSession* session = findIMSession(session_id);
if (!session)
{
LL_WARNS() << "session " << session_id << " does not exist " << LL_ENDL;
return LLStringUtil::null;
}
return session->mHistoryFileName;
}
// TODO get rid of other participant ID
void LLIMModel::sendTypingState(LLUUID session_id, LLUUID other_participant_id, BOOL typing)
{
static LLCachedControl<bool> fsSendTypingState(gSavedSettings, "FSSendTypingState");
if (!fsSendTypingState)
{
return;
}
std::string name;
LLAgentUI::buildFullname(name);
pack_instant_message(
gMessageSystem,
gAgent.getID(),
FALSE,
gAgent.getSessionID(),
other_participant_id,
name,
std::string("typing"),
IM_ONLINE,
(typing ? IM_TYPING_START : IM_TYPING_STOP),
session_id);
gAgent.sendReliableMessage();
}
void LLIMModel::sendLeaveSession(const LLUUID& session_id, const LLUUID& other_participant_id)
{
if(session_id.notNull())
{
std::string name;
LLAgentUI::buildFullname(name);
pack_instant_message(
gMessageSystem,
gAgent.getID(),
FALSE,
gAgent.getSessionID(),
other_participant_id,
name,
LLStringUtil::null,
IM_ONLINE,
IM_SESSION_LEAVE,
session_id);
gAgent.sendReliableMessage();
}
}
// *TODO this method is better be moved to the LLIMMgr
//<FS:TS> FIRE-787: break up too long chat lines into multiple messages
// This code is broken out for proper handling of multiple IMs after splitting.
void deliverMessage(const std::string& utf8_text,
const LLUUID& im_session_id,
const LLUUID& other_participant_id,
EInstantMessage dialog)
{
std::string name;
bool sent = false;
LLAgentUI::buildFullname(name);
const LLRelationship* info = NULL;
info = LLAvatarTracker::instance().getBuddyInfo(other_participant_id);
U8 offline = (!info || info->isOnline()) ? IM_ONLINE : IM_OFFLINE;
// Old call to send messages to SLim client, no longer supported.
//if((offline == IM_OFFLINE) && (LLVoiceClient::getInstance()->isOnlineSIP(other_participant_id)))
//{
// // User is online through the OOW connector, but not with a regular viewer. Try to send the message via SLVoice.
// sent = LLVoiceClient::getInstance()->sendTextMessage(other_participant_id, utf8_text);
//}
if(!sent)
{
// Send message normally.
// default to IM_SESSION_SEND unless it's nothing special - in
// which case it's probably an IM to everyone.
U8 new_dialog = dialog;
if ( dialog != IM_NOTHING_SPECIAL )
{
new_dialog = IM_SESSION_SEND;
}
pack_instant_message(
gMessageSystem,
gAgent.getID(),
FALSE,
gAgent.getSessionID(),
other_participant_id,
name.c_str(),
utf8_text.c_str(),
offline,
(EInstantMessage)new_dialog,
im_session_id);
gAgent.sendReliableMessage();
}
bool is_group_chat = false;
LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(im_session_id);
if(session)
{
is_group_chat = session->isGroupSessionType();
}
// If there is a mute list and this is not a group chat...
if ( LLMuteList::getInstance() && !is_group_chat)
{
// ... the target should not be in our mute list for some message types.
// Auto-remove them if present.
switch( dialog )
{
case IM_NOTHING_SPECIAL:
case IM_GROUP_INVITATION:
case IM_INVENTORY_OFFERED:
case IM_SESSION_INVITE:
case IM_SESSION_P2P_INVITE:
case IM_SESSION_CONFERENCE_START:
case IM_SESSION_SEND: // This one is marginal - erring on the side of hearing.
case IM_LURE_USER:
case IM_GODLIKE_LURE_USER:
case IM_FRIENDSHIP_OFFERED:
LLMuteList::getInstance()->autoRemove(other_participant_id, LLMuteList::AR_IM);
break;
default: ; // do nothing
}
}
}
//</FS:TS> FIRE-787
void LLIMModel::sendMessage(const std::string& utf8_text,
const LLUUID& im_session_id,
const LLUUID& other_participant_id,
EInstantMessage dialog)
{
//<FS:TS> FIRE-787: break up too long chat lines into multiple messages
U32 split = MAX_MSG_BUF_SIZE - 1;
U32 pos = 0;
U32 total = utf8_text.length();
while(pos < total)
{
U32 next_split = split;
if (pos + next_split > total)
{
// just send the rest of the message
next_split = total - pos;
}
else
{
// first, try to split at a space
while((U8(utf8_text[pos + next_split]) != ' ')
&& (next_split > 0))
{
--next_split;
}
if (next_split == 0)
{
next_split = split;
// no space found, split somewhere not in the middle of UTF-8
while((U8(utf8_text[pos + next_split]) >= 0x80)
&& (U8(utf8_text[pos + next_split]) < 0xC0)
&& (next_split > 0))
{
--next_split;
}
}
if(next_split == 0)
{
next_split = split;
LL_WARNS("Splitting") <<
"utf-8 couldn't be split correctly" << LL_ENDL;
}
}
std::string send = utf8_text.substr(pos, next_split);
pos += next_split;
// *FIXME: Queue messages and wait for server
deliverMessage(send, im_session_id, other_participant_id, dialog);
}
//</FS:TS> FIRE-787
if((dialog == IM_NOTHING_SPECIAL) &&
(other_participant_id.notNull()))
{
// Do we have to replace the /me's here?
std::string from;
LLAgentUI::buildFullname(from);
LLIMModel::getInstance()->addMessage(im_session_id, from, gAgentID, utf8_text);
//local echo for the legacy communicate panel
std::string history_echo;
LLAgentUI::buildFullname(history_echo);
history_echo += ": " + utf8_text;
LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(im_session_id);
if (speaker_mgr)
{
speaker_mgr->speakerChatted(gAgentID);
speaker_mgr->setSpeakerTyping(gAgentID, FALSE);
}
}
// Add the recipient to the recent people list.
bool is_not_group_id = LLGroupMgr::getInstance()->getGroupData(other_participant_id) == NULL;
if (is_not_group_id)
{
LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(im_session_id); // <FS:Ansariel> Re-added; required because of FIRE-787
if( session == 0)//??? shouldn't really happen
{
LLRecentPeople::instance().add(other_participant_id);
return;
}
// IM_SESSION_INVITE means that this is an Ad-hoc incoming chat
// (it can be also Group chat but it is checked above)
// In this case mInitialTargetIDs contains Ad-hoc session ID and it should not be added
// to Recent People to prevent showing of an item with (?? ?)(?? ?), sans the spaces. See EXT-8246.
// Concrete participants will be added into this list once they sent message in chat.
if (IM_SESSION_INVITE == dialog) return;
if (IM_SESSION_CONFERENCE_START == dialog) // outgoing ad-hoc session
{
// Add only online members of conference to recent list (EXT-8658)
addSpeakersToRecent(im_session_id);
}
else // outgoing P2P session
{
// Add the recepient of the session.
if (!session->mInitialTargetIDs.empty())
{
LLRecentPeople::instance().add(*(session->mInitialTargetIDs.begin()));
}
}
}
}
void LLIMModel::addSpeakersToRecent(const LLUUID& im_session_id)
{
LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(im_session_id);
LLSpeakerMgr::speaker_list_t speaker_list;
if(speaker_mgr != NULL)
{
speaker_mgr->getSpeakerList(&speaker_list, true);
}
for(LLSpeakerMgr::speaker_list_t::iterator it = speaker_list.begin(); it != speaker_list.end(); it++)
{
const LLPointer<LLSpeaker>& speakerp = *it;
LLRecentPeople::instance().add(speakerp->mID);
}
}
void session_starter_helper(
const LLUUID& temp_session_id,
const LLUUID& other_participant_id,
EInstantMessage im_type)
{
LLMessageSystem *msg = gMessageSystem;
msg->newMessageFast(_PREHASH_ImprovedInstantMessage);
msg->nextBlockFast(_PREHASH_AgentData);
msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
msg->nextBlockFast(_PREHASH_MessageBlock);
msg->addBOOLFast(_PREHASH_FromGroup, FALSE);
msg->addUUIDFast(_PREHASH_ToAgentID, other_participant_id);
msg->addU8Fast(_PREHASH_Offline, IM_ONLINE);
msg->addU8Fast(_PREHASH_Dialog, im_type);
msg->addUUIDFast(_PREHASH_ID, temp_session_id);
msg->addU32Fast(_PREHASH_Timestamp, NO_TIMESTAMP); // no timestamp necessary
std::string name;
LLAgentUI::buildFullname(name);
msg->addStringFast(_PREHASH_FromAgentName, name);
msg->addStringFast(_PREHASH_Message, LLStringUtil::null);
msg->addU32Fast(_PREHASH_ParentEstateID, 0);
msg->addUUIDFast(_PREHASH_RegionID, LLUUID::null);
msg->addVector3Fast(_PREHASH_Position, gAgent.getPositionAgent());
}
void start_deprecated_conference_chat(
const LLUUID& temp_session_id,
const LLUUID& creator_id,
const LLUUID& other_participant_id,
const LLSD& agents_to_invite)
{
U8* bucket;
U8* pos;
S32 count;
S32 bucket_size;
// *FIX: this could suffer from endian issues
count = agents_to_invite.size();
bucket_size = UUID_BYTES * count;
bucket = new U8[bucket_size];
pos = bucket;
for(S32 i = 0; i < count; ++i)
{
LLUUID agent_id = agents_to_invite[i].asUUID();
memcpy(pos, &agent_id, UUID_BYTES);
pos += UUID_BYTES;
}
session_starter_helper(
temp_session_id,
other_participant_id,
IM_SESSION_CONFERENCE_START);
gMessageSystem->addBinaryDataFast(
_PREHASH_BinaryBucket,
bucket,
bucket_size);
gAgent.sendReliableMessage();
delete[] bucket;
}
// Returns true if any messages were sent, false otherwise.
// Is sort of equivalent to "does the server need to do anything?"
bool LLIMModel::sendStartSession(
const LLUUID& temp_session_id,
const LLUUID& other_participant_id,
const uuid_vec_t& ids,
EInstantMessage dialog)
{
if ( dialog == IM_SESSION_GROUP_START )
{
session_starter_helper(
temp_session_id,
other_participant_id,
dialog);
gMessageSystem->addBinaryDataFast(
_PREHASH_BinaryBucket,
EMPTY_BINARY_BUCKET,
EMPTY_BINARY_BUCKET_SIZE);
gAgent.sendReliableMessage();
return true;
}
else if ( dialog == IM_SESSION_CONFERENCE_START )
{
LLSD agents;
for (int i = 0; i < (S32) ids.size(); i++)
{
agents.append(ids[i]);
}
//we have a new way of starting conference calls now
LLViewerRegion* region = gAgent.getRegion();
if (region)
{
std::string url = region->getCapability(
"ChatSessionRequest");
LLCoros::instance().launch("startConfrenceCoro",
boost::bind(&startConfrenceCoro, url,
temp_session_id, gAgent.getID(), other_participant_id, agents));
}
else
{
start_deprecated_conference_chat(
temp_session_id,
gAgent.getID(),
other_participant_id,
agents);
}
//we also need to wait for reply from the server in case of ad-hoc chat (we'll get new session id)
return true;
}
return false;
}
// the other_participant_id is either an agent_id, a group_id, or an inventory
// folder item_id (collection of calling cards)
// static
LLUUID LLIMMgr::computeSessionID(
EInstantMessage dialog,
const LLUUID& other_participant_id)
{
LLUUID session_id;
if (IM_SESSION_GROUP_START == dialog)
{
// slam group session_id to the group_id (other_participant_id)
session_id = other_participant_id;
}
else if (IM_SESSION_CONFERENCE_START == dialog)
{
session_id.generate();
}
else if (IM_SESSION_INVITE == dialog)
{
// use provided session id for invites
session_id = other_participant_id;
}
else
{
LLUUID agent_id = gAgent.getID();
if (other_participant_id == agent_id)
{
// if we try to send an IM to ourselves then the XOR would be null
// so we just make the session_id the same as the agent_id
session_id = agent_id;
}
else
{
// peer-to-peer or peer-to-asset session_id is the XOR
session_id = other_participant_id ^ agent_id;
}
}
if (gAgent.isInGroup(session_id, TRUE) && (session_id != other_participant_id))
{
LL_WARNS() << "Group session id different from group id: IM type = " << dialog << ", session id = " << session_id << ", group id = " << other_participant_id << LL_ENDL;
}
return session_id;
}
void
LLIMMgr::showSessionStartError(
const std::string& error_string,
const LLUUID session_id)
{
if (!hasSession(session_id)) return;
LLSD args;
args["REASON"] = LLTrans::getString(error_string);
args["RECIPIENT"] = LLIMModel::getInstance()->getName(session_id);
LLSD payload;
payload["session_id"] = session_id;
LLNotificationsUtil::add(
"ChatterBoxSessionStartError",
args,
payload,
LLIMMgr::onConfirmForceCloseError);
}
void
LLIMMgr::showSessionEventError(
const std::string& event_string,
const std::string& error_string,
const LLUUID session_id)
{
LLSD args;
LLStringUtil::format_map_t event_args;
event_args["RECIPIENT"] = LLIMModel::getInstance()->getName(session_id);
args["REASON"] =
LLTrans::getString(error_string);
args["EVENT"] =
LLTrans::getString(event_string, event_args);
LLNotificationsUtil::add(
"ChatterBoxSessionEventError",
args);
}
void
LLIMMgr::showSessionForceClose(
const std::string& reason_string,
const LLUUID session_id)
{
if (!hasSession(session_id)) return;
LLSD args;
args["NAME"] = LLIMModel::getInstance()->getName(session_id);
args["REASON"] = LLTrans::getString(reason_string);
LLSD payload;
payload["session_id"] = session_id;
LLNotificationsUtil::add(
"ForceCloseChatterBoxSession",
args,
payload,
LLIMMgr::onConfirmForceCloseError);
}
//static
bool
LLIMMgr::onConfirmForceCloseError(
const LLSD& notification,
const LLSD& response)
{
//only 1 option really
LLUUID session_id = notification["payload"]["session_id"];
// <FS:Ansariel> [FS communication UI]
//LLFloater* floater = LLFloaterIMSession::findInstance(session_id);
LLFloater* floater = FSFloaterIM::findInstance(session_id);
// </FS:Ansariel> [FS communication UI]
if ( floater )
{
floater->closeFloater(FALSE);
}
return false;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Class LLCallDialogManager
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
LLCallDialogManager::LLCallDialogManager():
mPreviousSessionlName(""),
mCurrentSessionlName(""),
mSession(NULL),
mOldState(LLVoiceChannel::STATE_READY)
{
}
LLCallDialogManager::~LLCallDialogManager()
{
}
void LLCallDialogManager::initSingleton()
{
LLVoiceChannel::setCurrentVoiceChannelChangedCallback(LLCallDialogManager::onVoiceChannelChanged);
}
// static
void LLCallDialogManager::onVoiceChannelChanged(const LLUUID &session_id)
{
LLCallDialogManager::getInstance()->onVoiceChannelChangedInt(session_id);
}
void LLCallDialogManager::onVoiceChannelChangedInt(const LLUUID &session_id)
{
LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(session_id);
if(!session)
{
mPreviousSessionlName = mCurrentSessionlName;
mCurrentSessionlName = ""; // Empty string results in "Nearby Voice Chat" after substitution
return;
}
mSession = session;
static boost::signals2::connection prev_channel_state_changed_connection;
// disconnect previously connected callback to avoid have invalid sSession in onVoiceChannelStateChanged()
prev_channel_state_changed_connection.disconnect();
prev_channel_state_changed_connection =
mSession->mVoiceChannel->setStateChangedCallback(boost::bind(LLCallDialogManager::onVoiceChannelStateChanged, _1, _2, _3, _4));
if(mCurrentSessionlName != session->mName)
{
mPreviousSessionlName = mCurrentSessionlName;
mCurrentSessionlName = session->mName;
}
if (LLVoiceChannel::getCurrentVoiceChannel()->getState() == LLVoiceChannel::STATE_CALL_STARTED &&
LLVoiceChannel::getCurrentVoiceChannel()->getCallDirection() == LLVoiceChannel::OUTGOING_CALL)
{
//*TODO get rid of duplicated code
LLSD mCallDialogPayload;
mCallDialogPayload["session_id"] = mSession->mSessionID;
mCallDialogPayload["session_name"] = mSession->mName;
mCallDialogPayload["other_user_id"] = mSession->mOtherParticipantID;
mCallDialogPayload["old_channel_name"] = mPreviousSessionlName;
mCallDialogPayload["state"] = LLVoiceChannel::STATE_CALL_STARTED;
mCallDialogPayload["disconnected_channel_name"] = mSession->mName;
mCallDialogPayload["session_type"] = mSession->mSessionType;
LLOutgoingCallDialog* ocd = LLFloaterReg::getTypedInstance<LLOutgoingCallDialog>("outgoing_call", LLOutgoingCallDialog::OCD_KEY);
if(ocd)
{
ocd->show(mCallDialogPayload);
}
}
}
// static
void LLCallDialogManager::onVoiceChannelStateChanged(const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state, const LLVoiceChannel::EDirection& direction, bool ended_by_agent)
{
LLCallDialogManager::getInstance()->onVoiceChannelStateChangedInt(old_state, new_state, direction, ended_by_agent);
}
void LLCallDialogManager::onVoiceChannelStateChangedInt(const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state, const LLVoiceChannel::EDirection& direction, bool ended_by_agent)
{
LLSD mCallDialogPayload;
LLOutgoingCallDialog* ocd = NULL;
if(mOldState == new_state)
{
return;
}
mOldState = new_state;
mCallDialogPayload["session_id"] = mSession->mSessionID;
mCallDialogPayload["session_name"] = mSession->mName;
mCallDialogPayload["other_user_id"] = mSession->mOtherParticipantID;
mCallDialogPayload["old_channel_name"] = mPreviousSessionlName;
mCallDialogPayload["state"] = new_state;
mCallDialogPayload["disconnected_channel_name"] = mSession->mName;
mCallDialogPayload["session_type"] = mSession->mSessionType;
mCallDialogPayload["ended_by_agent"] = ended_by_agent;
switch(new_state)
{
case LLVoiceChannel::STATE_CALL_STARTED :
// do not show "Calling to..." if it is incoming call
if(direction == LLVoiceChannel::INCOMING_CALL)
{
return;
}
break;
case LLVoiceChannel::STATE_HUNG_UP:
// this state is coming before session is changed
break;
case LLVoiceChannel::STATE_CONNECTED :
ocd = LLFloaterReg::findTypedInstance<LLOutgoingCallDialog>("outgoing_call", LLOutgoingCallDialog::OCD_KEY);
if (ocd)
{
ocd->closeFloater();
}
return;
default:
break;
}
ocd = LLFloaterReg::getTypedInstance<LLOutgoingCallDialog>("outgoing_call", LLOutgoingCallDialog::OCD_KEY);
if(ocd)
{
ocd->show(mCallDialogPayload);
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Class LLCallDialog
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
LLCallDialog::LLCallDialog(const LLSD& payload)
: LLDockableFloater(NULL, false, payload),
mPayload(payload),
mLifetime(DEFAULT_LIFETIME)
{
setAutoFocus(FALSE);
// force docked state since this floater doesn't save it between recreations
setDocked(true);
}
LLCallDialog::~LLCallDialog()
{
LLUI::getInstance()->removePopup(this);
}
BOOL LLCallDialog::postBuild()
{
if (!LLDockableFloater::postBuild() || !gToolBarView)
return FALSE;
dockToToolbarButton("speak");
return TRUE;
}
void LLCallDialog::dockToToolbarButton(const std::string& toolbarButtonName)
{
LLDockControl::DocAt dock_pos = getDockControlPos(toolbarButtonName);
LLView *anchor_panel = gToolBarView->findChildView(toolbarButtonName);
setUseTongue(anchor_panel);
setDockControl(new LLDockControl(anchor_panel, this, getDockTongue(dock_pos), dock_pos));
}
LLDockControl::DocAt LLCallDialog::getDockControlPos(const std::string& toolbarButtonName)
{
LLCommandId command_id(toolbarButtonName);
S32 toolbar_loc = gToolBarView->hasCommand(command_id);
LLDockControl::DocAt doc_at = LLDockControl::TOP;
switch (toolbar_loc)
{
case LLToolBarEnums::TOOLBAR_LEFT:
doc_at = LLDockControl::RIGHT;
break;
case LLToolBarEnums::TOOLBAR_RIGHT:
doc_at = LLDockControl::LEFT;
break;
}
return doc_at;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Class LLOutgoingCallDialog
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
LLOutgoingCallDialog::LLOutgoingCallDialog(const LLSD& payload) :
LLCallDialog(payload)
{
LLOutgoingCallDialog* instance = LLFloaterReg::findTypedInstance<LLOutgoingCallDialog>("outgoing_call", LLOutgoingCallDialog::OCD_KEY);
if(instance && instance->getVisible())
{
instance->onCancel(instance);
}
}
void LLCallDialog::draw()
{
if (lifetimeHasExpired())
{
onLifetimeExpired();
}
if (getDockControl() != NULL)
{
LLDockableFloater::draw();
}
}
// virtual
void LLCallDialog::onOpen(const LLSD& key)
{
LLDockableFloater::onOpen(key);
// it should be over the all floaters. EXT-5116
LLUI::getInstance()->addPopup(this);
}
void LLCallDialog::setIcon(const LLSD& session_id, const LLSD& participant_id)
{
bool participant_is_avatar = LLVoiceClient::getInstance()->isParticipantAvatar(session_id);
bool is_group = participant_is_avatar && gAgent.isInGroup(session_id, TRUE);
LLAvatarIconCtrl* avatar_icon = getChild<LLAvatarIconCtrl>("avatar_icon");
LLGroupIconCtrl* group_icon = getChild<LLGroupIconCtrl>("group_icon");
avatar_icon->setVisible(!is_group);
group_icon->setVisible(is_group);
if (is_group)
{
group_icon->setValue(session_id);
}
else if (participant_is_avatar)
{
avatar_icon->setValue(participant_id);
}
else
{
LL_WARNS() << "Participant neither avatar nor group" << LL_ENDL;
group_icon->setValue(session_id);
}
}
bool LLCallDialog::lifetimeHasExpired()
{
if (mLifetimeTimer.getStarted())
{
F32 elapsed_time = mLifetimeTimer.getElapsedTimeF32();
if (elapsed_time > mLifetime)
{
return true;
}
}
return false;
}
void LLCallDialog::onLifetimeExpired()
{
mLifetimeTimer.stop();
closeFloater();
}
void LLOutgoingCallDialog::show(const LLSD& key)
{
mPayload = key;
//will be false only if voice in parcel is disabled and channel we leave is nearby(checked further)
bool show_oldchannel = LLViewerParcelMgr::getInstance()->allowAgentVoice();
// hide all text at first
hideAllText();
// init notification's lifetime
std::istringstream ss( getString("lifetime") );
if (!(ss >> mLifetime))
{
mLifetime = DEFAULT_LIFETIME;
}
// customize text strings
// tell the user which voice channel they are leaving
if (!mPayload["old_channel_name"].asString().empty())
{
std::string old_caller_name = mPayload["old_channel_name"].asString();
getChild<LLUICtrl>("leaving")->setTextArg("[CURRENT_CHAT]", old_caller_name);
show_oldchannel = true;
}
else
{
getChild<LLUICtrl>("leaving")->setTextArg("[CURRENT_CHAT]", getString("localchat"));
}
if (!mPayload["disconnected_channel_name"].asString().empty())
{
std::string channel_name = mPayload["disconnected_channel_name"].asString();
getChild<LLUICtrl>("nearby")->setTextArg("[VOICE_CHANNEL_NAME]", channel_name);
// skipping "You will now be reconnected to nearby" in notification when call is ended by disabling voice,
// so no reconnection to nearby chat happens (EXT-4397)
bool voice_works = LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking();
std::string reconnect_nearby = voice_works ? LLTrans::getString("reconnect_nearby") : std::string();
getChild<LLUICtrl>("nearby")->setTextArg("[RECONNECT_NEARBY]", reconnect_nearby);
const std::string& nearby_str = mPayload["ended_by_agent"] ? NEARBY_P2P_BY_AGENT : NEARBY_P2P_BY_OTHER;
getChild<LLUICtrl>(nearby_str)->setTextArg("[RECONNECT_NEARBY]", reconnect_nearby);
}
std::string callee_name = mPayload["session_name"].asString();
if (callee_name == "anonymous") // obsolete? Likely was part of avaline support
{
callee_name = getString("anonymous");
}
LLSD callee_id = mPayload["other_user_id"];
// Beautification: Since you know who you called, just show display name
std::string title = callee_name;
std::string final_callee_name = callee_name;
if (mPayload["session_type"].asInteger() == LLIMModel::LLIMSession::P2P_SESSION)
{
LLAvatarName av_name;
if (LLAvatarNameCache::get(callee_id, &av_name))
{
final_callee_name = av_name.getDisplayName();
title = av_name.getCompleteName();
}
}
getChild<LLUICtrl>("calling")->setTextArg("[CALLEE_NAME]", final_callee_name);
getChild<LLUICtrl>("connecting")->setTextArg("[CALLEE_NAME]", final_callee_name);
setTitle(title);
// for outgoing group calls callee_id == group id == session id
setIcon(callee_id, callee_id);
// stop timer by default
mLifetimeTimer.stop();
// show only necessary strings and controls
switch(mPayload["state"].asInteger())
{
case LLVoiceChannel::STATE_CALL_STARTED :
getChild<LLTextBox>("calling")->setVisible(true);
getChild<LLButton>("Cancel")->setVisible(true);
if(show_oldchannel)
{
getChild<LLTextBox>("leaving")->setVisible(true);
}
break;
// STATE_READY is here to show appropriate text for ad-hoc and group calls when floater is shown(EXT-6893)
case LLVoiceChannel::STATE_READY :
case LLVoiceChannel::STATE_RINGING :
if(show_oldchannel)
{
getChild<LLTextBox>("leaving")->setVisible(true);
}
getChild<LLTextBox>("connecting")->setVisible(true);
break;
case LLVoiceChannel::STATE_ERROR :
getChild<LLTextBox>("noanswer")->setVisible(true);
getChild<LLButton>("Cancel")->setVisible(false);
setCanClose(true);
mLifetimeTimer.start();
break;
case LLVoiceChannel::STATE_HUNG_UP :
if (mPayload["session_type"].asInteger() == LLIMModel::LLIMSession::P2P_SESSION)
{
const std::string& nearby_str = mPayload["ended_by_agent"] ? NEARBY_P2P_BY_AGENT : NEARBY_P2P_BY_OTHER;
getChild<LLTextBox>(nearby_str)->setVisible(true);
}
else
{
getChild<LLTextBox>("nearby")->setVisible(true);
}
getChild<LLButton>("Cancel")->setVisible(false);
setCanClose(true);
mLifetimeTimer.start();
}
openFloater(LLOutgoingCallDialog::OCD_KEY);
}
void LLOutgoingCallDialog::hideAllText()
{
getChild<LLTextBox>("calling")->setVisible(false);
getChild<LLTextBox>("leaving")->setVisible(false);
getChild<LLTextBox>("connecting")->setVisible(false);
getChild<LLTextBox>("nearby_P2P_by_other")->setVisible(false);
getChild<LLTextBox>("nearby_P2P_by_agent")->setVisible(false);
getChild<LLTextBox>("nearby")->setVisible(false);
getChild<LLTextBox>("noanswer")->setVisible(false);
}
//static
void LLOutgoingCallDialog::onCancel(void* user_data)
{
LLOutgoingCallDialog* self = (LLOutgoingCallDialog*)user_data;
if (!gIMMgr)
return;
LLUUID session_id = self->mPayload["session_id"].asUUID();
gIMMgr->endCall(session_id);
self->closeFloater();
}
BOOL LLOutgoingCallDialog::postBuild()
{
BOOL success = LLCallDialog::postBuild();
childSetAction("Cancel", onCancel, this);
setCanDrag(FALSE);
return success;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Class LLIncomingCallDialog
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
const std::array<std::string, 4> voice_call_types =
{
"VoiceInviteP2P",
"VoiceInviteGroup",
"VoiceInviteAdHoc",
"InviteAdHoc"
};
bool is_voice_call_type(const std::string &value)
{
return std::find(voice_call_types.begin(), voice_call_types.end(), value) != voice_call_types.end();
}
LLIncomingCallDialog::LLIncomingCallDialog(const LLSD& payload) :
LLCallDialog(payload),
mAvatarNameCacheConnection()
{
}
void LLIncomingCallDialog::onLifetimeExpired()
{
std::string session_handle = mPayload["session_handle"].asString();
if (LLVoiceClient::getInstance()->isValidChannel(session_handle))
{
// restart notification's timer if call is still valid
mLifetimeTimer.start();
}
else
{
// close invitation if call is already not valid
mLifetimeTimer.stop();
LLUUID session_id = mPayload["session_id"].asUUID();
gIMMgr->clearPendingAgentListUpdates(session_id);
gIMMgr->clearPendingInvitation(session_id);
closeFloater();
}
}
BOOL LLIncomingCallDialog::postBuild()
{
LLCallDialog::postBuild();
if (!mPayload.isMap() || mPayload.size() == 0)
{
LL_INFOS("IMVIEW") << "IncomingCall: invalid argument" << LL_ENDL;
return TRUE;
}
LLUUID session_id = mPayload["session_id"].asUUID();
LLSD caller_id = mPayload["caller_id"];
std::string caller_name = mPayload["caller_name"].asString();
if (session_id.isNull() && caller_id.asUUID().isNull())
{
LL_INFOS("IMVIEW") << "IncomingCall: invalid ids" << LL_ENDL;
return TRUE;
}
std::string notify_box_type = mPayload["notify_box_type"].asString();
if (!is_voice_call_type(notify_box_type))
{
LL_INFOS("IMVIEW") << "IncomingCall: notify_box_type was not provided" << LL_ENDL;
return TRUE;
}
// init notification's lifetime
std::istringstream ss( getString("lifetime") );
if (!(ss >> mLifetime))
{
mLifetime = DEFAULT_LIFETIME;
}
std::string call_type;
if (gAgent.isInGroup(session_id, TRUE))
{
LLStringUtil::format_map_t args;
LLGroupData data;
if (gAgent.getGroupData(session_id, data))
{
args["[GROUP]"] = data.mName;
call_type = getString(notify_box_type, args);
}
}
else
{
call_type = getString(notify_box_type);
}
if (caller_name == "anonymous") // obsolete? Likely was part of avaline support
{
caller_name = getString("anonymous");
setCallerName(caller_name, caller_name, call_type);
}
else
{
// Get the full name information
if (mAvatarNameCacheConnection.connected())
{
mAvatarNameCacheConnection.disconnect();
}
mAvatarNameCacheConnection = LLAvatarNameCache::get(caller_id, boost::bind(&LLIncomingCallDialog::onAvatarNameCache, this, _1, _2, call_type));
}
setIcon(session_id, caller_id);
childSetAction("Accept", onAccept, this);
childSetAction("Reject", onReject, this);
childSetAction("Start IM", onStartIM, this);
setDefaultBtn("Accept");
if(notify_box_type != "VoiceInviteGroup" && notify_box_type != "VoiceInviteAdHoc")
{
// starting notification's timer for P2P invitations
mLifetimeTimer.start();
}
else
{
mLifetimeTimer.stop();
}
//it's not possible to connect to existing Ad-Hoc/Group chat through incoming ad-hoc call
bool is_avatar = LLVoiceClient::getInstance()->isParticipantAvatar(session_id);
getChildView("Start IM")->setVisible( is_avatar && notify_box_type != "VoiceInviteAdHoc" && notify_box_type != "VoiceInviteGroup");
setCanDrag(FALSE);
return TRUE;
}
void LLIncomingCallDialog::setCallerName(const std::string& ui_title,
const std::string& ui_label,
const std::string& call_type)
{
// call_type may be a string like " is calling."
LLUICtrl* caller_name_widget = getChild<LLUICtrl>("caller name");
caller_name_widget->setValue(ui_label + " " + call_type);
}
void LLIncomingCallDialog::onAvatarNameCache(const LLUUID& agent_id,
const LLAvatarName& av_name,
const std::string& call_type)
{
mAvatarNameCacheConnection.disconnect();
std::string title = av_name.getCompleteName();
setCallerName(title, av_name.getCompleteName(), call_type);
}
void LLIncomingCallDialog::onOpen(const LLSD& key)
{
LLCallDialog::onOpen(key);
// <FS:Ansariel> FIRE-7556: Configurable User Interface sounds; This is controlled in llui.cpp
//if (gSavedSettings.getBOOL("PlaySoundIncomingVoiceCall"))
//{
// // play a sound for incoming voice call if respective property is set
// make_ui_sound("UISndStartIM");
//}
make_ui_sound("UISndIncomingVoiceCall");
// </FS:Ansariel>
LLStringUtil::format_map_t args;
LLGroupData data;
// if it's a group call, retrieve group name to use it in question
if (gAgent.getGroupData(key["session_id"].asUUID(), data))
{
args["[GROUP]"] = data.mName;
}
}
//static
void LLIncomingCallDialog::onAccept(void* user_data)
{
LLIncomingCallDialog* self = (LLIncomingCallDialog*)user_data;
processCallResponse(0, self->mPayload);
self->closeFloater();
}
//static
void LLIncomingCallDialog::onReject(void* user_data)
{
LLIncomingCallDialog* self = (LLIncomingCallDialog*)user_data;
processCallResponse(1, self->mPayload);
self->closeFloater();
}
//static
void LLIncomingCallDialog::onStartIM(void* user_data)
{
LLIncomingCallDialog* self = (LLIncomingCallDialog*)user_data;
processCallResponse(2, self->mPayload);
self->closeFloater();
}
// static
void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload)
{
if (!gIMMgr || gDisconnected)
return;
LLUUID session_id = payload["session_id"].asUUID();
LLUUID caller_id = payload["caller_id"].asUUID();
std::string session_name = payload["session_name"].asString();
EInstantMessage type = (EInstantMessage)payload["type"].asInteger();
LLIMMgr::EInvitationType inv_type = (LLIMMgr::EInvitationType)payload["inv_type"].asInteger();
bool voice = true;
switch(response)
{
case 2: // start IM: just don't start the voice chat
{
voice = false;
/* FALLTHROUGH */
}
case 0: // accept
{
if (type == IM_SESSION_P2P_INVITE)
{
// create a normal IM session
session_id = gIMMgr->addP2PSession(
session_name,
caller_id,
payload["session_handle"].asString(),
payload["session_uri"].asString());
if (voice)
{
gIMMgr->startCall(session_id, LLVoiceChannel::INCOMING_CALL);
}
else
{
LLAvatarActions::startIM(caller_id);
}
gIMMgr->clearPendingAgentListUpdates(session_id);
gIMMgr->clearPendingInvitation(session_id);
}
else
{
//session name should not be empty, but it can contain spaces so we don't trim
std::string correct_session_name = session_name;
if (session_name.empty())
{
LL_WARNS() << "Received an empty session name from a server" << LL_ENDL;
switch(type){
case IM_SESSION_CONFERENCE_START:
case IM_SESSION_GROUP_START:
case IM_SESSION_INVITE:
if (gAgent.isInGroup(session_id, TRUE))
{
LLGroupData data;
if (!gAgent.getGroupData(session_id, data)) break;
correct_session_name = data.mName;
}
else
{
// *NOTE: really should be using callbacks here
LLAvatarName av_name;
if (LLAvatarNameCache::get(caller_id, &av_name))
{
correct_session_name = av_name.getCompleteName();
correct_session_name.append(ADHOC_NAME_SUFFIX);
}
}
LL_INFOS("IMVIEW") << "Corrected session name is " << correct_session_name << LL_ENDL;
break;
default:
LL_WARNS("IMVIEW") << "Received an empty session name from a server and failed to generate a new proper session name" << LL_ENDL;
break;
}
}
gIMMgr->addSession(correct_session_name, type, session_id, true);
std::string url = gAgent.getRegion()->getCapability(
"ChatSessionRequest");
if (voice)
{
LLCoros::instance().launch("chatterBoxInvitationCoro",
boost::bind(&chatterBoxInvitationCoro, url,
session_id, inv_type));
// send notification message to the corresponding chat
if (payload["notify_box_type"].asString() == "VoiceInviteGroup" || payload["notify_box_type"].asString() == "VoiceInviteAdHoc")
{
LLStringUtil::format_map_t string_args;
string_args["[NAME]"] = payload["caller_name"].asString();
std::string message = LLTrans::getString("name_started_call", string_args);
LLIMModel::getInstance()->addMessageSilently(session_id, SYSTEM_FROM, LLUUID::null, message);
}
}
}
if (voice)
{
break;
}
}
case 1: // decline
{
if (type == IM_SESSION_P2P_INVITE)
{
if(LLVoiceClient::getInstance())
{
std::string s = payload["session_handle"].asString();
LLVoiceClient::getInstance()->declineInvite(s);
}
}
else
{
std::string url = gAgent.getRegion()->getCapability(
"ChatSessionRequest");
LLSD data;
data["method"] = "decline invitation";
data["session-id"] = session_id;
LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, data,
"Invitation declined",
"Invitation decline failed.");
}
}
gIMMgr->clearPendingAgentListUpdates(session_id);
gIMMgr->clearPendingInvitation(session_id);
}
}
bool inviteUserResponse(const LLSD& notification, const LLSD& response)
{
if (!gIMMgr || gDisconnected)
return false;
const LLSD& payload = notification["payload"];
LLUUID session_id = payload["session_id"].asUUID();
EInstantMessage type = (EInstantMessage)payload["type"].asInteger();
LLIMMgr::EInvitationType inv_type = (LLIMMgr::EInvitationType)payload["inv_type"].asInteger();
S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
switch(option)
{
case 0: // accept
{
if (type == IM_SESSION_P2P_INVITE)
{
// create a normal IM session
session_id = gIMMgr->addP2PSession(
payload["session_name"].asString(),
payload["caller_id"].asUUID(),
payload["session_handle"].asString(),
payload["session_uri"].asString());
gIMMgr->startCall(session_id);
gIMMgr->clearPendingAgentListUpdates(session_id);
gIMMgr->clearPendingInvitation(session_id);
}
else
{
gIMMgr->addSession(
payload["session_name"].asString(),
type,
session_id, true);
std::string url = gAgent.getRegion()->getCapability(
"ChatSessionRequest");
LLCoros::instance().launch("chatterBoxInvitationCoro",
boost::bind(&chatterBoxInvitationCoro, url,
session_id, inv_type));
}
}
break;
case 2: // mute (also implies ignore, so this falls through to the "ignore" case below)
{
// mute the sender of this invite
if (!LLMuteList::getInstance()->isMuted(payload["caller_id"].asUUID()))
{
LLMute mute(payload["caller_id"].asUUID(), payload["caller_name"].asString(), LLMute::AGENT);
LLMuteList::getInstance()->add(mute);
}
}
/* FALLTHROUGH */
case 1: // decline
{
if (type == IM_SESSION_P2P_INVITE)
{
std::string s = payload["session_handle"].asString();
LLVoiceClient::getInstance()->declineInvite(s);
}
else
{
std::string url = gAgent.getRegion()->getCapability(
"ChatSessionRequest");
LLSD data;
data["method"] = "decline invitation";
data["session-id"] = session_id;
LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, data,
"Invitation declined.",
"Invitation decline failed.");
}
}
gIMMgr->clearPendingAgentListUpdates(session_id);
gIMMgr->clearPendingInvitation(session_id);
break;
}
return false;
}
//
// Member Functions
//
LLIMMgr::LLIMMgr()
{
mPendingInvitations = LLSD::emptyMap();
mPendingAgentListUpdates = LLSD::emptyMap();
// <FS:Ansariel> [FS communication UI]
//LLIMModel::getInstance()->addNewMsgCallback(boost::bind(&LLFloaterIMSession::sRemoveTypingIndicator, _1));
LLIMModel::getInstance()->addNewMsgCallback(boost::bind(&FSFloaterIM::sRemoveTypingIndicator, _1));
// </FS:Ansariel> [FS communication UI]
gSavedPerAccountSettings.declareBOOL("FetchGroupChatHistory", TRUE, "Fetch recent messages from group chat servers when a group window opens", LLControlVariable::PERSIST_ALWAYS);
}
// Add a message to a session.
void LLIMMgr::addMessage(
const LLUUID& session_id,
const LLUUID& target_id,
const std::string& from,
const std::string& msg,
bool is_offline_msg,
const std::string& session_name,
EInstantMessage dialog,
U32 parent_estate_id,
const LLUUID& region_id,
const LLVector3& position,
bool is_region_msg,
U32 timestamp, // May be zero
bool is_announcement, // <FS:Ansariel> Special parameter indicating announcements
bool keyword_alert_performed) // <FS:Ansariel> Pass info if keyword alert has been performed
{
LLUUID other_participant_id = target_id;
LLUUID new_session_id = session_id;
if (new_session_id.isNull())
{
//no session ID...compute new one
new_session_id = computeSessionID(dialog, other_participant_id);
}
//*NOTE session_name is empty in case of incoming P2P sessions
std::string fixed_session_name = from;
bool name_is_setted = false;
if(!session_name.empty() && session_name.size()>1)
{
fixed_session_name = session_name;
name_is_setted = true;
}
bool skip_message = false;
bool from_linden = LLMuteList::isLinden(from);
// <FS:Ansariel> FIRE-14564: VoiceCallFriendsOnly prevents receiving of
//if (gSavedPerAccountSettings.getBOOL("VoiceCallsFriendsOnly") && !from_linden)
if (gSavedPerAccountSettings.getBOOL("VoiceCallsFriendsOnly") && !from_linden &&
(dialog == IM_NOTHING_SPECIAL || (dialog == IM_SESSION_INVITE && !gAgent.isInGroup(new_session_id))) )
// </FS:Ansariel>
{
// Evaluate if we need to skip this message when that setting is true (default is false)
skip_message = (LLAvatarTracker::instance().getBuddyInfo(other_participant_id) == NULL); // Skip non friends...
skip_message &= !(other_participant_id == gAgentID); // You are your best friend... Don't skip yourself
}
bool new_session = !hasSession(new_session_id);
// <FS:PP> Configurable IM sounds
static LLCachedControl<U32> PlayModeUISndNewIncomingIMSession(gSavedSettings, "PlayModeUISndNewIncomingIMSession");
static LLCachedControl<U32> PlayModeUISndNewIncomingGroupIMSession(gSavedSettings, "PlayModeUISndNewIncomingGroupIMSession");
static LLCachedControl<U32> PlayModeUISndNewIncomingConfIMSession(gSavedSettings, "PlayModeUISndNewIncomingConfIMSession");
bool do_not_disturb = gAgent.isDoNotDisturb();
bool is_group_chat = false;
if (dialog != IM_NOTHING_SPECIAL)
{
is_group_chat = gAgent.isInGroup(new_session_id);
}
// </FS:PP> Configurable IM sounds
if (new_session)
{
// Group chat session was initiated by muted resident, do not start this session viewerside
// do not send leave msg either, so we are able to get group messages from other participants
if ((IM_SESSION_INVITE == dialog) && gAgent.isInGroup(new_session_id) &&
LLMuteList::getInstance()->isMuted(other_participant_id, LLMute::flagTextChat) && !from_linden)
{
return;
}
LLAvatarName av_name;
if (LLAvatarNameCache::get(other_participant_id, &av_name) && !name_is_setted)
{
fixed_session_name = av_name.getDisplayName();
}
// <FS:Ansariel> Clear muted group chat early to prevent contacts floater
// (re-)gaining focus; the server already knows the correct
// session id, so we can leave it!
if (is_group_chat && !exoGroupMuteList::instance().isLoaded())
{
LL_INFOS() << "Received group chat from " << fixed_session_name << " (" << new_session_id.asString() << ") before mute list has been loaded - skipping message" << LL_ENDL;
exoGroupMuteList::instance().addDeferredGroupChat(new_session_id);
return;
}
if (exoGroupMuteList::instance().isMuted(new_session_id))
{
LL_INFOS() << "Muting group chat from " << new_session_id.asString() << ": " << fixed_session_name << LL_ENDL;
if (gSavedSettings.getBOOL("FSReportMutedGroupChat"))
{
LLStringUtil::format_map_t args;
args["NAME"] = LLSLURL("group", new_session_id, "about").getSLURLString();
report_to_nearby_chat(LLTrans::getString("GroupChatMuteNotice", args));
}
clearPendingInvitation(new_session_id);
clearPendingAgentListUpdates(new_session_id);
LLIMModel::getInstance()->sendLeaveSession(new_session_id, other_participant_id);
return;
}
// </FS:Ansariel>
// <FS:Ansariel> FIRE-13613: First group IM received that was initiated by a muted
// resident leads to leaving the group chat session
if (IM_NOTHING_SPECIAL != dialog && IM_SESSION_P2P_INVITE != dialog &&
gAgent.isInGroup(new_session_id) && LLMuteList::getInstance()->isMuted(other_participant_id) && !from_linden)
{
LL_INFOS() << "Ignoring group chat from " << fixed_session_name << " (" << new_session_id.asString() << ") initiated by muted resident." << LL_ENDL;
exoGroupMuteList::instance().addDeferredGroupChat(new_session_id);
return;
}
// </FS:Ansariel>
LLIMModel::getInstance()->newSession(new_session_id, fixed_session_name, dialog, other_participant_id, false, is_offline_msg);
LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(new_session_id);
if (session)
{
skip_message &= !session->isGroupSessionType(); // Do not skip group chats...
if (skip_message)
{
gIMMgr->leaveSession(new_session_id);
}
// When we get a new IM, and if you are a god, display a bit
// of information about the source. This is to help liaisons
// when answering questions.
if (gAgent.isGodlike())
{
// *TODO:translate (low priority, god ability)
std::ostringstream bonus_info;
bonus_info << LLTrans::getString("***") + " " + LLTrans::getString("IMParentEstate") + ":" + " "
<< parent_estate_id
<< ((parent_estate_id == 1) ? "," + LLTrans::getString("IMMainland") : "")
<< ((parent_estate_id == 5) ? "," + LLTrans::getString("IMTeen") : "");
// once we have web-services (or something) which returns
// information about a region id, we can print this out
// and even have it link to map-teleport or something.
//<< "*** region_id: " << region_id << std::endl
//<< "*** position: " << position << std::endl;
LLIMModel::instance().addMessage(new_session_id, from, other_participant_id, bonus_info.str(), true, is_region_msg);
}
// Logically it would make more sense to reject the session sooner, in another area of the
// code, but the session has to be established inside the server before it can be left.
if (LLMuteList::getInstance()->isMuted(other_participant_id, LLMute::flagTextChat) && !from_linden)
{
LL_WARNS() << "Leaving IM session from initiating muted resident " << from << LL_ENDL;
if (!gIMMgr->leaveSession(new_session_id))
{
LL_INFOS("IMVIEW") << "Session " << new_session_id << " does not exist." << LL_ENDL;
}
return;
}
// Fetch group chat history, enabled by default.
if (gSavedPerAccountSettings.getBOOL("FetchGroupChatHistory"))
{
std::string chat_url = gAgent.getRegion()->getCapability("ChatSessionRequest");
LLCoros::instance().launch("chatterBoxHistoryCoro",
boost::bind(&chatterBoxHistoryCoro, chat_url, session_id, from, msg, timestamp));
}
// <FS:PP> Configurable IM sounds
// //Play sound for new conversations
// if (!skip_message & !gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNewConversation") == TRUE))
// <FS:PP> Option to automatically ignore and leave all conference (ad-hoc) chats
static LLCachedControl<bool> ignoreAdHocSessions(gSavedSettings, "FSIgnoreAdHocSessions");
if (dialog != IM_NOTHING_SPECIAL && !is_group_chat && ignoreAdHocSessions && !from_linden)
{
static LLCachedControl<bool> dontIgnoreAdHocFromFriends(gSavedSettings, "FSDontIgnoreAdHocFromFriends");
if (!dontIgnoreAdHocFromFriends || (dontIgnoreAdHocFromFriends && LLAvatarTracker::instance().getBuddyInfo(other_participant_id) == NULL))
{
static LLCachedControl<bool> reportIgnoredAdHocSession(gSavedSettings, "FSReportIgnoredAdHocSession");
//<FS:Beq> [FIRE-21385] Add inviter name/UUID to ad-hoc ignored messages
LLSD args;
args["AVATAR_NAME"] = LLSLURL("agent", other_participant_id, "about").getSLURLString();
// LL_INFOS() << "Ignoring conference (ad-hoc) chat from " << new_session_id.asString() << LL_ENDL;
LL_INFOS() << "Ignoring conference (ad-hoc) chat from " << args["AVATAR_NAME"] << LL_ENDL;
if (!gIMMgr->leaveSession(new_session_id))
{
LL_WARNS() << "Ad-hoc session " << new_session_id.asString() << " does not exist." << LL_ENDL;
}
else if (reportIgnoredAdHocSession)
{
// report_to_nearby_chat(LLTrans::getString("IgnoredAdHocSession"));
report_to_nearby_chat(LLTrans::getString("IgnoredAdHocSession", args));
}
//</FS:Beq>
return;
}
}
// </FS:PP>
if(!do_not_disturb && PlayModeUISndNewIncomingIMSession != 0 && dialog == IM_NOTHING_SPECIAL)
{
make_ui_sound("UISndNewIncomingIMSession");
}
else if(!do_not_disturb && PlayModeUISndNewIncomingGroupIMSession != 0 && dialog != IM_NOTHING_SPECIAL && is_group_chat)
{
make_ui_sound("UISndNewIncomingGroupIMSession");
}
else if(!do_not_disturb && PlayModeUISndNewIncomingConfIMSession != 0 && dialog != IM_NOTHING_SPECIAL && !is_group_chat)
{
make_ui_sound("UISndNewIncomingConfIMSession");
}
}
else
{
// Failed to create a session, most likely due to empty name (name cache failed?)
LL_WARNS() << "Failed to create IM session " << fixed_session_name << LL_ENDL;
}
}
// <FS:Zi> FIRE-30424 - Incoming Group message sound plays for muted posters
else if(LLMuteList::getInstance()->isMuted(other_participant_id, LLMute::flagTextChat) && !from_linden)
{
// if this message was from a muted resident, there is no point in playing any sounds or
// doing anything else in this function, so return right here
return;
}
// </FS:Zi>
else if(!do_not_disturb && PlayModeUISndNewIncomingIMSession == 2 && dialog == IM_NOTHING_SPECIAL)
{
make_ui_sound("UISndNewIncomingIMSession");
}
else if(!do_not_disturb && PlayModeUISndNewIncomingGroupIMSession == 2 && dialog != IM_NOTHING_SPECIAL && is_group_chat)
{
make_ui_sound("UISndNewIncomingGroupIMSession");
}
else if(!do_not_disturb && PlayModeUISndNewIncomingConfIMSession == 2 && dialog != IM_NOTHING_SPECIAL && !is_group_chat)
{
make_ui_sound("UISndNewIncomingConfIMSession");
// </FS:PP>
}
// <FS:WoLf> IM Sounds only for sessions not in focus
else if(!do_not_disturb && PlayModeUISndNewIncomingIMSession == 3 && dialog == IM_NOTHING_SPECIAL)
{
// <FS:Ansariel> [FS communication UI]
//LLIMFloater* im_floater = LLIMFloater::findInstance(session_id);
FSFloaterIM* im_floater = FSFloaterIM::findInstance(session_id);
// </FS:Ansariel> [FS communication UI]
if (im_floater && !im_floater->hasFocus())
{
make_ui_sound("UISndNewIncomingIMSession");
}
}
else if(!do_not_disturb && PlayModeUISndNewIncomingGroupIMSession == 3 && dialog != IM_NOTHING_SPECIAL && is_group_chat)
{
// <FS:Ansariel> [FS communication UI]
//LLIMFloater* im_floater = LLIMFloater::findInstance(session_id);
FSFloaterIM* im_floater = FSFloaterIM::findInstance(session_id);
// </FS:Ansariel> [FS communication UI]
if (im_floater && !im_floater->hasFocus())
{
make_ui_sound("UISndNewIncomingGroupIMSession");
}
}
else if(!do_not_disturb && PlayModeUISndNewIncomingConfIMSession == 3 && dialog != IM_NOTHING_SPECIAL && !is_group_chat)
{
// <FS:Ansariel> [FS communication UI]
//LLIMFloater* im_floater = LLIMFloater::findInstance(session_id);
FSFloaterIM* im_floater = FSFloaterIM::findInstance(session_id);
// </FS:Ansariel> [FS communication UI]
if (im_floater && !im_floater->hasFocus())
{
make_ui_sound("UISndNewIncomingConfIMSession");
}
}
// </FS:WoLf>
if (!LLMuteList::getInstance()->isMuted(other_participant_id, LLMute::flagTextChat) && !skip_message)
{
// <FS:Ansariel> Added is_announcement parameter
//LLIMModel::instance().addMessage(new_session_id, from, other_participant_id, msg, true, is_region_msg, timestamp);
LLIMModel::instance().addMessage(new_session_id, from, other_participant_id, msg, true, is_region_msg, timestamp, is_announcement, keyword_alert_performed);
}
// Open conversation floater if offline messages are present
// <FS:CR> Only open it when the user opts to do so...
//if (is_offline_msg && !skip_message)
if (is_offline_msg && gSavedSettings.getBOOL("FSOpenIMContainerOnOfflineMessage"))
{
// LLFloaterReg::showInstance("im_container");
// LLFloaterReg::getTypedInstance<LLFloaterIMContainer>("im_container")->
// flashConversationItemWidget(new_session_id, true);
LLFloaterReg::showInstance("fs_im_container");
// </FS:CR>
}
}
// <FS:Ansariel> FIRE-15138: Sharing inventory item sometimes doesn't obey UseLegacyIMLogNames setting
void add_system_message_name_cb(LLAvatarName avatar_name, const std::string& message)
{
std::string session_name = avatar_name.getLegacyName();
// <FS:Ansariel> [Legacy IM logfile names]
if (gSavedSettings.getBOOL("UseLegacyIMLogNames"))
{
session_name = session_name.substr(0, session_name.find(" Resident"));;
}
else
{
session_name = LLCacheName::buildUsername(session_name);
}
// </FS:Ansariel> [Legacy IM logfile names]
LLIMModel::instance().logToFile(session_name, SYSTEM_FROM, LLUUID::null, message);
}
// </FS:Ansariel>
void LLIMMgr::addSystemMessage(const LLUUID& session_id, const std::string& message_name, const LLSD& args)
{
LLUIString message;
// null session id means near me (chat history)
if (session_id.isNull())
{
message = LLTrans::getString(message_name);
message.setArgs(args);
LLChat chat(message);
chat.mSourceType = CHAT_SOURCE_SYSTEM;
// <FS:Ansariel> [FS communication UI]
//LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance<LLFloaterIMNearbyChat>("nearby_chat");
FSFloaterNearbyChat* nearby_chat = FSFloaterNearbyChat::getInstance();
// </FS:Ansariel> [FS communication UI]
if (nearby_chat)
{
nearby_chat->addMessage(chat);
}
}
else // going to IM session
{
message = LLTrans::getString(message_name + "-im");
message.setArgs(args);
if (hasSession(session_id))
{
gIMMgr->addMessage(session_id, LLUUID::null, SYSTEM_FROM, message.getString());
}
// log message to file
else
{
// <FS:Ansariel> FIRE-15138: Sharing inventory item sometimes doesn't obey UseLegacyIMLogNames setting
//LLAvatarName av_name;
//// since we select user to share item with - his name is already in cache
//LLAvatarNameCache::get(args["user_id"], &av_name);
//std::string session_name = LLCacheName::buildUsername(av_name.getUserName());
//LLIMModel::instance().logToFile(session_name, SYSTEM_FROM, LLUUID::null, message.getString());
LLAvatarNameCache::get(args["user_id"].asUUID(), boost::bind(&add_system_message_name_cb, _2, message.getString()));
// </FS:Ansariel>
}
}
}
S32 LLIMMgr::getNumberOfUnreadIM()
{
std::map<LLUUID, LLIMModel::LLIMSession*>::iterator it;
S32 num = 0;
for(it = LLIMModel::getInstance()->mId2SessionMap.begin(); it != LLIMModel::getInstance()->mId2SessionMap.end(); ++it)
{
num += (*it).second->mNumUnread;
}
return num;
}
S32 LLIMMgr::getNumberOfUnreadParticipantMessages()
{
std::map<LLUUID, LLIMModel::LLIMSession*>::iterator it;
S32 num = 0;
for(it = LLIMModel::getInstance()->mId2SessionMap.begin(); it != LLIMModel::getInstance()->mId2SessionMap.end(); ++it)
{
num += (*it).second->mParticipantUnreadMessageCount;
}
return num;
}
void LLIMMgr::autoStartCallOnStartup(const LLUUID& session_id)
{
LLIMModel::LLIMSession *session = LLIMModel::getInstance()->findIMSession(session_id);
if (!session) return;
if (session->mSessionInitialized)
{
startCall(session_id);
}
else
{
session->mStartCallOnInitialize = true;
}
}
LLUUID LLIMMgr::addP2PSession(const std::string& name,
const LLUUID& other_participant_id,
const std::string& voice_session_handle,
const std::string& caller_uri)
{
LLUUID session_id = addSession(name, IM_NOTHING_SPECIAL, other_participant_id, true);
LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(session_id);
if (speaker_mgr)
{
LLVoiceChannelP2P* voice_channel = dynamic_cast<LLVoiceChannelP2P*>(speaker_mgr->getVoiceChannel());
if (voice_channel)
{
voice_channel->setSessionHandle(voice_session_handle, caller_uri);
}
}
return session_id;
}
// This adds a session to the talk view. The name is the local name of
// the session, dialog specifies the type of session. If the session
// exists, it is brought forward. Specifying id = NULL results in an
// im session to everyone. Returns the uuid of the session.
LLUUID LLIMMgr::addSession(
const std::string& name,
EInstantMessage dialog,
const LLUUID& other_participant_id, bool voice)
{
std::vector<LLUUID> ids;
ids.push_back(other_participant_id);
LLUUID session_id = addSession(name, dialog, other_participant_id, ids, voice);
return session_id;
}
// Adds a session using the given session_id. If the session already exists
// the dialog type is assumed correct. Returns the uuid of the session.
LLUUID LLIMMgr::addSession(
const std::string& name,
EInstantMessage dialog,
const LLUUID& other_participant_id,
const std::vector<LLUUID>& ids, bool voice,
const LLUUID& floater_id)
{
if (ids.empty())
{
return LLUUID::null;
}
if (name.empty())
{
LL_WARNS() << "Session name cannot be null!" << LL_ENDL;
return LLUUID::null;
}
LLUUID session_id = computeSessionID(dialog,other_participant_id);
if (floater_id.notNull())
{
// <FS:CR> [FS communications UI]
// LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(floater_id);
FSFloaterIM* im_floater = FSFloaterIM::findInstance(session_id);
// </FS:CR>
if (im_floater)
{
// The IM floater should be initialized with a new session_id
// so that it is found by that id when creating a chiclet in LLFloaterIMSession::onIMChicletCreated,
// and a new floater is not created.
im_floater->initIMSession(session_id);
im_floater->reloadMessages();
}
}
bool new_session = (LLIMModel::getInstance()->findIMSession(session_id) == NULL);
//works only for outgoing ad-hoc sessions
if (new_session && IM_SESSION_CONFERENCE_START == dialog && ids.size())
{
LLIMModel::LLIMSession* ad_hoc_found = LLIMModel::getInstance()->findAdHocIMSession(ids);
if (ad_hoc_found)
{
new_session = false;
session_id = ad_hoc_found->mSessionID;
}
}
//Notify observers that a session was added
if (new_session)
{
LLIMModel::getInstance()->newSession(session_id, name, dialog, other_participant_id, ids, voice);
}
//Notifies observers that the session was already added
else
{
std::string session_name = LLIMModel::getInstance()->getName(session_id);
LLIMMgr::getInstance()->notifyObserverSessionActivated(session_id, session_name, other_participant_id);
}
//we don't need to show notes about online/offline, mute/unmute users' statuses for existing sessions
if (!new_session) return session_id;
LL_INFOS("IMVIEW") << "LLIMMgr::addSession, new session added, name = " << name << ", session id = " << session_id << LL_ENDL;
//Per Plan's suggestion commented "explicit offline status warning" out to make Dessie happier (see EXT-3609)
//*TODO After February 2010 remove this commented out line if no one will be missing that warning
//noteOfflineUsers(session_id, floater, ids);
// Only warn for regular IMs - not group IMs
if( dialog == IM_NOTHING_SPECIAL )
{
noteMutedUsers(session_id, ids);
}
notifyObserverSessionVoiceOrIMStarted(session_id);
return session_id;
}
bool LLIMMgr::leaveSession(const LLUUID& session_id)
{
LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(session_id);
if (!im_session) return false;
// [SL:KB] - Patch: Chat-GroupSnooze | Checked: 2012-06-16 (Catznip-3.3)
// Only group sessions can be snoozed
if ( (im_session->isGroupSessionType()) && (LLIMModel::LLIMSession::CLOSE_SNOOZE == im_session->mCloseAction) )
{
static LLCachedControl<S32> s_nSnoozeTime(gSavedSettings, "GroupSnoozeTime", 900);
snoozed_sessions_t::iterator itSession = mSnoozedSessions.find(session_id);
F64 expirationTime = LLTimer::getTotalSeconds() + F64(s_nSnoozeTime);
if (im_session->mSnoozeTime > -1)
{
expirationTime = LLTimer::getTotalSeconds() + F64(im_session->mSnoozeTime);
im_session->mSnoozeTime = -1;
}
if (mSnoozedSessions.end() != itSession)
itSession->second = expirationTime;
else
mSnoozedSessions.insert(std::pair<LLUUID, F64>(session_id, expirationTime));
}
else
{
LLIMModel::getInstance()->sendLeaveSession(session_id, im_session->mOtherParticipantID);
}
// [/SL:KB]
// LLIMModel::getInstance()->sendLeaveSession(session_id, im_session->mOtherParticipantID);
gIMMgr->removeSession(session_id);
return true;
}
// Removes data associated with a particular session specified by session_id
void LLIMMgr::removeSession(const LLUUID& session_id)
{
llassert_always(hasSession(session_id));
clearPendingInvitation(session_id);
clearPendingAgentListUpdates(session_id);
LLIMModel::getInstance()->clearSession(session_id);
LL_INFOS("IMVIEW") << "LLIMMgr::removeSession, session removed, session id = " << session_id << LL_ENDL;
notifyObserverSessionRemoved(session_id);
}
void LLIMMgr::inviteToSession(
const LLUUID& session_id,
const std::string& session_name,
const LLUUID& caller_id,
const std::string& caller_name,
EInstantMessage type,
EInvitationType inv_type,
const std::string& session_handle,
const std::string& session_uri)
{
std::string notify_box_type;
// voice invite question is different from default only for group call (EXT-7118)
std::string question_type = "VoiceInviteQuestionDefault";
BOOL voice_invite = FALSE;
bool is_linden = LLMuteList::isLinden(caller_name);
if(type == IM_SESSION_P2P_INVITE)
{
//P2P is different...they only have voice invitations
notify_box_type = "VoiceInviteP2P";
voice_invite = TRUE;
}
else if ( gAgent.isInGroup(session_id, TRUE) )
{
//only really old school groups have voice invitations
notify_box_type = "VoiceInviteGroup";
question_type = "VoiceInviteQuestionGroup";
voice_invite = TRUE;
}
else if ( inv_type == INVITATION_TYPE_VOICE )
{
//else it's an ad-hoc
//and a voice ad-hoc
notify_box_type = "VoiceInviteAdHoc";
voice_invite = TRUE;
}
else if ( inv_type == INVITATION_TYPE_IMMEDIATE )
{
notify_box_type = "InviteAdHoc";
}
LLSD payload;
payload["session_id"] = session_id;
payload["session_name"] = session_name;
payload["caller_id"] = caller_id;
payload["caller_name"] = caller_name;
payload["type"] = type;
payload["inv_type"] = inv_type;
payload["session_handle"] = session_handle;
payload["session_uri"] = session_uri;
payload["notify_box_type"] = notify_box_type;
payload["question_type"] = question_type;
//ignore invites from muted residents
if (!is_linden)
{
if (LLMuteList::getInstance()->isMuted(caller_id, LLMute::flagVoiceChat)
&& voice_invite && "VoiceInviteQuestionDefault" == question_type)
{
LL_INFOS("IMVIEW") << "Rejecting voice call from initiating muted resident " << caller_name << LL_ENDL;
LLIncomingCallDialog::processCallResponse(1, payload);
return;
}
else if (LLMuteList::getInstance()->isMuted(caller_id, LLMute::flagAll & ~LLMute::flagVoiceChat) && !voice_invite)
{
LL_INFOS("IMVIEW") << "Rejecting session invite from initiating muted resident " << caller_name << LL_ENDL;
return;
}
}
LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(session_id);
if (channelp && channelp->callStarted())
{
// you have already started a call to the other user, so just accept the invite
LLIncomingCallDialog::processCallResponse(0, payload);
return;
}
if (voice_invite)
{
bool isRejectGroupCall = (gSavedSettings.getBOOL("VoiceCallsRejectGroup") && (notify_box_type == "VoiceInviteGroup"));
bool isRejectNonFriendCall = (gSavedPerAccountSettings.getBOOL("VoiceCallsFriendsOnly") && (LLAvatarTracker::instance().getBuddyInfo(caller_id) == NULL));
// <FS:PP> FIRE-6522: Options to automatically decline all group and personal voice chat requests
// if (isRejectGroupCall || isRejectNonFriendCall || gAgent.isDoNotDisturb())
bool isRejectAdHocCall = (gSavedSettings.getBOOL("VoiceCallsRejectAdHoc") && (notify_box_type == "VoiceInviteAdHoc"));
bool isRejectP2PCall = (gSavedSettings.getBOOL("VoiceCallsRejectP2P") && (notify_box_type == "VoiceInviteP2P"));
if (isRejectGroupCall || isRejectNonFriendCall || gAgent.isDoNotDisturb() || isRejectAdHocCall || isRejectP2PCall)
// </FS:PP>
{
// <FS:PP> FIRE-6522
// if (gAgent.isDoNotDisturb() && !isRejectGroupCall && !isRejectNonFriendCall)
if (gAgent.isDoNotDisturb() && !isRejectGroupCall && !isRejectNonFriendCall && !isRejectAdHocCall && !isRejectP2PCall)
// </FS:PP>
{
if (!hasSession(session_id) && (type == IM_SESSION_P2P_INVITE))
{
std::string fixed_session_name = caller_name;
if(!session_name.empty() && session_name.size()>1)
{
fixed_session_name = session_name;
}
else
{
LLAvatarName av_name;
if (LLAvatarNameCache::get(caller_id, &av_name))
{
fixed_session_name = av_name.getDisplayName();
}
}
LLIMModel::getInstance()->newSession(session_id, fixed_session_name, IM_NOTHING_SPECIAL, caller_id, false, false);
}
LLSD args;
addSystemMessage(session_id, "you_auto_rejected_call", args);
send_do_not_disturb_message(gMessageSystem, caller_id, session_id);
}
// silently decline the call
LLIncomingCallDialog::processCallResponse(1, payload);
return;
}
}
if ( !mPendingInvitations.has(session_id.asString()) )
{
if (caller_name.empty())
{
LLAvatarNameCache::get(caller_id,
boost::bind(&LLIMMgr::onInviteNameLookup, payload, _1, _2));
}
else
{
LLFloaterReg::showInstance("incoming_call", payload, FALSE);
}
// Add the caller to the Recent List here (at this point
// "incoming_call" floater is shown and the recipient can
// reject the call), because even if a recipient will reject
// the call, the caller should be added to the recent list
// anyway. STORM-507.
if(type == IM_SESSION_P2P_INVITE)
LLRecentPeople::instance().add(caller_id);
mPendingInvitations[session_id.asString()] = LLSD();
}
}
void LLIMMgr::onInviteNameLookup(LLSD payload, const LLUUID& id, const LLAvatarName& av_name)
{
payload["caller_name"] = av_name.getUserName();
payload["session_name"] = payload["caller_name"].asString();
std::string notify_box_type = payload["notify_box_type"].asString();
LLFloaterReg::showInstance("incoming_call", payload, FALSE);
}
//*TODO disconnects all sessions
void LLIMMgr::disconnectAllSessions()
{
//*TODO disconnects all IM sessions
}
BOOL LLIMMgr::hasSession(const LLUUID& session_id)
{
return LLIMModel::getInstance()->findIMSession(session_id) != NULL;
}
// [SL:KB] - Patch: Chat-GroupSnooze | Checked: 2012-06-16 (Catznip-3.3)
bool LLIMMgr::checkSnoozeExpiration(const LLUUID& session_id) const
{
snoozed_sessions_t::const_iterator itSession = mSnoozedSessions.find(session_id);
return (mSnoozedSessions.end() != itSession) && (itSession->second <= LLTimer::getTotalSeconds());
}
bool LLIMMgr::isSnoozedSession(const LLUUID& session_id) const
{
return (mSnoozedSessions.end() != mSnoozedSessions.find(session_id));
}
bool LLIMMgr::restoreSnoozedSession(const LLUUID& session_id)
{
snoozed_sessions_t::iterator itSession = mSnoozedSessions.find(session_id);
if (mSnoozedSessions.end() != itSession)
{
mSnoozedSessions.erase(itSession);
LLGroupData groupData;
if (gAgent.getGroupData(session_id, groupData))
{
gIMMgr->addSession(groupData.mName, IM_SESSION_INVITE, session_id);
uuid_vec_t ids;
LLIMModel::sendStartSession(session_id, session_id, ids, IM_SESSION_GROUP_START);
if (!gAgent.isDoNotDisturb() && gSavedSettings.getU32("PlayModeUISndNewIncomingGroupIMSession") != 0)
{
make_ui_sound("UISndNewIncomingGroupIMSession");
}
return true;
}
}
return false;
}
// [/SL:KB]
void LLIMMgr::clearPendingInvitation(const LLUUID& session_id)
{
if ( mPendingInvitations.has(session_id.asString()) )
{
mPendingInvitations.erase(session_id.asString());
}
}
void LLIMMgr::processAgentListUpdates(const LLUUID& session_id, const LLSD& body)
{
// <FS:Ansariel> [FS communication UI]
//LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id);
FSFloaterIM* im_floater = FSFloaterIM::findInstance(session_id);
// </FS:Ansariel> [FS communication UI]
if ( im_floater )
{
im_floater->processAgentListUpdates(body);
}
LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(session_id);
if (speaker_mgr)
{
speaker_mgr->updateSpeakers(body);
// also the same call is added into LLVoiceClient::participantUpdatedEvent because
// sometimes it is called AFTER LLViewerChatterBoxSessionAgentListUpdates::post()
// when moderation state changed too late. See EXT-3544.
speaker_mgr->update(true);
}
else
{
//we don't have a speaker manager yet..something went wrong
//we are probably receiving an update here before
//a start or an acceptance of an invitation. Race condition.
gIMMgr->addPendingAgentListUpdates(
session_id,
body);
}
}
LLSD LLIMMgr::getPendingAgentListUpdates(const LLUUID& session_id)
{
if ( mPendingAgentListUpdates.has(session_id.asString()) )
{
return mPendingAgentListUpdates[session_id.asString()];
}
else
{
return LLSD();
}
}
void LLIMMgr::addPendingAgentListUpdates(
const LLUUID& session_id,
const LLSD& updates)
{
LLSD::map_const_iterator iter;
if ( !mPendingAgentListUpdates.has(session_id.asString()) )
{
//this is a new agent list update for this session
mPendingAgentListUpdates[session_id.asString()] = LLSD::emptyMap();
}
if (
updates.has("agent_updates") &&
updates["agent_updates"].isMap() &&
updates.has("updates") &&
updates["updates"].isMap() )
{
//new school update
LLSD update_types = LLSD::emptyArray();
LLSD::array_iterator array_iter;
update_types.append("agent_updates");
update_types.append("updates");
for (
array_iter = update_types.beginArray();
array_iter != update_types.endArray();
++array_iter)
{
//we only want to include the last update for a given agent
for (
iter = updates[array_iter->asString()].beginMap();
iter != updates[array_iter->asString()].endMap();
++iter)
{
mPendingAgentListUpdates[session_id.asString()][array_iter->asString()][iter->first] =
iter->second;
}
}
}
else if (
updates.has("updates") &&
updates["updates"].isMap() )
{
//old school update where the SD contained just mappings
//of agent_id -> "LEAVE"/"ENTER"
//only want to keep last update for each agent
for (
iter = updates["updates"].beginMap();
iter != updates["updates"].endMap();
++iter)
{
mPendingAgentListUpdates[session_id.asString()]["updates"][iter->first] =
iter->second;
}
}
}
void LLIMMgr::clearPendingAgentListUpdates(const LLUUID& session_id)
{
if ( mPendingAgentListUpdates.has(session_id.asString()) )
{
mPendingAgentListUpdates.erase(session_id.asString());
}
}
void LLIMMgr::notifyObserverSessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id, bool has_offline_msg)
{
for (session_observers_list_t::iterator it = mSessionObservers.begin(); it != mSessionObservers.end(); it++)
{
(*it)->sessionAdded(session_id, name, other_participant_id, has_offline_msg);
}
}
void LLIMMgr::notifyObserverSessionActivated(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id)
{
for (session_observers_list_t::iterator it = mSessionObservers.begin(); it != mSessionObservers.end(); it++)
{
(*it)->sessionActivated(session_id, name, other_participant_id);
}
}
void LLIMMgr::notifyObserverSessionVoiceOrIMStarted(const LLUUID& session_id)
{
for (session_observers_list_t::iterator it = mSessionObservers.begin(); it != mSessionObservers.end(); it++)
{
(*it)->sessionVoiceOrIMStarted(session_id);
}
}
void LLIMMgr::notifyObserverSessionRemoved(const LLUUID& session_id)
{
for (session_observers_list_t::iterator it = mSessionObservers.begin(); it != mSessionObservers.end(); it++)
{
(*it)->sessionRemoved(session_id);
}
}
void LLIMMgr::notifyObserverSessionIDUpdated( const LLUUID& old_session_id, const LLUUID& new_session_id )
{
for (session_observers_list_t::iterator it = mSessionObservers.begin(); it != mSessionObservers.end(); it++)
{
(*it)->sessionIDUpdated(old_session_id, new_session_id);
}
}
void LLIMMgr::addSessionObserver(LLIMSessionObserver *observer)
{
mSessionObservers.push_back(observer);
}
void LLIMMgr::removeSessionObserver(LLIMSessionObserver *observer)
{
mSessionObservers.remove(observer);
}
bool LLIMMgr::startCall(const LLUUID& session_id, LLVoiceChannel::EDirection direction)
{
LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(session_id);
if (!voice_channel) return false;
voice_channel->setCallDirection(direction);
voice_channel->activate();
return true;
}
bool LLIMMgr::endCall(const LLUUID& session_id)
{
LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(session_id);
if (!voice_channel) return false;
voice_channel->deactivate();
LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(session_id);
if (im_session)
{
// need to update speakers' state
im_session->mSpeakers->update(FALSE);
}
return true;
}
bool LLIMMgr::isVoiceCall(const LLUUID& session_id)
{
LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(session_id);
if (!im_session) return false;
return im_session->mStartedAsIMCall;
}
void LLIMMgr::updateDNDMessageStatus()
{
if (LLIMModel::getInstance()->mId2SessionMap.empty()) return;
std::map<LLUUID, LLIMModel::LLIMSession*>::const_iterator it = LLIMModel::getInstance()->mId2SessionMap.begin();
for (; it != LLIMModel::getInstance()->mId2SessionMap.end(); ++it)
{
LLIMModel::LLIMSession* session = (*it).second;
if (session->isP2P())
{
setDNDMessageSent(session->mSessionID,false);
}
}
}
bool LLIMMgr::isDNDMessageSend(const LLUUID& session_id)
{
LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(session_id);
if (!im_session) return false;
return im_session->mIsDNDsend;
}
void LLIMMgr::setDNDMessageSent(const LLUUID& session_id, bool is_send)
{
LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(session_id);
if (!im_session) return;
im_session->mIsDNDsend = is_send;
}
void LLIMMgr::addNotifiedNonFriendSessionID(const LLUUID& session_id)
{
mNotifiedNonFriendSessions.insert(session_id);
}
bool LLIMMgr::isNonFriendSessionNotified(const LLUUID& session_id)
{
return mNotifiedNonFriendSessions.end() != mNotifiedNonFriendSessions.find(session_id);
}
void LLIMMgr::noteOfflineUsers(
const LLUUID& session_id,
const std::vector<LLUUID>& ids)
{
S32 count = ids.size();
if(count == 0)
{
const std::string& only_user = LLTrans::getString("only_user_message");
LLIMModel::getInstance()->addMessage(session_id, SYSTEM_FROM, LLUUID::null, only_user);
}
else
{
const LLRelationship* info = NULL;
LLAvatarTracker& at = LLAvatarTracker::instance();
LLIMModel& im_model = LLIMModel::instance();
for(S32 i = 0; i < count; ++i)
{
info = at.getBuddyInfo(ids.at(i));
LLAvatarName av_name;
if (info
&& !info->isOnline()
&& LLAvatarNameCache::get(ids.at(i), &av_name))
{
LLUIString offline = LLTrans::getString("offline_message");
// Use display name only because this user is your friend
// Ansariel: No please! Take preference settings into account!
if ((gSavedSettings.getBOOL("NameTagShowUsernames")) && (gSavedSettings.getBOOL("UseDisplayNames")))
{
offline.setArg("[NAME]", av_name.getCompleteName());
}
else if (gSavedSettings.getBOOL("UseDisplayNames"))
{
offline.setArg("[NAME]", av_name.getDisplayName());
}
else
{
offline.setArg("[NAME]", av_name.getUserNameForDisplay());
}
im_model.proccessOnlineOfflineNotification(session_id, offline);
}
}
}
}
void LLIMMgr::noteMutedUsers(const LLUUID& session_id,
const std::vector<LLUUID>& ids)
{
// Don't do this if we don't have a mute list.
LLMuteList *ml = LLMuteList::getInstance();
if( !ml )
{
return;
}
S32 count = ids.size();
if(count > 0)
{
LLIMModel* im_model = LLIMModel::getInstance();
for(S32 i = 0; i < count; ++i)
{
if( ml->isMuted(ids.at(i)) )
{
LLUIString muted = LLTrans::getString("muted_message");
im_model->addMessage(session_id, SYSTEM_FROM, LLUUID::null, muted);
break;
}
}
}
}
void LLIMMgr::processIMTypingStart(const LLUUID& from_id, const EInstantMessage im_type)
{
processIMTypingCore(from_id, im_type, TRUE);
}
void LLIMMgr::processIMTypingStop(const LLUUID& from_id, const EInstantMessage im_type)
{
processIMTypingCore(from_id, im_type, FALSE);
}
// <FS:Ansariel> Announce incoming IMs
void typingNameCallback(const LLUUID& av_id, const LLAvatarName& av_name, const LLUUID& session_id)
{
LLStringUtil::format_map_t args;
args["[NAME]"] = av_name.getCompleteName();
BOOL is_muted = LLMuteList::getInstance()->isMuted(av_id, av_name.getCompleteName(), LLMute::flagTextChat);
bool is_friend = (LLAvatarTracker::instance().getBuddyInfo(av_id) == NULL) ? false : true;
static LLCachedControl<bool> VoiceCallsFriendsOnly(gSavedPerAccountSettings, "VoiceCallsFriendsOnly");
if (!is_muted && ( (VoiceCallsFriendsOnly && is_friend) || !VoiceCallsFriendsOnly ))
{
gIMMgr->addMessage(
session_id,
av_id,
SYSTEM_FROM, // Use system name instead of NULL Growl notifier acts funny with NULL here.
LLTrans::getString("IM_announce_incoming", args),
false,
LLStringUtil::null,
IM_NOTHING_SPECIAL,
0,
LLUUID::null,
LLVector3::zero,
false,
0,
true
);
}
// Send busy and auto-response messages now or they won't be send
// later because a session has already been created by showing the
// incoming IM announcement.
// The logic was originally copied from process_improved_im() in llviewermessage.cpp
bool is_busy = gAgent.isDoNotDisturb();
BOOL is_autorespond = gAgent.getAutorespond();
BOOL is_autorespond_nonfriends = gAgent.getAutorespondNonFriends();
BOOL is_autorespond_muted = gSavedPerAccountSettings.getBOOL("FSSendMutedAvatarResponse");
BOOL is_linden = LLMuteList::getInstance()->isLinden(av_name.getAccountName());
static LLCachedControl<bool> FSSendAwayAvatarResponse(gSavedPerAccountSettings, "FSSendAwayAvatarResponse");
BOOL is_afk = gAgent.getAFK();
if (RlvActions::canReceiveIM(av_id) && !is_linden &&
(!VoiceCallsFriendsOnly || is_friend) &&
((is_busy && (!is_muted || (is_muted && !is_autorespond_muted))) ||
(is_autorespond && !is_muted) || (is_autorespond_nonfriends && !is_friend && !is_muted) || (FSSendAwayAvatarResponse && is_afk && !is_muted)) )
{
std::string my_name;
std::string response;
LLAgentUI::buildFullname(my_name);
if (is_busy)
{
response = gSavedPerAccountSettings.getString("DoNotDisturbModeResponse");
}
else if (is_autorespond_nonfriends && !is_friend)
{
response = gSavedPerAccountSettings.getString("FSAutorespondNonFriendsResponse");
}
else if (is_autorespond)
{
response = gSavedPerAccountSettings.getString("FSAutorespondModeResponse");
}
else if (is_afk && FSSendAwayAvatarResponse)
{
response = gSavedPerAccountSettings.getString("FSAwayAvatarResponse");
}
else
{
LL_WARNS() << "Unknown auto-response mode" << LL_ENDL;
}
pack_instant_message(
gMessageSystem,
gAgent.getID(),
FALSE,
gAgent.getSessionID(),
av_id,
my_name,
response,
IM_ONLINE,
IM_DO_NOT_DISTURB_AUTO_RESPONSE,
session_id);
gAgent.sendReliableMessage();
LLStringUtil::format_map_t args;
args["MESSAGE"] = response;
gIMMgr->addMessage(
session_id,
gAgentID,
LLStringUtil::null, // Pass null value so no name gets prepended
LLTrans::getString("IM_autoresponse_sent", args),
false,
LLStringUtil::null,
IM_NOTHING_SPECIAL,
0,
LLUUID::null,
LLVector3::zero,
false,
0,
true
);
// Send inventory item on autoresponse
LLUUID item_id(gSavedPerAccountSettings.getString("FSAutoresponseItemUUID"));
if (item_id.notNull())
{
LLInventoryItem* item = dynamic_cast<LLInventoryItem*>(gInventory.getItem(item_id));
if (item)
{
gIMMgr->addMessage(
session_id,
gAgentID,
LLStringUtil::null, // Pass null value so no name gets prepended
LLTrans::getString("IM_autoresponse_item_sent", LLSD().with("[ITEM_NAME]", item->getName())),
false,
LLStringUtil::null,
IM_NOTHING_SPECIAL,
0,
LLUUID::null,
LLVector3::zero,
false,
0,
true);
LLGiveInventory::doGiveInventoryItem(av_id, item, session_id);
}
}
}
}
// </FS:Ansariel>
void LLIMMgr::processIMTypingCore(const LLUUID& from_id, const EInstantMessage im_type, BOOL typing)
{
LLUUID session_id = computeSessionID(im_type, from_id);
// <FS:Ansariel> Announce incoming IMs
static LLCachedControl<bool> announceIncomingIM(gSavedSettings, "FSAnnounceIncomingIM");
if (typing && !gIMMgr->hasSession(session_id) && announceIncomingIM)
{
LLAvatarNameCache::get(from_id, boost::bind(&typingNameCallback, _1, _2, session_id));
}
// </FS:Ansariel>
// <FS:Ansariel> [FS communication UI]
//LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id);
FSFloaterIM* im_floater = FSFloaterIM::findInstance(session_id);
// </FS:Ansariel> [FS communication UI]
if ( im_floater )
{
im_floater->processIMTyping(from_id, typing);
}
}
class LLViewerChatterBoxSessionStartReply : public LLHTTPNode
{
public:
virtual void describe(Description& desc) const
{
desc.shortInfo("Used for receiving a reply to a request to initialize an ChatterBox session");
desc.postAPI();
desc.input(
"{\"client_session_id\": UUID, \"session_id\": UUID, \"success\" boolean, \"reason\": string");
desc.source(__FILE__, __LINE__);
}
virtual void post(ResponsePtr response,
const LLSD& context,
const LLSD& input) const
{
LLSD body;
LLUUID temp_session_id;
LLUUID session_id;
bool success;
body = input["body"];
success = body["success"].asBoolean();
temp_session_id = body["temp_session_id"].asUUID();
if ( success )
{
session_id = body["session_id"].asUUID();
LLIMModel::getInstance()->processSessionInitializedReply(temp_session_id, session_id);
LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(session_id);
if (speaker_mgr)
{
speaker_mgr->setSpeakers(body);
speaker_mgr->updateSpeakers(gIMMgr->getPendingAgentListUpdates(session_id));
}
// <FS:Ansariel> [FS communication UI]
//LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id);
FSFloaterIM* im_floater = FSFloaterIM::findInstance(session_id);
// </FS:Ansariel> [FS communication UI]
if ( im_floater )
{
if ( body.has("session_info") )
{
//im_floater->processSessionUpdate(body["session_info"]); // <FS:Ansariel> Method does nothing
// Send request for chat history, if enabled.
if (gSavedPerAccountSettings.getBOOL("FetchGroupChatHistory"))
{
std::string url = gAgent.getRegion()->getCapability("ChatSessionRequest");
LLCoros::instance().launch("chatterBoxHistoryCoro",
boost::bind(&chatterBoxHistoryCoro, url, session_id, "", "", 0));
}
}
}
gIMMgr->clearPendingAgentListUpdates(session_id);
}
else
{
//throw an error dialog and close the temp session's floater
gIMMgr->showSessionStartError(body["error"].asString(), temp_session_id);
}
gIMMgr->clearPendingAgentListUpdates(session_id);
}
};
class LLViewerChatterBoxSessionEventReply : public LLHTTPNode
{
public:
virtual void describe(Description& desc) const
{
desc.shortInfo("Used for receiving a reply to a ChatterBox session event");
desc.postAPI();
desc.input(
"{\"event\": string, \"reason\": string, \"success\": boolean, \"session_id\": UUID");
desc.source(__FILE__, __LINE__);
}
virtual void post(ResponsePtr response,
const LLSD& context,
const LLSD& input) const
{
LLUUID session_id;
bool success;
LLSD body = input["body"];
success = body["success"].asBoolean();
session_id = body["session_id"].asUUID();
if ( !success )
{
//throw an error dialog
gIMMgr->showSessionEventError(
body["event"].asString(),
body["error"].asString(),
session_id);
}
}
};
class LLViewerForceCloseChatterBoxSession: public LLHTTPNode
{
public:
virtual void post(ResponsePtr response,
const LLSD& context,
const LLSD& input) const
{
LLUUID session_id;
std::string reason;
session_id = input["body"]["session_id"].asUUID();
reason = input["body"]["reason"].asString();
gIMMgr->showSessionForceClose(reason, session_id);
}
};
class LLViewerChatterBoxSessionAgentListUpdates : public LLHTTPNode
{
public:
virtual void post(
ResponsePtr responder,
const LLSD& context,
const LLSD& input) const
{
const LLUUID& session_id = input["body"]["session_id"].asUUID();
gIMMgr->processAgentListUpdates(session_id, input["body"]);
}
};
class LLViewerChatterBoxSessionUpdate : public LLHTTPNode
{
public:
virtual void post(
ResponsePtr responder,
const LLSD& context,
const LLSD& input) const
{
LLUUID session_id = input["body"]["session_id"].asUUID();
// <FS:Ansariel> Method does nothing
//// <FS:Ansariel> [FS communication UI]
////LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id);
//FSFloaterIM* im_floater = FSFloaterIM::findInstance(session_id);
//// </FS:Ansariel> [FS communication UI]
//if ( im_floater )
//{
// im_floater->processSessionUpdate(input["body"]["info"]);
//}
LLIMSpeakerMgr* im_mgr = LLIMModel::getInstance()->getSpeakerManager(session_id);
if (im_mgr)
{
im_mgr->processSessionUpdate(input["body"]["info"]);
}
}
};
class LLViewerChatterBoxInvitation : public LLHTTPNode
{
public:
virtual void post(
ResponsePtr response,
const LLSD& context,
const LLSD& input) const
{
//for backwards compatiblity reasons...we need to still
//check for 'text' or 'voice' invitations...bleh
if ( input["body"].has("instantmessage") )
{
LLSD message_params =
input["body"]["instantmessage"]["message_params"];
//do something here to have the IM invite behave
//just like a normal IM
//this is just replicated code from process_improved_im
//and should really go in it's own function -jwolk
std::string message = message_params["message"].asString();
std::string name = message_params["from_name"].asString();
LLUUID from_id = message_params["from_id"].asUUID();
LLUUID session_id = message_params["id"].asUUID();
std::vector<U8> bin_bucket = message_params["data"]["binary_bucket"].asBinary();
U8 offline = (U8)message_params["offline"].asInteger();
time_t timestamp =
(time_t) message_params["timestamp"].asInteger();
BOOL is_do_not_disturb = gAgent.isDoNotDisturb();
//don't return if user is muted b/c proper way to ignore a muted user who
//initiated an adhoc/group conference is to create then leave the session (see STORM-1731)
if (is_do_not_disturb)
{
return;
}
// [RLVa:KB] - Checked: 2010-11-30 (RLVa-1.3.0)
if ( (RlvActions::hasBehaviour(RLV_BHVR_RECVIM)) || (RlvActions::hasBehaviour(RLV_BHVR_RECVIMFROM)) )
{
if (gAgent.isInGroup(session_id)) // Group chat: don't accept the invite if not an exception
{
if (!RlvActions::canReceiveIM(session_id))
return;
}
else if (!RlvActions::canReceiveIM(from_id)) // Conference chat: don't block; censor if not an exception
{
message = RlvStrings::getString(RlvStringKeys::Blocked::RecvIm);
}
}
// [/RLVa:KB]
// <FS> Mute group chat port from Phoenix
if (from_id != gAgentID) // FIRE-14222: OpenSim routes agent's chat through here - don't mute it!
{
BOOL FSMuteAllGroups = gSavedSettings.getBOOL("FSMuteAllGroups");
BOOL FSMuteGroupWhenNoticesDisabled = gSavedSettings.getBOOL("FSMuteGroupWhenNoticesDisabled");
LLGroupData group_data;
if (gAgent.getGroupData(session_id, group_data))
{
if (FSMuteAllGroups || (FSMuteGroupWhenNoticesDisabled && !group_data.mAcceptNotices))
{
LL_INFOS() << "Muting group chat: " << group_data.mName << LL_ENDL;
if (gSavedSettings.getBOOL("FSReportMutedGroupChat"))
{
LLStringUtil::format_map_t args;
args["NAME"] = LLSLURL("group", session_id, "about").getSLURLString();
report_to_nearby_chat(LLTrans::getString("GroupChatMuteNotice", args));
}
//KC: make sure we leave the group chat at the server end as well
std::string aname;
gAgent.buildFullname(aname);
pack_instant_message(
gMessageSystem,
gAgentID,
FALSE,
gAgentSessionID,
from_id,
aname,
LLStringUtil::null,
IM_ONLINE,
IM_SESSION_LEAVE,
session_id);
gAgent.sendReliableMessage();
gIMMgr->leaveSession(session_id);
return;
}
}
// <FS:Ansariel> Groupdata debug
else
{
LL_INFOS("Agent_GroupData") << "GROUPDEBUG: Group chat mute: No agent group data for group " << session_id.asString() << LL_ENDL;
}
// </FS:Ansariel>
}
// </FS> Mute group chat port from Phoenix
// standard message, not from system
std::string saved;
if(offline == IM_OFFLINE)
{
LLStringUtil::format_map_t args;
args["[LONG_TIMESTAMP]"] = formatted_time(timestamp);
saved = LLTrans::getString("Saved_message", args);
}
std::string buffer = saved + message;
// <FS:CR> FIRE-9762 - Don't bail here on OpenSim, we'll need to echo local posts
#ifdef OPENSIM
bool is_opensim = LLGridManager::getInstance()->isInOpenSim();
if (!is_opensim && from_id == gAgentID)
#else // OPENSIM
if(from_id == gAgentID)
#endif // OPENSIM
// </FS:CR>
{
return;
}
gIMMgr->addMessage(
session_id,
from_id,
name,
buffer,
IM_OFFLINE == offline,
std::string((char*)&bin_bucket[0]),
IM_SESSION_INVITE,
message_params["parent_estate_id"].asInteger(),
message_params["region_id"].asUUID(),
ll_vector3_from_sd(message_params["position"]),
false, // is_region_message
timestamp);
// <FS:CR> FIRE-9762 - OK, return here if we must!
#ifdef OPENSIM
if (is_opensim && from_id == gAgentID)
{
return;
}
#endif // OPENSIM
// </FS:CR>
if (LLMuteList::getInstance()->isMuted(from_id, name, LLMute::flagTextChat))
{
return;
}
//K now we want to accept the invitation
std::string url = gAgent.getRegionCapability("ChatSessionRequest");
if ( url != "" )
{
LLCoros::instance().launch("chatterBoxInvitationCoro",
boost::bind(&chatterBoxInvitationCoro, url,
session_id, LLIMMgr::INVITATION_TYPE_INSTANT_MESSAGE));
}
} //end if invitation has instant message
else if ( input["body"].has("voice") )
{
if(!LLVoiceClient::getInstance()->voiceEnabled() || !LLVoiceClient::getInstance()->isVoiceWorking())
{
// Don't display voice invites unless the user has voice enabled.
return;
}
gIMMgr->inviteToSession(
input["body"]["session_id"].asUUID(),
input["body"]["session_name"].asString(),
input["body"]["from_id"].asUUID(),
input["body"]["from_name"].asString(),
IM_SESSION_INVITE,
LLIMMgr::INVITATION_TYPE_VOICE);
}
else if ( input["body"].has("immediate") )
{
gIMMgr->inviteToSession(
input["body"]["session_id"].asUUID(),
input["body"]["session_name"].asString(),
input["body"]["from_id"].asUUID(),
input["body"]["from_name"].asString(),
IM_SESSION_INVITE,
LLIMMgr::INVITATION_TYPE_IMMEDIATE);
}
}
};
LLHTTPRegistration<LLViewerChatterBoxSessionStartReply>
gHTTPRegistrationMessageChatterboxsessionstartreply(
"/message/ChatterBoxSessionStartReply");
LLHTTPRegistration<LLViewerChatterBoxSessionEventReply>
gHTTPRegistrationMessageChatterboxsessioneventreply(
"/message/ChatterBoxSessionEventReply");
LLHTTPRegistration<LLViewerForceCloseChatterBoxSession>
gHTTPRegistrationMessageForceclosechatterboxsession(
"/message/ForceCloseChatterBoxSession");
LLHTTPRegistration<LLViewerChatterBoxSessionAgentListUpdates>
gHTTPRegistrationMessageChatterboxsessionagentlistupdates(
"/message/ChatterBoxSessionAgentListUpdates");
LLHTTPRegistration<LLViewerChatterBoxSessionUpdate>
gHTTPRegistrationMessageChatterBoxSessionUpdate(
"/message/ChatterBoxSessionUpdate");
LLHTTPRegistration<LLViewerChatterBoxInvitation>
gHTTPRegistrationMessageChatterBoxInvitation(
"/message/ChatterBoxInvitation");