From 4419bb870986c6900fc096338622d27b999cd771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20N=C3=A6sbye=20Christensen?= Date: Sun, 11 Feb 2024 01:23:28 +0100 Subject: more misc: BOOL (int) to real bool --- indra/newview/llimview.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'indra/newview/llimview.cpp') diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index 61a01d7418..e3567f4595 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -99,16 +99,16 @@ const LLUUID LLOutgoingCallDialog::OCD_KEY = LLUUID("7CF78E11-0CFE-498D-ADB9-141 LLIMMgr* gIMMgr = NULL; -BOOL LLSessionTimeoutTimer::tick() +bool LLSessionTimeoutTimer::tick() { - if (mSessionId.isNull()) return TRUE; + 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; + return true; } -- cgit v1.2.3 From a5261a5fa8fad810ecb5c260d92c3e771822bf58 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Tue, 20 Feb 2024 23:46:23 +0100 Subject: Convert BOOL to bool in llui --- indra/newview/llimview.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'indra/newview/llimview.cpp') diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index e3567f4595..c17418c6b6 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -2351,14 +2351,14 @@ LLCallDialog::~LLCallDialog() LLUI::getInstance()->removePopup(this); } -BOOL LLCallDialog::postBuild() +bool LLCallDialog::postBuild() { if (!LLDockableFloater::postBuild() || !gToolBarView) - return FALSE; + return false; dockToToolbarButton("speak"); - return TRUE; + return true; } void LLCallDialog::dockToToolbarButton(const std::string& toolbarButtonName) @@ -2621,13 +2621,13 @@ void LLOutgoingCallDialog::onCancel(void* user_data) } -BOOL LLOutgoingCallDialog::postBuild() +bool LLOutgoingCallDialog::postBuild() { - BOOL success = LLCallDialog::postBuild(); + bool success = LLCallDialog::postBuild(); childSetAction("Cancel", onCancel, this); - setCanDrag(FALSE); + setCanDrag(false); return success; } @@ -2675,14 +2675,14 @@ void LLIncomingCallDialog::onLifetimeExpired() } } -BOOL LLIncomingCallDialog::postBuild() +bool LLIncomingCallDialog::postBuild() { LLCallDialog::postBuild(); if (!mPayload.isMap() || mPayload.size() == 0) { LL_INFOS("IMVIEW") << "IncomingCall: invalid argument" << LL_ENDL; - return TRUE; + return true; } LLUUID session_id = mPayload["session_id"].asUUID(); @@ -2692,14 +2692,14 @@ BOOL LLIncomingCallDialog::postBuild() if (session_id.isNull() && caller_id.asUUID().isNull()) { LL_INFOS("IMVIEW") << "IncomingCall: invalid ids" << LL_ENDL; - return TRUE; + 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; + return true; } // init notification's lifetime @@ -2761,8 +2761,8 @@ BOOL LLIncomingCallDialog::postBuild() 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; + setCanDrag(false); + return true; } void LLIncomingCallDialog::setCallerName(const std::string& ui_title, -- cgit v1.2.3 From 60d3dd98a44230c21803c1606552ee098ed9fa7c Mon Sep 17 00:00:00 2001 From: Ansariel Date: Wed, 21 Feb 2024 21:05:14 +0100 Subject: Convert remaining BOOL to bool --- indra/newview/llimview.cpp | 74 +++++++++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 37 deletions(-) (limited to 'indra/newview/llimview.cpp') diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index c17418c6b6..e9b15b2bc1 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -206,7 +206,7 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg) if (msg["source_type"].asInteger() == CHAT_SOURCE_OBJECT) { user_preferences = gSavedSettings.getString("NotificationObjectIMOptions"); - if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundObjectIM") == TRUE)) + if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundObjectIM") == true)) { make_ui_sound("UISndNewIncomingIMSession"); } @@ -214,7 +214,7 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg) else { user_preferences = gSavedSettings.getString("NotificationNearbyChatOptions"); - if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNearbyChatIM") == TRUE)) + if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNearbyChatIM") == true)) { make_ui_sound("UISndNewIncomingIMSession"); } @@ -225,7 +225,7 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg) if (LLAvatarTracker::instance().isBuddy(participant_id)) { user_preferences = gSavedSettings.getString("NotificationFriendIMOptions"); - if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundFriendIM") == TRUE)) + if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundFriendIM") == true)) { make_ui_sound("UISndNewIncomingIMSession"); } @@ -233,7 +233,7 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg) else { user_preferences = gSavedSettings.getString("NotificationNonFriendIMOptions"); - if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNonFriendIM") == TRUE)) + if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNonFriendIM") == true)) { make_ui_sound("UISndNewIncomingIMSession"); } @@ -242,7 +242,7 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg) else if(session->isAdHocSessionType()) { user_preferences = gSavedSettings.getString("NotificationConferenceIMOptions"); - if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundConferenceIM") == TRUE)) + if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundConferenceIM") == true)) { make_ui_sound("UISndNewIncomingIMSession"); } @@ -250,7 +250,7 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg) else if(session->isGroupSessionType()) { user_preferences = gSavedSettings.getString("NotificationGroupChatOptions"); - if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundGroupChatIM") == TRUE)) + if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundGroupChatIM") == true)) { make_ui_sound("UISndNewIncomingIMSession"); } @@ -708,7 +708,7 @@ LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string& } else { - //tick returns TRUE - timer will be deleted after the tick + //tick returns true - timer will be deleted after the tick new LLSessionTimeoutTimer(mSessionID, SESSION_INITIALIZATION_TIMEOUT); } @@ -925,7 +925,7 @@ void LLIMModel::LLIMSession::addMessage(const std::string& from, if (mSpeakers && from_id.notNull()) { mSpeakers->speakerChatted(from_id); - mSpeakers->setSpeakerTyping(from_id, FALSE); + mSpeakers->setSpeakerTyping(from_id, false); } } @@ -1288,7 +1288,7 @@ bool LLIMModel::LLIMSession::isOutgoingAdHoc() const bool LLIMModel::LLIMSession::isAdHoc() { - return IM_SESSION_CONFERENCE_START == mType || (IM_SESSION_INVITE == mType && !gAgent.isInGroup(mSessionID, TRUE)); + return IM_SESSION_CONFERENCE_START == mType || (IM_SESSION_INVITE == mType && !gAgent.isInGroup(mSessionID, true)); } bool LLIMModel::LLIMSession::isP2P() @@ -1298,7 +1298,7 @@ bool LLIMModel::LLIMSession::isP2P() bool LLIMModel::LLIMSession::isGroupChat() { - return IM_SESSION_GROUP_START == mType || (IM_SESSION_INVITE == mType && gAgent.isInGroup(mSessionID, TRUE)); + return IM_SESSION_GROUP_START == mType || (IM_SESSION_INVITE == mType && gAgent.isInGroup(mSessionID, true)); } LLUUID LLIMModel::LLIMSession::generateOutgoingAdHocHash() const @@ -1755,7 +1755,7 @@ const std::string& LLIMModel::getHistoryFileName(const LLUUID& session_id) const // TODO get rid of other participant ID -void LLIMModel::sendTypingState(LLUUID session_id, LLUUID other_participant_id, BOOL typing) +void LLIMModel::sendTypingState(LLUUID session_id, LLUUID other_participant_id, bool typing) { std::string name; LLAgentUI::buildFullname(name); @@ -1763,7 +1763,7 @@ void LLIMModel::sendTypingState(LLUUID session_id, LLUUID other_participant_id, pack_instant_message( gMessageSystem, gAgent.getID(), - FALSE, + false, gAgent.getSessionID(), other_participant_id, name, @@ -1783,7 +1783,7 @@ void LLIMModel::sendLeaveSession(const LLUUID& session_id, const LLUUID& other_p pack_instant_message( gMessageSystem, gAgent.getID(), - FALSE, + false, gAgent.getSessionID(), other_participant_id, name, @@ -1831,7 +1831,7 @@ void LLIMModel::sendMessage(const std::string& utf8_text, pack_instant_message( gMessageSystem, gAgent.getID(), - FALSE, + false, gAgent.getSessionID(), other_participant_id, name.c_str(), @@ -1890,7 +1890,7 @@ void LLIMModel::sendMessage(const std::string& utf8_text, if (speaker_mgr) { speaker_mgr->speakerChatted(gAgentID); - speaker_mgr->setSpeakerTyping(gAgentID, FALSE); + speaker_mgr->setSpeakerTyping(gAgentID, false); } } @@ -1955,7 +1955,7 @@ void session_starter_helper( msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); msg->nextBlockFast(_PREHASH_MessageBlock); - msg->addBOOLFast(_PREHASH_FromGroup, FALSE); + msg->addBOOLFast(_PREHASH_FromGroup, false); msg->addUUIDFast(_PREHASH_ToAgentID, other_participant_id); msg->addU8Fast(_PREHASH_Offline, IM_ONLINE); msg->addU8Fast(_PREHASH_Dialog, im_type); @@ -2109,7 +2109,7 @@ LLUUID LLIMMgr::computeSessionID( } } - if (gAgent.isInGroup(session_id, TRUE) && (session_id != other_participant_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; } @@ -2192,7 +2192,7 @@ LLIMMgr::onConfirmForceCloseError( LLFloater* floater = LLFloaterIMSession::findInstance(session_id); if ( floater ) { - floater->closeFloater(FALSE); + floater->closeFloater(false); } return false; } @@ -2341,7 +2341,7 @@ LLCallDialog::LLCallDialog(const LLSD& payload) mPayload(payload), mLifetime(DEFAULT_LIFETIME) { - setAutoFocus(FALSE); + setAutoFocus(false); // force docked state since this floater doesn't save it between recreations setDocked(true); } @@ -2432,7 +2432,7 @@ 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); + bool is_group = participant_is_avatar && gAgent.isInGroup(session_id, true); LLAvatarIconCtrl* avatar_icon = getChild("avatar_icon"); LLGroupIconCtrl* group_icon = getChild("group_icon"); @@ -2710,7 +2710,7 @@ bool LLIncomingCallDialog::postBuild() } std::string call_type; - if (gAgent.isInGroup(session_id, TRUE)) + if (gAgent.isInGroup(session_id, true)) { LLStringUtil::format_map_t args; LLGroupData data; @@ -2881,7 +2881,7 @@ void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload case IM_SESSION_CONFERENCE_START: case IM_SESSION_GROUP_START: case IM_SESSION_INVITE: - if (gAgent.isInGroup(session_id, TRUE)) + if (gAgent.isInGroup(session_id, true)) { LLGroupData data; if (!gAgent.getGroupData(session_id, data)) break; @@ -3056,7 +3056,7 @@ LLIMMgr::LLIMMgr() LLIMModel::getInstance()->addNewMsgCallback(boost::bind(&LLFloaterIMSession::sRemoveTypingIndicator, _1)); - gSavedPerAccountSettings.declareBOOL("FetchGroupChatHistory", TRUE, "Fetch recent messages from group chat servers when a group window opens", LLControlVariable::PERSIST_ALWAYS); + 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. @@ -3168,7 +3168,7 @@ void LLIMMgr::addMessage( } //Play sound for new conversations - if (!skip_message & !gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNewConversation") == TRUE)) + if (!skip_message & !gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNewConversation") == true)) { make_ui_sound("UISndNewIncomingIMSession"); } @@ -3429,7 +3429,7 @@ void LLIMMgr::inviteToSession( // voice invite question is different from default only for group call (EXT-7118) std::string question_type = "VoiceInviteQuestionDefault"; - BOOL voice_invite = FALSE; + bool voice_invite = false; bool is_linden = LLMuteList::isLinden(caller_name); @@ -3437,21 +3437,21 @@ void LLIMMgr::inviteToSession( { //P2P is different...they only have voice invitations notify_box_type = "VoiceInviteP2P"; - voice_invite = TRUE; + voice_invite = true; } - else if ( gAgent.isInGroup(session_id, 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; + 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; + voice_invite = true; } else if ( inv_type == INVITATION_TYPE_IMMEDIATE ) { @@ -3540,7 +3540,7 @@ void LLIMMgr::inviteToSession( } else { - LLFloaterReg::showInstance("incoming_call", payload, FALSE); + LLFloaterReg::showInstance("incoming_call", payload, false); } // Add the caller to the Recent List here (at this point @@ -3562,7 +3562,7 @@ void LLIMMgr::onInviteNameLookup(LLSD payload, const LLUUID& id, const LLAvatarN std::string notify_box_type = payload["notify_box_type"].asString(); - LLFloaterReg::showInstance("incoming_call", payload, FALSE); + LLFloaterReg::showInstance("incoming_call", payload, false); } //*TODO disconnects all sessions @@ -3571,7 +3571,7 @@ void LLIMMgr::disconnectAllSessions() //*TODO disconnects all IM sessions } -BOOL LLIMMgr::hasSession(const LLUUID& session_id) +bool LLIMMgr::hasSession(const LLUUID& session_id) { return LLIMModel::getInstance()->findIMSession(session_id) != NULL; } @@ -3763,7 +3763,7 @@ bool LLIMMgr::endCall(const LLUUID& session_id) if (im_session) { // need to update speakers' state - im_session->mSpeakers->update(FALSE); + im_session->mSpeakers->update(false); } return true; } @@ -3881,15 +3881,15 @@ void LLIMMgr::noteMutedUsers(const LLUUID& session_id, void LLIMMgr::processIMTypingStart(const LLUUID& from_id, const EInstantMessage im_type) { - processIMTypingCore(from_id, im_type, TRUE); + processIMTypingCore(from_id, im_type, true); } void LLIMMgr::processIMTypingStop(const LLUUID& from_id, const EInstantMessage im_type) { - processIMTypingCore(from_id, im_type, FALSE); + processIMTypingCore(from_id, im_type, false); } -void LLIMMgr::processIMTypingCore(const LLUUID& from_id, const EInstantMessage im_type, BOOL typing) +void LLIMMgr::processIMTypingCore(const LLUUID& from_id, const EInstantMessage im_type, bool typing) { LLUUID session_id = computeSessionID(im_type, from_id); LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); @@ -4084,7 +4084,7 @@ public: time_t timestamp = (time_t) message_params["timestamp"].asInteger(); - BOOL is_do_not_disturb = gAgent.isDoNotDisturb(); + 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) -- cgit v1.2.3 From f9473e8afcb624cc1b101195bf15943ec372b56f Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Mon, 6 May 2024 16:52:34 +0200 Subject: secondlife/viewer#1333 BOOL to bool conversion leftovers: ternaries --- indra/newview/llimview.cpp | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) (limited to 'indra/newview/llimview.cpp') diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index 808b9511e0..10fe740138 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -206,26 +206,26 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg) if (msg["source_type"].asInteger() == CHAT_SOURCE_OBJECT) { user_preferences = gSavedSettings.getString("NotificationObjectIMOptions"); - if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundObjectIM") == true)) + if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundObjectIM"))) { make_ui_sound("UISndNewIncomingIMSession"); } } else { - user_preferences = gSavedSettings.getString("NotificationNearbyChatOptions"); - if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNearbyChatIM") == true)) + user_preferences = gSavedSettings.getString("NotificationNearbyChatOptions"); + if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNearbyChatIM"))) { make_ui_sound("UISndNewIncomingIMSession"); - } + } } } - else if(session->isP2PSessionType()) + else if (session->isP2PSessionType()) { if (LLAvatarTracker::instance().isBuddy(participant_id)) { user_preferences = gSavedSettings.getString("NotificationFriendIMOptions"); - if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundFriendIM") == true)) + if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundFriendIM"))) { make_ui_sound("UISndNewIncomingIMSession"); } @@ -233,24 +233,24 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg) else { user_preferences = gSavedSettings.getString("NotificationNonFriendIMOptions"); - if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNonFriendIM") == true)) + if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNonFriendIM"))) { make_ui_sound("UISndNewIncomingIMSession"); - } - } + } + } } - else if(session->isAdHocSessionType()) + else if (session->isAdHocSessionType()) { user_preferences = gSavedSettings.getString("NotificationConferenceIMOptions"); - if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundConferenceIM") == true)) + if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundConferenceIM"))) { make_ui_sound("UISndNewIncomingIMSession"); - } + } } else if(session->isGroupSessionType()) { user_preferences = gSavedSettings.getString("NotificationGroupChatOptions"); - if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundGroupChatIM") == true)) + if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundGroupChatIM"))) { make_ui_sound("UISndNewIncomingIMSession"); } @@ -300,8 +300,7 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg) else { store_dnd_message = true; - } - + } } // 2. Flash line item @@ -3170,7 +3169,7 @@ void LLIMMgr::addMessage( } //Play sound for new conversations - if (!skip_message & !gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNewConversation") == true)) + if (!skip_message & !gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNewConversation"))) { make_ui_sound("UISndNewIncomingIMSession"); } -- cgit v1.2.3 From e2e37cced861b98de8c1a7c9c0d3a50d2d90e433 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Wed, 22 May 2024 21:25:21 +0200 Subject: Fix line endlings --- indra/newview/llimview.cpp | 8408 ++++++++++++++++++++++---------------------- 1 file changed, 4204 insertions(+), 4204 deletions(-) (limited to 'indra/newview/llimview.cpp') diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index 30ee4d7e67..62bc95db82 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -1,4204 +1,4204 @@ -/** - * @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" -#include "llfloaterimsession.h" -#include "llfloaterimcontainer.h" -#include "llgroupiconctrl.h" -#include "llmd5.h" -#include "llmutelist.h" -#include "llrecentpeople.h" -#include "llviewermessage.h" -#include "llviewerwindow.h" -#include "llnotifications.h" -#include "llnotificationsutil.h" -#include "llfloaterimnearbychat.h" -#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" - -#include - -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) -{ - 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); -} - - -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"]; - LLNotificationsUtil::add("IMToast", args, args, boost::bind(&LLFloaterIMContainer::showConversation, LLFloaterIMContainer::getInstance(), msg["session_id"].asUUID())); -} - -void notify_of_message(const LLSD& msg, bool is_dnd_msg) -{ - 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("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"))) - { - make_ui_sound("UISndNewIncomingIMSession"); - } - } - else - { - user_preferences = gSavedSettings.getString("NotificationNearbyChatOptions"); - if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNearbyChatIM"))) - { - 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"))) - { - make_ui_sound("UISndNewIncomingIMSession"); - } - } - else - { - user_preferences = gSavedSettings.getString("NotificationNonFriendIMOptions"); - if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNonFriendIM"))) - { - make_ui_sound("UISndNewIncomingIMSession"); - } - } - } - else if (session->isAdHocSessionType()) - { - user_preferences = gSavedSettings.getString("NotificationConferenceIMOptions"); - if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundConferenceIM"))) - { - make_ui_sound("UISndNewIncomingIMSession"); - } - } - else if(session->isGroupSessionType()) - { - user_preferences = gSavedSettings.getString("NotificationGroupChatOptions"); - if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundGroupChatIM"))) - { - 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)); - } - } -} - -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); -} - -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); -} - -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; - } - - if (LLApp::isExiting() || gDisconnected) - { - LL_DEBUGS("ChatHistory") << "Ignoring chat history response, shutting down" << 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 - LLFloaterIMSession* floater = LLFloaterReg::findTypedInstance("impanel", sessionId); - 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() -{ - addNewMsgCallback(boost::bind(&LLFloaterIMSession::newIMCallback, _1)); - 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), - 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::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 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 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 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)) - { - mHistoryFileName = LLCacheName::buildUsername(av_name.getUserName()); - } - else - { - // Incoming P2P sessions include a name that we can use to build a history file name - mHistoryFileName = LLCacheName::buildUsername(mName); - } - - // 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& sorted_uuids) -{ - LLMD5 md5_uuid; - - std::set::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; - } - - LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(old_session_id); - 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) -{ - LLIMSession* session = findIMSession(session_id); - - if (!session) - { - LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL; - return false; - } - - // This is where a normal arriving message is added to the session. Note that the time string created here is without the full date - session->addMessage(from, from_id, utf8_text, LLLogChat::timestamp2LogString(timestamp, false), false, is_region_msg, timestamp); - - 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); -} - -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 */) -{ - if (gSavedSettings.getBOOL("TranslateChat") && (from != SYSTEM_FROM)) - { - 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); - } -} - -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) -{ - LLIMSession* session = addMessageSilently(session_id, from, from_id, utf8_text, log2file, is_region_msg, time_stamp); - 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; - - mNewMsgSignal(arg); -} - -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 */) -{ - 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); - if (log2file) - { - 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) -{ - 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 -void LLIMModel::sendMessage(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 - } - } - - 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) - { - 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& 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"]; - - LLFloater* floater = LLFloaterIMSession::findInstance(session_id); - 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("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("outgoing_call", LLOutgoingCallDialog::OCD_KEY); - if (ocd) - { - ocd->closeFloater(); - } - return; - - default: - break; - } - - ocd = LLFloaterReg::getTypedInstance("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("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("avatar_icon"); - LLGroupIconCtrl* group_icon = getChild("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("leaving")->setTextArg("[CURRENT_CHAT]", old_caller_name); - show_oldchannel = true; - } - else - { - getChild("leaving")->setTextArg("[CURRENT_CHAT]", getString("localchat")); - } - - if (!mPayload["disconnected_channel_name"].asString().empty()) - { - std::string channel_name = mPayload["disconnected_channel_name"].asString(); - getChild("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("nearby")->setTextArg("[RECONNECT_NEARBY]", reconnect_nearby); - - const std::string& nearby_str = mPayload["ended_by_agent"] ? NEARBY_P2P_BY_AGENT : NEARBY_P2P_BY_OTHER; - getChild(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("calling")->setTextArg("[CALLEE_NAME]", final_callee_name); - getChild("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("calling")->setVisible(true); - getChild("Cancel")->setVisible(true); - if(show_oldchannel) - { - getChild("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("leaving")->setVisible(true); - } - getChild("connecting")->setVisible(true); - break; - case LLVoiceChannel::STATE_ERROR : - getChild("noanswer")->setVisible(true); - getChild("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(nearby_str)->setVisible(true); - } - else - { - getChild("nearby")->setVisible(true); - } - getChild("Cancel")->setVisible(false); - setCanClose(true); - mLifetimeTimer.start(); - } - - openFloater(LLOutgoingCallDialog::OCD_KEY); -} - -void LLOutgoingCallDialog::hideAllText() -{ - getChild("calling")->setVisible(false); - getChild("leaving")->setVisible(false); - getChild("connecting")->setVisible(false); - getChild("nearby_P2P_by_other")->setVisible(false); - getChild("nearby_P2P_by_agent")->setVisible(false); - getChild("nearby")->setVisible(false); - getChild("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 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("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); - - if (gSavedSettings.getBOOL("PlaySoundIncomingVoiceCall")) - { - // play a sound for incoming voice call if respective property is set - make_ui_sound("UISndStartIM"); - } - - 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) - 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(); - - LLIMModel::getInstance()->addNewMsgCallback(boost::bind(&LLFloaterIMSession::sRemoveTypingIndicator, _1)); - - 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 -{ - 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); - if (gSavedPerAccountSettings.getBOOL("VoiceCallsFriendsOnly") && !from_linden) - { - // 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); - 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(); - } - 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.getRegionCapability("ChatSessionRequest"); - if (!chat_url.empty()) - { - LLCoros::instance().launch("chatterBoxHistoryCoro", boost::bind(&chatterBoxHistoryCoro, chat_url, session_id, from, msg, timestamp)); - } - } - - //Play sound for new conversations - if (!skip_message & !gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNewConversation"))) - { - make_ui_sound("UISndNewIncomingIMSession"); - } - } - 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; - } - } - - if (!LLMuteList::getInstance()->isMuted(other_participant_id, LLMute::flagTextChat) && !skip_message) - { - LLIMModel::instance().addMessage(new_session_id, from, other_participant_id, msg, true, is_region_msg, timestamp); - } - - // Open conversation floater if offline messages are present - if (is_offline_msg && !skip_message) - { - LLFloaterReg::showInstance("im_container"); - LLFloaterReg::getTypedInstance("im_container")-> - flashConversationItemWidget(new_session_id, true); - } -} - -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; - - LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); - 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 - { - 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()); - } - } -} - -S32 LLIMMgr::getNumberOfUnreadIM() -{ - std::map::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::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(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 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& 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()) - { - LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(floater_id); - - 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; - - 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)); - if (isRejectGroupCall || isRejectNonFriendCall || gAgent.isDoNotDisturb()) - { - if (gAgent.isDoNotDisturb() && !isRejectGroupCall && !isRejectNonFriendCall) - { - 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; -} - -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) -{ - LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); - 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::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& 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 - offline.setArg("[NAME]", av_name.getDisplayName()); - im_model.proccessOnlineOfflineNotification(session_id, offline); - } - } - } -} - -void LLIMMgr::noteMutedUsers(const LLUUID& session_id, - const std::vector& 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); -} - -void LLIMMgr::processIMTypingCore(const LLUUID& from_id, const EInstantMessage im_type, bool typing) -{ - LLUUID session_id = computeSessionID(im_type, from_id); - LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); - 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 - { - if (LLApp::isExiting() || gDisconnected) - { - LL_DEBUGS("ChatHistory") << "Ignoring ChatterBox session, Shutting down" << LL_ENDL; - return; - } - - 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)); - } - - LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); - if ( im_floater ) - { - if ( body.has("session_info") ) - { - im_floater->processSessionUpdate(body["session_info"]); - - // 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(); - LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); - 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 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; - } - - // 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; - - if(from_id == gAgentID) - { - 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); - - 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 - gHTTPRegistrationMessageChatterboxsessionstartreply( - "/message/ChatterBoxSessionStartReply"); - -LLHTTPRegistration - gHTTPRegistrationMessageChatterboxsessioneventreply( - "/message/ChatterBoxSessionEventReply"); - -LLHTTPRegistration - gHTTPRegistrationMessageForceclosechatterboxsession( - "/message/ForceCloseChatterBoxSession"); - -LLHTTPRegistration - gHTTPRegistrationMessageChatterboxsessionagentlistupdates( - "/message/ChatterBoxSessionAgentListUpdates"); - -LLHTTPRegistration - gHTTPRegistrationMessageChatterBoxSessionUpdate( - "/message/ChatterBoxSessionUpdate"); - -LLHTTPRegistration - gHTTPRegistrationMessageChatterBoxInvitation( - "/message/ChatterBoxInvitation"); - +/** + * @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" +#include "llfloaterimsession.h" +#include "llfloaterimcontainer.h" +#include "llgroupiconctrl.h" +#include "llmd5.h" +#include "llmutelist.h" +#include "llrecentpeople.h" +#include "llviewermessage.h" +#include "llviewerwindow.h" +#include "llnotifications.h" +#include "llnotificationsutil.h" +#include "llfloaterimnearbychat.h" +#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" + +#include + +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) +{ + 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); +} + + +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"]; + LLNotificationsUtil::add("IMToast", args, args, boost::bind(&LLFloaterIMContainer::showConversation, LLFloaterIMContainer::getInstance(), msg["session_id"].asUUID())); +} + +void notify_of_message(const LLSD& msg, bool is_dnd_msg) +{ + 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("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"))) + { + make_ui_sound("UISndNewIncomingIMSession"); + } + } + else + { + user_preferences = gSavedSettings.getString("NotificationNearbyChatOptions"); + if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNearbyChatIM"))) + { + 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"))) + { + make_ui_sound("UISndNewIncomingIMSession"); + } + } + else + { + user_preferences = gSavedSettings.getString("NotificationNonFriendIMOptions"); + if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNonFriendIM"))) + { + make_ui_sound("UISndNewIncomingIMSession"); + } + } + } + else if (session->isAdHocSessionType()) + { + user_preferences = gSavedSettings.getString("NotificationConferenceIMOptions"); + if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundConferenceIM"))) + { + make_ui_sound("UISndNewIncomingIMSession"); + } + } + else if(session->isGroupSessionType()) + { + user_preferences = gSavedSettings.getString("NotificationGroupChatOptions"); + if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundGroupChatIM"))) + { + 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)); + } + } +} + +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); +} + +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); +} + +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; + } + + if (LLApp::isExiting() || gDisconnected) + { + LL_DEBUGS("ChatHistory") << "Ignoring chat history response, shutting down" << 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 + LLFloaterIMSession* floater = LLFloaterReg::findTypedInstance("impanel", sessionId); + 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() +{ + addNewMsgCallback(boost::bind(&LLFloaterIMSession::newIMCallback, _1)); + 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), + 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::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 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 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 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)) + { + mHistoryFileName = LLCacheName::buildUsername(av_name.getUserName()); + } + else + { + // Incoming P2P sessions include a name that we can use to build a history file name + mHistoryFileName = LLCacheName::buildUsername(mName); + } + + // 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& sorted_uuids) +{ + LLMD5 md5_uuid; + + std::set::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; + } + + LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(old_session_id); + 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) +{ + LLIMSession* session = findIMSession(session_id); + + if (!session) + { + LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL; + return false; + } + + // This is where a normal arriving message is added to the session. Note that the time string created here is without the full date + session->addMessage(from, from_id, utf8_text, LLLogChat::timestamp2LogString(timestamp, false), false, is_region_msg, timestamp); + + 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); +} + +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 */) +{ + if (gSavedSettings.getBOOL("TranslateChat") && (from != SYSTEM_FROM)) + { + 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); + } +} + +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) +{ + LLIMSession* session = addMessageSilently(session_id, from, from_id, utf8_text, log2file, is_region_msg, time_stamp); + 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; + + mNewMsgSignal(arg); +} + +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 */) +{ + 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); + if (log2file) + { + 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) +{ + 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 +void LLIMModel::sendMessage(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 + } + } + + 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) + { + 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& 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"]; + + LLFloater* floater = LLFloaterIMSession::findInstance(session_id); + 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("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("outgoing_call", LLOutgoingCallDialog::OCD_KEY); + if (ocd) + { + ocd->closeFloater(); + } + return; + + default: + break; + } + + ocd = LLFloaterReg::getTypedInstance("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("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("avatar_icon"); + LLGroupIconCtrl* group_icon = getChild("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("leaving")->setTextArg("[CURRENT_CHAT]", old_caller_name); + show_oldchannel = true; + } + else + { + getChild("leaving")->setTextArg("[CURRENT_CHAT]", getString("localchat")); + } + + if (!mPayload["disconnected_channel_name"].asString().empty()) + { + std::string channel_name = mPayload["disconnected_channel_name"].asString(); + getChild("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("nearby")->setTextArg("[RECONNECT_NEARBY]", reconnect_nearby); + + const std::string& nearby_str = mPayload["ended_by_agent"] ? NEARBY_P2P_BY_AGENT : NEARBY_P2P_BY_OTHER; + getChild(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("calling")->setTextArg("[CALLEE_NAME]", final_callee_name); + getChild("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("calling")->setVisible(true); + getChild("Cancel")->setVisible(true); + if(show_oldchannel) + { + getChild("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("leaving")->setVisible(true); + } + getChild("connecting")->setVisible(true); + break; + case LLVoiceChannel::STATE_ERROR : + getChild("noanswer")->setVisible(true); + getChild("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(nearby_str)->setVisible(true); + } + else + { + getChild("nearby")->setVisible(true); + } + getChild("Cancel")->setVisible(false); + setCanClose(true); + mLifetimeTimer.start(); + } + + openFloater(LLOutgoingCallDialog::OCD_KEY); +} + +void LLOutgoingCallDialog::hideAllText() +{ + getChild("calling")->setVisible(false); + getChild("leaving")->setVisible(false); + getChild("connecting")->setVisible(false); + getChild("nearby_P2P_by_other")->setVisible(false); + getChild("nearby_P2P_by_agent")->setVisible(false); + getChild("nearby")->setVisible(false); + getChild("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 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("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); + + if (gSavedSettings.getBOOL("PlaySoundIncomingVoiceCall")) + { + // play a sound for incoming voice call if respective property is set + make_ui_sound("UISndStartIM"); + } + + 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) + 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(); + + LLIMModel::getInstance()->addNewMsgCallback(boost::bind(&LLFloaterIMSession::sRemoveTypingIndicator, _1)); + + 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 +{ + 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); + if (gSavedPerAccountSettings.getBOOL("VoiceCallsFriendsOnly") && !from_linden) + { + // 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); + 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(); + } + 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.getRegionCapability("ChatSessionRequest"); + if (!chat_url.empty()) + { + LLCoros::instance().launch("chatterBoxHistoryCoro", boost::bind(&chatterBoxHistoryCoro, chat_url, session_id, from, msg, timestamp)); + } + } + + //Play sound for new conversations + if (!skip_message & !gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNewConversation"))) + { + make_ui_sound("UISndNewIncomingIMSession"); + } + } + 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; + } + } + + if (!LLMuteList::getInstance()->isMuted(other_participant_id, LLMute::flagTextChat) && !skip_message) + { + LLIMModel::instance().addMessage(new_session_id, from, other_participant_id, msg, true, is_region_msg, timestamp); + } + + // Open conversation floater if offline messages are present + if (is_offline_msg && !skip_message) + { + LLFloaterReg::showInstance("im_container"); + LLFloaterReg::getTypedInstance("im_container")-> + flashConversationItemWidget(new_session_id, true); + } +} + +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; + + LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); + 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 + { + 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()); + } + } +} + +S32 LLIMMgr::getNumberOfUnreadIM() +{ + std::map::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::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(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 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& 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()) + { + LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(floater_id); + + 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; + + 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)); + if (isRejectGroupCall || isRejectNonFriendCall || gAgent.isDoNotDisturb()) + { + if (gAgent.isDoNotDisturb() && !isRejectGroupCall && !isRejectNonFriendCall) + { + 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; +} + +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) +{ + LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); + 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::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& 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 + offline.setArg("[NAME]", av_name.getDisplayName()); + im_model.proccessOnlineOfflineNotification(session_id, offline); + } + } + } +} + +void LLIMMgr::noteMutedUsers(const LLUUID& session_id, + const std::vector& 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); +} + +void LLIMMgr::processIMTypingCore(const LLUUID& from_id, const EInstantMessage im_type, bool typing) +{ + LLUUID session_id = computeSessionID(im_type, from_id); + LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); + 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 + { + if (LLApp::isExiting() || gDisconnected) + { + LL_DEBUGS("ChatHistory") << "Ignoring ChatterBox session, Shutting down" << LL_ENDL; + return; + } + + 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)); + } + + LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); + if ( im_floater ) + { + if ( body.has("session_info") ) + { + im_floater->processSessionUpdate(body["session_info"]); + + // 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(); + LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); + 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 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; + } + + // 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; + + if(from_id == gAgentID) + { + 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); + + 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 + gHTTPRegistrationMessageChatterboxsessionstartreply( + "/message/ChatterBoxSessionStartReply"); + +LLHTTPRegistration + gHTTPRegistrationMessageChatterboxsessioneventreply( + "/message/ChatterBoxSessionEventReply"); + +LLHTTPRegistration + gHTTPRegistrationMessageForceclosechatterboxsession( + "/message/ForceCloseChatterBoxSession"); + +LLHTTPRegistration + gHTTPRegistrationMessageChatterboxsessionagentlistupdates( + "/message/ChatterBoxSessionAgentListUpdates"); + +LLHTTPRegistration + gHTTPRegistrationMessageChatterBoxSessionUpdate( + "/message/ChatterBoxSessionUpdate"); + +LLHTTPRegistration + gHTTPRegistrationMessageChatterBoxInvitation( + "/message/ChatterBoxInvitation"); + -- cgit v1.2.3 From b42f9d836b4c0f7fbd4bdae1734021e2a09fdbe8 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Sat, 1 Jun 2024 15:49:26 +0200 Subject: Re-enable a lot of compiler warnings for MSVC and address the C4267 "possible loss of precision" warnings --- indra/newview/llimview.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'indra/newview/llimview.cpp') diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index 62bc95db82..667d11e194 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -741,7 +741,7 @@ void LLIMModel::LLIMSession::onAdHocNameCache(const LLAvatarName& av_name) if (!av_name.isValidName()) { - S32 separator_index = mName.rfind(" "); + auto separator_index = mName.rfind(" "); std::string name = mName.substr(0, separator_index); ++separator_index; std::string conference_word = mName.substr(separator_index, mName.length()); @@ -1188,7 +1188,7 @@ void LLIMModel::LLIMSession::addMessagesFromServerHistory(const LLSD& history, } } - S32 shifted_size = shift_msgs.size(); + auto 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(); @@ -1250,7 +1250,7 @@ LLIMModel::LLIMSession* LLIMModel::findIMSession(const LLUUID& session_id) const //*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(); + auto num = ids.size(); if (!num) return NULL; if (mId2SessionMap.empty()) return NULL; @@ -1504,7 +1504,7 @@ void LLIMModel::getMessagesSilently(const LLUUID& session_id, chat_message_list_ return; } - int i = session->mMsgs.size() - start_index; + auto i = session->mMsgs.size() - start_index; for (chat_message_list_t::iterator iter = session->mMsgs.begin(); iter != session->mMsgs.end() && i > 0; @@ -1985,12 +1985,12 @@ void start_deprecated_conference_chat( { U8* bucket; U8* pos; - S32 count; + size_t count; S32 bucket_size; // *FIX: this could suffer from endian issues count = agents_to_invite.size(); - bucket_size = UUID_BYTES * count; + bucket_size = UUID_BYTES * static_cast(count); bucket = new U8[bucket_size]; pos = bucket; @@ -3830,7 +3830,7 @@ void LLIMMgr::noteOfflineUsers( const LLUUID& session_id, const std::vector& ids) { - S32 count = ids.size(); + auto count = ids.size(); if(count == 0) { const std::string& only_user = LLTrans::getString("only_user_message"); @@ -3868,7 +3868,7 @@ void LLIMMgr::noteMutedUsers(const LLUUID& session_id, return; } - S32 count = ids.size(); + auto count = ids.size(); if(count > 0) { LLIMModel* im_model = LLIMModel::getInstance(); -- cgit v1.2.3 From 9e45c1e506e0cdf8ade6136da9bbf867c93d16e6 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Sun, 9 Jun 2024 16:17:03 +0200 Subject: Fix issues resulting from b42f9d836b4c0f7fbd4bdae1734021e2a09fdbe8 --- indra/newview/llimview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/newview/llimview.cpp') diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index 667d11e194..7ed10b062c 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -1504,7 +1504,7 @@ void LLIMModel::getMessagesSilently(const LLUUID& session_id, chat_message_list_ return; } - auto i = session->mMsgs.size() - start_index; + int i = static_cast(session->mMsgs.size()) - start_index; for (chat_message_list_t::iterator iter = session->mMsgs.begin(); iter != session->mMsgs.end() && i > 0; -- cgit v1.2.3