diff options
-rw-r--r-- | indra/newview/llimprocessing.cpp | 19 | ||||
-rw-r--r-- | indra/newview/llimview.cpp | 712 | ||||
-rw-r--r-- | indra/newview/llimview.h | 66 | ||||
-rw-r--r-- | indra/newview/lllogchat.cpp | 121 | ||||
-rw-r--r-- | indra/newview/lllogchat.h | 3 | ||||
-rw-r--r-- | indra/newview/llnotificationhandlerutil.cpp | 2 |
6 files changed, 690 insertions, 233 deletions
diff --git a/indra/newview/llimprocessing.cpp b/indra/newview/llimprocessing.cpp index af2d168aef..81f51ca0ea 100644 --- a/indra/newview/llimprocessing.cpp +++ b/indra/newview/llimprocessing.cpp @@ -521,7 +521,9 @@ void LLIMProcessing::processNewMessage(LLUUID from_id, dialog, parent_estate_id, region_id, - position); + position, + false, // is_region_msg + timestamp); if (!gIMMgr->isDNDMessageSend(session_id)) { @@ -592,7 +594,8 @@ void LLIMProcessing::processNewMessage(LLUUID from_id, parent_estate_id, region_id, position, - region_message); + region_message, + timestamp); } else { @@ -1111,7 +1114,9 @@ void LLIMProcessing::processNewMessage(LLUUID from_id, IM_SESSION_INVITE, parent_estate_id, region_id, - position); + position, + false, // is_region_msg + timestamp); } else { @@ -1131,12 +1136,14 @@ void LLIMProcessing::processNewMessage(LLUUID from_id, from_id, name, buffer, - IM_OFFLINE == offline, - ll_safe_string((char*)binary_bucket), + (IM_OFFLINE == offline), + ll_safe_string((char*)binary_bucket), // session name IM_SESSION_INVITE, parent_estate_id, region_id, - position); + position, + false, // is_region_msg + timestamp); } break; diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index 5039e15047..3125e80296 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -1,25 +1,25 @@ -/** +/** * @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$ */ @@ -77,11 +77,17 @@ 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"); @@ -112,7 +118,7 @@ void process_dnd_im(const LLSD& notification) LLUUID sessionID = data["SESSION_ID"].asUUID(); LLUUID fromID = data["FROM_ID"].asUUID(); - //re-create the IM session if needed + //re-create the IM session if needed //(when coming out of DND mode upon app restart) if(!gIMMgr->hasSession(sessionID)) { @@ -123,13 +129,13 @@ void process_dnd_im(const LLSD& notification) { name = av_name.getDisplayName(); } - - - LLIMModel::getInstance()->newSession(sessionID, - name, - IM_NOTHING_SPECIAL, - fromID, - false, + + + 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) } @@ -312,8 +318,8 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg) } else { - if (is_dnd_msg && (ON_TOP == conversations_floater_status || - NOT_ON_TOP == conversations_floater_status || + 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); @@ -372,7 +378,7 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg) } if (store_dnd_message) { - // If in DND mode, allow notification to be stored so upon DND exit + // 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() @@ -393,7 +399,7 @@ void startConfrenceCoro(std::string url, { LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("TwitterConnect", httpPolicy)); + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("ConferenceChatStart", httpPolicy)); LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); LLSD postData; @@ -433,7 +439,7 @@ void chatterBoxInvitationCoro(std::string url, LLUUID sessionId, LLIMMgr::EInvit { LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("TwitterConnect", httpPolicy)); + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("ConferenceInviteStart", httpPolicy)); LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); LLSD postData; @@ -511,33 +517,124 @@ void chatterBoxInvitationCoro(std::string url, LLUUID sessionId, LLIMMgr::EInvit } -void translateSuccess(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, const std::string& utf8_text, - bool log2file, std::string originalMsg, std::string expectLang, std::string translation, const std::string detected_language) +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 + // filter out non-interesting responses if (!translation.empty() && ((detected_language.empty()) || (expectLang != detected_language)) && (LLStringUtil::compareInsensitive(translation, originalMsg) != 0)) - { - message_txt += " (" + LLTranslate::removeNoTranslateTags(translation) + ")"; + { // Note - if this format changes, also fix code in addMessagesFromServerHistory() + message_txt += XL8_START_TAG + LLTranslate::removeNoTranslateTags(translation) + XL8_END_TAG; } - LLIMModel::getInstance()->processAddingMessage(session_id, from, from_id, message_txt, log2file); + // 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, - bool log2file, int status, const std::string err_msg) + 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 += " (" + msg + ")"; + 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; + } + + // 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; - LLIMModel::getInstance()->processAddingMessage(session_id, from, from_id, message_txt, log2file); + 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<LLFloaterIMSession>("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() +LLIMModel::LLIMModel() { addNewMsgCallback(boost::bind(&LLFloaterIMSession::newIMCallback, _1)); addNewMsgCallback(boost::bind(&on_new_message, _1)); @@ -582,25 +679,25 @@ LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string& 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. + // 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, + if (!LLIMModel::getInstance()->sendStartSession(mSessionID, mOtherParticipantID, mInitialTargetIDs, mType)) { //we don't need to wait for any responses @@ -682,7 +779,7 @@ void LLIMModel::LLIMSession::onVoiceChannelStateChanged(const LLVoiceChannel::ES 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); + LLIMModel::getInstance()->addMessage(mSessionID, SYSTEM_FROM, LLUUID::null, message); break; } case LLVoiceChannel::STATE_CONNECTED : @@ -786,14 +883,21 @@ void LLIMModel::LLIMSession::sessionInitReplyReceived(const LLUUID& new_session_ } } -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, bool is_region_msg) +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; - message["index"] = (LLSD::Integer)mMsgs.size(); + 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; @@ -814,7 +918,7 @@ void LLIMModel::LLIMSession::addMessage(const std::string& from, const LLUUID& f } } - mMsgs.push_front(message); + mMsgs.push_front(message); // Add most recent messages to the front of mMsgs if (mSpeakers && from_id.notNull()) { @@ -823,35 +927,281 @@ void LLIMModel::LLIMSession::addMessage(const std::string& from, const LLUUID& f } } -void LLIMModel::LLIMSession::addMessagesFromHistory(const std::list<LLSD>& history) +void LLIMModel::LLIMSession::addMessagesFromHistoryCache(const chat_message_list_t& history) { - std::list<LLSD>::const_iterator it = history.begin(); - while (it != history.end()) - { - const LLSD& msg = *it; + // 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); + } - 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; - std::string timestamp = msg[LL_IM_TIME]; - std::string text = msg[LL_IM_TEXT]; + // 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 + } +} - addMessage(from, from_id, text, timestamp, true); +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; + } - it++; - } + 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; @@ -860,26 +1210,29 @@ void LLIMModel::LLIMSession::chatFromLogFile(LLLogChat::ELogLineType type, const if (type == LLLogChat::LOG_LINE) { - self->addMessage("", LLSD(), msg["message"].asString(), "", true); + 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) { - self->addMessage(msg["from"].asString(), msg["from_id"].asUUID(), msg["message"].asString(), msg["time"].asString(), true); + 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") ) { - std::list<LLSD> chat_history; - - //involves parsing of a chat history + // read and parse chat history from local file + chat_message_list_t chat_history; LLLogChat::loadChatHistory(mHistoryFileName, chat_history, LLSD(), isGroupChat()); - addMessagesFromHistory(chat_history); - } + addMessagesFromHistoryCache(chat_history); + } } LLIMModel::LLIMSession* LLIMModel::findIMSession(const LLUUID& session_id) const @@ -899,7 +1252,7 @@ LLIMModel::LLIMSession* LLIMModel::findAdHocIMSession(const uuid_vec_t& ids) for (; it != mId2SessionMap.end(); ++it) { LLIMSession* session = (*it).second; - + if (!session->isAdHoc()) continue; if (session->mInitialTargetIDs.size() != num) continue; @@ -910,8 +1263,8 @@ LLIMModel::LLIMSession* LLIMModel::findAdHocIMSession(const uuid_vec_t& ids) { tmp_list.remove(*iter); ++iter; - - if (tmp_list.empty()) + + if (tmp_list.empty()) { break; } @@ -967,7 +1320,7 @@ void LLIMModel::LLIMSession::buildHistoryFileName() 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 + * 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. @@ -980,7 +1333,7 @@ void LLIMModel::LLIMSession::buildHistoryFileName() else { //in case of incoming ad-hoc sessions - mHistoryFileName = mName + " " + LLLogChat::timestamp(true) + " " + mSessionID.asString().substr(0, 4); + mHistoryFileName = mName + " " + LLLogChat::timestamp2LogString(0, true) + " " + mSessionID.asString().substr(0, 4); } } else if (isP2P()) // look up username to use as the log name @@ -1011,7 +1364,7 @@ void LLIMModel::LLIMSession::buildHistoryFileName() LLUUID LLIMModel::LLIMSession::generateHash(const std::set<LLUUID>& sorted_uuids) { LLMD5 md5_uuid; - + std::set<LLUUID>::const_iterator it = sorted_uuids.begin(); while (it != sorted_uuids.end()) { @@ -1073,7 +1426,7 @@ void LLIMModel::testMessages() 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); @@ -1083,7 +1436,7 @@ void LLIMModel::testMessages() } //session name should not be empty -bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, +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()) @@ -1125,7 +1478,7 @@ bool LLIMModel::clearSession(const LLUUID& session_id) return true; } -void LLIMModel::getMessages(const LLUUID& session_id, std::list<LLSD>& messages, int start_index, const bool sendNoUnreadMsgs) +void LLIMModel::getMessages(const LLUUID& session_id, chat_message_list_t& messages, int start_index, const bool sendNoUnreadMsgs) { getMessagesSilently(session_id, messages, start_index); @@ -1135,7 +1488,7 @@ void LLIMModel::getMessages(const LLUUID& session_id, std::list<LLSD>& messages, } } -void LLIMModel::getMessagesSilently(const LLUUID& session_id, std::list<LLSD>& messages, int start_index) +void LLIMModel::getMessagesSilently(const LLUUID& session_id, chat_message_list_t& messages, int start_index) { LLIMSession* session = findIMSession(session_id); if (!session) @@ -1146,7 +1499,7 @@ void LLIMModel::getMessagesSilently(const LLUUID& session_id, std::list<LLSD>& m int i = session->mMsgs.size() - start_index; - for (std::list<LLSD>::iterator iter = session->mMsgs.begin(); + for (chat_message_list_t::iterator iter = session->mMsgs.begin(); iter != session->mMsgs.end() && i > 0; iter++) { @@ -1168,7 +1521,7 @@ void LLIMModel::sendNoUnreadMessages(const LLUUID& session_id) session->mNumUnread = 0; session->mParticipantUnreadMessageCount = 0; - + LLSD arg; arg["session_id"] = session_id; arg["num_unread"] = 0; @@ -1176,17 +1529,23 @@ void LLIMModel::sendNoUnreadMessages(const LLUUID& session_id) 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) { - +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) + if (!session) { LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL; return false; } - session->addMessage(from, from_id, utf8_text, LLLogChat::timestamp(false), false, is_region_msg); //might want to add date separately + // 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; } @@ -1194,14 +1553,14 @@ bool LLIMModel::addToHistory(const LLUUID& session_id, const std::string& from, 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() && + if (!from_id.isNull() && LLAvatarNameCache::get(from_id, &av_name) && !av_name.isDisplayNameDefault()) - { + { from_name = av_name.getCompleteName(); } @@ -1216,36 +1575,37 @@ bool LLIMModel::logToFile(const std::string& file_name, const std::string& from, } void LLIMModel::proccessOnlineOfflineNotification( - const LLUUID& session_id, - const std::string& utf8_text) + 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 */) { - +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, log2file, utf8_text, from_lang, _1, _2), - boost::bind(&translateFailure, session_id, from, from_id, utf8_text, log2file, _1, _2)); + 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); + 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 /* = true */, bool is_region_msg /* = false */) + 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); - if (!session) return; + 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. @@ -1261,15 +1621,16 @@ void LLIMModel::processAddingMessage(const LLUUID& session_id, const std::string arg["message"] = utf8_text; arg["from"] = from; arg["from_id"] = from_id; - arg["time"] = LLLogChat::timestamp(false); + 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, bool is_region_msg) +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); @@ -1285,12 +1646,12 @@ LLIMModel::LLIMSession* LLIMModel::addMessageSilently(const LLUUID& session_id, from_name = SYSTEM_FROM; } - addToHistory(session_id, from_name, from_id, utf8_text, is_region_msg); + 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 @@ -1392,7 +1753,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); @@ -1423,7 +1784,7 @@ void LLIMModel::sendLeaveSession(const LLUUID& session_id, const LLUUID& other_p FALSE, gAgent.getSessionID(), other_participant_id, - name, + name, LLStringUtil::null, IM_ONLINE, IM_SESSION_LEAVE, @@ -1444,7 +1805,7 @@ void LLIMModel::sendMessage(const std::string& utf8_text, 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))) @@ -1452,7 +1813,7 @@ void LLIMModel::sendMessage(const std::string& utf8_text, // // 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. @@ -1509,7 +1870,7 @@ void LLIMModel::sendMessage(const std::string& utf8_text, } } - if((dialog == IM_NOTHING_SPECIAL) && + if((dialog == IM_NOTHING_SPECIAL) && (other_participant_id.notNull())) { // Do we have to replace the /me's here? @@ -1547,7 +1908,7 @@ void LLIMModel::sendMessage(const std::string& utf8_text, // 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) @@ -1629,7 +1990,7 @@ void start_deprecated_conference_chat( for(S32 i = 0; i < count; ++i) { LLUUID agent_id = agents_to_invite[i].asUUID(); - + memcpy(pos, &agent_id, UUID_BYTES); pos += UUID_BYTES; } @@ -1645,7 +2006,7 @@ void start_deprecated_conference_chat( bucket_size); gAgent.sendReliableMessage(); - + delete[] bucket; } @@ -1866,7 +2227,7 @@ 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; @@ -1889,7 +2250,7 @@ void LLCallDialogManager::onVoiceChannelChangedInt(const LLUUID &session_id) 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; @@ -1904,7 +2265,7 @@ void LLCallDialogManager::onVoiceChannelChangedInt(const LLUUID &session_id) if(ocd) { ocd->show(mCallDialogPayload); - } + } } } @@ -1937,7 +2298,7 @@ void LLCallDialogManager::onVoiceChannelStateChangedInt(const LLVoiceChannel::ES 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) @@ -1966,7 +2327,7 @@ void LLCallDialogManager::onVoiceChannelStateChangedInt(const LLVoiceChannel::ES if(ocd) { ocd->show(mCallDialogPayload); - } + } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1992,9 +2353,9 @@ BOOL LLCallDialog::postBuild() { if (!LLDockableFloater::postBuild() || !gToolBarView) return FALSE; - + dockToToolbarButton("speak"); - + return TRUE; } @@ -2012,20 +2373,20 @@ LLDockControl::DocAt LLCallDialog::getDockControlPos(const std::string& toolbarB { 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; } @@ -2040,7 +2401,7 @@ LLCallDialog(payload) if(instance && instance->getVisible()) { instance->onCancel(instance); - } + } } void LLCallDialog::draw() @@ -2097,7 +2458,7 @@ bool LLCallDialog::lifetimeHasExpired() if (mLifetimeTimer.getStarted()) { F32 elapsed_time = mLifetimeTimer.getElapsedTimeF32(); - if (elapsed_time > mLifetime) + if (elapsed_time > mLifetime) { return true; } @@ -2139,7 +2500,7 @@ void LLOutgoingCallDialog::show(const LLSD& key) } else { - getChild<LLUICtrl>("leaving")->setTextArg("[CURRENT_CHAT]", getString("localchat")); + getChild<LLUICtrl>("leaving")->setTextArg("[CURRENT_CHAT]", getString("localchat")); } if (!mPayload["disconnected_channel_name"].asString().empty()) @@ -2163,7 +2524,7 @@ void LLOutgoingCallDialog::show(const LLSD& key) { 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; @@ -2219,7 +2580,7 @@ void LLOutgoingCallDialog::show(const LLSD& key) { const std::string& nearby_str = mPayload["ended_by_agent"] ? NEARBY_P2P_BY_AGENT : NEARBY_P2P_BY_OTHER; getChild<LLTextBox>(nearby_str)->setVisible(true); - } + } else { getChild<LLTextBox>("nearby")->setVisible(true); @@ -2253,7 +2614,7 @@ void LLOutgoingCallDialog::onCancel(void* user_data) LLUUID session_id = self->mPayload["session_id"].asUUID(); gIMMgr->endCall(session_id); - + self->closeFloater(); } @@ -2338,7 +2699,7 @@ BOOL LLIncomingCallDialog::postBuild() 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)) @@ -2513,7 +2874,7 @@ void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload 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: @@ -2531,17 +2892,17 @@ void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload if (LLAvatarNameCache::get(caller_id, &av_name)) { correct_session_name = av_name.getCompleteName(); - correct_session_name.append(ADHOC_NAME_SUFFIX); + correct_session_name.append(ADHOC_NAME_SUFFIX); } } LL_INFOS("IMVIEW") << "Corrected session name is " << correct_session_name << LL_ENDL; break; - default: + 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( @@ -2553,7 +2914,7 @@ void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload boost::bind(&chatterBoxInvitationCoro, url, session_id, inv_type)); - // send notification message to the corresponding chat + // 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; @@ -2588,7 +2949,7 @@ void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload data["session-id"] = session_id; LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, data, - "Invitation declined", + "Invitation declined", "Invitation decline failed."); } } @@ -2608,7 +2969,7 @@ bool inviteUserResponse(const LLSD& notification, const LLSD& response) EInstantMessage type = (EInstantMessage)payload["type"].asInteger(); LLIMMgr::EInvitationType inv_type = (LLIMMgr::EInvitationType)payload["inv_type"].asInteger(); S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - switch(option) + switch(option) { case 0: // accept { @@ -2652,7 +3013,7 @@ bool inviteUserResponse(const LLSD& notification, const LLSD& response) } } /* FALLTHROUGH */ - + case 1: // decline { if (type == IM_SESSION_P2P_INVITE) @@ -2668,8 +3029,8 @@ bool inviteUserResponse(const LLSD& notification, const LLSD& response) LLSD data; data["method"] = "decline invitation"; data["session-id"] = session_id; - LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, data, - "Invitation declined.", + LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, data, + "Invitation declined.", "Invitation decline failed."); } } @@ -2678,7 +3039,7 @@ bool inviteUserResponse(const LLSD& notification, const LLSD& response) gIMMgr->clearPendingInvitation(session_id); break; } - + return false; } @@ -2694,7 +3055,7 @@ LLIMMgr::LLIMMgr() LLIMModel::getInstance()->addNewMsgCallback(boost::bind(&LLFloaterIMSession::sRemoveTypingIndicator, _1)); } -// Add a message to a session. +// Add a message to a session. void LLIMMgr::addMessage( const LLUUID& session_id, const LLUUID& target_id, @@ -2706,7 +3067,8 @@ void LLIMMgr::addMessage( U32 parent_estate_id, const LLUUID& region_id, const LLVector3& position, - bool is_region_msg) + bool is_region_msg, + U32 timestamp) // May be zero { LLUUID other_participant_id = target_id; @@ -2793,6 +3155,14 @@ void LLIMMgr::addMessage( return; } + // Fetch group chat history, enabled by default. + if (gSavedPerAccountSettings.getBOOL("FetchGroupChatHistory")) + { + std::string chat_url = gAgent.getRegion()->getCapability("ChatSessionRequest"); + LLCoros::instance().launch("chatterBoxHistoryCoro", + boost::bind(&chatterBoxHistoryCoro, chat_url, session_id, from, msg, timestamp)); + } + //Play sound for new conversations if (!skip_message & !gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNewConversation") == TRUE)) { @@ -2808,7 +3178,7 @@ void LLIMMgr::addMessage( 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); + LLIMModel::instance().addMessage(new_session_id, from, other_participant_id, msg, true, is_region_msg, timestamp); } // Open conversation floater if offline messages are present @@ -2823,7 +3193,7 @@ void LLIMMgr::addMessage( 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()) { @@ -2863,7 +3233,7 @@ void LLIMMgr::addSystemMessage(const LLUUID& session_id, const std::string& mess S32 LLIMMgr::getNumberOfUnreadIM() { std::map<LLUUID, LLIMModel::LLIMSession*>::iterator it; - + S32 num = 0; for(it = LLIMModel::getInstance()->mId2SessionMap.begin(); it != LLIMModel::getInstance()->mId2SessionMap.end(); ++it) { @@ -2890,7 +3260,7 @@ void LLIMMgr::autoStartCallOnStartup(const LLUUID& session_id) { LLIMModel::LLIMSession *session = LLIMModel::getInstance()->findIMSession(session_id); if (!session) return; - + if (session->mSessionInitialized) { startCall(session_id); @@ -2898,7 +3268,7 @@ void LLIMMgr::autoStartCallOnStartup(const LLUUID& session_id) else { session->mStartCallOnInitialize = true; - } + } } LLUUID LLIMMgr::addP2PSession(const std::string& name, @@ -2935,7 +3305,7 @@ LLUUID LLIMMgr::addSession( return session_id; } -// Adds a session using the given session_id. If the session already exists +// 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, @@ -2998,9 +3368,9 @@ LLUUID LLIMMgr::addSession( //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); @@ -3030,7 +3400,7 @@ bool LLIMMgr::leaveSession(const LLUUID& session_id) void LLIMMgr::removeSession(const LLUUID& session_id) { llassert_always(hasSession(session_id)); - + clearPendingInvitation(session_id); clearPendingAgentListUpdates(session_id); @@ -3042,9 +3412,9 @@ void LLIMMgr::removeSession(const LLUUID& session_id) } void LLIMMgr::inviteToSession( - const LLUUID& session_id, - const std::string& session_name, - const LLUUID& caller_id, + const LLUUID& session_id, + const std::string& session_name, + const LLUUID& caller_id, const std::string& caller_name, EInstantMessage type, EInvitationType inv_type, @@ -3161,22 +3531,22 @@ void LLIMMgr::inviteToSession( { if (caller_name.empty()) { - LLAvatarNameCache::get(caller_id, + 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 + + // 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(); } } @@ -3373,7 +3743,7 @@ bool LLIMMgr::startCall(const LLUUID& session_id, LLVoiceChannel::EDirection dir { LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(session_id); if (!voice_channel) return false; - + voice_channel->setCallDirection(direction); voice_channel->activate(); return true; @@ -3491,7 +3861,7 @@ void LLIMMgr::noteMutedUsers(const LLUUID& session_id, if(count > 0) { LLIMModel* im_model = LLIMModel::getInstance(); - + for(S32 i = 0; i < count; ++i) { if( ml->isMuted(ids.at(i)) ) @@ -3569,7 +3939,15 @@ public: 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); @@ -3698,7 +4076,7 @@ public: LLUUID session_id = message_params["id"].asUUID(); std::vector<U8> bin_bucket = message_params["data"]["binary_bucket"].asBinary(); U8 offline = (U8)message_params["offline"].asInteger(); - + time_t timestamp = (time_t) message_params["timestamp"].asInteger(); @@ -3735,7 +4113,9 @@ public: IM_SESSION_INVITE, message_params["parent_estate_id"].asInteger(), message_params["region_id"].asUUID(), - ll_vector3_from_sd(message_params["position"])); + ll_vector3_from_sd(message_params["position"]), + false, // is_region_message + timestamp); if (LLMuteList::getInstance()->isMuted(from_id, name, LLMute::flagTextChat)) { @@ -3761,8 +4141,8 @@ public: } gIMMgr->inviteToSession( - input["body"]["session_id"].asUUID(), - input["body"]["session_name"].asString(), + input["body"]["session_id"].asUUID(), + input["body"]["session_name"].asString(), input["body"]["from_id"].asUUID(), input["body"]["from_name"].asString(), IM_SESSION_INVITE, @@ -3771,8 +4151,8 @@ public: else if ( input["body"].has("immediate") ) { gIMMgr->inviteToSession( - input["body"]["session_id"].asUUID(), - input["body"]["session_name"].asString(), + input["body"]["session_id"].asUUID(), + input["body"]["session_name"].asString(), input["body"]["from_id"].asUUID(), input["body"]["from_name"].asString(), IM_SESSION_INVITE, diff --git a/indra/newview/llimview.h b/indra/newview/llimview.h index 5e99cc7fca..946eb02f26 100644 --- a/indra/newview/llimview.h +++ b/indra/newview/llimview.h @@ -42,6 +42,7 @@ class LLAvatarName; class LLFriendObserver; class LLCallDialogManager; class LLIMSpeakerMgr; + /** * Timeout Timer for outgoing Ad-Hoc/Group IM sessions which being initialized by the server */ @@ -63,11 +64,14 @@ private: class LLIMModel : public LLSingleton<LLIMModel> { LLSINGLETON(LLIMModel); + public: - struct LLIMSession : public boost::signals2::trackable + typedef std::list<LLSD> chat_message_list_t; + + struct LLIMSession : public boost::signals2::trackable { - typedef enum e_session_type + typedef enum e_session_type { // for now we have 4 predefined types for a session P2P_SESSION, GROUP_SESSION, @@ -75,15 +79,23 @@ public: NONE_SESSION, } SType; - LLIMSession(const LLUUID& session_id, const std::string& name, + 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); virtual ~LLIMSession(); void sessionInitReplyReceived(const LLUUID& new_session_id); - void addMessagesFromHistory(const std::list<LLSD>& history); - void addMessage(const std::string& from, const LLUUID& from_id, const std::string& utf8_text, const std::string& time, const bool is_history = false, bool is_region_msg = false); - void onVoiceChannelStateChanged(const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state, const LLVoiceChannel::EDirection& direction); - + void addMessagesFromHistoryCache(const std::list<LLSD>& history); // From local file + void addMessagesFromServerHistory(const LLSD& history, const std::string& target_from, const std::string& target_message, U32 timestamp); // From chat server + void addMessage(const std::string& from, + const LLUUID& from_id, + const std::string& utf8_text, + const std::string& time, + const bool is_history, + const bool is_region_msg, + U32 timestamp); + + void onVoiceChannelStateChanged(const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state, const LLVoiceChannel::EDirection& direction); + /** @deprecated */ static void chatFromLogFile(LLLogChat::ELogLineType type, const LLSD& msg, void* userdata); @@ -112,6 +124,10 @@ public: uuid_vec_t mInitialTargetIDs; std::string mHistoryFileName; + // Saved messages from the last minute of history read from the local group chat cache file + std::string mLastHistoryCacheDateTime; + chat_message_list_t mLastHistoryCacheMsgs; + // connection to voice channel state change signal boost::signals2::connection mVoiceChannelStateChangeConnection; @@ -121,7 +137,7 @@ public: // does include all incoming messages S32 mNumUnread; - std::list<LLSD> mMsgs; + chat_message_list_t mMsgs; LLVoiceChannel* mVoiceChannel; LLIMSpeakerMgr* mSpeakers; @@ -208,14 +224,27 @@ public: * and also saved into a file if log2file is specified. * It sends new message signal for each added message. */ - void 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); - void processAddingMessage(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); + void addMessage(const LLUUID& session_id, + const std::string& from, + const LLUUID& other_participant_id, + const std::string& utf8_text, + bool log2file = true, + bool is_region_msg = false, + U32 time_stamp = 0); + + void 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); /** * Similar to addMessage(...) above but won't send a signal about a new message added */ - LLIMModel::LLIMSession* 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); + LLIMModel::LLIMSession* 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); /** * Add a system message to an IM Model @@ -223,15 +252,15 @@ public: void proccessOnlineOfflineNotification(const LLUUID& session_id, const std::string& utf8_text); /** - * Get a session's name. - * For a P2P chat - it's an avatar's name, + * Get a session's name. + * For a P2P chat - it's an avatar's name, * For a group chat - it's a group's name * For an incoming ad-hoc chat - is received from the server and is in a from of "<Avatar's name> Conference" * It is updated in LLIMModel::LLIMSession's constructor to localize the "Conference". */ const std::string getName(const LLUUID& session_id) const; - /** + /** * Get number of unread messages in a session with session_id * Returns -1 if the session with session_id doesn't exist */ @@ -283,7 +312,7 @@ public: bool logToFile(const std::string& file_name, const std::string& from, const LLUUID& from_id, const std::string& utf8_text); private: - + /** * Populate supplied std::list with messages starting from index specified by start_index without * emitting no unread messages signal. @@ -293,7 +322,7 @@ private: /** * Add message to a list of message associated with session specified by session_id */ - bool addToHistory(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, const std::string& utf8_text, bool is_region_msg = false); + bool addToHistory(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, const std::string& utf8_text, bool is_region_msg, U32 timestamp); }; @@ -335,7 +364,8 @@ public: U32 parent_estate_id = 0, const LLUUID& region_id = LLUUID::null, const LLVector3& position = LLVector3::zero, - bool is_region_msg = false); + bool is_region_msg = false, + U32 timestamp = 0); void addSystemMessage(const LLUUID& session_id, const std::string& message_name, const LLSD& args); diff --git a/indra/newview/lllogchat.cpp b/indra/newview/lllogchat.cpp index fb9885b454..ba82ff0b0f 100644 --- a/indra/newview/lllogchat.cpp +++ b/indra/newview/lllogchat.cpp @@ -62,6 +62,7 @@ const S32 LOG_RECALL_SIZE = 2048; const std::string LL_IM_TIME("time"); +const std::string LL_IM_DATE_TIME("datetime"); const std::string LL_IM_TEXT("message"); const std::string LL_IM_FROM("from"); const std::string LL_IM_FROM_ID("from_id"); @@ -133,14 +134,14 @@ void append_to_last_message(std::list<LLSD>& messages, const std::string& line) messages.back()[LL_IM_TEXT] = im_text; } -std::string remove_utf8_bom(const char* buf) +const char* remove_utf8_bom(const char* buf) { - std::string res(buf); - if (res[0] == (char)0xEF && res[1] == (char)0xBB && res[2] == (char)0xBF) - { - res.erase(0, 3); + const char* start = buf; + if (start[0] == (char)0xEF && start[1] == (char)0xBB && start[2] == (char)0xBF) + { // If string starts with the magic bytes, return pointer after it. + start += 3; } - return res; + return start; } class LLLogChatTimeScanner: public LLSingleton<LLLogChatTimeScanner> @@ -315,7 +316,7 @@ std::string LLLogChat::cleanFileName(std::string filename) return filename; } -std::string LLLogChat::timestamp(bool withdate) +std::string LLLogChat::timestamp2LogString(U32 timestamp, bool withdate) { std::string timeStr; if (withdate) @@ -333,7 +334,14 @@ std::string LLLogChat::timestamp(bool withdate) } LLSD substitution; - substitution["datetime"] = (S32)time_corrected(); + if (timestamp == 0) + { + substitution["datetime"] = (S32)time_corrected(); + } + else + { // timestamp is correct utc already + substitution["datetime"] = (S32)timestamp; + } LLStringUtil::format (timeStr, substitution); return timeStr; @@ -355,7 +363,7 @@ void LLLogChat::saveHistory(const std::string& filename, llassert(tmp_filename.size()); return; } - + llofstream file(LLLogChat::makeLogFileName(filename).c_str(), std::ios_base::app); if (!file.is_open()) { @@ -366,7 +374,7 @@ void LLLogChat::saveHistory(const std::string& filename, LLSD item; if (gSavedPerAccountSettings.getBOOL("LogTimestamp")) - item["time"] = LLLogChat::timestamp(gSavedPerAccountSettings.getBOOL("LogTimestampDate")); + item["time"] = LLLogChat::timestamp2LogString(0, gSavedPerAccountSettings.getBOOL("LogTimestampDate")); item["from_id"] = from_id; item["message"] = line; @@ -374,7 +382,7 @@ void LLLogChat::saveHistory(const std::string& filename, //adding "Second Life:" for all system messages to make chat log history parsing more reliable if (from.empty() && from_id.isNull()) { - item["from"] = SYSTEM_FROM; + item["from"] = SYSTEM_FROM; } else { @@ -393,37 +401,60 @@ void LLLogChat::loadChatHistory(const std::string& file_name, std::list<LLSD>& m { if (file_name.empty()) { - LL_WARNS("LLLogChat::loadChatHistory") << "Session name is Empty!" << LL_ENDL; + LL_WARNS("LLLogChat::loadChatHistory") << "Local history file name is empty!" << LL_ENDL; return ; } bool load_all_history = load_params.has("load_all_history") ? load_params["load_all_history"].asBoolean() : false; - LLFILE* fptr = LLFile::fopen(LLLogChat::makeLogFileName(file_name), "r");/*Flawfinder: ignore*/ + // Stat the file to find it and get the last history entry time + llstat stat_data; + + std::string log_file_name = LLLogChat::makeLogFileName(file_name); + LL_DEBUGS("ChatHistory") << "First attempt to stat chat history file " << log_file_name << LL_ENDL; + + S32 no_stat = LLFile::stat(log_file_name, &stat_data); + + if (no_stat) + { + if (is_group) + { + std::string old_name(file_name); + old_name.erase(old_name.size() - GROUP_CHAT_SUFFIX.size()); // trim off " (group)" + log_file_name = LLLogChat::makeLogFileName(old_name); + LL_DEBUGS("ChatHistory") << "Attempting to stat adjusted chat history file " << log_file_name << LL_ENDL; + no_stat = LLFile::stat(log_file_name, &stat_data); + if (!no_stat) + { // Found it without "(group)", copy to new naming style. We already have the mod time in stat_data + log_file_name = LLLogChat::makeLogFileName(file_name); + LL_DEBUGS("ChatHistory") << "Attempt to stat copied history file " << log_file_name << LL_ENDL; + LLFile::copy(LLLogChat::makeLogFileName(old_name), log_file_name); + } + } + if (no_stat) + { + log_file_name = LLLogChat::oldLogFileName(file_name); + LL_DEBUGS("ChatHistory") << "Attempt to stat old history file name " << log_file_name << LL_ENDL; + no_stat = LLFile::stat(log_file_name, &stat_data); + if (no_stat) + { + LL_DEBUGS("ChatHistory") << "No previous conversation log file found for " << file_name << LL_ENDL; + return; //No previous conversation with this name. + } + } + } + + // If we got here, we managed to stat the file. + // Open the file to read + LLFILE* fptr = LLFile::fopen(log_file_name, "r"); /*Flawfinder: ignore*/ if (!fptr) - { - if (is_group) - { - std::string old_name(file_name); - old_name.erase(old_name.size() - GROUP_CHAT_SUFFIX.size()); - fptr = LLFile::fopen(LLLogChat::makeLogFileName(old_name), "r"); - if (fptr) - { - fclose(fptr); - LLFile::copy(LLLogChat::makeLogFileName(old_name), LLLogChat::makeLogFileName(file_name)); - } - fptr = LLFile::fopen(LLLogChat::makeLogFileName(file_name), "r"); - } - if (!fptr) - { - fptr = LLFile::fopen(LLLogChat::oldLogFileName(file_name), "r");/*Flawfinder: ignore*/ - if (!fptr) - { - return; //No previous conversation with this name. - } - } + { // Ok, this is strange but not really tragic in the big picture of things + LL_WARNS("ChatHistory") << "Unable to read file " << log_file_name << " after stat was successful" << LL_ENDL; + return; } + S32 save_num_messages = messages.size(); + char buffer[LOG_RECALL_SIZE]; /*Flawfinder: ignore*/ char *bptr; S32 len; @@ -441,6 +472,7 @@ void LLLogChat::loadChatHistory(const std::string& file_name, std::list<LLSD>& m while (fgets(buffer, LOG_RECALL_SIZE, fptr) && !feof(fptr)) { len = strlen(buffer) - 1; /*Flawfinder: ignore*/ + // backfill any end of line characters with nulls for (bptr = (buffer + len); (*bptr == '\n' || *bptr == '\r') && bptr>buffer; bptr--) *bptr='\0'; if (firstline) @@ -473,6 +505,10 @@ void LLLogChat::loadChatHistory(const std::string& file_name, std::list<LLSD>& m } } fclose(fptr); + + LL_DEBUGS("ChatHistory") << "Read " << (messages.size() - save_num_messages) + << " messages of chat history from " << log_file_name + << " file mod time " << (F64)stat_data.st_mtime << LL_ENDL; } bool LLLogChat::historyThreadsFinished(LLUUID session_id) @@ -837,7 +873,8 @@ bool LLLogChat::isTranscriptFileFound(std::string fullname) { //matching a timestamp boost::match_results<std::string::const_iterator> matches; - if (ll_regex_match(remove_utf8_bom(buffer), matches, TIMESTAMP)) + std::string line(remove_utf8_bom(buffer)); + if (ll_regex_match(line, matches, TIMESTAMP)) { result = true; } @@ -847,7 +884,7 @@ bool LLLogChat::isTranscriptFileFound(std::string fullname) return result; } -//*TODO mark object's names in a special way so that they will be distinguishable form avatar name +//*TODO mark object's names in a special way so that they will be distinguishable form avatar name //which are more strict by its nature (only firstname and secondname) //Example, an object's name can be written like "Object <actual_object's_name>" void LLChatLogFormatter::format(const LLSD& im, std::ostream& ostr) const @@ -865,7 +902,7 @@ void LLChatLogFormatter::format(const LLSD& im, std::ostream& ostr) const ostr << '[' << timestamp << ']' << TWO_SPACES; } - //*TODO mark object's names in a special way so that they will be distinguishable form avatar name + //*TODO mark object's names in a special way so that they will be distinguishable from avatar name //which are more strict by its nature (only firstname and secondname) //Example, an object's name can be written like "Object <actual_object's_name>" if (im[LL_IM_FROM].isDefined()) @@ -928,7 +965,9 @@ bool LLChatLogParser::parse(std::string& raw, LLSD& im, const LLSD& parse_params timestamp.erase(0, 1); timestamp.erase(timestamp.length()-1, 1); - if (cut_off_todays_date) + im[LL_IM_DATE_TIME] = timestamp; // Retain full date-time for merging chat histories + + if (cut_off_todays_date) { LLLogChatTimeScanner::instance().checkAndCutOffDate(timestamp); } @@ -936,9 +975,9 @@ bool LLChatLogParser::parse(std::string& raw, LLSD& im, const LLSD& parse_params im[LL_IM_TIME] = timestamp; } else - { - //timestamp is optional - im[LL_IM_TIME] = ""; + { //timestamp is optional + im[LL_IM_DATE_TIME] = ""; + im[LL_IM_TIME] = ""; } bool has_stuff = matches[IDX_STUFF].matched; diff --git a/indra/newview/lllogchat.h b/indra/newview/lllogchat.h index c4b61ee716..5dce8ab1d2 100644 --- a/indra/newview/lllogchat.h +++ b/indra/newview/lllogchat.h @@ -92,7 +92,7 @@ public: LOG_END }; - static std::string timestamp(bool withdate = false); + static std::string timestamp2LogString(U32 timestamp, bool withdate); static std::string makeLogFileName(std::string(filename)); static void renameLogFile(const std::string& old_filename, const std::string& new_filename); /** @@ -201,6 +201,7 @@ extern const std::string GROUP_CHAT_SUFFIX; // LLSD map lookup constants extern const std::string LL_IM_TIME; //("time"); +extern const std::string LL_IM_DATE_TIME; //("datetime"); extern const std::string LL_IM_TEXT; //("message"); extern const std::string LL_IM_FROM; //("from"); extern const std::string LL_IM_FROM_ID; //("from_id"); diff --git a/indra/newview/llnotificationhandlerutil.cpp b/indra/newview/llnotificationhandlerutil.cpp index 39a0b9b50e..85adfaab55 100644 --- a/indra/newview/llnotificationhandlerutil.cpp +++ b/indra/newview/llnotificationhandlerutil.cpp @@ -275,7 +275,7 @@ void LLHandlerUtil::addNotifPanelToIM(const LLNotificationPtr& notification) LLSD offer; offer["notification_id"] = notification->getID(); offer["from"] = SYSTEM_FROM; - offer["time"] = LLLogChat::timestamp(false); + offer["time"] = LLLogChat::timestamp2LogString(0, false); // Use current time offer["index"] = (LLSD::Integer)session->mMsgs.size(); session->mMsgs.push_front(offer); |