diff options
Diffstat (limited to 'indra/newview/lllogchat.cpp')
-rwxr-xr-x[-rw-r--r--] | indra/newview/lllogchat.cpp | 852 |
1 files changed, 680 insertions, 172 deletions
diff --git a/indra/newview/lllogchat.cpp b/indra/newview/lllogchat.cpp index 9adf374c71..7ddacf3033 100644..100755 --- a/indra/newview/lllogchat.cpp +++ b/indra/newview/lllogchat.cpp @@ -25,13 +25,16 @@ */ #include "llviewerprecompiledheaders.h" - +#include "llfloaterconversationpreview.h" #include "llagent.h" #include "llagentui.h" +#include "llavatarnamecache.h" #include "lllogchat.h" #include "lltrans.h" #include "llviewercontrol.h" +#include "lldiriterator.h" +#include "llfloaterimsessiontab.h" #include "llinstantmessage.h" #include "llsingleton.h" // for LLSingleton @@ -39,8 +42,10 @@ #include <boost/algorithm/string/replace.hpp> #include <boost/regex.hpp> #include <boost/regex/v4/match_results.hpp> +#include <boost/foreach.hpp> #if LL_MSVC +#pragma warning(push) // disable warning about boost::lexical_cast unreachable code // when it fails to parse the string #pragma warning (disable:4702) @@ -56,10 +61,11 @@ const S32 LOG_RECALL_SIZE = 2048; -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 LL_IM_TIME("time"); +const std::string LL_IM_TEXT("message"); +const std::string LL_IM_FROM("from"); +const std::string LL_IM_FROM_ID("from_id"); +const std::string LL_TRANSCRIPT_FILE_EXTENSION("txt"); const static std::string IM_SEPARATOR(": "); const static std::string NEW_LINE("\n"); @@ -82,6 +88,7 @@ const static std::string MULTI_LINE_PREFIX(" "); * Note: "You" was used as an avatar names in viewers of previous versions */ const static boost::regex TIMESTAMP_AND_STUFF("^(\\[\\d{4}/\\d{1,2}/\\d{1,2}\\s+\\d{1,2}:\\d{2}\\]\\s+|\\[\\d{1,2}:\\d{2}\\]\\s+)?(.*)$"); +const static boost::regex TIMESTAMP("^(\\[\\d{4}/\\d{1,2}/\\d{1,2}\\s+\\d{1,2}:\\d{2}\\]|\\[\\d{1,2}:\\d{2}\\]).*"); /** * Regular expression suitable to match names like @@ -114,6 +121,15 @@ const static int IDX_TEXT = 3; using namespace boost::posix_time; using namespace boost::gregorian; +void append_to_last_message(std::list<LLSD>& messages, const std::string& line) +{ + if (!messages.size()) return; + + std::string im_text = messages.back()[LL_IM_TEXT].asString(); + im_text.append(line); + messages.back()[LL_IM_TEXT] = im_text; +} + class LLLogChatTimeScanner: public LLSingleton<LLLogChatTimeScanner> { public: @@ -189,15 +205,22 @@ private: std::stringstream mTimeStream; }; +LLLogChat::save_history_signal_t * LLLogChat::sSaveHistorySignal = NULL; + +std::map<LLUUID,LLLoadHistoryThread *> LLLogChat::sLoadHistoryThreads; +std::map<LLUUID,LLDeleteHistoryThread *> LLLogChat::sDeleteHistoryThreads; +LLMutex* LLLogChat::sHistoryThreadsMutex = NULL; + + //static std::string LLLogChat::makeLogFileName(std::string filename) { /** - * Testing for in bound and out bound ad-hoc file names - * if it is then skip date stamping. - **/ - //LL_INFOS("") << "Befor:" << filename << LL_ENDL;/* uncomment if you want to verify step, delete on commit */ - boost::match_results<std::string::const_iterator> matches; + * Testing for in bound and out bound ad-hoc file names + * if it is then skip date stamping. + **/ + + boost::match_results<std::string::const_iterator> matches; bool inboundConf = boost::regex_match(filename, matches, INBOUND_CONFERENCE); bool outboundConf = boost::regex_match(filename, matches, OUTBOUND_CONFERENCE); if (!(inboundConf || outboundConf)) @@ -218,17 +241,17 @@ std::string LLLogChat::makeLogFileName(std::string filename) filename += dbuffer; } } - //LL_INFOS("") << "After:" << filename << LL_ENDL;/* uncomment if you want to verify step, delete on commit */ + filename = cleanFileName(filename); - filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_ACCOUNT_CHAT_LOGS,filename); - filename += ".txt"; - //LL_INFOS("") << "Full:" << filename << LL_ENDL;/* uncomment if you want to verify step, delete on commit */ + filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_ACCOUNT_CHAT_LOGS, filename); + filename += '.' + LL_TRANSCRIPT_FILE_EXTENSION; + return filename; } std::string LLLogChat::cleanFileName(std::string filename) { - std::string invalidChars = "\"\'\\/?*:.<>|"; + std::string invalidChars = "\"\'\\/?*:.<>|[]{}~"; // Cannot match glob or illegal filename chars std::string::size_type position = filename.find_first_of(invalidChars); while (position != filename.npos) { @@ -240,27 +263,24 @@ std::string LLLogChat::cleanFileName(std::string filename) std::string LLLogChat::timestamp(bool withdate) { - time_t utc_time; - utc_time = time_corrected(); - std::string timeStr; - LLSD substitution; - substitution["datetime"] = (S32) utc_time; - if (withdate) { - timeStr = "["+LLTrans::getString ("TimeYear")+"]/[" - +LLTrans::getString ("TimeMonth")+"]/[" - +LLTrans::getString ("TimeDay")+"] [" - +LLTrans::getString ("TimeHour")+"]:[" - +LLTrans::getString ("TimeMin")+"]"; + timeStr = "[" + LLTrans::getString ("TimeYear") + "]/[" + + LLTrans::getString ("TimeMonth") + "]/[" + + LLTrans::getString ("TimeDay") + "] [" + + LLTrans::getString ("TimeHour") + "]:[" + + LLTrans::getString ("TimeMin") + "]"; } else { timeStr = "[" + LLTrans::getString("TimeHour") + "]:[" - + LLTrans::getString ("TimeMin")+"]"; + + LLTrans::getString ("TimeMin")+"]"; } + LLSD substitution; + substitution["datetime"] = (S32)time_corrected(); + LLStringUtil::format (timeStr, substitution); return timeStr; } @@ -268,24 +288,24 @@ std::string LLLogChat::timestamp(bool withdate) //static void LLLogChat::saveHistory(const std::string& filename, - const std::string& from, - const LLUUID& from_id, - const std::string& line) + const std::string& from, + const LLUUID& from_id, + const std::string& line) { std::string tmp_filename = filename; LLStringUtil::trim(tmp_filename); if (tmp_filename.empty()) { std::string warn = "Chat history filename [" + filename + "] is empty!"; - llwarning(warn, 666); + LL_WARNS() << warn << LL_ENDL; llassert(tmp_filename.size()); return; } - llofstream file (LLLogChat::makeLogFileName(filename), std::ios_base::app); + llofstream file(LLLogChat::makeLogFileName(filename).c_str(), std::ios_base::app); if (!file.is_open()) { - llwarns << "Couldn't open chat history log! - " + filename << llendl; + LL_WARNS() << "Couldn't open chat history log! - " + filename << LL_ENDL; return; } @@ -310,108 +330,41 @@ void LLLogChat::saveHistory(const std::string& filename, 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 + if (NULL != sSaveHistorySignal) { - 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); + (*sSaveHistorySignal)(); } } -void append_to_last_message(std::list<LLSD>& messages, const std::string& line) -{ - if (!messages.size()) return; - - std::string im_text = messages.back()[IM_TEXT].asString(); - im_text.append(line); - messages.back()[IM_TEXT] = im_text; -} - // static -void LLLogChat::loadAllHistory(const std::string& file_name, std::list<LLSD>& messages) +void LLLogChat::loadChatHistory(const std::string& file_name, std::list<LLSD>& messages, const LLSD& load_params) { if (file_name.empty()) { - llwarns << "Session name is Empty!" << llendl; + LL_WARNS("LLLogChat::loadChatHistory") << "Session name is Empty!" << LL_ENDL; return ; } - //LL_INFOS("") << "Loading:" << file_name << LL_ENDL;/* uncomment if you want to verify step, delete on commit */ - //LL_INFOS("") << "Current:" << makeLogFileName(file_name) << LL_ENDL;/* uncomment if you want to verify step, delete on commit */ - LLFILE* fptr = LLFile::fopen(makeLogFileName(file_name), "r");/*Flawfinder: ignore*/ + + 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*/ if (!fptr) - { - fptr = LLFile::fopen(oldLogFileName(file_name), "r");/*Flawfinder: ignore*/ - if (!fptr) - { - if (!fptr) return; //No previous conversation with this name. - } - } - - //LL_INFOS("") << "Reading:" << file_name << LL_ENDL; + { + fptr = LLFile::fopen(LLLogChat::oldLogFileName(file_name), "r");/*Flawfinder: ignore*/ + if (!fptr) + { + return; //No previous conversation with this name. + } + } + 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. + if (load_all_history || fseek(fptr, (LOG_RECALL_SIZE - 1) * -1 , SEEK_END)) + { //We need to load the whole historyFile or it's smaller than recall size, so get it all. firstline = FALSE; if (fseek(fptr, 0, SEEK_SET)) { @@ -419,12 +372,11 @@ void LLLogChat::loadAllHistory(const std::string& file_name, std::list<LLSD>& me return; } } - - while (fgets(buffer, LOG_RECALL_SIZE, fptr) && !feof(fptr)) + 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) { firstline = FALSE; @@ -433,7 +385,7 @@ void LLLogChat::loadAllHistory(const std::string& file_name, std::list<LLSD>& me std::string line(buffer); - //updated 1.23 plaint text log format requires a space added before subsequent lines in a multilined message + //updated 1.23 plain text log format requires a space added before subsequent lines in a multilined message if (' ' == line[0]) { line.erase(0, MULTI_LINE_PREFIX.length()); @@ -447,9 +399,9 @@ void LLLogChat::loadAllHistory(const std::string& file_name, std::list<LLSD>& me else { LLSD item; - if (!LLChatLogParser::parse(line, item)) + if (!LLChatLogParser::parse(line, item, load_params)) { - item[IM_TEXT] = line; + item[LL_IM_TEXT] = line; } messages.push_back(item); } @@ -457,30 +409,425 @@ void LLLogChat::loadAllHistory(const std::string& file_name, std::list<LLSD>& me fclose(fptr); } +// static +bool LLLogChat::historyThreadsFinished(LLUUID session_id) +{ + LLMutexLock lock(historyThreadsMutex()); + bool finished = true; + std::map<LLUUID,LLLoadHistoryThread *>::iterator it = sLoadHistoryThreads.find(session_id); + if (it != sLoadHistoryThreads.end()) + { + finished = it->second->isFinished(); + } + if (!finished) + { + return false; + } + std::map<LLUUID,LLDeleteHistoryThread *>::iterator dit = sDeleteHistoryThreads.find(session_id); + if (dit != sDeleteHistoryThreads.end()) + { + finished = finished && dit->second->isFinished(); + } + return finished; +} + +// static +LLLoadHistoryThread* LLLogChat::getLoadHistoryThread(LLUUID session_id) +{ + LLMutexLock lock(historyThreadsMutex()); + std::map<LLUUID,LLLoadHistoryThread *>::iterator it = sLoadHistoryThreads.find(session_id); + if (it != sLoadHistoryThreads.end()) + { + return it->second; + } + return NULL; +} + +// static +LLDeleteHistoryThread* LLLogChat::getDeleteHistoryThread(LLUUID session_id) +{ + LLMutexLock lock(historyThreadsMutex()); + std::map<LLUUID,LLDeleteHistoryThread *>::iterator it = sDeleteHistoryThreads.find(session_id); + if (it != sDeleteHistoryThreads.end()) + { + return it->second; + } + return NULL; +} + +// static +bool LLLogChat::addLoadHistoryThread(LLUUID& session_id, LLLoadHistoryThread* lthread) +{ + LLMutexLock lock(historyThreadsMutex()); + std::map<LLUUID,LLLoadHistoryThread *>::const_iterator it = sLoadHistoryThreads.find(session_id); + if (it != sLoadHistoryThreads.end()) + { + return false; + } + sLoadHistoryThreads[session_id] = lthread; + return true; +} + +// static +bool LLLogChat::addDeleteHistoryThread(LLUUID& session_id, LLDeleteHistoryThread* dthread) +{ + LLMutexLock lock(historyThreadsMutex()); + std::map<LLUUID,LLDeleteHistoryThread *>::const_iterator it = sDeleteHistoryThreads.find(session_id); + if (it != sDeleteHistoryThreads.end()) + { + return false; + } + sDeleteHistoryThreads[session_id] = dthread; + return true; +} + +// static +void LLLogChat::cleanupHistoryThreads() +{ + LLMutexLock lock(historyThreadsMutex()); + std::vector<LLUUID> uuids; + std::map<LLUUID,LLLoadHistoryThread *>::iterator lit = sLoadHistoryThreads.begin(); + for (; lit != sLoadHistoryThreads.end(); lit++) + { + if (lit->second->isFinished() && sDeleteHistoryThreads[lit->first]->isFinished()) + { + delete lit->second; + delete sDeleteHistoryThreads[lit->first]; + uuids.push_back(lit->first); + } + } + std::vector<LLUUID>::iterator uuid_it = uuids.begin(); + for ( ;uuid_it != uuids.end(); uuid_it++) + { + sLoadHistoryThreads.erase(*uuid_it); + sDeleteHistoryThreads.erase(*uuid_it); + } +} + +//static +LLMutex* LLLogChat::historyThreadsMutex() +{ + if (sHistoryThreadsMutex == NULL) + { + sHistoryThreadsMutex = new LLMutex(NULL); + } + return sHistoryThreadsMutex; +} + +// static +std::string LLLogChat::oldLogFileName(std::string filename) +{ + // get Users log directory + std::string directory = gDirUtilp->getPerAccountChatLogsDir(); + + // add final OS dependent delimiter + directory += gDirUtilp->getDirDelimiter(); + + // lest make sure the file name has no invalid characters before making the pattern + filename = cleanFileName(filename); + + // create search pattern + std::string pattern = filename + ( filename == "chat" ? "-???\?-?\?-??.txt" : "-???\?-??.txt"); + + std::vector<std::string> allfiles; + LLDirIterator iter(directory, pattern); + std::string scanResult; + + while (iter.next(scanResult)) + { + allfiles.push_back(scanResult); + } + + if (allfiles.size() == 0) // if no result from date search, return generic filename + { + scanResult = directory + filename + '.' + LL_TRANSCRIPT_FILE_EXTENSION; + } + else + { + sort(allfiles.begin(), allfiles.end()); + scanResult = directory + allfiles.back(); + // this file is now the most recent version of the file. + } + + return scanResult; +} + +// static +void LLLogChat::findTranscriptFiles(std::string pattern, std::vector<std::string>& list_of_transcriptions) +{ + // get Users log directory + std::string dirname = gDirUtilp->getPerAccountChatLogsDir(); + + // add final OS dependent delimiter + dirname += gDirUtilp->getDirDelimiter(); + + LLDirIterator iter(dirname, pattern); + std::string filename; + while (iter.next(filename)) + { + std::string fullname = gDirUtilp->add(dirname, filename); + + LLFILE * filep = LLFile::fopen(fullname, "rb"); + if (NULL != filep) + { + if(makeLogFileName("chat")== fullname) + { + //Add Nearby chat history to the list of transcriptions + list_of_transcriptions.push_back(gDirUtilp->add(dirname, filename)); + LLFile::close(filep); + continue; + } + char buffer[LOG_RECALL_SIZE]; + + fseek(filep, 0, SEEK_END); // seek to end of file + S32 bytes_to_read = ftell(filep); // get current file pointer + fseek(filep, 0, SEEK_SET); // seek back to beginning of file + + // limit the number characters to read from file + if (bytes_to_read >= LOG_RECALL_SIZE) + { + bytes_to_read = LOG_RECALL_SIZE - 1; + } + + if (bytes_to_read > 0 && NULL != fgets(buffer, bytes_to_read, filep)) + { + //matching a timestamp + boost::match_results<std::string::const_iterator> matches; + if (boost::regex_match(std::string(buffer), matches, TIMESTAMP)) + { + list_of_transcriptions.push_back(gDirUtilp->add(dirname, filename)); + } + } + LLFile::close(filep); + } + } +} + +// static +void LLLogChat::getListOfTranscriptFiles(std::vector<std::string>& list_of_transcriptions) +{ + // create search pattern + std::string pattern = "*." + LL_TRANSCRIPT_FILE_EXTENSION; + findTranscriptFiles(pattern, list_of_transcriptions); +} + +// static +void LLLogChat::getListOfTranscriptBackupFiles(std::vector<std::string>& list_of_transcriptions) +{ + // create search pattern + std::string pattern = "*." + LL_TRANSCRIPT_FILE_EXTENSION + ".backup*"; + findTranscriptFiles(pattern, list_of_transcriptions); +} + +//static +boost::signals2::connection LLLogChat::setSaveHistorySignal(const save_history_signal_t::slot_type& cb) +{ + if (NULL == sSaveHistorySignal) + { + sSaveHistorySignal = new save_history_signal_t(); + } + + return sSaveHistorySignal->connect(cb); +} + +//static +bool LLLogChat::moveTranscripts(const std::string originDirectory, + const std::string targetDirectory, + std::vector<std::string>& listOfFilesToMove, + std::vector<std::string>& listOfFilesMoved) +{ + std::string newFullPath; + bool movedAllTranscripts = true; + std::string backupFileName; + unsigned backupFileCount; + + BOOST_FOREACH(const std::string& fullpath, listOfFilesToMove) + { + backupFileCount = 0; + newFullPath = targetDirectory + fullpath.substr(originDirectory.length(), std::string::npos); + + //The target directory contains that file already, so lets store it + if(LLFile::isfile(newFullPath)) + { + backupFileName = newFullPath + ".backup"; + + //If needed store backup file as .backup1 etc. + while(LLFile::isfile(backupFileName)) + { + ++backupFileCount; + backupFileName = newFullPath + ".backup" + boost::lexical_cast<std::string>(backupFileCount); + } + + //Rename the file to its backup name so it is not overwritten + LLFile::rename(newFullPath, backupFileName); + } + + S32 retry_count = 0; + while (retry_count < 5) + { + //success is zero + if (LLFile::rename(fullpath, newFullPath) != 0) + { + retry_count++; + S32 result = errno; + LL_WARNS("LLLogChat::moveTranscripts") << "Problem renaming " << fullpath << " - errorcode: " + << result << " attempt " << retry_count << LL_ENDL; + + ms_sleep(100); + } + else + { + listOfFilesMoved.push_back(newFullPath); + + if (retry_count) + { + LL_WARNS("LLLogChat::moveTranscripts") << "Successfully renamed " << fullpath << LL_ENDL; + } + break; + } + } + } + + if(listOfFilesMoved.size() != listOfFilesToMove.size()) + { + movedAllTranscripts = false; + } + + return movedAllTranscripts; +} + +//static +bool LLLogChat::moveTranscripts(const std::string currentDirectory, + const std::string newDirectory, + std::vector<std::string>& listOfFilesToMove) +{ + std::vector<std::string> listOfFilesMoved; + return moveTranscripts(currentDirectory, newDirectory, listOfFilesToMove, listOfFilesMoved); +} + +//static +void LLLogChat::deleteTranscripts() +{ + std::vector<std::string> list_of_transcriptions; + getListOfTranscriptFiles(list_of_transcriptions); + getListOfTranscriptBackupFiles(list_of_transcriptions); + + BOOST_FOREACH(const std::string& fullpath, list_of_transcriptions) + { + S32 retry_count = 0; + while (retry_count < 5) + { + if (0 != LLFile::remove(fullpath)) + { + retry_count++; + S32 result = errno; + LL_WARNS("LLLogChat::deleteTranscripts") << "Problem removing " << fullpath << " - errorcode: " + << result << " attempt " << retry_count << LL_ENDL; + + if(retry_count >= 5) + { + LL_WARNS("LLLogChat::deleteTranscripts") << "Failed to remove " << fullpath << LL_ENDL; + return; + } + + ms_sleep(100); + } + else + { + if (retry_count) + { + LL_WARNS("LLLogChat::deleteTranscripts") << "Successfully removed " << fullpath << LL_ENDL; + } + break; + } + } + } + + LLFloaterIMSessionTab::processChatHistoryStyleUpdate(true); +} + +// static +bool LLLogChat::isTranscriptExist(const LLUUID& avatar_id, bool is_group) +{ + std::vector<std::string> list_of_transcriptions; + LLLogChat::getListOfTranscriptFiles(list_of_transcriptions); + + if (list_of_transcriptions.size() > 0) + { + LLAvatarName avatar_name; + LLAvatarNameCache::get(avatar_id, &avatar_name); + std::string avatar_user_name = avatar_name.getAccountName(); + if(!is_group) + { + std::replace(avatar_user_name.begin(), avatar_user_name.end(), '.', '_'); + BOOST_FOREACH(std::string& transcript_file_name, list_of_transcriptions) + { + if (std::string::npos != transcript_file_name.find(avatar_user_name)) + { + return true; + } + } + } + else + { + std::string file_name; + gCacheName->getGroupName(avatar_id, file_name); + file_name = makeLogFileName(file_name); + BOOST_FOREACH(std::string& transcript_file_name, list_of_transcriptions) + { + if (transcript_file_name == file_name) + { + return true; + } + } + } + + } + + return false; +} + +bool LLLogChat::isNearbyTranscriptExist() +{ + std::vector<std::string> list_of_transcriptions; + LLLogChat::getListOfTranscriptFiles(list_of_transcriptions); + + std::string file_name; + file_name = makeLogFileName("chat"); + BOOST_FOREACH(std::string& transcript_file_name, list_of_transcriptions) + { + if (transcript_file_name == file_name) + { + return true; + } + } + return false; +} + //*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>" +//Example, an object's name can be written 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); + LL_WARNS() << "invalid LLSD type of an instant message" << LL_ENDL; return; } - if (im[IM_TIME].isDefined()) + if (im[LL_IM_TIME].isDefined()) { - std::string timestamp = im[IM_TIME].asString(); + std::string timestamp = im[LL_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()) + //Example, an object's name can be written like "Object <actual_object's_name>" + if (im[LL_IM_FROM].isDefined()) { - std::string from = im[IM_FROM].asString(); + std::string from = im[LL_IM_FROM].asString(); boost::trim(from); if (from.size()) { @@ -488,9 +835,9 @@ void LLChatLogFormatter::format(const LLSD& im, std::ostream& ostr) const } } - if (im[IM_TEXT].isDefined()) + if (im[LL_IM_TEXT].isDefined()) { - std::string im_text = im[IM_TEXT].asString(); + std::string im_text = im[LL_IM_TEXT].asString(); //multilined text will be saved with prepended spaces boost::replace_all(im_text, NEW_LINE, NEW_LINE_SPACE_PREFIX); @@ -498,10 +845,11 @@ void LLChatLogFormatter::format(const LLSD& im, std::ostream& ostr) const } } -bool LLChatLogParser::parse(std::string& raw, LLSD& im) +bool LLChatLogParser::parse(std::string& raw, LLSD& im, const LLSD& parse_params) { if (!raw.length()) return false; + bool cut_off_todays_date = parse_params.has("cut_off_todays_date") ? parse_params["cut_off_todays_date"].asBoolean() : true; im = LLSD::emptyMap(); //matching a timestamp @@ -516,13 +864,18 @@ bool LLChatLogParser::parse(std::string& raw, LLSD& im) boost::trim(timestamp); timestamp.erase(0, 1); timestamp.erase(timestamp.length()-1, 1); - LLLogChatTimeScanner::instance().checkAndCutOffDate(timestamp); - im[IM_TIME] = timestamp; + + if (cut_off_todays_date) + { + LLLogChatTimeScanner::instance().checkAndCutOffDate(timestamp); + } + + im[LL_IM_TIME] = timestamp; } else { //timestamp is optional - im[IM_TIME] = ""; + im[LL_IM_TIME] = ""; } bool has_stuff = matches[IDX_STUFF].matched; @@ -548,8 +901,8 @@ bool LLChatLogParser::parse(std::string& raw, LLSD& im) if (!has_name || name == SYSTEM_FROM) { //name is optional too - im[IM_FROM] = SYSTEM_FROM; - im[IM_FROM_ID] = LLUUID::null; + im[LL_IM_FROM] = SYSTEM_FROM; + im[LL_IM_FROM_ID] = LLUUID::null; } //possibly a case of complex object names consisting of 3+ words @@ -558,8 +911,8 @@ bool LLChatLogParser::parse(std::string& raw, LLSD& im) U32 divider_pos = stuff.find(NAME_TEXT_DIVIDER); if (divider_pos != std::string::npos && divider_pos < (stuff.length() - NAME_TEXT_DIVIDER.length())) { - im[IM_FROM] = stuff.substr(0, divider_pos); - im[IM_TEXT] = stuff.substr(divider_pos + NAME_TEXT_DIVIDER.length()); + im[LL_IM_FROM] = stuff.substr(0, divider_pos); + im[LL_IM_TEXT] = stuff.substr(divider_pos + NAME_TEXT_DIVIDER.length()); return true; } } @@ -567,7 +920,7 @@ bool LLChatLogParser::parse(std::string& raw, LLSD& im) if (!has_name) { //text is mandatory - im[IM_TEXT] = stuff; + im[LL_IM_TEXT] = stuff; return true; //parse as a message from Second Life } @@ -579,44 +932,199 @@ bool LLChatLogParser::parse(std::string& raw, LLSD& im) { std::string agent_name; LLAgentUI::buildFullname(agent_name); - im[IM_FROM] = agent_name; - im[IM_FROM_ID] = gAgentID; + im[LL_IM_FROM] = agent_name; + im[LL_IM_FROM_ID] = gAgentID; } else { - im[IM_FROM] = name; + im[LL_IM_FROM] = name; } - im[IM_TEXT] = name_and_text[IDX_TEXT]; + im[LL_IM_TEXT] = name_and_text[IDX_TEXT]; return true; //parsed name and message text, maybe have a timestamp too } -std::string LLLogChat::oldLogFileName(std::string filename) + +LLDeleteHistoryThread::LLDeleteHistoryThread(std::list<LLSD>* messages, LLLoadHistoryThread* loadThread) + : LLActionThread("delete chat history"), + mMessages(messages), + mLoadThread(loadThread) { - std::string scanResult; - std::string directory = gDirUtilp->getPerAccountChatLogsDir();/* get Users log directory */ - directory += gDirUtilp->getDirDelimiter();/* add final OS dependent delimiter */ - filename=cleanFileName(filename);/* lest make shure the file name has no invalad charecters befor making the pattern */ - std::string pattern = (filename+(( filename == "chat" ) ? "-???\?-?\?-??.txt" : "-???\?-??.txt"));/* create search pattern*/ - //LL_INFOS("") << "Checking:" << directory << " for " << pattern << LL_ENDL;/* uncomment if you want to verify step, delete on commit */ - std::vector<std::string> allfiles; +} +LLDeleteHistoryThread::~LLDeleteHistoryThread() +{ +} +void LLDeleteHistoryThread::run() +{ + if (mLoadThread != NULL) + { + mLoadThread->waitFinished(); + } + if (NULL != mMessages) + { + delete mMessages; + } + mMessages = NULL; + setFinished(); +} + +LLActionThread::LLActionThread(const std::string& name) + : LLThread(name), + mMutex(NULL), + mRunCondition(NULL), + mFinished(false) +{ +} + +LLActionThread::~LLActionThread() +{ +} + +void LLActionThread::waitFinished() +{ + mMutex.lock(); + if (!mFinished) + { + mMutex.unlock(); + mRunCondition.wait(); + } + else + { + mMutex.unlock(); + } +} + +void LLActionThread::setFinished() +{ + mMutex.lock(); + mFinished = true; + mMutex.unlock(); + mRunCondition.signal(); +} + +LLLoadHistoryThread::LLLoadHistoryThread(const std::string& file_name, std::list<LLSD>* messages, const LLSD& load_params) + : LLActionThread("load chat history"), + mMessages(messages), + mFileName(file_name), + mLoadParams(load_params), + mNewLoad(true), + mLoadEndSignal(NULL) +{ +} + +LLLoadHistoryThread::~LLLoadHistoryThread() +{ +} + +void LLLoadHistoryThread::run() +{ + if(mNewLoad) + { + loadHistory(mFileName, mMessages, mLoadParams); + int count = mMessages->size(); + LL_INFOS() << "mMessages->size(): " << count << LL_ENDL; + setFinished(); + } +} + +void LLLoadHistoryThread::loadHistory(const std::string& file_name, std::list<LLSD>* messages, const LLSD& load_params) +{ + if (file_name.empty()) + { + LL_WARNS("LLLogChat::loadHistory") << "Session 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*/ + + if (!fptr) + { + fptr = LLFile::fopen(LLLogChat::oldLogFileName(file_name), "r");/*Flawfinder: ignore*/ + if (!fptr) + { + mNewLoad = false; + (*mLoadEndSignal)(messages, file_name); + return; //No previous conversation with this name. + } + } - while (gDirUtilp->getNextFileInDir(directory, pattern, scanResult)) - { - //LL_INFOS("") << "Found :" << scanResult << LL_ENDL; - allfiles.push_back(scanResult); - } - - if (allfiles.size() == 0) // if no result from date search, return generic filename - { - scanResult = directory + filename + ".txt"; - } - else - { - std::sort(allfiles.begin(), allfiles.end()); - scanResult = directory + allfiles.back(); - // thisfile is now the most recent version of the file. - } - //LL_INFOS("") << "Reading:" << scanResult << LL_ENDL;/* uncomment if you want to verify step, delete on commit */ - return scanResult; + char buffer[LOG_RECALL_SIZE]; /*Flawfinder: ignore*/ + + char *bptr; + S32 len; + bool firstline = TRUE; + + if (load_all_history || fseek(fptr, (LOG_RECALL_SIZE - 1) * -1 , SEEK_END)) + { //We need to load the whole historyFile or it's smaller than recall size, so get it all. + firstline = FALSE; + if (fseek(fptr, 0, SEEK_SET)) + { + fclose(fptr); + mNewLoad = false; + (*mLoadEndSignal)(messages, file_name); + 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) + { + firstline = FALSE; + continue; + } + std::string line(buffer); + + //updated 1.23 plaint text log format requires a space added before subsequent lines in a multilined message + if (' ' == line[0]) + { + line.erase(0, MULTI_LINE_PREFIX.length()); + append_to_last_message(*messages, '\n' + line); + } + else if (0 == len && ('\n' == line[0] || '\r' == line[0])) + { + //to support old format's multilined messages with new lines used to divide paragraphs + append_to_last_message(*messages, line); + } + else + { + LLSD item; + if (!LLChatLogParser::parse(line, item, load_params)) + { + item[LL_IM_TEXT] = line; + } + messages->push_back(item); + } + } + + fclose(fptr); + mNewLoad = false; + (*mLoadEndSignal)(messages, file_name); +} + +boost::signals2::connection LLLoadHistoryThread::setLoadEndSignal(const load_end_signal_t::slot_type& cb) +{ + if (NULL == mLoadEndSignal) + { + mLoadEndSignal = new load_end_signal_t(); + } + + return mLoadEndSignal->connect(cb); +} + +void LLLoadHistoryThread::removeLoadEndSignal(const load_end_signal_t::slot_type& cb) +{ + if (NULL != mLoadEndSignal) + { + mLoadEndSignal->disconnect_all_slots(); + delete mLoadEndSignal; + } + mLoadEndSignal = NULL; } |