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 35KB

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