diff options
| author | Erik Kundiman <erik@megapahit.org> | 2026-10-10 13:02:06 +0800 |
|---|---|---|
| committer | Erik Kundiman <erik@megapahit.org> | 2026-10-10 13:02:06 +0800 |
| commit | 7a75f495974c2d6a6be66614fbc526fa666279b5 (patch) | |
| tree | b71db46012b3a6c85bbd457dfba88eeb5a30e284 /indra/newview/lllogchat.cpp | |
| parent | a36c78d643d759147b90bdd52753cc506702050c (diff) | |
| parent | db59a05ae2b69b87175ac1da5f87a532f47fce1a (diff) | |
Merge tag 'Second_Life_Release#db59a05a-26.5' into 26.5
Diffstat (limited to 'indra/newview/lllogchat.cpp')
| -rw-r--r-- | indra/newview/lllogchat.cpp | 255 |
1 files changed, 166 insertions, 89 deletions
diff --git a/indra/newview/lllogchat.cpp b/indra/newview/lllogchat.cpp index 8630438155..fa30b1de31 100644 --- a/indra/newview/lllogchat.cpp +++ b/indra/newview/lllogchat.cpp @@ -29,6 +29,7 @@ #include "llagent.h" #include "llagentui.h" #include "llavatarnamecache.h" +#include "llchatservicehistory.h" #include "lllogchat.h" #include "llregex.h" #include "lltrans.h" @@ -47,6 +48,13 @@ #include <boost/date_time/gregorian/gregorian.hpp> #include <boost/date_time/posix_time/posix_time.hpp> #include <boost/date_time/local_time_adjustor.hpp> +#include <memory> +#include <mutex> + +#if !LL_WINDOWS +#include <fcntl.h> +#include <unistd.h> +#endif const S32 LOG_RECALL_SIZE = 20480; @@ -114,6 +122,12 @@ const static int IDX_TEXT = 3; using namespace boost::posix_time; using namespace boost::gregorian; +namespace +{ + // A save and the deletion sweep linearize at this feature-private boundary. + LLMutex sTranscriptMutationMutex; +} + void append_to_last_message(std::list<LLSD>& messages, const std::string& line) { if (!messages.size()) return; @@ -357,6 +371,9 @@ void LLLogChat::saveHistory(const std::string& filename, return; } + // Keep open/write/close on one side of the deletion sweep, then unlock before + // notifying observers that may read or write transcripts. + std::unique_lock<LLMutex> mutation_lock(sTranscriptMutationMutex); llofstream file(LLLogChat::makeLogFileName(filename).c_str(), std::ios_base::app); if (!file.is_open()) { @@ -401,6 +418,7 @@ void LLLogChat::saveHistory(const std::string& filename, file << LLChatLogFormatter(item) << std::endl; file.close(); + mutation_lock.unlock(); LLLogChat::getInstance()->triggerHistorySignal(); } @@ -408,14 +426,16 @@ void LLLogChat::saveHistory(const std::string& filename, // static void LLLogChat::loadChatHistory(const std::string& file_name, std::list<LLSD>& messages, const LLSD& load_params, bool is_group) { + if (LLChatServiceHistory::historySuppressed()) + { + return; + } if (file_name.empty()) { 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; - // Stat the file to find it and get the last history entry time llstat stat_data; @@ -453,84 +473,122 @@ void LLLogChat::loadChatHistory(const std::string& file_name, std::list<LLSD>& m } } - // If we got here, we managed to stat the file. - // Open the file to read in binary mode to prevent interpreting other characters as EOF - LLFILE* fptr = LLFile::fopen(log_file_name, LLFILE_MODE("rb")); /*Flawfinder: ignore*/ - if (!fptr) - { // 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; + auto save_num_messages = messages.size(); + loadChatHistoryExact(log_file_name, messages, load_params); + + 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; +} + +// static +void LLLogChat::loadChatHistoryExact(const std::string& path, std::list<LLSD>& messages, + const LLSD& load_params) +{ + if (path.empty()) + { return; } - auto save_num_messages = messages.size(); - - char buffer[LOG_RECALL_SIZE]; /*Flawfinder: ignore*/ - char *bptr; - size_t len; - bool firstline = true; + // Binary mode keeps transcript contents independent of platform EOF handling. + std::unique_ptr<LLFILE, int (*)(LLFILE*)> input( + LLFile::fopen(path, LLFILE_MODE("rb")), LLFile::close); + if (!input) + { + LL_WARNS("ChatHistory") << "Unable to read file " << path << LL_ENDL; + return; + } - 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)) + // A bounded load seeks near EOF and discards the first partial line; full + // stitched reads parse from the beginning of each exact transcript path. + char buffer[LOG_RECALL_SIZE]; + bool skip_partial_line = true; + if (load_params["load_all_history"].asBoolean() || fseek(input.get(), (LOG_RECALL_SIZE - 1) * -1, SEEK_END)) + { + skip_partial_line = false; + if (fseek(input.get(), 0, SEEK_SET)) { - fclose(fptr); return; } } - while (fgets(buffer, LOG_RECALL_SIZE, fptr) && !feof(fptr)) + + while (fgets(buffer, LOG_RECALL_SIZE, input.get())) { - 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'; + // Normalize either newline style before applying legacy multiline rules. + size_t length = strlen(buffer); + while (length && (buffer[length - 1] == '\n' || buffer[length - 1] == '\r')) + { + buffer[--length] = '\0'; + } - if (firstline) + if (skip_partial_line) { - firstline = false; + skip_partial_line = false; continue; } std::string line(remove_utf8_bom(buffer)); - - // fast heuristic test for a mention URL in a string - // this is used to avoid costly regex calls + // Restore mention URLs only when the inexpensive marker indicates the + // formatter's markdown wrapper may be present. if (line.find("/mention)") != std::string::npos) { - // restore original mention URL from [@username](URL) format - static const boost::regex altered_mention_regex("\\[@([^\\]]+)\\]\\((" APP_HEADER_REGEX "/agent/[\\da-f-]+/mention)\\)", - boost::regex::perl | boost::regex::icase); - - // $2 captures the URL part - line = boost::regex_replace(line, altered_mention_regex, "$2"); + static const boost::regex mention_regex( + "\\[@([^\\]]+)\\]\\((" APP_HEADER_REGEX "/agent/[\\da-f-]+/mention)\\)", + boost::regex::perl | boost::regex::icase); + line = boost::regex_replace(line, mention_regex, "$2"); } - //updated 1.23 plain text log format requires a space added before subsequent lines in a multilined message - if (' ' == line[0]) + // Indented lines continue the prior multiline message in the current + // plaintext format. + if (!line.empty() && line[0] == ' ') { line.erase(0, MULTI_LINE_PREFIX.length()); append_to_last_message(messages, '\n' + line); + continue; } - 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 + + // Parser failures remain readable as plain transcript text. + LLSD item; + if (!LLChatLogParser::parse(line, item, load_params)) { - LLSD item; - if (!LLChatLogParser::parse(line, item, load_params)) - { - item[LL_IM_TEXT] = line; - } - messages.push_back(item); + item[LL_IM_TEXT] = line; } + messages.push_back(item); } - 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; +// static +void LLLogChat::getTranscriptFamily(const std::string& file_name, std::vector<std::string>& paths) +{ + paths.clear(); + if (file_name.empty()) + { + return; + } + + const std::string stem = cleanFileName(file_name); + const std::string directory = gDirUtilp->getPerAccountChatLogsDir() + + gDirUtilp->getDirDelimiter(); + const std::string ordinary = directory + stem + '.' + LL_TRANSCRIPT_FILE_EXTENSION; + if (LLFile::isfile(ordinary)) + { + paths.push_back(ordinary); + } + + // Monthly shards follow the ordinary file in lexical order for deterministic + // seam ordering across separate legacy files. + std::vector<std::string> shards; + // Split the glob before the separator so its question marks cannot form a C++ trigraph. + LLDirIterator iterator(directory, stem + "-????" "-??." + LL_TRANSCRIPT_FILE_EXTENSION); + std::string name; + while (iterator.next(name)) + { + shards.push_back(directory + name); + } + + std::sort(shards.begin(), shards.end()); + paths.insert(paths.end(), shards.begin(), shards.end()); } bool LLLogChat::historyThreadsFinished(LLUUID session_id) @@ -554,17 +612,6 @@ bool LLLogChat::historyThreadsFinished(LLUUID session_id) return finished; } -LLLoadHistoryThread* LLLogChat::getLoadHistoryThread(LLUUID session_id) -{ - LLMutexLock lock(historyThreadsMutex()); - std::map<LLUUID,LLLoadHistoryThread *>::iterator it = mLoadHistoryThreads.find(session_id); - if (it != mLoadHistoryThreads.end()) - { - return it->second; - } - return NULL; -} - LLDeleteHistoryThread* LLLogChat::getDeleteHistoryThread(LLUUID session_id) { LLMutexLock lock(historyThreadsMutex()); @@ -819,10 +866,31 @@ bool LLLogChat::moveTranscripts(const std::string currentDirectory, //static void LLLogChat::deleteTranscripts() { + deleteTranscriptContent(gDirUtilp->getPerAccountChatLogsDir()); + LLFloaterIMSessionTab::processChatHistoryStyleUpdate(true); +} + +// static +bool LLLogChat::deleteTranscriptContent(const std::string& directory) +{ + // Snapshot and remove the exact legacy targets while plaintext writers are excluded. + LLMutexLock mutation_lock(&sTranscriptMutationMutex); std::vector<std::string> list_of_transcriptions; - getListOfTranscriptFiles(list_of_transcriptions); - getListOfTranscriptBackupFiles(list_of_transcriptions); + const std::string separator = !directory.empty() && + (directory.back() == '/' || directory.back() == '\\') ? "" : "/"; + for (const std::string& pattern : { std::string("*.") + LL_TRANSCRIPT_FILE_EXTENSION, + std::string("*.") + LL_TRANSCRIPT_FILE_EXTENSION + ".backup*" }) + { + LLDirIterator iterator(directory, pattern); + std::string filename; + while (iterator.next(filename)) + { + list_of_transcriptions.push_back(directory + separator + filename); + } + } + // Retry each captured path independently while writers remain excluded. Any + // exhausted target keeps the durable account deletion in its pending state. for (const std::string& fullpath : list_of_transcriptions) { S32 retry_count = 0; @@ -838,7 +906,7 @@ void LLLogChat::deleteTranscripts() if(retry_count >= 5) { LL_WARNS("LLLogChat::deleteTranscripts") << "Failed to remove " << fullpath << LL_ENDL; - return; + return false; } ms_sleep(100); @@ -854,19 +922,44 @@ void LLLogChat::deleteTranscripts() } } - LLFloaterIMSessionTab::processChatHistoryStyleUpdate(true); +#if LL_WINDOWS + return true; +#else + // Flush removed directory entries before durable Delete can publish completion. + const int descriptor = ::open(directory.c_str(), O_RDONLY | O_DIRECTORY); + if (descriptor < 0) + { + return false; + } + + const bool synced = ::fsync(descriptor) == 0; + ::close(descriptor); + return synced; +#endif +} + +// static +void LLLogChat::notifyTranscriptCreated() +{ + LLLogChat::getInstance()->triggerHistorySignal(); } // static bool LLLogChat::isTranscriptExist(const LLUUID& avatar_id, bool is_group) { + // History actions share the deletion gate and both direct transcript sources. + if (LLChatServiceHistory::historySuppressed()) + { + return false; + } 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(), '.', '_'); - return isTranscriptFileFound(makeLogFileName(avatar_user_name)); + return isTranscriptFileFound(makeLogFileName(avatar_user_name)) || + LLChatServiceHistory::localHistoryExists(avatar_id); } else { @@ -1156,8 +1249,7 @@ LLLoadHistoryThread::LLLoadHistoryThread(const std::string& file_name, std::list mMessages(messages), mFileName(file_name), mLoadParams(load_params), - mNewLoad(true), - mLoadEndSignal(NULL) + mNewLoad(true) { } @@ -1208,7 +1300,7 @@ void LLLoadHistoryThread::loadHistory(const std::string& file_name, std::list<LL if (!fptr) { mNewLoad = false; - (*mLoadEndSignal)(messages, file_name); + mLoadEndSignal(messages, file_name); return; //No previous conversation with this name. } } @@ -1227,7 +1319,7 @@ void LLLoadHistoryThread::loadHistory(const std::string& file_name, std::list<LL { fclose(fptr); mNewLoad = false; - (*mLoadEndSignal)(messages, file_name); + mLoadEndSignal(messages, file_name); return; } } @@ -1271,25 +1363,10 @@ void LLLoadHistoryThread::loadHistory(const std::string& file_name, std::list<LL fclose(fptr); mNewLoad = false; - (*mLoadEndSignal)(messages, file_name); + 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; + return mLoadEndSignal.connect(cb); } |
