You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

LoggingPlugin.java 37KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917
  1. /*
  2. * Copyright (c) 2006-2013 DMDirc Developers
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  20. * SOFTWARE.
  21. */
  22. package com.dmdirc.addons.logging;
  23. import com.dmdirc.util.io.ReverseFileReader;
  24. import com.dmdirc.Channel;
  25. import com.dmdirc.FrameContainer;
  26. import com.dmdirc.Query;
  27. import com.dmdirc.Server;
  28. import com.dmdirc.actions.CoreActionType;
  29. import com.dmdirc.interfaces.actions.ActionType;
  30. import com.dmdirc.config.ConfigManager;
  31. import com.dmdirc.config.Identity;
  32. import com.dmdirc.config.IdentityManager;
  33. import com.dmdirc.config.prefs.PluginPreferencesCategory;
  34. import com.dmdirc.config.prefs.PreferencesCategory;
  35. import com.dmdirc.config.prefs.PreferencesDialogModel;
  36. import com.dmdirc.config.prefs.PreferencesSetting;
  37. import com.dmdirc.config.prefs.PreferencesType;
  38. import com.dmdirc.interfaces.ActionController;
  39. import com.dmdirc.interfaces.ActionListener;
  40. import com.dmdirc.interfaces.ConfigChangeListener;
  41. import com.dmdirc.logger.ErrorLevel;
  42. import com.dmdirc.logger.Logger;
  43. import com.dmdirc.parser.interfaces.ChannelClientInfo;
  44. import com.dmdirc.parser.interfaces.ChannelInfo;
  45. import com.dmdirc.parser.interfaces.ClientInfo;
  46. import com.dmdirc.parser.interfaces.Parser;
  47. import com.dmdirc.plugins.BasePlugin;
  48. import com.dmdirc.plugins.PluginInfo;
  49. import com.dmdirc.ui.messages.Styliser;
  50. import com.dmdirc.util.io.StreamUtils;
  51. import java.awt.Color;
  52. import java.io.BufferedWriter;
  53. import java.io.File;
  54. import java.io.FileNotFoundException;
  55. import java.io.FileWriter;
  56. import java.io.IOException;
  57. import java.math.BigInteger;
  58. import java.security.MessageDigest;
  59. import java.security.NoSuchAlgorithmException;
  60. import java.text.DateFormat;
  61. import java.text.SimpleDateFormat;
  62. import java.util.ArrayList;
  63. import java.util.Collection;
  64. import java.util.Collections;
  65. import java.util.Date;
  66. import java.util.HashMap;
  67. import java.util.Map;
  68. import java.util.Stack;
  69. import java.util.Timer;
  70. import java.util.TimerTask;
  71. /**
  72. * Adds logging facility to client.
  73. */
  74. public class LoggingPlugin extends BasePlugin implements ActionListener,
  75. ConfigChangeListener {
  76. /** Cached boolean settings. */
  77. private boolean networkfolders, filenamehash, addtime, stripcodes,
  78. channelmodeprefix, autobackbuffer, backbufferTimestamp, usedate;
  79. /** Cached string settings. */
  80. private String timestamp, usedateformat, logDirectory, colour;
  81. /** Cached int settings. */
  82. private int historyLines, backbufferLines;
  83. /** This plugin's plugin info. */
  84. private final PluginInfo pluginInfo;
  85. /** The action controller to use. */
  86. private final ActionController actionController;
  87. /** Global config. */
  88. private final ConfigManager config;
  89. /** Addon identity. */
  90. private final Identity identity;
  91. /** Parent Identity Manager. */
  92. private final IdentityManager identityManager;
  93. /** Timer used to close idle files. */
  94. protected Timer idleFileTimer;
  95. /** Map of open files. */
  96. protected final Map<String, OpenFile> openFiles
  97. = Collections.synchronizedMap(new HashMap<String, OpenFile>());
  98. /** Date format used for "File Opened At" log. */
  99. final DateFormat openedAtFormat = new SimpleDateFormat("EEEE MMMM dd, yyyy - HH:mm:ss");
  100. /**
  101. * Creates a new instance of this plugin.
  102. *
  103. * @param pluginInfo This plugin's plugin info
  104. * @param actionController The action controller to register listeners with
  105. * @param identityManager The Identity Manager that controls the current config
  106. */
  107. public LoggingPlugin(final PluginInfo pluginInfo,
  108. final ActionController actionController,
  109. final IdentityManager identityManager) {
  110. super();
  111. this.identityManager = identityManager;
  112. this.pluginInfo = pluginInfo;
  113. this.actionController = actionController;
  114. config = identityManager.getGlobalConfiguration();
  115. identity = identityManager.getGlobalAddonIdentity();
  116. registerCommand(new LoggingCommand(this), LoggingCommand.INFO);
  117. }
  118. /** {@inheritDoc} */
  119. @Override
  120. public void domainUpdated() {
  121. identity.setOption(getDomain(), "general.directory",
  122. identityManager.getConfigDir() + "logs" + System.getProperty("file.separator"));
  123. }
  124. /**
  125. * Called when the plugin is loaded.
  126. */
  127. @Override
  128. public void onLoad() {
  129. setCachedSettings();
  130. final File dir = new File(logDirectory);
  131. if (dir.exists()) {
  132. if (!dir.isDirectory()) {
  133. Logger.userError(ErrorLevel.LOW, "Unable to create logging dir (file exists instead)");
  134. }
  135. } else {
  136. if (!dir.mkdirs()) {
  137. Logger.userError(ErrorLevel.LOW, "Unable to create logging dir");
  138. }
  139. }
  140. config.addChangeListener(getDomain(), this);
  141. actionController.registerListener(this,
  142. CoreActionType.CHANNEL_OPENED,
  143. CoreActionType.CHANNEL_CLOSED,
  144. CoreActionType.CHANNEL_MESSAGE,
  145. CoreActionType.CHANNEL_SELF_MESSAGE,
  146. CoreActionType.CHANNEL_ACTION,
  147. CoreActionType.CHANNEL_SELF_ACTION,
  148. CoreActionType.CHANNEL_GOTTOPIC,
  149. CoreActionType.CHANNEL_TOPICCHANGE,
  150. CoreActionType.CHANNEL_JOIN,
  151. CoreActionType.CHANNEL_PART,
  152. CoreActionType.CHANNEL_QUIT,
  153. CoreActionType.CHANNEL_KICK,
  154. CoreActionType.CHANNEL_NICKCHANGE,
  155. CoreActionType.CHANNEL_MODECHANGE,
  156. CoreActionType.QUERY_OPENED,
  157. CoreActionType.QUERY_CLOSED,
  158. CoreActionType.QUERY_MESSAGE,
  159. CoreActionType.QUERY_SELF_MESSAGE,
  160. CoreActionType.QUERY_ACTION,
  161. CoreActionType.QUERY_SELF_ACTION);
  162. // Close idle files every hour.
  163. idleFileTimer = new Timer("LoggingPlugin Timer");
  164. idleFileTimer.schedule(new TimerTask() {
  165. /** {@inheritDoc} */
  166. @Override
  167. public void run() {
  168. timerTask();
  169. }
  170. }, 3600000);
  171. super.onLoad();
  172. }
  173. /**
  174. * What to do every hour when the timer fires.
  175. */
  176. protected void timerTask() {
  177. // Oldest time to allow
  178. final long oldestTime = System.currentTimeMillis() - 3480000;
  179. synchronized (openFiles) {
  180. final Collection<String> old = new ArrayList<String>(openFiles.size());
  181. for (Map.Entry<String, OpenFile> entry : openFiles.entrySet()) {
  182. if (entry.getValue().lastUsedTime < oldestTime) {
  183. StreamUtils.close(entry.getValue().writer);
  184. old.add(entry.getKey());
  185. }
  186. }
  187. openFiles.keySet().removeAll(old);
  188. }
  189. }
  190. /**
  191. * Called when this plugin is unloaded.
  192. */
  193. @Override
  194. public void onUnload() {
  195. if (idleFileTimer != null) {
  196. idleFileTimer.cancel();
  197. idleFileTimer.purge();
  198. }
  199. actionController.unregisterListener(this);
  200. synchronized (openFiles) {
  201. for (OpenFile file : openFiles.values()) {
  202. StreamUtils.close(file.writer);
  203. }
  204. openFiles.clear();
  205. }
  206. super.onUnload();
  207. }
  208. /** {@inheritDoc} */
  209. @Override
  210. public void showConfig(final PreferencesDialogModel manager) {
  211. final PreferencesCategory general = new PluginPreferencesCategory(
  212. pluginInfo, "Logging", "General configuration for Logging plugin.");
  213. final PreferencesCategory backbuffer = new PluginPreferencesCategory(
  214. pluginInfo, "Back Buffer", "Options related to the automatic backbuffer");
  215. final PreferencesCategory advanced = new PluginPreferencesCategory(
  216. pluginInfo, "Advanced", "Advanced configuration for Logging plugin. You shouldn't need to edit this unless you know what you are doing.");
  217. general.addSetting(new PreferencesSetting(PreferencesType.DIRECTORY,
  218. getDomain(), "general.directory", "Directory",
  219. "Directory for log files", manager.getConfigManager(),
  220. manager.getIdentity()));
  221. general.addSetting(new PreferencesSetting(PreferencesType.BOOLEAN,
  222. getDomain(), "general.networkfolders",
  223. "Separate logs by network",
  224. "Should the files be stored in a sub-dir with the networks name?",
  225. manager.getConfigManager(), manager.getIdentity()));
  226. general.addSetting(new PreferencesSetting(PreferencesType.BOOLEAN,
  227. getDomain(), "general.addtime", "Timestamp logs",
  228. "Should a timestamp be added to the log files?",
  229. manager.getConfigManager(), manager.getIdentity()));
  230. general.addSetting(new PreferencesSetting(PreferencesType.TEXT,
  231. getDomain(), "general.timestamp", "Timestamp format",
  232. "The String to pass to 'SimpleDateFormat' to format the timestamp",
  233. manager.getConfigManager(), manager.getIdentity()));
  234. general.addSetting(new PreferencesSetting(PreferencesType.BOOLEAN,
  235. getDomain(), "general.stripcodes", "Strip Control Codes",
  236. "Remove known irc control codes from lines before saving?",
  237. manager.getConfigManager(), manager.getIdentity()));
  238. general.addSetting(new PreferencesSetting(PreferencesType.BOOLEAN,
  239. getDomain(), "general.channelmodeprefix",
  240. "Show channel mode prefix", "Show the @,+ etc next to nicknames",
  241. manager.getConfigManager(), manager.getIdentity()));
  242. backbuffer.addSetting(new PreferencesSetting(PreferencesType.BOOLEAN,
  243. getDomain(), "backbuffer.autobackbuffer", "Automatically display",
  244. "Automatically display the backbuffer when a channel is joined",
  245. manager.getConfigManager(), manager.getIdentity()));
  246. backbuffer.addSetting(new PreferencesSetting(PreferencesType.COLOUR,
  247. getDomain(), "backbuffer.colour", "Colour to use for display",
  248. "Colour used when displaying the backbuffer",
  249. manager.getConfigManager(), manager.getIdentity()));
  250. backbuffer.addSetting(new PreferencesSetting(PreferencesType.INTEGER,
  251. getDomain(), "backbuffer.lines", "Number of lines to show",
  252. "Number of lines used when displaying backbuffer",
  253. manager.getConfigManager(), manager.getIdentity()));
  254. backbuffer.addSetting(new PreferencesSetting(PreferencesType.BOOLEAN,
  255. getDomain(), "backbuffer.timestamp", "Show Formatter-Timestamp",
  256. "Should the line be added to the frame with the timestamp from "
  257. + "the formatter aswell as the file contents",
  258. manager.getConfigManager(), manager.getIdentity()));
  259. advanced.addSetting(new PreferencesSetting(PreferencesType.BOOLEAN,
  260. getDomain(), "advanced.filenamehash", "Add Filename hash",
  261. "Add the MD5 hash of the channel/client name to the filename. "
  262. + "(This is used to allow channels with similar names "
  263. + "(ie a _ not a -) to be logged separately)",
  264. manager.getConfigManager(), manager.getIdentity()));
  265. advanced.addSetting(new PreferencesSetting(PreferencesType.BOOLEAN,
  266. getDomain(), "advanced.usedate", "Use Date directories",
  267. "Should the log files be in separate directories based on the date?",
  268. manager.getConfigManager(), manager.getIdentity()));
  269. advanced.addSetting(new PreferencesSetting(PreferencesType.TEXT,
  270. getDomain(), "advanced.usedateformat", "Archive format",
  271. "The String to pass to 'SimpleDateFormat' to format the "
  272. + "directory name(s) for archiving",
  273. manager.getConfigManager(), manager.getIdentity()));
  274. general.addSubCategory(backbuffer.setInline());
  275. general.addSubCategory(advanced.setInline());
  276. manager.getCategory("Plugins").addSubCategory(general.setInlineAfter());
  277. }
  278. /**
  279. * Log a query-related event.
  280. *
  281. * @param type The type of the event to process
  282. * @param format Format of messages that are about to be sent. (May be null)
  283. * @param arguments The arguments for the event
  284. */
  285. protected void handleQueryEvent(final CoreActionType type, final StringBuffer format, final Object... arguments) {
  286. final Query query = (Query) arguments[0];
  287. if (query.getServer() == null) {
  288. Logger.appError(ErrorLevel.MEDIUM, "Query object has no server (" + type.toString() + ")", new Exception("Query object has no server (" + type.toString() + ")"));
  289. return;
  290. }
  291. final Parser parser = query.getServer().getParser();
  292. ClientInfo client;
  293. if (parser == null) {
  294. // Without a parser object, we might not be able to find the file to log this to.
  295. if (networkfolders) {
  296. // We *wont* be able to, so rather than logging to an incorrect file we just won't log.
  297. return;
  298. }
  299. client = null;
  300. } else {
  301. client = parser.getClient(query.getHost());
  302. }
  303. final String filename = getLogFile(client);
  304. switch (type) {
  305. case QUERY_OPENED:
  306. if (autobackbuffer) {
  307. showBackBuffer(query, filename);
  308. }
  309. appendLine(filename, "*** Query opened at: %s", openedAtFormat.format(new Date()));
  310. appendLine(filename, "*** Query with User: %s", query.getHost());
  311. appendLine(filename, "");
  312. break;
  313. case QUERY_CLOSED:
  314. appendLine(filename, "*** Query closed at: %s", openedAtFormat.format(new Date()));
  315. if (openFiles.containsKey(filename)) {
  316. StreamUtils.close(openFiles.get(filename).writer);
  317. openFiles.remove(filename);
  318. }
  319. break;
  320. case QUERY_MESSAGE:
  321. case QUERY_SELF_MESSAGE:
  322. case QUERY_ACTION:
  323. case QUERY_SELF_ACTION:
  324. final boolean isME = (type == CoreActionType.QUERY_SELF_MESSAGE || type == CoreActionType.QUERY_SELF_ACTION);
  325. final String overrideNick = isME ? getDisplayName(parser.getLocalClient()) : "";
  326. if (type == CoreActionType.QUERY_MESSAGE || type == CoreActionType.QUERY_SELF_MESSAGE) {
  327. appendLine(filename, "<%s> %s", getDisplayName(client, overrideNick), arguments[2]);
  328. } else {
  329. appendLine(filename, "* %s %s", getDisplayName(client, overrideNick), arguments[2]);
  330. }
  331. break;
  332. }
  333. }
  334. /**
  335. * Log a channel-related event.
  336. *
  337. * @param type The type of the event to process
  338. * @param format Format of messages that are about to be sent. (May be null)
  339. * @param arguments The arguments for the event
  340. */
  341. protected void handleChannelEvent(final CoreActionType type, final StringBuffer format, final Object... arguments) {
  342. final Channel chan = ((Channel) arguments[0]);
  343. final ChannelInfo channel = chan.getChannelInfo();
  344. final String filename = getLogFile(channel);
  345. final ChannelClientInfo channelClient = (arguments.length > 1 && arguments[1] instanceof ChannelClientInfo) ? (ChannelClientInfo) arguments[1] : null;
  346. final ClientInfo client = channelClient == null ? null : channelClient.getClient();
  347. final String message = (arguments.length > 2 && arguments[2] instanceof String) ? (String) arguments[2] : null;
  348. switch (type) {
  349. case CHANNEL_OPENED:
  350. if (autobackbuffer) {
  351. showBackBuffer(chan, filename);
  352. }
  353. appendLine(filename, "*** Channel opened at: %s", openedAtFormat.format(new Date()));
  354. appendLine(filename, "");
  355. break;
  356. case CHANNEL_CLOSED:
  357. appendLine(filename, "*** Channel closed at: %s", openedAtFormat.format(new Date()));
  358. if (openFiles.containsKey(filename)) {
  359. StreamUtils.close(openFiles.get(filename).writer);
  360. openFiles.remove(filename);
  361. }
  362. break;
  363. case CHANNEL_MESSAGE:
  364. case CHANNEL_SELF_MESSAGE:
  365. case CHANNEL_ACTION:
  366. case CHANNEL_SELF_ACTION:
  367. if (type == CoreActionType.CHANNEL_MESSAGE || type == CoreActionType.CHANNEL_SELF_MESSAGE) {
  368. appendLine(filename, "<%s> %s", getDisplayName(client), message);
  369. } else {
  370. appendLine(filename, "* %s %s", getDisplayName(client), message);
  371. }
  372. break;
  373. case CHANNEL_GOTTOPIC:
  374. // ActionManager.processEvent(CoreActionType.CHANNEL_GOTTOPIC, this);
  375. final DateFormat timeFormat = new SimpleDateFormat("HH:mm:ss");
  376. final DateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
  377. appendLine(filename, "*** Topic is: %s", channel.getTopic());
  378. appendLine(filename, "*** Set at: %s on %s by %s", timeFormat.format(1000 * channel.getTopicTime()), dateFormat.format(1000 * channel.getTopicTime()), channel.getTopicSetter());
  379. break;
  380. case CHANNEL_TOPICCHANGE:
  381. appendLine(filename, "*** %s Changed the topic to: %s", getDisplayName(channelClient), message);
  382. break;
  383. case CHANNEL_JOIN:
  384. appendLine(filename, "*** %s (%s) joined the channel", getDisplayName(channelClient), client.toString());
  385. break;
  386. case CHANNEL_PART:
  387. if (message.isEmpty()) {
  388. appendLine(filename, "*** %s (%s) left the channel", getDisplayName(channelClient), client.toString());
  389. } else {
  390. appendLine(filename, "*** %s (%s) left the channel (%s)", getDisplayName(channelClient), client.toString(), message);
  391. }
  392. break;
  393. case CHANNEL_QUIT:
  394. if (message.isEmpty()) {
  395. appendLine(filename, "*** %s (%s) Quit IRC", getDisplayName(channelClient), client.toString());
  396. } else {
  397. appendLine(filename, "*** %s (%s) Quit IRC (%s)", getDisplayName(channelClient), client.toString(), message);
  398. }
  399. break;
  400. case CHANNEL_KICK:
  401. final String kickReason = (String) arguments[3];
  402. final ChannelClientInfo kickedClient = (ChannelClientInfo) arguments[2];
  403. if (kickReason.isEmpty()) {
  404. appendLine(filename, "*** %s was kicked by %s", getDisplayName(kickedClient), getDisplayName(channelClient));
  405. } else {
  406. appendLine(filename, "*** %s was kicked by %s (%s)", getDisplayName(kickedClient), getDisplayName(channelClient), kickReason);
  407. }
  408. break;
  409. case CHANNEL_NICKCHANGE:
  410. appendLine(filename, "*** %s is now %s", getDisplayName(channelClient, message), getDisplayName(channelClient));
  411. break;
  412. case CHANNEL_MODECHANGE:
  413. if (channelClient.getClient().getNickname().isEmpty()) {
  414. appendLine(filename, "*** Channel modes are: %s", message);
  415. } else {
  416. appendLine(filename, "*** %s set modes: %s", getDisplayName(channelClient), message);
  417. }
  418. break;
  419. }
  420. }
  421. /**
  422. * Process an event of the specified type.
  423. *
  424. * @param type The type of the event to process
  425. * @param format Format of messages that are about to be sent. (May be null)
  426. * @param arguments The arguments for the event
  427. */
  428. @Override
  429. public void processEvent(final ActionType type, final StringBuffer format, final Object... arguments) {
  430. if (type instanceof CoreActionType) {
  431. final CoreActionType thisType = (CoreActionType) type;
  432. switch (thisType) {
  433. case CHANNEL_OPENED:
  434. case CHANNEL_CLOSED:
  435. case CHANNEL_MESSAGE:
  436. case CHANNEL_SELF_MESSAGE:
  437. case CHANNEL_ACTION:
  438. case CHANNEL_SELF_ACTION:
  439. case CHANNEL_GOTTOPIC:
  440. case CHANNEL_TOPICCHANGE:
  441. case CHANNEL_JOIN:
  442. case CHANNEL_PART:
  443. case CHANNEL_QUIT:
  444. case CHANNEL_KICK:
  445. case CHANNEL_NICKCHANGE:
  446. case CHANNEL_MODECHANGE:
  447. handleChannelEvent(thisType, format, arguments);
  448. break;
  449. case QUERY_OPENED:
  450. case QUERY_CLOSED:
  451. case QUERY_MESSAGE:
  452. case QUERY_SELF_MESSAGE:
  453. case QUERY_ACTION:
  454. case QUERY_SELF_ACTION:
  455. handleQueryEvent(thisType, format, arguments);
  456. break;
  457. default:
  458. break;
  459. }
  460. }
  461. }
  462. /** {@inheritDoc} */
  463. @Override
  464. public void configChanged(final String domain, final String key) {
  465. setCachedSettings();
  466. }
  467. /**
  468. * Add a backbuffer to a frame.
  469. *
  470. * @param frame The frame to add the backbuffer lines to
  471. * @param filename File to get backbuffer from
  472. */
  473. protected void showBackBuffer(final FrameContainer frame, final String filename) {
  474. if (frame == null) {
  475. Logger.userError(ErrorLevel.LOW, "Given a null frame");
  476. return;
  477. }
  478. final File testFile = new File(filename);
  479. if (testFile.exists()) {
  480. try {
  481. final ReverseFileReader file = new ReverseFileReader(testFile);
  482. // Because the file includes a newline char at the end, an empty line
  483. // is returned by getLines. To counter this, we call getLines(1) and do
  484. // nothing with the output.
  485. file.getLines(1);
  486. final Stack<String> lines = file.getLines(backbufferLines);
  487. while (!lines.empty()) {
  488. frame.addLine(getColouredString(colour, lines.pop()), backbufferTimestamp);
  489. }
  490. file.close();
  491. frame.addLine(getColouredString(colour, "--- End of backbuffer\n"), backbufferTimestamp);
  492. } catch (FileNotFoundException e) {
  493. Logger.userError(ErrorLevel.LOW, "Unable to show backbuffer (Filename: " + filename + "): " + e.getMessage());
  494. } catch (IOException e) {
  495. Logger.userError(ErrorLevel.LOW, "Unable to show backbuffer (Filename: " + filename + "): " + e.getMessage());
  496. } catch (SecurityException e) {
  497. Logger.userError(ErrorLevel.LOW, "Unable to show backbuffer (Filename: " + filename + "): " + e.getMessage());
  498. }
  499. }
  500. }
  501. /**
  502. * Get a coloured String.
  503. * If colour is invalid, IRC Colour 14 will be used.
  504. *
  505. * @param colour The colour the string should be (IRC Colour or 6-digit hex colour)
  506. * @param line the line to colour
  507. * @return The given line with the appropriate irc codes appended/prepended to colour it.
  508. */
  509. protected static String getColouredString(final String colour, final String line) {
  510. String res = null;
  511. if (colour.length() < 3) {
  512. int num;
  513. try {
  514. num = Integer.parseInt(colour);
  515. } catch (NumberFormatException ex) {
  516. num = -1;
  517. }
  518. if (num >= 0 && num <= 15) {
  519. res = String.format("%c%02d%s%1$c", Styliser.CODE_COLOUR, num, line);
  520. }
  521. } else if (colour.length() == 6) {
  522. try {
  523. Color.decode("#" + colour);
  524. res = String.format("%c%s%s%1$c", Styliser.CODE_HEXCOLOUR, colour, line);
  525. } catch (NumberFormatException ex) { /* Do Nothing */ }
  526. }
  527. if (res == null) {
  528. res = String.format("%c%02d%s%1$c", Styliser.CODE_COLOUR, 14, line);
  529. }
  530. return res;
  531. }
  532. /**
  533. * Add a line to a file.
  534. *
  535. * @param filename Name of file to write to
  536. * @param format Format of line to add. (NewLine will be added Automatically)
  537. * @param args Arguments for format
  538. * @return true on success, else false.
  539. */
  540. protected boolean appendLine(final String filename, final String format, final Object... args) {
  541. return appendLine(filename, String.format(format, args));
  542. }
  543. /**
  544. * Add a line to a file.
  545. *
  546. * @param filename Name of file to write to
  547. * @param line Line to add. (NewLine will be added Automatically)
  548. * @return true on success, else false.
  549. */
  550. protected boolean appendLine(final String filename, final String line) {
  551. final StringBuffer finalLine = new StringBuffer();
  552. if (addtime) {
  553. String dateString;
  554. try {
  555. final DateFormat dateFormat = new SimpleDateFormat(timestamp);
  556. dateString = dateFormat.format(new Date()).trim();
  557. } catch (IllegalArgumentException iae) {
  558. // Default to known good format
  559. final DateFormat dateFormat = new SimpleDateFormat("[dd/MM/yyyy HH:mm:ss]");
  560. dateString = dateFormat.format(new Date()).trim();
  561. Logger.userError(ErrorLevel.LOW, "Dateformat String '" + timestamp + "' is invalid. For more information: http://java.sun.com/javase/6/docs/api/java/text/SimpleDateFormat.html");
  562. }
  563. finalLine.append(dateString);
  564. finalLine.append(' ');
  565. }
  566. if (stripcodes) {
  567. finalLine.append(Styliser.stipControlCodes(line));
  568. } else {
  569. finalLine.append(line);
  570. }
  571. //System.out.println("[Adding] "+filename+" => "+finalLine);
  572. BufferedWriter out = null;
  573. try {
  574. if (openFiles.containsKey(filename)) {
  575. final OpenFile of = openFiles.get(filename);
  576. of.lastUsedTime = System.currentTimeMillis();
  577. out = of.writer;
  578. } else {
  579. out = new BufferedWriter(new FileWriter(filename, true));
  580. openFiles.put(filename, new OpenFile(out));
  581. }
  582. out.write(finalLine.toString());
  583. out.newLine();
  584. out.flush();
  585. return true;
  586. } catch (IOException e) {
  587. /*
  588. * Do Nothing
  589. *
  590. * Makes no sense to keep adding errors to the logger when we can't
  591. * write to the file, as chances are it will happen on every incomming
  592. * line.
  593. */
  594. }
  595. return false;
  596. }
  597. /**
  598. * Get the name of the log file for a specific object.
  599. *
  600. * @param obj Object to get name for
  601. * @return the name of the log file to use for this object.
  602. */
  603. protected String getLogFile(final Object obj) {
  604. final StringBuffer directory = new StringBuffer();
  605. final StringBuffer file = new StringBuffer();
  606. String md5String = "";
  607. directory.append(logDirectory);
  608. if (directory.charAt(directory.length() - 1) != File.separatorChar) {
  609. directory.append(File.separatorChar);
  610. }
  611. if (obj == null) {
  612. file.append("null.log");
  613. } else if (obj instanceof ChannelInfo) {
  614. final ChannelInfo channel = (ChannelInfo) obj;
  615. if (channel.getParser() != null) {
  616. addNetworkDir(directory, file, channel.getParser().getNetworkName());
  617. }
  618. file.append(sanitise(channel.getName().toLowerCase()));
  619. md5String = channel.getName();
  620. } else if (obj instanceof ClientInfo) {
  621. final ClientInfo client = (ClientInfo) obj;
  622. if (client.getParser() != null) {
  623. addNetworkDir(directory, file, client.getParser().getNetworkName());
  624. }
  625. file.append(sanitise(client.getNickname().toLowerCase()));
  626. md5String = client.getNickname();
  627. } else {
  628. file.append(sanitise(obj.toString().toLowerCase()));
  629. md5String = obj.toString();
  630. }
  631. if (usedate) {
  632. final String dateFormat = usedateformat;
  633. final String dateDir = new SimpleDateFormat(dateFormat).format(new Date());
  634. directory.append(dateDir);
  635. if (directory.charAt(directory.length() - 1) != File.separatorChar) {
  636. directory.append(File.separatorChar);
  637. }
  638. if (!new File(directory.toString()).exists() && !(new File(directory.toString())).mkdirs()) {
  639. Logger.userError(ErrorLevel.LOW, "Unable to create date dirs");
  640. }
  641. }
  642. if (filenamehash) {
  643. file.append('.');
  644. file.append(md5(md5String));
  645. }
  646. file.append(".log");
  647. return directory.toString() + file.toString();
  648. }
  649. /**
  650. * This function adds the networkName to the log file.
  651. * It first tries to create a directory for each network, if that fails
  652. * it will prepend the networkName to the filename instead.
  653. *
  654. * @param directory Current directory name
  655. * @param file Current file name
  656. * @param networkName Name of network
  657. */
  658. protected void addNetworkDir(final StringBuffer directory, final StringBuffer file, final String networkName) {
  659. if (!networkfolders) {
  660. return;
  661. }
  662. final String network = sanitise(networkName.toLowerCase());
  663. boolean prependNetwork = false;
  664. // Check dir exists
  665. final File dir = new File(directory.toString() + network + System.getProperty("file.separator"));
  666. if (dir.exists() && !dir.isDirectory()) {
  667. Logger.userError(ErrorLevel.LOW, "Unable to create networkfolders dir (file exists instead)");
  668. // Prepend network name to file instead.
  669. prependNetwork = true;
  670. } else if (!dir.exists() && !dir.mkdirs()) {
  671. Logger.userError(ErrorLevel.LOW, "Unable to create networkfolders dir");
  672. prependNetwork = true;
  673. }
  674. if (prependNetwork) {
  675. file.insert(0, " -- ");
  676. file.insert(0, network);
  677. } else {
  678. directory.append(network);
  679. directory.append(System.getProperty("file.separator"));
  680. }
  681. }
  682. /**
  683. * Sanitise a string to be used as a filename.
  684. *
  685. * @param name String to sanitise
  686. * @return Sanitised version of name that can be used as a filename.
  687. */
  688. protected static String sanitise(final String name) {
  689. // Replace illegal chars with
  690. return name.replaceAll("[^\\w\\.\\s\\-\\#\\&\\_]", "_");
  691. }
  692. /**
  693. * Get the md5 hash of a string.
  694. *
  695. * @param string String to hash
  696. * @return md5 hash of given string
  697. */
  698. protected static String md5(final String string) {
  699. try {
  700. final MessageDigest m = MessageDigest.getInstance("MD5");
  701. m.update(string.getBytes(), 0, string.length());
  702. return new BigInteger(1, m.digest()).toString(16);
  703. } catch (NoSuchAlgorithmException e) {
  704. return "";
  705. }
  706. }
  707. /**
  708. * Get name to display for client.
  709. *
  710. * @param client The client to get the display name for
  711. * @return name to display
  712. */
  713. protected String getDisplayName(final ClientInfo client) {
  714. return getDisplayName(client, "");
  715. }
  716. /**
  717. * Get name to display for client.
  718. *
  719. * @param client The client to get the display name for
  720. * @param overrideNick Nickname to display instead of real nickname
  721. * @return name to display
  722. */
  723. protected String getDisplayName(final ClientInfo client, final String overrideNick) {
  724. if (overrideNick.isEmpty()) {
  725. return (client == null) ? "Unknown Client" : client.getNickname();
  726. } else {
  727. return overrideNick;
  728. }
  729. }
  730. /**
  731. * Get name to display for channelClient (Taking into account the channelmodeprefix setting).
  732. *
  733. * @param channelClient The client to get the display name for
  734. * @return name to display
  735. */
  736. protected String getDisplayName(final ChannelClientInfo channelClient) {
  737. return getDisplayName(channelClient, "");
  738. }
  739. /**
  740. * Get name to display for channelClient (Taking into account the channelmodeprefix setting).
  741. *
  742. * @param channelClient The client to get the display name for
  743. * @param overrideNick Nickname to display instead of real nickname
  744. * @return name to display
  745. */
  746. protected String getDisplayName(final ChannelClientInfo channelClient, final String overrideNick) {
  747. if (channelClient == null) {
  748. return (overrideNick.isEmpty()) ? "Unknown Client" : overrideNick;
  749. } else if (overrideNick.isEmpty()) {
  750. return channelmodeprefix ? channelClient.toString() : channelClient.getClient().getNickname();
  751. } else {
  752. return channelmodeprefix ? channelClient.getImportantModePrefix() + overrideNick : overrideNick;
  753. }
  754. }
  755. /**
  756. * Shows the history window for the specified target, if available.
  757. *
  758. * @param target The window whose history we're trying to open
  759. * @return True if the history is available, false otherwise
  760. */
  761. protected boolean showHistory(final FrameContainer target) {
  762. Object component;
  763. if (target instanceof Channel) {
  764. component = ((Channel) target).getChannelInfo();
  765. } else if (target instanceof Query) {
  766. final Parser parser = ((Query) target).getServer().getParser();
  767. component = parser.getClient(((Query) target).getHost());
  768. } else if (target instanceof Server) {
  769. component = ((Server) target).getParser();
  770. } else {
  771. // Unknown component
  772. return false;
  773. }
  774. final String log = getLogFile(component);
  775. if (!new File(log).exists()) {
  776. // File doesn't exist
  777. return false;
  778. }
  779. ReverseFileReader reader;
  780. try {
  781. reader = new ReverseFileReader(log);
  782. } catch (FileNotFoundException ex) {
  783. return false;
  784. } catch (IOException ex) {
  785. return false;
  786. } catch (SecurityException ex) {
  787. return false;
  788. }
  789. new HistoryWindow("History", reader, target, historyLines);
  790. return true;
  791. }
  792. /** Updates cached settings. */
  793. public void setCachedSettings() {
  794. networkfolders = config.getOptionBool(getDomain(), "general.networkfolders");
  795. filenamehash = config.getOptionBool(getDomain(), "advanced.filenamehash");
  796. addtime = config.getOptionBool(getDomain(), "general.addtime");
  797. stripcodes = config.getOptionBool(getDomain(), "general.stripcodes");
  798. channelmodeprefix = config.getOptionBool(getDomain(), "general.channelmodeprefix");
  799. autobackbuffer = config.getOptionBool(getDomain(), "backbuffer.autobackbuffer");
  800. backbufferTimestamp = config.getOptionBool(getDomain(), "backbuffer.timestamp");
  801. usedate = config.getOptionBool(getDomain(), "advanced.usedate");
  802. timestamp = config.getOption(getDomain(), "general.timestamp");
  803. usedateformat = config.getOption(getDomain(), "advanced.usedateformat");
  804. historyLines = config.getOptionInt(getDomain(), "history.lines");
  805. colour = config.getOption(getDomain(), "backbuffer.colour");
  806. backbufferLines = config.getOptionInt(getDomain(), "backbuffer.lines");
  807. logDirectory = config.getOption(getDomain(), "general.directory");
  808. }
  809. /** Open File. */
  810. protected static class OpenFile {
  811. /** Last used time. */
  812. public long lastUsedTime = System.currentTimeMillis();
  813. /** Open file's writer. */
  814. public BufferedWriter writer = null;
  815. /**
  816. * Creates a new open file.
  817. *
  818. * @param writer Writer that has file open
  819. */
  820. protected OpenFile(final BufferedWriter writer) {
  821. this.writer = writer;
  822. }
  823. }
  824. }