lllogchat.cpp 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. /**
  2. * @file lllogchat.cpp
  3. * @brief LLLogChat class implementation
  4. *
  5. * $LicenseInfo:firstyear=2002&license=viewergpl$
  6. *
  7. * Copyright (c) 2002-2009, Linden Research, Inc.
  8. * Copyright (c) 2011-2023, Henri Beauchamp.
  9. *
  10. * Second Life Viewer Source Code
  11. * The source code in this file ("Source Code") is provided by Linden Lab
  12. * to you under the terms of the GNU General Public License, version 2.0
  13. * ("GPL"), unless you have obtained a separate licensing agreement
  14. * ("Other License"), formally executed by you and Linden Lab. Terms of
  15. * the GPL can be found in doc/GPL-license.txt in this distribution, or
  16. * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
  17. *
  18. * There are special exceptions to the terms and conditions of the GPL as
  19. * it is applied to this Source Code. View the full text of the exception
  20. * in the file doc/FLOSS-exception.txt in this software distribution, or
  21. * online at
  22. * http://secondlifegrid.net/programs/open_source/licensing/flossexception
  23. *
  24. * By copying, modifying or distributing this software, you acknowledge
  25. * that you have read and understood your obligations described above,
  26. * and agree to abide by those obligations.
  27. *
  28. * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
  29. * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
  30. * COMPLETENESS OR PERFORMANCE.
  31. * $/LicenseInfo$
  32. */
  33. #include "llviewerprecompiledheaders.h"
  34. #include "lllogchat.h"
  35. #include "llcorehttputil.h"
  36. #include "lldir.h"
  37. #include "llsdutil.h"
  38. #include "llagent.h"
  39. #include "llfloaterim.h"
  40. #include "llgridmanager.h"
  41. #include "llmutelist.h"
  42. #include "llviewercontrol.h"
  43. //static
  44. std::string LLLogChat::timestamp(bool no_date, time_t ts)
  45. {
  46. std::string format;
  47. static LLCachedControl<bool> withdate(gSavedSettings, "LogTimestampDate");
  48. if (!no_date && withdate)
  49. {
  50. static LLCachedControl<std::string> date_fmt(gSavedSettings,
  51. "ShortDateFormat");
  52. format = date_fmt;
  53. format += " ";
  54. }
  55. static LLCachedControl<bool> with_seconds(gSavedSettings,
  56. "LogTimestampSeconds");
  57. if (with_seconds)
  58. {
  59. static LLCachedControl<std::string> long_fmt(gSavedSettings,
  60. "LongTimeFormat");
  61. format += long_fmt;
  62. }
  63. else
  64. {
  65. static LLCachedControl<std::string> short_fmt(gSavedSettings,
  66. "ShortTimeFormat");
  67. format += short_fmt;
  68. }
  69. if (!ts)
  70. {
  71. ts = time_corrected();
  72. }
  73. return "[" + LLGridManager::getTimeStamp(ts, format) + "] ";
  74. }
  75. //static
  76. std::string LLLogChat::makeLogFileName(std::string filename)
  77. {
  78. if (filename.empty())
  79. {
  80. filename = "chat";
  81. }
  82. else if (gIsInSecondLife &&
  83. gSavedPerAccountSettings.getBool("LogFileNameWithoutResident"))
  84. {
  85. LLStringUtil::replaceString(filename, " Resident", "");
  86. }
  87. if (gSavedPerAccountSettings.getBool("LogFileNamewithDate"))
  88. {
  89. time_t now;
  90. time(&now);
  91. char dbuffer[20];
  92. if (filename == "chat")
  93. {
  94. strftime(dbuffer, 20, "-%Y-%m-%d", localtime(&now));
  95. }
  96. else
  97. {
  98. strftime(dbuffer, 20, "-%Y-%m", localtime(&now));
  99. }
  100. filename += dbuffer;
  101. }
  102. filename = LLDir::getScrubbedFileName(filename);
  103. filename = gDirUtil.getFullPath(LL_PATH_PER_ACCOUNT_CHAT_LOGS, filename);
  104. filename += ".txt";
  105. return filename;
  106. }
  107. //static
  108. void LLLogChat::saveHistory(const std::string& filename,
  109. const std::string& line)
  110. {
  111. std::string log_filename = makeLogFileName(filename);
  112. LLFILE* fp = LLFile::open(log_filename, "a");
  113. if (!fp)
  114. {
  115. llwarns << "Could not write into chat/IM history log file: "
  116. << log_filename << llendl;
  117. return;
  118. }
  119. fprintf(fp, "%s\n", line.c_str());
  120. LLFile::close (fp);
  121. }
  122. //static
  123. void LLLogChat::loadHistory(const std::string& filename,
  124. void (*callback)(S32, const LLSD&, void*),
  125. void* userdata, const LLUUID& session_id)
  126. {
  127. std::string log_filename = makeLogFileName(filename);
  128. // Inform the floater about the log file name to use.
  129. callback(LOG_FILENAME, llsd::map("filename", log_filename), userdata);
  130. // For server messages timestamp comparisons; returns 0 for non-existent
  131. // file. HB
  132. time_t last_modified = LLFile::lastModidied(log_filename);
  133. LLFILE* fp = last_modified ? LLFile::open(log_filename, "r") : NULL;
  134. if (fp)
  135. {
  136. U32 bsize = gSavedPerAccountSettings.getU32("LogShowHistoryMaxSize");
  137. // The minimum must be larger than the largest line (1024 characters
  138. // for the largest text line + timestamp size + resident name size). HB
  139. bsize = llmax(bsize, 2U) * 1024U;
  140. char* buffer = (char*)malloc((size_t)bsize * sizeof(char));
  141. if (!buffer)
  142. {
  143. llwarns << "Failure to allocate buffer !" << llendl;
  144. LLFile::close(fp);
  145. callback(LOG_END, LLSD(), userdata);
  146. // Better aborting altogether if memory is *that* low; i.e. do not
  147. // even bother to attempt and get the server log at this point. HB
  148. return;
  149. }
  150. char* bptr;
  151. S32 len;
  152. bool firstline = true;
  153. if (fseek(fp, 1L - (long)bsize, SEEK_END))
  154. {
  155. // File is smaller than recall size. Get it all.
  156. firstline = false; // No risk of truncation.
  157. if (fseek(fp, 0, SEEK_SET))
  158. {
  159. llwarns << "Failure to seek file: " << log_filename << llendl;
  160. LLFile::close(fp);
  161. free(buffer);
  162. callback(LOG_END, LLSD(), userdata);
  163. // Better aborting now if the file system is corrupted ! HB
  164. return;
  165. }
  166. }
  167. while (fgets(buffer, bsize, fp))
  168. {
  169. len = strlen(buffer) - 1;
  170. for (bptr = buffer + len;
  171. (*bptr == '\n' || *bptr == '\r') && bptr > buffer; --bptr)
  172. {
  173. *bptr = '\0';
  174. }
  175. if (firstline) // Skip the truncated first line.
  176. {
  177. firstline = false;
  178. continue;
  179. }
  180. callback(LOG_LINE, llsd::map("line", std::string(buffer)),
  181. userdata);
  182. }
  183. LLFile::close(fp);
  184. free(buffer);
  185. }
  186. if (session_id.isNull() ||
  187. !gSavedPerAccountSettings.getBool("FetchGroupChatHistory"))
  188. {
  189. // Not a group chat, or the user does not want us to fetch history from
  190. // the server. We are done. HB
  191. callback(LOG_END, LLSD(), userdata);
  192. return;
  193. }
  194. const std::string& url = gAgent.getRegionCapability("ChatSessionRequest");
  195. if (url.empty())
  196. {
  197. // No such capability. We are done.
  198. callback(LOG_END, LLSD(), userdata);
  199. return;
  200. }
  201. // This callback will cause all incoming messages to get queued until the
  202. // server log has been retreived and printed. HB
  203. callback(LOG_SERVER_FETCH, LLSD(), userdata);
  204. // Fetch the server log asynchronously.
  205. gCoros.launch("fetchHistoryCoro",
  206. boost::bind(&LLLogChat::fetchHistoryCoro, url, session_id,
  207. callback, last_modified));
  208. }
  209. //static
  210. void LLLogChat::fetchHistoryCoro(const std::string& url, LLUUID session_id,
  211. void (*callback)(S32, const LLSD&, void*),
  212. time_t last_modified)
  213. {
  214. LLSD query;
  215. query["method"] = "fetch history";
  216. query["session-id"] = session_id;
  217. LLCoreHttpUtil::HttpCoroutineAdapter adapter("FetchHistory");
  218. LLSD result = adapter.postAndSuspend(url, query);
  219. LLFloaterIMSession* floaterp =
  220. LLFloaterIMSession::findInstance(session_id);
  221. if (!floaterp)
  222. {
  223. llinfos << "Received a reply for closed session Id: " << session_id
  224. << ". Ignored." << llendl;
  225. return;
  226. }
  227. // Note: in the (unlikely) event we would change the callback userdata NOT
  228. // to point on the corresponding IM floater, this would have to be changed
  229. // here too (i.e. userdata would have to be passed to this method) ! HB
  230. void* userdata = floaterp;
  231. LLCore::HttpStatus status =
  232. LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(result);
  233. if (!status)
  234. {
  235. llwarns << "Failed to retrieve the server log for IM session Id: "
  236. << session_id << llendl;
  237. callback(LOG_END, LLSD(), userdata);
  238. return;
  239. }
  240. const LLSD& history =
  241. result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_CONTENT];
  242. if (!history.isArray() || !history.size())
  243. {
  244. // No log available, or bad format (not an array).
  245. callback(LOG_END, LLSD(), userdata);
  246. return;
  247. }
  248. // Take the host computer/server clocks difference into acount and add 1s
  249. // to avoid rounding issues. HB
  250. last_modified += gUTCOffset + 1;
  251. static LLCachedControl<bool> stamp(gSavedPerAccountSettings,
  252. "IMLogTimestamp");
  253. std::string log_line, tmp;
  254. LLUUID from_id;
  255. for (LLSD::array_const_iterator it = history.beginArray(),
  256. end = history.endArray();
  257. it != end; ++it)
  258. {
  259. const LLSD& data = *it;
  260. if (!data.isMap() || !data.has("message") || !data.has("time"))
  261. {
  262. LL_DEBUGS("ServerIMLog") << "Skipping message due incomplete info: "
  263. << ll_pretty_print_sd(data) << LL_ENDL;
  264. continue;
  265. }
  266. time_t msg_stamp = data["time"].asInteger();
  267. if (msg_stamp <= last_modified)
  268. {
  269. LL_DEBUGS("ServerIMLog") << "Skipping message due to time stamp: "
  270. << msg_stamp << " - Last modified: "
  271. << last_modified << " - Skipped message: "
  272. << data["message"].asString() << LL_ENDL;
  273. continue;
  274. }
  275. if (data.has("from_id"))
  276. {
  277. from_id.set(data["from_id"], false);
  278. if (LLMuteList::isMuted(from_id, LLMute::flagTextChat))
  279. {
  280. // Do not list muted avatars' prose.
  281. continue;
  282. }
  283. }
  284. else
  285. {
  286. from_id.setNull();
  287. LL_DEBUGS("ServerIMLog") << "Message without a source Id: "
  288. << data["message"].asString() << LL_ENDL;
  289. }
  290. LLSD cbdata;
  291. cbdata["from_id"] = from_id.asString();
  292. // Get the text, and check for an emote.
  293. log_line = data["message"].asString();
  294. bool emote = log_line.compare(0, 4, "/me ") == 0;
  295. if (emote)
  296. {
  297. log_line.erase(0, 3);
  298. }
  299. // This will be used to compare with recently received messages:
  300. // since we cannot trust the time stamps or names formats, we only
  301. // retain the text (without "/me" for emotes, since this would not
  302. // appear in logs). HB
  303. cbdata["message"] = log_line;
  304. if (data.has("from"))
  305. {
  306. if (emote)
  307. {
  308. log_line = data["from"].asString() + log_line;
  309. }
  310. else
  311. {
  312. log_line = data["from"].asString() + ": " + log_line;
  313. }
  314. }
  315. if (stamp)
  316. {
  317. log_line = timestamp(false, msg_stamp) + log_line;
  318. }
  319. // This is the actual, full logged line to display in the floater. HB
  320. cbdata["line"] = log_line;
  321. callback(LOG_SERVER, cbdata, userdata);
  322. }
  323. callback(LOG_END, LLSD(), userdata);
  324. }