diff options
| author | Ansariel <ansariel.hiller@phoenixviewer.com> | 2024-05-22 19:04:52 +0200 |
|---|---|---|
| committer | Ansariel <ansariel.hiller@phoenixviewer.com> | 2024-05-22 19:04:52 +0200 |
| commit | 1b67dd855c41f5a0cda7ec2a68d98071986ca703 (patch) | |
| tree | ab243607f74f78200787bba5b9b88f07ef1b966f /indra/newview/llconversationlog.cpp | |
| parent | 6d6eabca44d08d5b97bfe3e941d2b9687c2246ea (diff) | |
| parent | e1623bb276f83a43ce7a197e388720c05bdefe61 (diff) | |
Merge remote-tracking branch 'origin/main' into DRTVWR-600-maint-A
# Conflicts:
# autobuild.xml
# indra/cmake/CMakeLists.txt
# indra/cmake/GoogleMock.cmake
# indra/llaudio/llaudioengine_fmodstudio.cpp
# indra/llaudio/llaudioengine_fmodstudio.h
# indra/llaudio/lllistener_fmodstudio.cpp
# indra/llaudio/lllistener_fmodstudio.h
# indra/llaudio/llstreamingaudio_fmodstudio.cpp
# indra/llaudio/llstreamingaudio_fmodstudio.h
# indra/llcharacter/llmultigesture.cpp
# indra/llcharacter/llmultigesture.h
# indra/llimage/llimage.cpp
# indra/llimage/llimagepng.cpp
# indra/llimage/llimageworker.cpp
# indra/llimage/tests/llimageworker_test.cpp
# indra/llmessage/tests/llmockhttpclient.h
# indra/llprimitive/llgltfmaterial.h
# indra/llrender/llfontfreetype.cpp
# indra/llui/llcombobox.cpp
# indra/llui/llfolderview.cpp
# indra/llui/llfolderviewmodel.h
# indra/llui/lllineeditor.cpp
# indra/llui/lllineeditor.h
# indra/llui/lltextbase.cpp
# indra/llui/lltextbase.h
# indra/llui/lltexteditor.cpp
# indra/llui/lltextvalidate.cpp
# indra/llui/lltextvalidate.h
# indra/llui/lluictrl.h
# indra/llui/llview.cpp
# indra/llwindow/llwindowmacosx.cpp
# indra/newview/app_settings/settings.xml
# indra/newview/llappearancemgr.cpp
# indra/newview/llappearancemgr.h
# indra/newview/llavatarpropertiesprocessor.cpp
# indra/newview/llavatarpropertiesprocessor.h
# indra/newview/llbreadcrumbview.cpp
# indra/newview/llbreadcrumbview.h
# indra/newview/llbreastmotion.cpp
# indra/newview/llbreastmotion.h
# indra/newview/llconversationmodel.h
# indra/newview/lldensityctrl.cpp
# indra/newview/lldensityctrl.h
# indra/newview/llface.inl
# indra/newview/llfloatereditsky.cpp
# indra/newview/llfloatereditwater.cpp
# indra/newview/llfloateremojipicker.h
# indra/newview/llfloaterimsessiontab.cpp
# indra/newview/llfloaterprofiletexture.cpp
# indra/newview/llfloaterprofiletexture.h
# indra/newview/llgesturemgr.cpp
# indra/newview/llgesturemgr.h
# indra/newview/llimpanel.cpp
# indra/newview/llimpanel.h
# indra/newview/llinventorybridge.cpp
# indra/newview/llinventorybridge.h
# indra/newview/llinventoryclipboard.cpp
# indra/newview/llinventoryclipboard.h
# indra/newview/llinventoryfunctions.cpp
# indra/newview/llinventoryfunctions.h
# indra/newview/llinventorygallery.cpp
# indra/newview/lllistbrowser.cpp
# indra/newview/lllistbrowser.h
# indra/newview/llpanelobjectinventory.cpp
# indra/newview/llpanelprofile.cpp
# indra/newview/llpanelprofile.h
# indra/newview/llpreviewgesture.cpp
# indra/newview/llsavedsettingsglue.cpp
# indra/newview/llsavedsettingsglue.h
# indra/newview/lltooldraganddrop.cpp
# indra/newview/llurllineeditorctrl.cpp
# indra/newview/llvectorperfoptions.cpp
# indra/newview/llvectorperfoptions.h
# indra/newview/llviewerparceloverlay.cpp
# indra/newview/llviewertexlayer.cpp
# indra/newview/llviewertexturelist.cpp
# indra/newview/macmain.h
# indra/test/test.cpp
Diffstat (limited to 'indra/newview/llconversationlog.cpp')
| -rw-r--r-- | indra/newview/llconversationlog.cpp | 1306 |
1 files changed, 653 insertions, 653 deletions
diff --git a/indra/newview/llconversationlog.cpp b/indra/newview/llconversationlog.cpp index e4d2c8f39a..9ee2be4c24 100644 --- a/indra/newview/llconversationlog.cpp +++ b/indra/newview/llconversationlog.cpp @@ -1,653 +1,653 @@ -/** - * @file llconversationlog.h - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, 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$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llagent.h" -#include "llavatarnamecache.h" -#include "llconversationlog.h" -#include "lldiriterator.h" -#include "llnotificationsutil.h" -#include "lltrans.h" - -#include "boost/lexical_cast.hpp" - -const S32Days CONVERSATION_LIFETIME = (S32Days)30; // lifetime of LLConversation is 30 days by spec - -struct ConversationParams : public LLInitParam::Block<ConversationParams> -{ - Mandatory<U64Seconds > time; - Mandatory<std::string> timestamp; - Mandatory<SessionType> conversation_type; - Mandatory<std::string> conversation_name, - history_filename; - Mandatory<LLUUID> session_id, - participant_id; - Mandatory<bool> has_offline_ims; -}; - -/************************************************************************/ -/* LLConversation implementation */ -/************************************************************************/ - -LLConversation::LLConversation(const ConversationParams& params) -: mTime(params.time), - mTimestamp(params.timestamp.isProvided() ? params.timestamp : createTimestamp(params.time)), - mConversationType(params.conversation_type), - mConversationName(params.conversation_name), - mHistoryFileName(params.history_filename), - mSessionID(params.session_id), - mParticipantID(params.participant_id), - mHasOfflineIMs(params.has_offline_ims) -{ - setListenIMFloaterOpened(); -} - -LLConversation::LLConversation(const LLIMModel::LLIMSession& session) -: mTime(time_corrected()), - mTimestamp(createTimestamp(mTime)), - mConversationType(session.mSessionType), - mConversationName(session.mName), - mHistoryFileName(session.mHistoryFileName), - mSessionID(session.isOutgoingAdHoc() ? session.generateOutgoingAdHocHash() : session.mSessionID), - mParticipantID(session.mOtherParticipantID), - mHasOfflineIMs(session.mHasOfflineMessage) -{ - setListenIMFloaterOpened(); -} - -LLConversation::LLConversation(const LLConversation& conversation) -{ - mTime = conversation.getTime(); - mTimestamp = conversation.getTimestamp(); - mConversationType = conversation.getConversationType(); - mConversationName = conversation.getConversationName(); - mHistoryFileName = conversation.getHistoryFileName(); - mSessionID = conversation.getSessionID(); - mParticipantID = conversation.getParticipantID(); - mHasOfflineIMs = conversation.hasOfflineMessages(); - - setListenIMFloaterOpened(); -} - -LLConversation::~LLConversation() -{ - mIMFloaterShowedConnection.disconnect(); -} - -void LLConversation::updateTimestamp() -{ - mTime = (U64Seconds)time_corrected(); - mTimestamp = createTimestamp(mTime); -} - -void LLConversation::onIMFloaterShown(const LLUUID& session_id) -{ - if (mSessionID == session_id) - { - mHasOfflineIMs = false; - } -} - -// static -const std::string LLConversation::createTimestamp(const U64Seconds& utc_time) -{ - std::string timeStr; - LLSD substitution; - substitution["datetime"] = (S32)utc_time.value(); - - timeStr = "["+LLTrans::getString ("TimeMonth")+"]/[" - +LLTrans::getString ("TimeDay")+"]/[" - +LLTrans::getString ("TimeYear")+"] [" - +LLTrans::getString ("TimeHour")+"]:[" - +LLTrans::getString ("TimeMin")+"]"; - - - LLStringUtil::format (timeStr, substitution); - return timeStr; -} - -bool LLConversation::isOlderThan(U32Days days) const -{ - U64Seconds now(time_corrected()); - U32Days age = now - mTime; - - return age > days; -} - -void LLConversation::setListenIMFloaterOpened() -{ - LLFloaterIMSession* floater = LLFloaterIMSession::findInstance(mSessionID); - - bool offline_ims_visible = LLFloaterIMSession::isVisible(floater) && floater->hasFocus(); - - // we don't need to listen for im floater with this conversation is opened - // if floater is already opened or this conversation doesn't have unread offline messages - if (mHasOfflineIMs && !offline_ims_visible) - { - mIMFloaterShowedConnection = LLFloaterIMSession::setIMFloaterShowedCallback(boost::bind(&LLConversation::onIMFloaterShown, this, _1)); - } - else - { - mHasOfflineIMs = false; - } -} - -/************************************************************************/ -/* LLConversationLogFriendObserver implementation */ -/************************************************************************/ - -// Note : An LLSingleton like LLConversationLog cannot be an LLFriendObserver -// at the same time. -// This is because avatar observers are deleted by the observed object which -// conflicts with the way LLSingleton are deleted. - -class LLConversationLogFriendObserver : public LLFriendObserver -{ -public: - LLConversationLogFriendObserver() {} - virtual ~LLConversationLogFriendObserver() {} - virtual void changed(U32 mask); -}; - -void LLConversationLogFriendObserver::changed(U32 mask) -{ - if (mask & (LLFriendObserver::ADD | LLFriendObserver::REMOVE)) - { - LLConversationLog::instance().notifyObservers(); - } -} - -/************************************************************************/ -/* LLConversationLog implementation */ -/************************************************************************/ - -LLConversationLog::LLConversationLog() : - mAvatarNameCacheConnection(), - mLoggingEnabled(false) -{ -} - -void LLConversationLog::enableLogging(S32 log_mode) -{ - mLoggingEnabled = log_mode > 0; - if (log_mode > 0) - { - mConversations.clear(); - loadFromFile(getFileName()); - LLIMMgr::instance().addSessionObserver(this); - mNewMessageSignalConnection = LLIMModel::instance().addNewMsgCallback(boost::bind(&LLConversationLog::onNewMessageReceived, this, _1)); - - mFriendObserver = new LLConversationLogFriendObserver; - LLAvatarTracker::instance().addObserver(mFriendObserver); - } - else - { - saveToFile(getFileName()); - - LLIMMgr::instance().removeSessionObserver(this); - mNewMessageSignalConnection.disconnect(); - LLAvatarTracker::instance().removeObserver(mFriendObserver); - } - - notifyObservers(); -} - -void LLConversationLog::logConversation(const LLUUID& session_id, bool has_offline_msg) -{ - const LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(session_id); - LLConversation* conversation = findConversation(session); - - if (session && session->mOtherParticipantID != gAgentID) - { - if (conversation) - { - if(has_offline_msg) - { - updateOfflineIMs(session, has_offline_msg); - } - updateConversationTimestamp(conversation); - } - else - { - createConversation(session); - } - } -} - -void LLConversationLog::createConversation(const LLIMModel::LLIMSession* session) -{ - if (session) - { - LLConversation conversation(*session); - mConversations.push_back(conversation); - - if (LLIMModel::LLIMSession::P2P_SESSION == session->mSessionType) - { - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - mAvatarNameCacheConnection = LLAvatarNameCache::get(session->mOtherParticipantID, boost::bind(&LLConversationLog::onAvatarNameCache, this, _1, _2, session)); - } - - notifyObservers(); - } -} - -void LLConversationLog::updateConversationName(const LLIMModel::LLIMSession* session, const std::string& name) -{ - if (!session) - { - return; - } - - LLConversation* conversation = findConversation(session); - if (conversation) - { - conversation->setConversationName(name); - notifyParticularConversationObservers(conversation->getSessionID(), LLConversationLogObserver::CHANGED_NAME); - } -} - -void LLConversationLog::updateOfflineIMs(const LLIMModel::LLIMSession* session, bool new_messages) -{ - if (!session) - { - return; - } - - LLConversation* conversation = findConversation(session); - if (conversation) - { - conversation->setOfflineMessages(new_messages); - notifyParticularConversationObservers(conversation->getSessionID(), LLConversationLogObserver::CHANGED_OfflineIMs); - } -} - -void LLConversationLog::updateConversationTimestamp(LLConversation* conversation) -{ - if (conversation) - { - conversation->updateTimestamp(); - notifyParticularConversationObservers(conversation->getSessionID(), LLConversationLogObserver::CHANGED_TIME); - } -} - -LLConversation* LLConversationLog::findConversation(const LLIMModel::LLIMSession* session) -{ - if (session) - { - const LLUUID session_id = session->isOutgoingAdHoc() ? session->generateOutgoingAdHocHash() : session->mSessionID; - - conversations_vec_t::iterator conv_it = mConversations.begin(); - for(; conv_it != mConversations.end(); ++conv_it) - { - if (conv_it->getSessionID() == session_id) - { - return &*conv_it; - } - } - } - - return NULL; -} - -void LLConversationLog::removeConversation(const LLConversation& conversation) -{ - conversations_vec_t::iterator conv_it = mConversations.begin(); - for(; conv_it != mConversations.end(); ++conv_it) - { - if (conv_it->getSessionID() == conversation.getSessionID() && conv_it->getTime() == conversation.getTime()) - { - mConversations.erase(conv_it); - notifyObservers(); - cache(); - return; - } - } -} - -const LLConversation* LLConversationLog::getConversation(const LLUUID& session_id) -{ - conversations_vec_t::const_iterator conv_it = mConversations.begin(); - for(; conv_it != mConversations.end(); ++conv_it) - { - if (conv_it->getSessionID() == session_id) - { - return &*conv_it; - } - } - - return NULL; -} - -void LLConversationLog::addObserver(LLConversationLogObserver* observer) -{ - mObservers.insert(observer); -} - -void LLConversationLog::removeObserver(LLConversationLogObserver* observer) -{ - mObservers.erase(observer); -} - -void LLConversationLog::sessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id, bool has_offline_msg) -{ - logConversation(session_id, has_offline_msg); -} - -void LLConversationLog::cache() -{ - if (gSavedPerAccountSettings.getS32("KeepConversationLogTranscripts") > 0) - { - saveToFile(getFileName()); - } -} - -void LLConversationLog::getListOfBackupLogs(std::vector<std::string>& list_of_backup_logs) -{ - // get Users log directory - std::string dirname = gDirUtilp->getPerAccountChatLogsDir(); - - // add final OS dependent delimiter - dirname += gDirUtilp->getDirDelimiter(); - - // create search pattern - std::string pattern = "conversation.log.backup*"; - - LLDirIterator iter(dirname, pattern); - std::string filename; - while (iter.next(filename)) - { - list_of_backup_logs.push_back(gDirUtilp->add(dirname, filename)); - } -} - -void LLConversationLog::deleteBackupLogs() -{ - std::vector<std::string> backup_logs; - getListOfBackupLogs(backup_logs); - - for (const std::string& fullpath : backup_logs) - { - LLFile::remove(fullpath); - } -} - -void LLConversationLog::verifyFilename(const LLUUID& session_id, const std::string &expected_filename, const std::string &new_session_name) -{ - conversations_vec_t::iterator conv_it = mConversations.begin(); - for (; conv_it != mConversations.end(); ++conv_it) - { - if (conv_it->getSessionID() == session_id) - { - if (conv_it->getHistoryFileName() != expected_filename) - { - LLLogChat::renameLogFile(conv_it->getHistoryFileName(), expected_filename); - conv_it->updateHistoryFileName(expected_filename); - conv_it->setConversationName(new_session_name); - } - break; - } - } -} - -bool LLConversationLog::moveLog(const std::string &originDirectory, const std::string &targetDirectory) -{ - - std::string backupFileName; - unsigned backupFileCount = 0; - - //Does the file exist in the current path, if it does lets move it - if(LLFile::isfile(originDirectory)) - { - //The target directory contains that file already, so lets store it - if(LLFile::isfile(targetDirectory)) - { - backupFileName = targetDirectory + ".backup"; - - //If needed store backup file as .backup1 etc. - while(LLFile::isfile(backupFileName)) - { - ++backupFileCount; - backupFileName = targetDirectory + ".backup" + std::to_string(backupFileCount); - } - - //Rename the file to its backup name so it is not overwritten - LLFile::rename(targetDirectory, backupFileName); - } - - //Move the file from the current path to target path - if(LLFile::rename(originDirectory, targetDirectory) != 0) - { - return false; - } - } - - return true; -} - -void LLConversationLog::initLoggingState() -{ - if (gSavedPerAccountSettings.controlExists("KeepConversationLogTranscripts")) - { - LLControlVariable * keep_log_ctrlp = gSavedPerAccountSettings.getControl("KeepConversationLogTranscripts").get(); - S32 log_mode = keep_log_ctrlp->getValue(); - keep_log_ctrlp->getSignal()->connect(boost::bind(&LLConversationLog::enableLogging, this, _2)); - if (log_mode > 0) - { - enableLogging(log_mode); - } - } -} - -std::string LLConversationLog::getFileName() -{ - std::string filename = "conversation"; - std::string log_address = gDirUtilp->getExpandedFilename(LL_PATH_PER_ACCOUNT_CHAT_LOGS, filename); - if (!log_address.empty()) - { - log_address += ".log"; - } - return log_address; -} - -bool LLConversationLog::saveToFile(const std::string& filename) -{ - if (!filename.size()) - { - LL_WARNS() << "Call log list filename is empty!" << LL_ENDL; - return false; - } - - LLFILE* fp = LLFile::fopen(filename, "wb"); - if (!fp) - { - LL_WARNS() << "Couldn't open call log list" << filename << LL_ENDL; - return false; - } - - std::string participant_id; - std::string conversation_id; - - conversations_vec_t::const_iterator conv_it = mConversations.begin(); - for (; conv_it != mConversations.end(); ++conv_it) - { - conv_it->getSessionID().toString(conversation_id); - conv_it->getParticipantID().toString(participant_id); - - bool is_adhoc = (conv_it->getConversationType() == LLIMModel::LLIMSession::ADHOC_SESSION); - std::string conv_name = is_adhoc ? conv_it->getConversationName() : LLURI::escape(conv_it->getConversationName()); - std::string file_name = is_adhoc ? conv_it->getHistoryFileName() : LLURI::escape(conv_it->getHistoryFileName()); - - // examples of two file entries - // [1343221177] 0 1 0 John Doe| 7e4ec5be-783f-49f5-71dz-16c58c64c145 4ec62a74-c246-0d25-2af6-846beac2aa55 john.doe| - // [1343222639] 2 0 0 Ad-hoc Conference| c3g67c89-c479-4c97-b21d-32869bcfe8rc 68f1c33e-4135-3e3e-a897-8c9b23115c09 Ad-hoc Conference hash597394a0-9982-766d-27b8-c75560213b9a| - fprintf(fp, "[%lld] %d %d %d %s| %s %s %s|\n", - (S64)conv_it->getTime().value(), - (S32)conv_it->getConversationType(), - (S32)0, - (S32)conv_it->hasOfflineMessages(), - conv_name.c_str(), - participant_id.c_str(), - conversation_id.c_str(), - file_name.c_str()); - } - fclose(fp); - return true; -} -bool LLConversationLog::loadFromFile(const std::string& filename) -{ - if(!filename.size()) - { - LL_WARNS() << "Call log list filename is empty!" << LL_ENDL; - return false; - } - - LLFILE* fp = LLFile::fopen(filename, "rb"); - if (!fp) - { - LL_WARNS() << "Couldn't open call log list" << filename << LL_ENDL; - return false; - } - bool purge_required = false; - - static constexpr int UTF_BUFFER{ 1024 }; // long enough to handle the most extreme Unicode nonsense and some to spare - - char buffer[UTF_BUFFER]; - char conv_name_buffer[MAX_STRING]; - char part_id_buffer[MAX_STRING]; - char conv_id_buffer[MAX_STRING]; - char history_file_name[MAX_STRING]; - S32 has_offline_ims; - S32 stype; - S64 time; - // before CHUI-348 it was a flag of conversation voice state - int prereserved_unused; - - memset(buffer, '\0', UTF_BUFFER); - while (!feof(fp) && fgets(buffer, UTF_BUFFER, fp)) - { - // force blank for added safety - memset(conv_name_buffer, '\0', MAX_STRING); - memset(part_id_buffer, '\0', MAX_STRING); - memset(conv_id_buffer, '\0', MAX_STRING); - memset(history_file_name, '\0', MAX_STRING); - - sscanf(buffer, "[%lld] %d %d %d %[^|]| %s %s %[^|]|", - &time, - &stype, - &prereserved_unused, - &has_offline_ims, - conv_name_buffer, - part_id_buffer, - conv_id_buffer, - history_file_name); - - bool is_adhoc = ((SessionType)stype == LLIMModel::LLIMSession::ADHOC_SESSION); - std::string conv_name = is_adhoc ? conv_name_buffer : LLURI::unescape(conv_name_buffer); - std::string file_name = is_adhoc ? history_file_name : LLURI::unescape(history_file_name); - - ConversationParams params; - params.time(LLUnits::Seconds::fromValue(time)) - .conversation_type((SessionType)stype) - .has_offline_ims(has_offline_ims) - .conversation_name(conv_name) - .participant_id(LLUUID(part_id_buffer)) - .session_id(LLUUID(conv_id_buffer)) - .history_filename(file_name); - - LLConversation conversation(params); - - // CHUI-325 - // The conversation log should be capped to the last 30 days. Conversations with the last utterance - // being over 30 days old should be purged from the conversation log text file on login. - if (conversation.isOlderThan(CONVERSATION_LIFETIME)) - { - purge_required = true; - continue; - } - - mConversations.push_back(conversation); - memset(buffer, '\0', UTF_BUFFER); - } - fclose(fp); - - if(purge_required) - { - LLFile::remove(filename); - cache(); - } - - notifyObservers(); - return true; -} - -void LLConversationLog::notifyObservers() -{ - std::set<LLConversationLogObserver*>::const_iterator iter = mObservers.begin(); - for (; iter != mObservers.end(); ++iter) - { - (*iter)->changed(); - } -} - -void LLConversationLog::notifyParticularConversationObservers(const LLUUID& session_id, U32 mask) -{ - std::set<LLConversationLogObserver*>::const_iterator iter = mObservers.begin(); - for (; iter != mObservers.end(); ++iter) - { - (*iter)->changed(session_id, mask); - } -} - -void LLConversationLog::onNewMessageReceived(const LLSD& data) -{ - const LLUUID session_id = data["session_id"].asUUID(); - logConversation(session_id, false); -} - -void LLConversationLog::onAvatarNameCache(const LLUUID& participant_id, const LLAvatarName& av_name, const LLIMModel::LLIMSession* session) -{ - mAvatarNameCacheConnection.disconnect(); - updateConversationName(session, av_name.getCompleteName()); -} - -void LLConversationLog::onClearLog() -{ - LLNotificationsUtil::add("PreferenceChatClearLog", LLSD(), LLSD(), boost::bind(&LLConversationLog::onClearLogResponse, this, _1, _2)); -} - -void LLConversationLog::onClearLogResponse(const LLSD& notification, const LLSD& response) -{ - if (0 == LLNotificationsUtil::getSelectedOption(notification, response)) - { - mConversations.clear(); - notifyObservers(); - cache(); - deleteBackupLogs(); - } -} +/**
+ * @file llconversationlog.h
+ *
+ * $LicenseInfo:firstyear=2002&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2012, 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$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "llagent.h"
+#include "llavatarnamecache.h"
+#include "llconversationlog.h"
+#include "lldiriterator.h"
+#include "llnotificationsutil.h"
+#include "lltrans.h"
+
+#include "boost/lexical_cast.hpp"
+
+const S32Days CONVERSATION_LIFETIME = (S32Days)30; // lifetime of LLConversation is 30 days by spec
+
+struct ConversationParams : public LLInitParam::Block<ConversationParams>
+{
+ Mandatory<U64Seconds > time;
+ Mandatory<std::string> timestamp;
+ Mandatory<SessionType> conversation_type;
+ Mandatory<std::string> conversation_name,
+ history_filename;
+ Mandatory<LLUUID> session_id,
+ participant_id;
+ Mandatory<bool> has_offline_ims;
+};
+
+/************************************************************************/
+/* LLConversation implementation */
+/************************************************************************/
+
+LLConversation::LLConversation(const ConversationParams& params)
+: mTime(params.time),
+ mTimestamp(params.timestamp.isProvided() ? params.timestamp : createTimestamp(params.time)),
+ mConversationType(params.conversation_type),
+ mConversationName(params.conversation_name),
+ mHistoryFileName(params.history_filename),
+ mSessionID(params.session_id),
+ mParticipantID(params.participant_id),
+ mHasOfflineIMs(params.has_offline_ims)
+{
+ setListenIMFloaterOpened();
+}
+
+LLConversation::LLConversation(const LLIMModel::LLIMSession& session)
+: mTime(time_corrected()),
+ mTimestamp(createTimestamp(mTime)),
+ mConversationType(session.mSessionType),
+ mConversationName(session.mName),
+ mHistoryFileName(session.mHistoryFileName),
+ mSessionID(session.isOutgoingAdHoc() ? session.generateOutgoingAdHocHash() : session.mSessionID),
+ mParticipantID(session.mOtherParticipantID),
+ mHasOfflineIMs(session.mHasOfflineMessage)
+{
+ setListenIMFloaterOpened();
+}
+
+LLConversation::LLConversation(const LLConversation& conversation)
+{
+ mTime = conversation.getTime();
+ mTimestamp = conversation.getTimestamp();
+ mConversationType = conversation.getConversationType();
+ mConversationName = conversation.getConversationName();
+ mHistoryFileName = conversation.getHistoryFileName();
+ mSessionID = conversation.getSessionID();
+ mParticipantID = conversation.getParticipantID();
+ mHasOfflineIMs = conversation.hasOfflineMessages();
+
+ setListenIMFloaterOpened();
+}
+
+LLConversation::~LLConversation()
+{
+ mIMFloaterShowedConnection.disconnect();
+}
+
+void LLConversation::updateTimestamp()
+{
+ mTime = (U64Seconds)time_corrected();
+ mTimestamp = createTimestamp(mTime);
+}
+
+void LLConversation::onIMFloaterShown(const LLUUID& session_id)
+{
+ if (mSessionID == session_id)
+ {
+ mHasOfflineIMs = false;
+ }
+}
+
+// static
+const std::string LLConversation::createTimestamp(const U64Seconds& utc_time)
+{
+ std::string timeStr;
+ LLSD substitution;
+ substitution["datetime"] = (S32)utc_time.value();
+
+ timeStr = "["+LLTrans::getString ("TimeMonth")+"]/["
+ +LLTrans::getString ("TimeDay")+"]/["
+ +LLTrans::getString ("TimeYear")+"] ["
+ +LLTrans::getString ("TimeHour")+"]:["
+ +LLTrans::getString ("TimeMin")+"]";
+
+
+ LLStringUtil::format (timeStr, substitution);
+ return timeStr;
+}
+
+bool LLConversation::isOlderThan(U32Days days) const
+{
+ U64Seconds now(time_corrected());
+ U32Days age = now - mTime;
+
+ return age > days;
+}
+
+void LLConversation::setListenIMFloaterOpened()
+{
+ LLFloaterIMSession* floater = LLFloaterIMSession::findInstance(mSessionID);
+
+ bool offline_ims_visible = LLFloaterIMSession::isVisible(floater) && floater->hasFocus();
+
+ // we don't need to listen for im floater with this conversation is opened
+ // if floater is already opened or this conversation doesn't have unread offline messages
+ if (mHasOfflineIMs && !offline_ims_visible)
+ {
+ mIMFloaterShowedConnection = LLFloaterIMSession::setIMFloaterShowedCallback(boost::bind(&LLConversation::onIMFloaterShown, this, _1));
+ }
+ else
+ {
+ mHasOfflineIMs = false;
+ }
+}
+
+/************************************************************************/
+/* LLConversationLogFriendObserver implementation */
+/************************************************************************/
+
+// Note : An LLSingleton like LLConversationLog cannot be an LLFriendObserver
+// at the same time.
+// This is because avatar observers are deleted by the observed object which
+// conflicts with the way LLSingleton are deleted.
+
+class LLConversationLogFriendObserver : public LLFriendObserver
+{
+public:
+ LLConversationLogFriendObserver() {}
+ virtual ~LLConversationLogFriendObserver() {}
+ virtual void changed(U32 mask);
+};
+
+void LLConversationLogFriendObserver::changed(U32 mask)
+{
+ if (mask & (LLFriendObserver::ADD | LLFriendObserver::REMOVE))
+ {
+ LLConversationLog::instance().notifyObservers();
+ }
+}
+
+/************************************************************************/
+/* LLConversationLog implementation */
+/************************************************************************/
+
+LLConversationLog::LLConversationLog() :
+ mAvatarNameCacheConnection(),
+ mLoggingEnabled(false)
+{
+}
+
+void LLConversationLog::enableLogging(S32 log_mode)
+{
+ mLoggingEnabled = log_mode > 0;
+ if (log_mode > 0)
+ {
+ mConversations.clear();
+ loadFromFile(getFileName());
+ LLIMMgr::instance().addSessionObserver(this);
+ mNewMessageSignalConnection = LLIMModel::instance().addNewMsgCallback(boost::bind(&LLConversationLog::onNewMessageReceived, this, _1));
+
+ mFriendObserver = new LLConversationLogFriendObserver;
+ LLAvatarTracker::instance().addObserver(mFriendObserver);
+ }
+ else
+ {
+ saveToFile(getFileName());
+
+ LLIMMgr::instance().removeSessionObserver(this);
+ mNewMessageSignalConnection.disconnect();
+ LLAvatarTracker::instance().removeObserver(mFriendObserver);
+ }
+
+ notifyObservers();
+}
+
+void LLConversationLog::logConversation(const LLUUID& session_id, bool has_offline_msg)
+{
+ const LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(session_id);
+ LLConversation* conversation = findConversation(session);
+
+ if (session && session->mOtherParticipantID != gAgentID)
+ {
+ if (conversation)
+ {
+ if(has_offline_msg)
+ {
+ updateOfflineIMs(session, has_offline_msg);
+ }
+ updateConversationTimestamp(conversation);
+ }
+ else
+ {
+ createConversation(session);
+ }
+ }
+}
+
+void LLConversationLog::createConversation(const LLIMModel::LLIMSession* session)
+{
+ if (session)
+ {
+ LLConversation conversation(*session);
+ mConversations.push_back(conversation);
+
+ if (LLIMModel::LLIMSession::P2P_SESSION == session->mSessionType)
+ {
+ if (mAvatarNameCacheConnection.connected())
+ {
+ mAvatarNameCacheConnection.disconnect();
+ }
+ mAvatarNameCacheConnection = LLAvatarNameCache::get(session->mOtherParticipantID, boost::bind(&LLConversationLog::onAvatarNameCache, this, _1, _2, session));
+ }
+
+ notifyObservers();
+ }
+}
+
+void LLConversationLog::updateConversationName(const LLIMModel::LLIMSession* session, const std::string& name)
+{
+ if (!session)
+ {
+ return;
+ }
+
+ LLConversation* conversation = findConversation(session);
+ if (conversation)
+ {
+ conversation->setConversationName(name);
+ notifyParticularConversationObservers(conversation->getSessionID(), LLConversationLogObserver::CHANGED_NAME);
+ }
+}
+
+void LLConversationLog::updateOfflineIMs(const LLIMModel::LLIMSession* session, bool new_messages)
+{
+ if (!session)
+ {
+ return;
+ }
+
+ LLConversation* conversation = findConversation(session);
+ if (conversation)
+ {
+ conversation->setOfflineMessages(new_messages);
+ notifyParticularConversationObservers(conversation->getSessionID(), LLConversationLogObserver::CHANGED_OfflineIMs);
+ }
+}
+
+void LLConversationLog::updateConversationTimestamp(LLConversation* conversation)
+{
+ if (conversation)
+ {
+ conversation->updateTimestamp();
+ notifyParticularConversationObservers(conversation->getSessionID(), LLConversationLogObserver::CHANGED_TIME);
+ }
+}
+
+LLConversation* LLConversationLog::findConversation(const LLIMModel::LLIMSession* session)
+{
+ if (session)
+ {
+ const LLUUID session_id = session->isOutgoingAdHoc() ? session->generateOutgoingAdHocHash() : session->mSessionID;
+
+ conversations_vec_t::iterator conv_it = mConversations.begin();
+ for(; conv_it != mConversations.end(); ++conv_it)
+ {
+ if (conv_it->getSessionID() == session_id)
+ {
+ return &*conv_it;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+void LLConversationLog::removeConversation(const LLConversation& conversation)
+{
+ conversations_vec_t::iterator conv_it = mConversations.begin();
+ for(; conv_it != mConversations.end(); ++conv_it)
+ {
+ if (conv_it->getSessionID() == conversation.getSessionID() && conv_it->getTime() == conversation.getTime())
+ {
+ mConversations.erase(conv_it);
+ notifyObservers();
+ cache();
+ return;
+ }
+ }
+}
+
+const LLConversation* LLConversationLog::getConversation(const LLUUID& session_id)
+{
+ conversations_vec_t::const_iterator conv_it = mConversations.begin();
+ for(; conv_it != mConversations.end(); ++conv_it)
+ {
+ if (conv_it->getSessionID() == session_id)
+ {
+ return &*conv_it;
+ }
+ }
+
+ return NULL;
+}
+
+void LLConversationLog::addObserver(LLConversationLogObserver* observer)
+{
+ mObservers.insert(observer);
+}
+
+void LLConversationLog::removeObserver(LLConversationLogObserver* observer)
+{
+ mObservers.erase(observer);
+}
+
+void LLConversationLog::sessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id, bool has_offline_msg)
+{
+ logConversation(session_id, has_offline_msg);
+}
+
+void LLConversationLog::cache()
+{
+ if (gSavedPerAccountSettings.getS32("KeepConversationLogTranscripts") > 0)
+ {
+ saveToFile(getFileName());
+ }
+}
+
+void LLConversationLog::getListOfBackupLogs(std::vector<std::string>& list_of_backup_logs)
+{
+ // get Users log directory
+ std::string dirname = gDirUtilp->getPerAccountChatLogsDir();
+
+ // add final OS dependent delimiter
+ dirname += gDirUtilp->getDirDelimiter();
+
+ // create search pattern
+ std::string pattern = "conversation.log.backup*";
+
+ LLDirIterator iter(dirname, pattern);
+ std::string filename;
+ while (iter.next(filename))
+ {
+ list_of_backup_logs.push_back(gDirUtilp->add(dirname, filename));
+ }
+}
+
+void LLConversationLog::deleteBackupLogs()
+{
+ std::vector<std::string> backup_logs;
+ getListOfBackupLogs(backup_logs);
+
+ for (const std::string& fullpath : backup_logs)
+ {
+ LLFile::remove(fullpath);
+ }
+}
+
+void LLConversationLog::verifyFilename(const LLUUID& session_id, const std::string &expected_filename, const std::string &new_session_name)
+{
+ conversations_vec_t::iterator conv_it = mConversations.begin();
+ for (; conv_it != mConversations.end(); ++conv_it)
+ {
+ if (conv_it->getSessionID() == session_id)
+ {
+ if (conv_it->getHistoryFileName() != expected_filename)
+ {
+ LLLogChat::renameLogFile(conv_it->getHistoryFileName(), expected_filename);
+ conv_it->updateHistoryFileName(expected_filename);
+ conv_it->setConversationName(new_session_name);
+ }
+ break;
+ }
+ }
+}
+
+bool LLConversationLog::moveLog(const std::string &originDirectory, const std::string &targetDirectory)
+{
+
+ std::string backupFileName;
+ unsigned backupFileCount = 0;
+
+ //Does the file exist in the current path, if it does lets move it
+ if(LLFile::isfile(originDirectory))
+ {
+ //The target directory contains that file already, so lets store it
+ if(LLFile::isfile(targetDirectory))
+ {
+ backupFileName = targetDirectory + ".backup";
+
+ //If needed store backup file as .backup1 etc.
+ while(LLFile::isfile(backupFileName))
+ {
+ ++backupFileCount;
+ backupFileName = targetDirectory + ".backup" + std::to_string(backupFileCount);
+ }
+
+ //Rename the file to its backup name so it is not overwritten
+ LLFile::rename(targetDirectory, backupFileName);
+ }
+
+ //Move the file from the current path to target path
+ if(LLFile::rename(originDirectory, targetDirectory) != 0)
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void LLConversationLog::initLoggingState()
+{
+ if (gSavedPerAccountSettings.controlExists("KeepConversationLogTranscripts"))
+ {
+ LLControlVariable * keep_log_ctrlp = gSavedPerAccountSettings.getControl("KeepConversationLogTranscripts").get();
+ S32 log_mode = keep_log_ctrlp->getValue();
+ keep_log_ctrlp->getSignal()->connect(boost::bind(&LLConversationLog::enableLogging, this, _2));
+ if (log_mode > 0)
+ {
+ enableLogging(log_mode);
+ }
+ }
+}
+
+std::string LLConversationLog::getFileName()
+{
+ std::string filename = "conversation";
+ std::string log_address = gDirUtilp->getExpandedFilename(LL_PATH_PER_ACCOUNT_CHAT_LOGS, filename);
+ if (!log_address.empty())
+ {
+ log_address += ".log";
+ }
+ return log_address;
+}
+
+bool LLConversationLog::saveToFile(const std::string& filename)
+{
+ if (!filename.size())
+ {
+ LL_WARNS() << "Call log list filename is empty!" << LL_ENDL;
+ return false;
+ }
+
+ LLFILE* fp = LLFile::fopen(filename, "wb");
+ if (!fp)
+ {
+ LL_WARNS() << "Couldn't open call log list" << filename << LL_ENDL;
+ return false;
+ }
+
+ std::string participant_id;
+ std::string conversation_id;
+
+ conversations_vec_t::const_iterator conv_it = mConversations.begin();
+ for (; conv_it != mConversations.end(); ++conv_it)
+ {
+ conv_it->getSessionID().toString(conversation_id);
+ conv_it->getParticipantID().toString(participant_id);
+
+ bool is_adhoc = (conv_it->getConversationType() == LLIMModel::LLIMSession::ADHOC_SESSION);
+ std::string conv_name = is_adhoc ? conv_it->getConversationName() : LLURI::escape(conv_it->getConversationName());
+ std::string file_name = is_adhoc ? conv_it->getHistoryFileName() : LLURI::escape(conv_it->getHistoryFileName());
+
+ // examples of two file entries
+ // [1343221177] 0 1 0 John Doe| 7e4ec5be-783f-49f5-71dz-16c58c64c145 4ec62a74-c246-0d25-2af6-846beac2aa55 john.doe|
+ // [1343222639] 2 0 0 Ad-hoc Conference| c3g67c89-c479-4c97-b21d-32869bcfe8rc 68f1c33e-4135-3e3e-a897-8c9b23115c09 Ad-hoc Conference hash597394a0-9982-766d-27b8-c75560213b9a|
+ fprintf(fp, "[%lld] %d %d %d %s| %s %s %s|\n",
+ (S64)conv_it->getTime().value(),
+ (S32)conv_it->getConversationType(),
+ (S32)0,
+ (S32)conv_it->hasOfflineMessages(),
+ conv_name.c_str(),
+ participant_id.c_str(),
+ conversation_id.c_str(),
+ file_name.c_str());
+ }
+ fclose(fp);
+ return true;
+}
+bool LLConversationLog::loadFromFile(const std::string& filename)
+{
+ if(!filename.size())
+ {
+ LL_WARNS() << "Call log list filename is empty!" << LL_ENDL;
+ return false;
+ }
+
+ LLFILE* fp = LLFile::fopen(filename, "rb");
+ if (!fp)
+ {
+ LL_WARNS() << "Couldn't open call log list" << filename << LL_ENDL;
+ return false;
+ }
+ bool purge_required = false;
+
+ static constexpr int UTF_BUFFER{ 1024 }; // long enough to handle the most extreme Unicode nonsense and some to spare
+
+ char buffer[UTF_BUFFER];
+ char conv_name_buffer[MAX_STRING];
+ char part_id_buffer[MAX_STRING];
+ char conv_id_buffer[MAX_STRING];
+ char history_file_name[MAX_STRING];
+ S32 has_offline_ims;
+ S32 stype;
+ S64 time;
+ // before CHUI-348 it was a flag of conversation voice state
+ int prereserved_unused;
+
+ memset(buffer, '\0', UTF_BUFFER);
+ while (!feof(fp) && fgets(buffer, UTF_BUFFER, fp))
+ {
+ // force blank for added safety
+ memset(conv_name_buffer, '\0', MAX_STRING);
+ memset(part_id_buffer, '\0', MAX_STRING);
+ memset(conv_id_buffer, '\0', MAX_STRING);
+ memset(history_file_name, '\0', MAX_STRING);
+
+ sscanf(buffer, "[%lld] %d %d %d %[^|]| %s %s %[^|]|",
+ &time,
+ &stype,
+ &prereserved_unused,
+ &has_offline_ims,
+ conv_name_buffer,
+ part_id_buffer,
+ conv_id_buffer,
+ history_file_name);
+
+ bool is_adhoc = ((SessionType)stype == LLIMModel::LLIMSession::ADHOC_SESSION);
+ std::string conv_name = is_adhoc ? conv_name_buffer : LLURI::unescape(conv_name_buffer);
+ std::string file_name = is_adhoc ? history_file_name : LLURI::unescape(history_file_name);
+
+ ConversationParams params;
+ params.time(LLUnits::Seconds::fromValue(time))
+ .conversation_type((SessionType)stype)
+ .has_offline_ims(has_offline_ims)
+ .conversation_name(conv_name)
+ .participant_id(LLUUID(part_id_buffer))
+ .session_id(LLUUID(conv_id_buffer))
+ .history_filename(file_name);
+
+ LLConversation conversation(params);
+
+ // CHUI-325
+ // The conversation log should be capped to the last 30 days. Conversations with the last utterance
+ // being over 30 days old should be purged from the conversation log text file on login.
+ if (conversation.isOlderThan(CONVERSATION_LIFETIME))
+ {
+ purge_required = true;
+ continue;
+ }
+
+ mConversations.push_back(conversation);
+ memset(buffer, '\0', UTF_BUFFER);
+ }
+ fclose(fp);
+
+ if(purge_required)
+ {
+ LLFile::remove(filename);
+ cache();
+ }
+
+ notifyObservers();
+ return true;
+}
+
+void LLConversationLog::notifyObservers()
+{
+ std::set<LLConversationLogObserver*>::const_iterator iter = mObservers.begin();
+ for (; iter != mObservers.end(); ++iter)
+ {
+ (*iter)->changed();
+ }
+}
+
+void LLConversationLog::notifyParticularConversationObservers(const LLUUID& session_id, U32 mask)
+{
+ std::set<LLConversationLogObserver*>::const_iterator iter = mObservers.begin();
+ for (; iter != mObservers.end(); ++iter)
+ {
+ (*iter)->changed(session_id, mask);
+ }
+}
+
+void LLConversationLog::onNewMessageReceived(const LLSD& data)
+{
+ const LLUUID session_id = data["session_id"].asUUID();
+ logConversation(session_id, false);
+}
+
+void LLConversationLog::onAvatarNameCache(const LLUUID& participant_id, const LLAvatarName& av_name, const LLIMModel::LLIMSession* session)
+{
+ mAvatarNameCacheConnection.disconnect();
+ updateConversationName(session, av_name.getCompleteName());
+}
+
+void LLConversationLog::onClearLog()
+{
+ LLNotificationsUtil::add("PreferenceChatClearLog", LLSD(), LLSD(), boost::bind(&LLConversationLog::onClearLogResponse, this, _1, _2));
+}
+
+void LLConversationLog::onClearLogResponse(const LLSD& notification, const LLSD& response)
+{
+ if (0 == LLNotificationsUtil::getSelectedOption(notification, response))
+ {
+ mConversations.clear();
+ notifyObservers();
+ cache();
+ deleteBackupLogs();
+ }
+}
|
