DN-181 Chat & IM logs saved in unreadable .llsd instead of .txt

meow-7.2.2
Leyla Farazha 2010-10-22 17:41:06 -07:00
parent 48e6f36f98
commit cb5d8d1a92
7 changed files with 288 additions and 77 deletions

View File

@ -38,6 +38,8 @@
#include "message.h"
#include "llmemtype.h"
#include <boost/regex.hpp>
// llsd serialization constants
static const std::string AGENTS("agents");
static const std::string GROUPS("groups");
@ -551,6 +553,31 @@ std::string LLCacheName::buildUsername(const std::string& full_name)
return full_name;
}
//static
std::string LLCacheName::buildLegacyName(const std::string& complete_name)
{
boost::regex complete_name_regex("(.+)( \\()([A-Za-z]+)(.[A-Za-z]+)*(\\))");
boost::match_results<std::string::const_iterator> name_results;
if (!boost::regex_match(complete_name, name_results, complete_name_regex)) return complete_name;
std::string legacy_name = name_results[3];
// capitalize the first letter
std::string cap_letter = legacy_name.substr(0, 1);
LLStringUtil::toUpper(cap_letter);
legacy_name = cap_letter + legacy_name.substr(1);
if (name_results[4].matched)
{
std::string last_name = name_results[4];
std::string cap_letter = last_name.substr(1, 1);
LLStringUtil::toUpper(cap_letter);
last_name = cap_letter + last_name.substr(2);
legacy_name = legacy_name + " " + last_name;
}
return legacy_name;
}
// This is a little bit kludgy. LLCacheNameCallback is a slot instead of a function pointer.
// The reason it is a slot is so that the legacy get() function below can bind an old callback
// and pass it as a slot. The reason it isn't a boost::function is so that trackable behavior

View File

@ -90,6 +90,12 @@ public:
// "Random Linden" -> "random.linden"
static std::string buildUsername(const std::string& name);
// Converts a complete display name to a legacy name
// if possible, otherwise returns the input
// "Alias (random.linden)" -> "Random Linden"
// "Something random" -> "Something random"
static std::string buildLegacyName(const std::string& name);
// If available, this method copies the group name into the string
// provided. The caller must allocate at least
// DB_GROUP_NAME_BUF_SIZE characters. If not available, this

View File

@ -54,6 +54,7 @@
#include "llviewertexteditor.h"
#include "llworld.h"
#include "lluiconstants.h"
#include "llstring.h"
#include "llviewercontrol.h"
@ -260,7 +261,7 @@ public:
if((chat.mFromID.isNull() && chat.mFromName.empty()) || chat.mFromName == SYSTEM_FROM && chat.mFromID.isNull())
{
mSourceType = CHAT_SOURCE_SYSTEM;
}
}
mUserNameFont = style_params.font();
LLTextBox* user_name = getChild<LLTextBox>("user_name");
@ -268,14 +269,14 @@ public:
user_name->setColor(style_params.color());
if (chat.mFromName.empty()
|| mSourceType == CHAT_SOURCE_SYSTEM
|| mAvatarID.isNull())
|| mSourceType == CHAT_SOURCE_SYSTEM)
{
mFrom = LLTrans::getString("SECOND_LIFE");
user_name->setValue(mFrom);
updateMinUserNameWidth();
}
else if (mSourceType == CHAT_SOURCE_AGENT
&& !mAvatarID.isNull()
&& chat.mChatStyle != CHAT_STYLE_HISTORY)
{
// ...from a normal user, lookup the name and fill in later.
@ -288,7 +289,41 @@ public:
LLAvatarNameCache::get(mAvatarID,
boost::bind(&LLChatHistoryHeader::onAvatarNameCache, this, _1, _2));
}
else {
else if (chat.mChatStyle == CHAT_STYLE_HISTORY ||
mSourceType == CHAT_SOURCE_AGENT)
{
//if it's an avatar name with a username add formatting
S32 username_start = chat.mFromName.rfind(" (");
S32 username_end = chat.mFromName.rfind(')');
if (username_start != std::string::npos &&
username_end == (chat.mFromName.length() - 1))
{
mFrom = chat.mFromName.substr(0, username_start);
user_name->setValue(mFrom);
if (gSavedSettings.getBOOL("NameTagShowUsernames"))
{
std::string username = chat.mFromName.substr(username_start + 2);
username = username.substr(0, username.length() - 1);
LLStyle::Params style_params_name;
LLColor4 userNameColor = LLUIColorTable::instance().getColor("EmphasisColor");
style_params_name.color(userNameColor);
style_params_name.font.name("SansSerifSmall");
style_params_name.font.style("NORMAL");
style_params_name.readonly_color(userNameColor);
user_name->appendText(" - " + username, FALSE, style_params_name);
}
}
else
{
mFrom = chat.mFromName;
user_name->setValue(mFrom);
updateMinUserNameWidth();
}
}
else
{
// ...from an object, just use name as given
mFrom = chat.mFromName;
user_name->setValue(mFrom);
@ -367,7 +402,9 @@ public:
user_name->setValue( LLSD(av_name.mDisplayName ) );
user_name->setToolTip( av_name.mUsername );
if (gSavedSettings.getBOOL("NameTagShowUsernames") && LLAvatarNameCache::useDisplayNames())
if (gSavedSettings.getBOOL("NameTagShowUsernames") &&
LLAvatarNameCache::useDisplayNames() &&
!av_name.mIsDisplayNameDefault)
{
LLStyle::Params style_params_name;
LLColor4 userNameColor = LLUIColorTable::instance().getColor("EmphasisColor");

View File

@ -430,8 +430,9 @@ void LLIMModel::LLIMSession::addMessagesFromHistory(const std::list<LLSD>& histo
}
else
{
// Legacy chat logs only wrote the legacy name, not the agent_id
gCacheName->getUUID(from, from_id);
// convert it to a legacy name if we have a complete name
std::string legacy_name = gCacheName->buildLegacyName(from);
gCacheName->getUUID(legacy_name, from_id);
}
std::string timestamp = msg[IM_TIME];
@ -526,8 +527,15 @@ bool LLIMModel::LLIMSession::isOtherParticipantAvaline()
void LLIMModel::LLIMSession::onAvatarNameCache(const LLUUID& avatar_id, const LLAvatarName& av_name)
{
// if username is empty, display names isn't enabled, use the display name
mHistoryFileName = av_name.mUsername.empty() ? av_name.mDisplayName : av_name.mUsername;
if (av_name.getLegacyName().empty())
{
// if display names is off the legacy name will be the display name
mHistoryFileName = LLCacheName::cleanFullName(av_name.mDisplayName);
}
else
{
mHistoryFileName = LLCacheName::cleanFullName(av_name.getLegacyName());
}
}
void LLIMModel::LLIMSession::buildHistoryFileName()
@ -737,8 +745,18 @@ 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.getBOOL("LogInstantMessages"))
{
LLLogChat::saveHistory(file_name, from, from_id, utf8_text);
{
std::string from_name = from;
LLAvatarName av_name;
if (!from_id.isNull() &&
LLAvatarNameCache::get(from_id, &av_name) &&
!av_name.mIsDisplayNameDefault)
{
from_name = av_name.getCompleteName();
}
LLLogChat::saveHistory(file_name, from_name, from_id, utf8_text);
return true;
}
else

View File

@ -26,18 +26,13 @@
#include "llviewerprecompiledheaders.h"
#include "lllogchat.h"
// viewer includes
#include "llagent.h"
#include "llagentui.h"
#include "lllogchat.h"
#include "lltrans.h"
#include "llviewercontrol.h"
// library includes
#include "llchat.h"
#include "llinstantmessage.h"
#include "llsdserialize.h"
#include "llsingleton.h" // for LLSingleton
#include <boost/algorithm/string/trim.hpp>
@ -65,7 +60,6 @@ const std::string IM_TIME("time");
const std::string IM_TEXT("message");
const std::string IM_FROM("from");
const std::string IM_FROM_ID("from_id");
const std::string IM_SOURCE_TYPE("source_type");
const static std::string IM_SEPARATOR(": ");
const static std::string NEW_LINE("\n");
@ -93,7 +87,7 @@ const static boost::regex TIMESTAMP_AND_STUFF("^(\\[\\d{4}/\\d{1,2}/\\d{1,2}\\s+
* Regular expression suitable to match names like
* "You", "Second Life", "Igor ProductEngine", "Object", "Mega House"
*/
const static boost::regex NAME_AND_TEXT("(You:|Second Life:|[^\\s:]+\\s*[:]{1}|\\S+\\s+[^\\s:]+[:]{1})?(\\s*)(.*)");
const static boost::regex NAME_AND_TEXT("([^:]+[:]{1})?(\\s*)(.*)");
//is used to parse complex object names like "Xstreet SL Terminal v2.2.5 st"
const static std::string NAME_TEXT_DIVIDER(": ");
@ -190,8 +184,7 @@ std::string LLLogChat::makeLogFileName(std::string filename)
{
filename = cleanFileName(filename);
filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_ACCOUNT_CHAT_LOGS,filename);
// new files are llsd notation format
filename += ".llsd";
filename += ".txt";
return filename;
}
@ -240,18 +233,6 @@ void LLLogChat::saveHistory(const std::string& filename,
const std::string& from,
const LLUUID& from_id,
const std::string& line)
{
LLChat chat;
chat.mText = line;
chat.mFromName = from;
chat.mFromID = from_id;
// default to being from an agent
chat.mSourceType = CHAT_SOURCE_AGENT;
saveHistory(filename, chat);
}
//static
void LLLogChat::saveHistory(const std::string& filename, const LLChat& chat)
{
std::string tmp_filename = filename;
LLStringUtil::trim(tmp_filename);
@ -273,27 +254,89 @@ void LLLogChat::saveHistory(const std::string& filename, const LLChat& chat)
LLSD item;
if (gSavedPerAccountSettings.getBOOL("LogTimestamp"))
item[IM_TIME] = LLLogChat::timestamp(gSavedPerAccountSettings.getBOOL("LogTimestampDate"));
item["time"] = LLLogChat::timestamp(gSavedPerAccountSettings.getBOOL("LogTimestampDate"));
item[IM_FROM_ID] = chat.mFromID;
item[IM_TEXT] = chat.mText;
item[IM_SOURCE_TYPE] = chat.mSourceType;
item["from_id"] = from_id;
item["message"] = line;
//adding "Second Life:" for all system messages to make chat log history parsing more reliable
if (chat.mFromName.empty() && chat.mFromID.isNull())
if (from.empty() && from_id.isNull())
{
item[IM_FROM] = SYSTEM_FROM;
item["from"] = SYSTEM_FROM;
}
else
{
item[IM_FROM] = chat.mFromName;
item["from"] = from;
}
file << LLSDOStreamer<LLSDNotationFormatter>(item) << std::endl;
file << LLChatLogFormatter(item) << std::endl;
file.close();
}
void LLLogChat::loadHistory(const std::string& filename, void (*callback)(ELogLineType, const LLSD&, void*), void* userdata)
{
if(!filename.size())
{
llwarns << "Filename is Empty!" << llendl;
return ;
}
LLFILE* fptr = LLFile::fopen(makeLogFileName(filename), "r"); /*Flawfinder: ignore*/
if (!fptr)
{
callback(LOG_EMPTY, LLSD(), userdata);
return; //No previous conversation with this name.
}
else
{
char buffer[LOG_RECALL_SIZE]; /*Flawfinder: ignore*/
char *bptr;
S32 len;
bool firstline=TRUE;
if ( fseek(fptr, (LOG_RECALL_SIZE - 1) * -1 , SEEK_END) )
{ //File is smaller than recall size. Get it all.
firstline = FALSE;
if ( fseek(fptr, 0, SEEK_SET) )
{
fclose(fptr);
return;
}
}
while ( fgets(buffer, LOG_RECALL_SIZE, fptr) && !feof(fptr) )
{
len = strlen(buffer) - 1; /*Flawfinder: ignore*/
for ( bptr = (buffer + len); (*bptr == '\n' || *bptr == '\r') && bptr>buffer; bptr--) *bptr='\0';
if (!firstline)
{
LLSD item;
std::string line(buffer);
std::istringstream iss(line);
if (!LLChatLogParser::parse(line, item))
{
item["message"] = line;
callback(LOG_LINE, item, userdata);
}
else
{
callback(LOG_LLSD, item, userdata);
}
}
else
{
firstline = FALSE;
}
}
callback(LOG_END, LLSD(), userdata);
fclose(fptr);
}
}
void append_to_last_message(std::list<LLSD>& messages, const std::string& line)
{
if (!messages.size()) return;
@ -367,25 +410,53 @@ void LLLogChat::loadAllHistory(const std::string& file_name, std::list<LLSD>& me
fclose(fptr);
}
// static
bool LLChatLogParser::parse(const std::string& raw, LLSD& im)
//*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 writen like "Object <actual_object's_name>"
void LLChatLogFormatter::format(const LLSD& im, std::ostream& ostr) const
{
if (!im.isMap())
{
llwarning("invalid LLSD type of an instant message", 0);
return;
}
if (im[IM_TIME].isDefined())
{
std::string timestamp = im[IM_TIME].asString();
boost::trim(timestamp);
ostr << '[' << timestamp << ']' << TWO_SPACES;
}
//*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 writen like "Object <actual_object's_name>"
if (im[IM_FROM].isDefined())
{
std::string from = im[IM_FROM].asString();
boost::trim(from);
if (from.size())
{
ostr << from << IM_SEPARATOR;
}
}
if (im[IM_TEXT].isDefined())
{
std::string im_text = im[IM_TEXT].asString();
//multilined text will be saved with prepended spaces
boost::replace_all(im_text, NEW_LINE, NEW_LINE_SPACE_PREFIX);
ostr << im_text;
}
}
bool LLChatLogParser::parse(std::string& raw, LLSD& im)
{
if (!raw.length()) return false;
im = LLSD::emptyMap();
// In Viewer 2.1 we added UUID to chat/IM logging so we can look up
// display names
if (raw[0] == '{')
{
// ...this is a viewer 2.1, new-style LLSD notation format log
std::istringstream raw_stream(raw);
LLPointer<LLSDParser> parser = new LLSDNotationParser();
S32 count = parser->parse(raw_stream, im, raw.length());
// expect several map items per parsed line
return (count != LLSDParser::PARSE_FAILURE);
}
//matching a timestamp
boost::match_results<std::string::const_iterator> matches;
if (!boost::regex_match(raw, matches, TIMESTAMP_AND_STUFF)) return false;

View File

@ -41,21 +41,48 @@ public:
};
static std::string timestamp(bool withdate = false);
static std::string makeLogFileName(std::string(filename));
// Log a single line item to the appropriate chat file
static void saveHistory(const std::string& filename, const LLChat& chat);
// Prefer the above version - it saves more metadata about the item
static void saveHistory(const std::string& filename,
const std::string& from,
const LLUUID& from_id,
const std::string& line);
/** @deprecated @see loadAllHistory() */
static void loadHistory(const std::string& filename,
void (*callback)(ELogLineType, const LLSD&, void*),
void* userdata);
static void loadAllHistory(const std::string& file_name, std::list<LLSD>& messages);
private:
static std::string cleanFileName(std::string filename);
};
/**
* Formatter for the plain text chat log files
*/
class LLChatLogFormatter
{
public:
LLChatLogFormatter(const LLSD& im) : mIM(im) {}
virtual ~LLChatLogFormatter() {};
friend std::ostream& operator<<(std::ostream& str, const LLChatLogFormatter& formatter)
{
formatter.format(formatter.mIM, str);
return str;
}
protected:
/**
* Format an instant message to a stream
* Timestamps and sender names are required
* New lines of multilined messages are prepended with a space
*/
void format(const LLSD& im, std::ostream& ostr) const;
LLSD mIM;
};
/**
* Parser for the plain text chat log files
*/
@ -74,7 +101,7 @@ public:
*
* @return false if failed to parse mandatory data - message text
*/
static bool parse(const std::string& raw, LLSD& im);
static bool parse(std::string& raw, LLSD& im);
protected:
LLChatLogParser();
@ -86,6 +113,5 @@ extern const std::string IM_TIME; //("time");
extern const std::string IM_TEXT; //("message");
extern const std::string IM_FROM; //("from");
extern const std::string IM_FROM_ID; //("from_id");
extern const std::string IM_SOURCE_TYPE; //("source_type");
#endif

View File

@ -46,6 +46,8 @@
#include "llchathistory.h"
#include "llstylemap.h"
#include "llavatarnamecache.h"
#include "lldraghandle.h"
#include "llbottomtray.h"
@ -179,7 +181,21 @@ void LLNearbyChat::addMessage(const LLChat& chat,bool archive,const LLSD &args)
if (gSavedPerAccountSettings.getBOOL("LogNearbyChat"))
{
LLLogChat::saveHistory("chat", chat);
std::string from_name = chat.mFromName;
if (chat.mSourceType == CHAT_SOURCE_AGENT)
{
// if the chat is coming from an agent, log the complete name
LLAvatarName av_name;
LLAvatarNameCache::get(chat.mFromID, &av_name);
if (!av_name.mIsDisplayNameDefault)
{
from_name = av_name.getCompleteName();
}
}
LLLogChat::saveHistory("chat", from_name, chat.mFromID, chat.mText);
}
}
@ -248,11 +264,23 @@ void LLNearbyChat::processChatHistoryStyleUpdate(const LLSD& newvalue)
nearby_chat->updateChatHistoryStyle();
}
bool isTwoWordsName(const std::string& name)
bool isWordsName(const std::string& name)
{
//checking for a single space
S32 pos = name.find(' ', 0);
return std::string::npos != pos && name.rfind(' ', name.length()) == pos && 0 != pos && name.length()-1 != pos;
// checking to see if it's display name plus username in parentheses
S32 open_paren = name.find(" (", 0);
S32 close_paren = name.find(')', 0);
if (open_paren != std::string::npos &&
close_paren == name.length()-1)
{
return true;
}
else
{
//checking for a single space
S32 pos = name.find(' ', 0);
return std::string::npos != pos && name.rfind(' ', name.length()) == pos && 0 != pos && name.length()-1 != pos;
}
}
void LLNearbyChat::loadHistory()
@ -275,9 +303,10 @@ void LLNearbyChat::loadHistory()
from_id = msg[IM_FROM_ID].asUUID();
}
else
{
gCacheName->getUUID(from, from_id);
}
{
std::string legacy_name = gCacheName->buildLegacyName(from);
gCacheName->getUUID(legacy_name, from_id);
}
LLChat chat;
chat.mFromName = from;
@ -286,18 +315,15 @@ void LLNearbyChat::loadHistory()
chat.mTimeStr = msg[IM_TIME].asString();
chat.mChatStyle = CHAT_STYLE_HISTORY;
if (msg.has(IM_SOURCE_TYPE))
{
S32 source_type = msg[IM_SOURCE_TYPE].asInteger();
chat.mSourceType = (EChatSourceType)source_type;
}
else if (from_id.isNull() && SYSTEM_FROM == from)
chat.mSourceType = CHAT_SOURCE_AGENT;
if (from_id.isNull() && SYSTEM_FROM == from)
{
chat.mSourceType = CHAT_SOURCE_SYSTEM;
}
else if (from_id.isNull())
{
chat.mSourceType = isTwoWordsName(from) ? CHAT_SOURCE_UNKNOWN : CHAT_SOURCE_OBJECT;
chat.mSourceType = isWordsName(from) ? CHAT_SOURCE_UNKNOWN : CHAT_SOURCE_OBJECT;
}
addMessage(chat, true, do_not_log);