diff options
| author | simon <simon@lindenlab.com> | 2023-02-13 15:19:27 -0800 | 
|---|---|---|
| committer | simon <simon@lindenlab.com> | 2023-02-13 15:19:27 -0800 | 
| commit | f257ee7d3c7464b8aee3bf8c5f5c84cd278836cc (patch) | |
| tree | 2981d9b20321e2502d20568c85606415fd8ffb33 /indra/newview | |
| parent | f60f12d94ebe3862f5b1eef55795dabc0ba72693 (diff) | |
SL-18268 - Viewer update to read group chat history.
Feature described at https://community.secondlife.com/blogs/entry/12652-coming-soon-to-a-viewer-near-you-group-chat-history/
Diffstat (limited to 'indra/newview')
| -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); | 
