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/
master
simon 2023-02-13 15:19:27 -08:00
parent f60f12d94e
commit f257ee7d3c
6 changed files with 692 additions and 235 deletions

View File

@ -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;

File diff suppressed because it is too large Load Diff

View File

@ -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);

View File

@ -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;

View File

@ -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");

View File

@ -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);