123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354 |
- /**
- * @file lllogchat.cpp
- * @brief LLLogChat class implementation
- *
- * $LicenseInfo:firstyear=2002&license=viewergpl$
- *
- * Copyright (c) 2002-2009, Linden Research, Inc.
- * Copyright (c) 2011-2023, Henri Beauchamp.
- *
- * Second Life Viewer Source Code
- * The source code in this file ("Source Code") is provided by Linden Lab
- * to you under the terms of the GNU General Public License, version 2.0
- * ("GPL"), unless you have obtained a separate licensing agreement
- * ("Other License"), formally executed by you and Linden Lab. Terms of
- * the GPL can be found in doc/GPL-license.txt in this distribution, or
- * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
- *
- * There are special exceptions to the terms and conditions of the GPL as
- * it is applied to this Source Code. View the full text of the exception
- * in the file doc/FLOSS-exception.txt in this software distribution, or
- * online at
- * http://secondlifegrid.net/programs/open_source/licensing/flossexception
- *
- * By copying, modifying or distributing this software, you acknowledge
- * that you have read and understood your obligations described above,
- * and agree to abide by those obligations.
- *
- * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
- * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
- * COMPLETENESS OR PERFORMANCE.
- * $/LicenseInfo$
- */
- #include "llviewerprecompiledheaders.h"
- #include "lllogchat.h"
- #include "llcorehttputil.h"
- #include "lldir.h"
- #include "llsdutil.h"
- #include "llagent.h"
- #include "llfloaterim.h"
- #include "llgridmanager.h"
- #include "llmutelist.h"
- #include "llviewercontrol.h"
- //static
- std::string LLLogChat::timestamp(bool no_date, time_t ts)
- {
- std::string format;
- static LLCachedControl<bool> withdate(gSavedSettings, "LogTimestampDate");
- if (!no_date && withdate)
- {
- static LLCachedControl<std::string> date_fmt(gSavedSettings,
- "ShortDateFormat");
- format = date_fmt;
- format += " ";
- }
- static LLCachedControl<bool> with_seconds(gSavedSettings,
- "LogTimestampSeconds");
- if (with_seconds)
- {
- static LLCachedControl<std::string> long_fmt(gSavedSettings,
- "LongTimeFormat");
- format += long_fmt;
- }
- else
- {
- static LLCachedControl<std::string> short_fmt(gSavedSettings,
- "ShortTimeFormat");
- format += short_fmt;
- }
- if (!ts)
- {
- ts = time_corrected();
- }
- return "[" + LLGridManager::getTimeStamp(ts, format) + "] ";
- }
- //static
- std::string LLLogChat::makeLogFileName(std::string filename)
- {
- if (filename.empty())
- {
- filename = "chat";
- }
- else if (gIsInSecondLife &&
- gSavedPerAccountSettings.getBool("LogFileNameWithoutResident"))
- {
- LLStringUtil::replaceString(filename, " Resident", "");
- }
- if (gSavedPerAccountSettings.getBool("LogFileNamewithDate"))
- {
- time_t now;
- time(&now);
- char dbuffer[20];
- if (filename == "chat")
- {
- strftime(dbuffer, 20, "-%Y-%m-%d", localtime(&now));
- }
- else
- {
- strftime(dbuffer, 20, "-%Y-%m", localtime(&now));
- }
- filename += dbuffer;
- }
- filename = LLDir::getScrubbedFileName(filename);
- filename = gDirUtil.getFullPath(LL_PATH_PER_ACCOUNT_CHAT_LOGS, filename);
- filename += ".txt";
- return filename;
- }
- //static
- void LLLogChat::saveHistory(const std::string& filename,
- const std::string& line)
- {
- std::string log_filename = makeLogFileName(filename);
- LLFILE* fp = LLFile::open(log_filename, "a");
- if (!fp)
- {
- llwarns << "Could not write into chat/IM history log file: "
- << log_filename << llendl;
- return;
- }
- fprintf(fp, "%s\n", line.c_str());
- LLFile::close (fp);
- }
- //static
- void LLLogChat::loadHistory(const std::string& filename,
- void (*callback)(S32, const LLSD&, void*),
- void* userdata, const LLUUID& session_id)
- {
- std::string log_filename = makeLogFileName(filename);
- // Inform the floater about the log file name to use.
- callback(LOG_FILENAME, llsd::map("filename", log_filename), userdata);
- // For server messages timestamp comparisons; returns 0 for non-existent
- // file. HB
- time_t last_modified = LLFile::lastModidied(log_filename);
- LLFILE* fp = last_modified ? LLFile::open(log_filename, "r") : NULL;
- if (fp)
- {
- U32 bsize = gSavedPerAccountSettings.getU32("LogShowHistoryMaxSize");
- // The minimum must be larger than the largest line (1024 characters
- // for the largest text line + timestamp size + resident name size). HB
- bsize = llmax(bsize, 2U) * 1024U;
- char* buffer = (char*)malloc((size_t)bsize * sizeof(char));
- if (!buffer)
- {
- llwarns << "Failure to allocate buffer !" << llendl;
- LLFile::close(fp);
- callback(LOG_END, LLSD(), userdata);
- // Better aborting altogether if memory is *that* low; i.e. do not
- // even bother to attempt and get the server log at this point. HB
- return;
- }
- char* bptr;
- S32 len;
- bool firstline = true;
- if (fseek(fp, 1L - (long)bsize, SEEK_END))
- {
- // File is smaller than recall size. Get it all.
- firstline = false; // No risk of truncation.
- if (fseek(fp, 0, SEEK_SET))
- {
- llwarns << "Failure to seek file: " << log_filename << llendl;
- LLFile::close(fp);
- free(buffer);
- callback(LOG_END, LLSD(), userdata);
- // Better aborting now if the file system is corrupted ! HB
- return;
- }
- }
- while (fgets(buffer, bsize, fp))
- {
- len = strlen(buffer) - 1;
- for (bptr = buffer + len;
- (*bptr == '\n' || *bptr == '\r') && bptr > buffer; --bptr)
- {
- *bptr = '\0';
- }
- if (firstline) // Skip the truncated first line.
- {
- firstline = false;
- continue;
- }
- callback(LOG_LINE, llsd::map("line", std::string(buffer)),
- userdata);
- }
- LLFile::close(fp);
- free(buffer);
- }
- if (session_id.isNull() ||
- !gSavedPerAccountSettings.getBool("FetchGroupChatHistory"))
- {
- // Not a group chat, or the user does not want us to fetch history from
- // the server. We are done. HB
- callback(LOG_END, LLSD(), userdata);
- return;
- }
- const std::string& url = gAgent.getRegionCapability("ChatSessionRequest");
- if (url.empty())
- {
- // No such capability. We are done.
- callback(LOG_END, LLSD(), userdata);
- return;
- }
- // This callback will cause all incoming messages to get queued until the
- // server log has been retreived and printed. HB
- callback(LOG_SERVER_FETCH, LLSD(), userdata);
- // Fetch the server log asynchronously.
- gCoros.launch("fetchHistoryCoro",
- boost::bind(&LLLogChat::fetchHistoryCoro, url, session_id,
- callback, last_modified));
- }
- //static
- void LLLogChat::fetchHistoryCoro(const std::string& url, LLUUID session_id,
- void (*callback)(S32, const LLSD&, void*),
- time_t last_modified)
- {
- LLSD query;
- query["method"] = "fetch history";
- query["session-id"] = session_id;
- LLCoreHttpUtil::HttpCoroutineAdapter adapter("FetchHistory");
- LLSD result = adapter.postAndSuspend(url, query);
- LLFloaterIMSession* floaterp =
- LLFloaterIMSession::findInstance(session_id);
- if (!floaterp)
- {
- llinfos << "Received a reply for closed session Id: " << session_id
- << ". Ignored." << llendl;
- return;
- }
- // Note: in the (unlikely) event we would change the callback userdata NOT
- // to point on the corresponding IM floater, this would have to be changed
- // here too (i.e. userdata would have to be passed to this method) ! HB
- void* userdata = floaterp;
- LLCore::HttpStatus status =
- LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(result);
- if (!status)
- {
- llwarns << "Failed to retrieve the server log for IM session Id: "
- << session_id << llendl;
- callback(LOG_END, LLSD(), userdata);
- return;
- }
- const LLSD& history =
- result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_CONTENT];
- if (!history.isArray() || !history.size())
- {
- // No log available, or bad format (not an array).
- callback(LOG_END, LLSD(), userdata);
- return;
- }
- // Take the host computer/server clocks difference into acount and add 1s
- // to avoid rounding issues. HB
- last_modified += gUTCOffset + 1;
- static LLCachedControl<bool> stamp(gSavedPerAccountSettings,
- "IMLogTimestamp");
- std::string log_line, tmp;
- LLUUID from_id;
- for (LLSD::array_const_iterator it = history.beginArray(),
- end = history.endArray();
- it != end; ++it)
- {
- const LLSD& data = *it;
- if (!data.isMap() || !data.has("message") || !data.has("time"))
- {
- LL_DEBUGS("ServerIMLog") << "Skipping message due incomplete info: "
- << ll_pretty_print_sd(data) << LL_ENDL;
- continue;
- }
- time_t msg_stamp = data["time"].asInteger();
- if (msg_stamp <= last_modified)
- {
- LL_DEBUGS("ServerIMLog") << "Skipping message due to time stamp: "
- << msg_stamp << " - Last modified: "
- << last_modified << " - Skipped message: "
- << data["message"].asString() << LL_ENDL;
- continue;
- }
- if (data.has("from_id"))
- {
- from_id.set(data["from_id"], false);
- if (LLMuteList::isMuted(from_id, LLMute::flagTextChat))
- {
- // Do not list muted avatars' prose.
- continue;
- }
- }
- else
- {
- from_id.setNull();
- LL_DEBUGS("ServerIMLog") << "Message without a source Id: "
- << data["message"].asString() << LL_ENDL;
- }
- LLSD cbdata;
- cbdata["from_id"] = from_id.asString();
- // Get the text, and check for an emote.
- log_line = data["message"].asString();
- bool emote = log_line.compare(0, 4, "/me ") == 0;
- if (emote)
- {
- log_line.erase(0, 3);
- }
- // This will be used to compare with recently received messages:
- // since we cannot trust the time stamps or names formats, we only
- // retain the text (without "/me" for emotes, since this would not
- // appear in logs). HB
- cbdata["message"] = log_line;
- if (data.has("from"))
- {
- if (emote)
- {
- log_line = data["from"].asString() + log_line;
- }
- else
- {
- log_line = data["from"].asString() + ": " + log_line;
- }
- }
- if (stamp)
- {
- log_line = timestamp(false, msg_stamp) + log_line;
- }
- // This is the actual, full logged line to display in the floater. HB
- cbdata["line"] = log_line;
- callback(LOG_SERVER, cbdata, userdata);
- }
- callback(LOG_END, LLSD(), userdata);
- }
|