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

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