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.

LoggingManager.java 31KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829
  1. /*
  2. * Copyright (c) 2006-2014 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.Channel;
  24. import com.dmdirc.ClientModule.GlobalConfig;
  25. import com.dmdirc.DMDircMBassador;
  26. import com.dmdirc.FrameContainer;
  27. import com.dmdirc.Query;
  28. import com.dmdirc.commandline.CommandLineOptionsModule.Directory;
  29. import com.dmdirc.events.BaseChannelActionEvent;
  30. import com.dmdirc.events.BaseChannelMessageEvent;
  31. import com.dmdirc.events.BaseQueryActionEvent;
  32. import com.dmdirc.events.BaseQueryMessageEvent;
  33. import com.dmdirc.events.ChannelClosedEvent;
  34. import com.dmdirc.events.ChannelGottopicEvent;
  35. import com.dmdirc.events.ChannelJoinEvent;
  36. import com.dmdirc.events.ChannelKickEvent;
  37. import com.dmdirc.events.ChannelModechangeEvent;
  38. import com.dmdirc.events.ChannelNickchangeEvent;
  39. import com.dmdirc.events.ChannelOpenedEvent;
  40. import com.dmdirc.events.ChannelPartEvent;
  41. import com.dmdirc.events.ChannelQuitEvent;
  42. import com.dmdirc.events.ChannelTopicChangeEvent;
  43. import com.dmdirc.events.QueryClosedEvent;
  44. import com.dmdirc.events.QueryOpenedEvent;
  45. import com.dmdirc.events.UserErrorEvent;
  46. import com.dmdirc.interfaces.PrivateChat;
  47. import com.dmdirc.interfaces.config.AggregateConfigProvider;
  48. import com.dmdirc.interfaces.config.ConfigChangeListener;
  49. import com.dmdirc.logger.ErrorLevel;
  50. import com.dmdirc.parser.interfaces.ChannelClientInfo;
  51. import com.dmdirc.parser.interfaces.ChannelInfo;
  52. import com.dmdirc.parser.interfaces.ClientInfo;
  53. import com.dmdirc.parser.interfaces.Parser;
  54. import com.dmdirc.plugins.PluginDomain;
  55. import com.dmdirc.ui.WindowManager;
  56. import com.dmdirc.ui.messages.ColourManagerFactory;
  57. import com.dmdirc.ui.messages.Styliser;
  58. import com.dmdirc.util.URLBuilder;
  59. import com.dmdirc.util.io.ReverseFileReader;
  60. import com.dmdirc.util.io.StreamUtils;
  61. import java.awt.Color;
  62. import java.io.BufferedWriter;
  63. import java.io.File;
  64. import java.io.FileWriter;
  65. import java.io.IOException;
  66. import java.math.BigInteger;
  67. import java.security.MessageDigest;
  68. import java.security.NoSuchAlgorithmException;
  69. import java.text.DateFormat;
  70. import java.text.SimpleDateFormat;
  71. import java.util.ArrayList;
  72. import java.util.Collection;
  73. import java.util.Collections;
  74. import java.util.Date;
  75. import java.util.HashMap;
  76. import java.util.Map;
  77. import java.util.Stack;
  78. import java.util.Timer;
  79. import java.util.TimerTask;
  80. import javax.annotation.Nullable;
  81. import javax.inject.Inject;
  82. import javax.inject.Provider;
  83. import javax.inject.Singleton;
  84. import net.engio.mbassy.listener.Handler;
  85. /**
  86. * Manages logging activities.
  87. */
  88. @Singleton
  89. public class LoggingManager implements ConfigChangeListener {
  90. /** Date format used for "File Opened At" log. */
  91. private static final DateFormat OPENED_AT_FORMAT = new SimpleDateFormat(
  92. "EEEE MMMM dd, yyyy - HH:mm:ss");
  93. /** Object for synchronising access to the date forma.t */
  94. private static final Object FORMAT_LOCK = new Object();
  95. /** This plugin's plugin info. */
  96. private final String domain;
  97. /** Global config. */
  98. private final AggregateConfigProvider config;
  99. /** The manager to add history windows to. */
  100. private final WindowManager windowManager;
  101. /** Map of open files. */
  102. private final Map<String, OpenFile> openFiles = Collections.synchronizedMap(
  103. new HashMap<String, OpenFile>());
  104. private final URLBuilder urlBuilder;
  105. private final DMDircMBassador eventBus;
  106. private final Provider<String> directoryProvider;
  107. private final ColourManagerFactory colourManagerFactory;
  108. /** Timer used to close idle files. */
  109. private Timer idleFileTimer;
  110. /** Cached boolean settings. */
  111. private boolean networkfolders;
  112. private boolean filenamehash;
  113. private boolean addtime;
  114. private boolean stripcodes;
  115. private boolean channelmodeprefix;
  116. private boolean autobackbuffer;
  117. private boolean backbufferTimestamp;
  118. private boolean usedate;
  119. /** Cached string settings. */
  120. private String timestamp;
  121. private String usedateformat;
  122. private String colour;
  123. /** Cached int settings. */
  124. private int historyLines;
  125. private int backbufferLines;
  126. @Inject
  127. public LoggingManager(@PluginDomain(LoggingPlugin.class) final String domain,
  128. @GlobalConfig final AggregateConfigProvider globalConfig,
  129. final WindowManager windowManager, final URLBuilder urlBuilder, final DMDircMBassador eventBus,
  130. @Directory(LoggingModule.LOGS_DIRECTORY) final Provider<String> directoryProvider,
  131. final ColourManagerFactory colourManagerFactory) {
  132. this.domain = domain;
  133. this.config = globalConfig;
  134. this.windowManager = windowManager;
  135. this.urlBuilder = urlBuilder;
  136. this.eventBus = eventBus;
  137. this.directoryProvider = directoryProvider;
  138. this.colourManagerFactory = colourManagerFactory;
  139. }
  140. public void load() {
  141. setCachedSettings();
  142. final File dir = new File(directoryProvider.get());
  143. if (dir.exists()) {
  144. if (!dir.isDirectory()) {
  145. eventBus.publishAsync(new UserErrorEvent(ErrorLevel.LOW, null,
  146. "Unable to create logging dir (file exists instead)", ""));
  147. }
  148. } else {
  149. if (!dir.mkdirs()) {
  150. eventBus.publishAsync(new UserErrorEvent(ErrorLevel.LOW, null,
  151. "Unable to create logging dir", ""));
  152. }
  153. }
  154. config.addChangeListener(domain, this);
  155. // Close idle files every hour.
  156. idleFileTimer = new Timer("LoggingPlugin Timer");
  157. idleFileTimer.schedule(new TimerTask() {
  158. @Override
  159. public void run() {
  160. timerTask();
  161. }
  162. }, 3600000);
  163. eventBus.subscribe(this);
  164. }
  165. public void unload() {
  166. if (idleFileTimer != null) {
  167. idleFileTimer.cancel();
  168. idleFileTimer.purge();
  169. }
  170. synchronized (openFiles) {
  171. for (OpenFile file : openFiles.values()) {
  172. StreamUtils.close(file.writer);
  173. }
  174. openFiles.clear();
  175. }
  176. eventBus.unsubscribe(this);
  177. }
  178. /**
  179. * What to do every hour when the timer fires.
  180. */
  181. protected void timerTask() {
  182. // Oldest time to allow
  183. final long oldestTime = System.currentTimeMillis() - 3480000;
  184. synchronized (openFiles) {
  185. final Collection<String> old = new ArrayList<>(openFiles.size());
  186. for (Map.Entry<String, OpenFile> entry : openFiles.entrySet()) {
  187. if (entry.getValue().lastUsedTime < oldestTime) {
  188. StreamUtils.close(entry.getValue().writer);
  189. old.add(entry.getKey());
  190. }
  191. }
  192. openFiles.keySet().removeAll(old);
  193. }
  194. }
  195. @Handler
  196. public void handleQueryOpened(final QueryOpenedEvent event) {
  197. final Parser parser = event.getQuery().getConnection().getParser();
  198. final ClientInfo client = parser.getClient(event.getQuery().getHost());
  199. final String filename = getLogFile(client);
  200. if (autobackbuffer) {
  201. showBackBuffer(event.getQuery(), filename);
  202. }
  203. synchronized (FORMAT_LOCK) {
  204. appendLine(filename, "*** Query opened at: %s", OPENED_AT_FORMAT.format(new Date()));
  205. appendLine(filename, "*** Query with User: %s", event.getQuery().getHost());
  206. appendLine(filename, "");
  207. }
  208. }
  209. @Handler
  210. public void handleQueryClosed(final QueryClosedEvent event) {
  211. final Parser parser = event.getQuery().getConnection().getParser();
  212. final ClientInfo client = parser.getClient(event.getQuery().getHost());
  213. final String filename = getLogFile(client);
  214. synchronized (FORMAT_LOCK) {
  215. appendLine(filename, "*** Query closed at: %s", OPENED_AT_FORMAT.format(new Date()));
  216. }
  217. if (openFiles.containsKey(filename)) {
  218. StreamUtils.close(openFiles.get(filename).writer);
  219. openFiles.remove(filename);
  220. }
  221. }
  222. @Handler
  223. public void handleQueryActions(final BaseQueryActionEvent event) {
  224. final ClientInfo client = event.getClient();
  225. final String filename = getLogFile(client);
  226. appendLine(filename, "* %s %s", client.getNickname(), event.getMessage());
  227. }
  228. @Handler
  229. public void handleQueryMessages(final BaseQueryMessageEvent event) {
  230. final ClientInfo client = event.getClient();
  231. final String filename = getLogFile(client);
  232. appendLine(filename, "<%s> %s", client.getNickname(), event.getMessage());
  233. }
  234. @Handler
  235. public void handleChannelMessage(final BaseChannelMessageEvent event) {
  236. final String filename = getLogFile(event.getChannel().getChannelInfo());
  237. appendLine(filename, "<%s> %s", getDisplayName(event.getClient()), event.getMessage());
  238. }
  239. @Handler
  240. public void handleChannelAction(final BaseChannelActionEvent event) {
  241. final String filename = getLogFile(event.getChannel().getChannelInfo());
  242. appendLine(filename, "* %s %s", getDisplayName(event.getClient()), event.getMessage());
  243. }
  244. @Handler
  245. public void handleChannelGotTopic(final ChannelGottopicEvent event) {
  246. final String filename = getLogFile(event.getChannel().getChannelInfo());
  247. final ChannelInfo channel = event.getChannel().getChannelInfo();
  248. final DateFormat timeFormat = new SimpleDateFormat("HH:mm:ss");
  249. final DateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
  250. appendLine(filename, "*** Topic is: %s", channel.getTopic());
  251. appendLine(filename, "*** Set at: %s on %s by %s", timeFormat.format(1000 * channel.
  252. getTopicTime()), dateFormat.format(1000 * channel.getTopicTime()), channel.
  253. getTopicSetter());
  254. }
  255. @Handler
  256. public void handleChannelTopicChange(final ChannelTopicChangeEvent event) {
  257. final String filename = getLogFile(event.getChannel().getChannelInfo());
  258. final ChannelClientInfo channelClient = event.getClient();
  259. appendLine(filename, "*** %s Changed the topic to: %s",
  260. getDisplayName(channelClient), event.getTopic());
  261. }
  262. @Handler
  263. public void handleChannelJoin(final ChannelJoinEvent event) {
  264. final String filename = getLogFile(event.getChannel().getChannelInfo());
  265. final ChannelClientInfo channelClient = event.getClient();
  266. final ClientInfo client = channelClient.getClient();
  267. appendLine(filename, "*** %s (%s) joined the channel",
  268. getDisplayName(channelClient), client.toString());
  269. }
  270. @Handler
  271. public void handleChannelPart(final ChannelPartEvent event) {
  272. final String filename = getLogFile(event.getChannel().getChannelInfo());
  273. final String message = event.getMessage();
  274. final ChannelClientInfo channelClient = event.getClient();
  275. final ClientInfo client = channelClient.getClient();
  276. if (message.isEmpty()) {
  277. appendLine(filename, "*** %s (%s) left the channel",
  278. getDisplayName(channelClient), client.toString());
  279. } else {
  280. appendLine(filename, "*** %s (%s) left the channel (%s)",
  281. getDisplayName(channelClient), client.toString(), message);
  282. }
  283. }
  284. @Handler
  285. public void handleChannelQuit(final ChannelQuitEvent event) {
  286. final String filename = getLogFile(event.getChannel().getChannelInfo());
  287. final String reason = event.getMessage();
  288. final ChannelClientInfo channelClient = event.getClient();
  289. final ClientInfo client = channelClient.getClient();
  290. if (reason.isEmpty()) {
  291. appendLine(filename, "*** %s (%s) Quit IRC",
  292. getDisplayName(channelClient), client.toString());
  293. } else {
  294. appendLine(filename, "*** %s (%s) Quit IRC (%s)",
  295. getDisplayName(channelClient), client.toString(), reason);
  296. }
  297. }
  298. @Handler
  299. public void handleChannelKick(final ChannelKickEvent event) {
  300. final ChannelClientInfo victim = event.getVictim();
  301. final ChannelClientInfo perpetrator = event.getClient();
  302. final String reason = event.getReason();
  303. final String filename = getLogFile(event.getChannel().getChannelInfo());
  304. if (reason.isEmpty()) {
  305. appendLine(filename, "*** %s was kicked by %s",
  306. getDisplayName(victim), getDisplayName(perpetrator));
  307. } else {
  308. appendLine(filename, "*** %s was kicked by %s (%s)",
  309. getDisplayName(victim), getDisplayName(perpetrator), reason);
  310. }
  311. }
  312. @Handler
  313. public void handleNickChange(final ChannelNickchangeEvent event) {
  314. final String filename = getLogFile(event.getChannel().getChannelInfo());
  315. appendLine(filename, "*** %s is now %s", getDisplayName(event.getClient(),
  316. event.getOldNick()), getDisplayName(event.getClient()));
  317. }
  318. @Handler
  319. public void handleModeChange(final ChannelModechangeEvent event) {
  320. final String filename = getLogFile(event.getChannel().getChannelInfo());
  321. if (event.getClient().getClient().getNickname().isEmpty()) {
  322. appendLine(filename, "*** Channel modes are: %s", event.getModes());
  323. } else {
  324. appendLine(filename, "*** %s set modes: %s",
  325. getDisplayName(event.getClient()), event.getModes());
  326. }
  327. }
  328. @Override
  329. public void configChanged(final String domain, final String key) {
  330. setCachedSettings();
  331. }
  332. @Handler
  333. public void handleChannelOpened(final ChannelOpenedEvent event) {
  334. final String filename = getLogFile(event.getChannel().getName());
  335. if (autobackbuffer) {
  336. showBackBuffer(event.getChannel(), filename);
  337. }
  338. synchronized (FORMAT_LOCK) {
  339. appendLine(filename, "*** Channel opened at: %s", OPENED_AT_FORMAT.format(new Date()));
  340. appendLine(filename, "");
  341. }
  342. }
  343. @Handler
  344. public void handleChannelClosed(final ChannelClosedEvent event) {
  345. final String filename = getLogFile(event.getChannel().getName());
  346. synchronized (FORMAT_LOCK) {
  347. appendLine(filename, "*** Channel closed at: %s", OPENED_AT_FORMAT.format(new Date()));
  348. }
  349. if (openFiles.containsKey(filename)) {
  350. StreamUtils.close(openFiles.get(filename).writer);
  351. openFiles.remove(filename);
  352. }
  353. }
  354. /**
  355. * Add a backbuffer to a frame.
  356. *
  357. * @param frame The frame to add the backbuffer lines to
  358. * @param filename File to get backbuffer from
  359. */
  360. protected void showBackBuffer(final FrameContainer frame, final String filename) {
  361. if (frame == null) {
  362. eventBus.publishAsync(new UserErrorEvent(ErrorLevel.LOW, null, "Given a null frame", ""));
  363. return;
  364. }
  365. final File testFile = new File(filename);
  366. if (testFile.exists()) {
  367. try {
  368. final ReverseFileReader file = new ReverseFileReader(testFile);
  369. // Because the file includes a newline char at the end, an empty line
  370. // is returned by getLines. To counter this, we call getLines(1) and do
  371. // nothing with the output.
  372. file.getLines(1);
  373. final Stack<String> lines = file.getLines(backbufferLines);
  374. while (!lines.empty()) {
  375. frame.addLine(getColouredString(colour, lines.pop()), backbufferTimestamp);
  376. }
  377. file.close();
  378. frame.addLine(getColouredString(colour, "--- End of backbuffer\n"),
  379. backbufferTimestamp);
  380. } catch (IOException | SecurityException e) {
  381. eventBus.publishAsync(new UserErrorEvent(ErrorLevel.LOW, e,
  382. "Unable to show backbuffer (Filename: " + filename + "): " + e.getMessage(),
  383. ""));
  384. }
  385. }
  386. }
  387. /**
  388. * Get a coloured String. If colour is invalid, IRC Colour 14 will be used.
  389. *
  390. * @param colour The colour the string should be (IRC Colour or 6-digit hex colour)
  391. * @param line the line to colour
  392. *
  393. * @return The given line with the appropriate irc codes appended/prepended to colour it.
  394. */
  395. protected static String getColouredString(final String colour, final String line) {
  396. String res = null;
  397. if (colour.length() < 3) {
  398. int num;
  399. try {
  400. num = Integer.parseInt(colour);
  401. } catch (NumberFormatException ex) {
  402. num = -1;
  403. }
  404. if (num >= 0 && num <= 15) {
  405. res = String.format("%c%02d%s%1$c", Styliser.CODE_COLOUR, num, line);
  406. }
  407. } else if (colour.length() == 6) {
  408. try {
  409. Color.decode('#' + colour);
  410. res = String.format("%c%s%s%1$c", Styliser.CODE_HEXCOLOUR, colour, line);
  411. } catch (NumberFormatException ex) { /* Do Nothing */ }
  412. }
  413. if (res == null) {
  414. res = String.format("%c%02d%s%1$c", Styliser.CODE_COLOUR, 14, line);
  415. }
  416. return res;
  417. }
  418. /**
  419. * Add a line to a file.
  420. *
  421. * @param filename Name of file to write to
  422. * @param format Format of line to add. (NewLine will be added Automatically)
  423. * @param args Arguments for format
  424. *
  425. * @return true on success, else false.
  426. */
  427. protected boolean appendLine(final String filename, final String format, final Object... args) {
  428. return appendLine(filename, String.format(format, args));
  429. }
  430. /**
  431. * Add a line to a file.
  432. *
  433. * @param filename Name of file to write to
  434. * @param line Line to add. (NewLine will be added Automatically)
  435. *
  436. * @return true on success, else false.
  437. */
  438. protected boolean appendLine(final String filename, final String line) {
  439. final StringBuilder finalLine = new StringBuilder();
  440. if (addtime) {
  441. String dateString;
  442. try {
  443. final DateFormat dateFormat = new SimpleDateFormat(timestamp);
  444. dateString = dateFormat.format(new Date()).trim();
  445. } catch (IllegalArgumentException iae) {
  446. // Default to known good format
  447. final DateFormat dateFormat = new SimpleDateFormat("[dd/MM/yyyy HH:mm:ss]");
  448. dateString = dateFormat.format(new Date()).trim();
  449. eventBus.publishAsync(new UserErrorEvent(ErrorLevel.LOW, iae,
  450. "Dateformat String '" + timestamp + "' is invalid. For more information: "
  451. + "http://java.sun.com/javase/6/docs/api/java/text/SimpleDateFormat.html",
  452. ""));
  453. }
  454. finalLine.append(dateString);
  455. finalLine.append(' ');
  456. }
  457. if (stripcodes) {
  458. finalLine.append(Styliser.stipControlCodes(line));
  459. } else {
  460. finalLine.append(line);
  461. }
  462. try {
  463. final BufferedWriter out;
  464. if (openFiles.containsKey(filename)) {
  465. final OpenFile of = openFiles.get(filename);
  466. of.lastUsedTime = System.currentTimeMillis();
  467. out = of.writer;
  468. } else {
  469. out = new BufferedWriter(new FileWriter(filename, true));
  470. openFiles.put(filename, new OpenFile(out));
  471. }
  472. out.write(finalLine.toString());
  473. out.newLine();
  474. out.flush();
  475. return true;
  476. } catch (IOException e) {
  477. /*
  478. * Do Nothing
  479. *
  480. * Makes no sense to keep adding errors to the logger when we can't write to the file,
  481. * as chances are it will happen on every incomming line.
  482. */
  483. }
  484. return false;
  485. }
  486. /**
  487. * Get the name of the log file for a specific object.
  488. *
  489. * @param channel Channel to get the name for
  490. *
  491. * @return the name of the log file to use for this object.
  492. */
  493. protected String getLogFile(final ChannelInfo channel) {
  494. final StringBuffer directory = getLogDirectory();
  495. final StringBuffer file = new StringBuffer();
  496. if (channel.getParser() != null) {
  497. addNetworkDir(directory, file, channel.getParser().getNetworkName());
  498. }
  499. file.append(sanitise(channel.getName().toLowerCase()));
  500. return getPath(directory, file, channel.getName());
  501. }
  502. /**
  503. * Get the name of the log file for a specific object.
  504. *
  505. * @param client Client to get the name for
  506. *
  507. * @return the name of the log file to use for this object.
  508. */
  509. protected String getLogFile(final ClientInfo client) {
  510. final StringBuffer directory = getLogDirectory();
  511. final StringBuffer file = new StringBuffer();
  512. if (client.getParser() != null) {
  513. addNetworkDir(directory, file, client.getParser().getNetworkName());
  514. }
  515. file.append(sanitise(client.getNickname().toLowerCase()));
  516. return getPath(directory, file, client.getNickname());
  517. }
  518. /**
  519. * Get the name of the log file for a specific object.
  520. *
  521. * @param descriptor Description of the object to get a log file for.
  522. *
  523. * @return the name of the log file to use for this object.
  524. */
  525. protected String getLogFile(@Nullable final String descriptor) {
  526. final StringBuffer directory = getLogDirectory();
  527. final StringBuffer file = new StringBuffer();
  528. final String md5String;
  529. if (descriptor == null) {
  530. file.append("null.log");
  531. md5String = "";
  532. } else {
  533. file.append(sanitise(descriptor.toLowerCase()));
  534. md5String = descriptor;
  535. }
  536. return getPath(directory, file, md5String);
  537. }
  538. /**
  539. * Gets the path for the given file and directory. Only intended to be used from getLogFile
  540. * methods.
  541. *
  542. * @param directory Log file directory
  543. * @param file Log file path
  544. * @param md5String Log file object MD5 hash
  545. *
  546. * @return Name of the log file
  547. */
  548. protected String getPath(final StringBuffer directory, final StringBuffer file,
  549. final String md5String) {
  550. if (usedate) {
  551. final String dateFormat = usedateformat;
  552. final String dateDir = new SimpleDateFormat(dateFormat).format(new Date());
  553. directory.append(dateDir);
  554. if (directory.charAt(directory.length() - 1) != File.separatorChar) {
  555. directory.append(File.separatorChar);
  556. }
  557. if (!new File(directory.toString()).exists()
  558. && !new File(directory.toString()).mkdirs()) {
  559. eventBus.publishAsync(new UserErrorEvent(ErrorLevel.LOW, null,
  560. "Unable to create date dirs", ""));
  561. }
  562. }
  563. if (filenamehash) {
  564. file.append('.');
  565. file.append(md5(md5String));
  566. }
  567. file.append(".log");
  568. return directory + file.toString();
  569. }
  570. /**
  571. * Sanitises the log file directory.
  572. *
  573. * @return Log directory
  574. */
  575. private StringBuffer getLogDirectory() {
  576. final StringBuffer directory = new StringBuffer();
  577. directory.append(directoryProvider.get());
  578. if (directory.charAt(directory.length() - 1) != File.separatorChar) {
  579. directory.append(File.separatorChar);
  580. }
  581. return directory;
  582. }
  583. /**
  584. * This function adds the networkName to the log file. It first tries to create a directory for
  585. * each network, if that fails it will prepend the networkName to the filename instead.
  586. *
  587. * @param directory Current directory name
  588. * @param file Current file name
  589. * @param networkName Name of network
  590. */
  591. protected void addNetworkDir(final StringBuffer directory, final StringBuffer file,
  592. final String networkName) {
  593. if (!networkfolders) {
  594. return;
  595. }
  596. final String network = sanitise(networkName.toLowerCase());
  597. boolean prependNetwork = false;
  598. // Check dir exists
  599. final File dir = new File(directory + network + System.getProperty(
  600. "file.separator"));
  601. if (dir.exists() && !dir.isDirectory()) {
  602. eventBus.publishAsync(new UserErrorEvent(ErrorLevel.LOW, null,
  603. "Unable to create networkfolders dir (file exists instead)", ""));
  604. // Prepend network name to file instead.
  605. prependNetwork = true;
  606. } else if (!dir.exists() && !dir.mkdirs()) {
  607. eventBus.publishAsync(new UserErrorEvent(ErrorLevel.LOW, null,
  608. "Unable to create networkfolders dir", ""));
  609. prependNetwork = true;
  610. }
  611. if (prependNetwork) {
  612. file.insert(0, " -- ");
  613. file.insert(0, network);
  614. } else {
  615. directory.append(network);
  616. directory.append(System.getProperty("file.separator"));
  617. }
  618. }
  619. /**
  620. * Sanitise a string to be used as a filename.
  621. *
  622. * @param name String to sanitise
  623. *
  624. * @return Sanitised version of name that can be used as a filename.
  625. */
  626. protected static String sanitise(final String name) {
  627. // Replace illegal chars with
  628. return name.replaceAll("[^\\w\\.\\s\\-#&_]", "_");
  629. }
  630. /**
  631. * Get the md5 hash of a string.
  632. *
  633. * @param string String to hash
  634. *
  635. * @return md5 hash of given string
  636. */
  637. protected static String md5(final String string) {
  638. try {
  639. final MessageDigest m = MessageDigest.getInstance("MD5");
  640. m.update(string.getBytes(), 0, string.length());
  641. return new BigInteger(1, m.digest()).toString(16);
  642. } catch (NoSuchAlgorithmException e) {
  643. return "";
  644. }
  645. }
  646. /**
  647. * Get name to display for channelClient (Taking into account the channelmodeprefix setting).
  648. *
  649. * @param channelClient The client to get the display name for
  650. *
  651. * @return name to display
  652. */
  653. protected String getDisplayName(final ChannelClientInfo channelClient) {
  654. return getDisplayName(channelClient, "");
  655. }
  656. /**
  657. * Get name to display for channelClient (Taking into account the channelmodeprefix setting).
  658. *
  659. * @param channelClient The client to get the display name for
  660. * @param overrideNick Nickname to display instead of real nickname
  661. *
  662. * @return name to display
  663. */
  664. protected String getDisplayName(final ChannelClientInfo channelClient, final String overrideNick) {
  665. if (channelClient == null) {
  666. return overrideNick.isEmpty() ? "Unknown Client" : overrideNick;
  667. } else if (overrideNick.isEmpty()) {
  668. return channelmodeprefix ? channelClient.toString() : channelClient.getClient().
  669. getNickname();
  670. } else {
  671. return channelmodeprefix ? channelClient.getImportantModePrefix() + overrideNick
  672. : overrideNick;
  673. }
  674. }
  675. /**
  676. * Shows the history window for the specified target, if available.
  677. *
  678. * @param target The window whose history we're trying to open
  679. *
  680. * @return True if the history is available, false otherwise
  681. */
  682. protected boolean showHistory(final FrameContainer target) {
  683. final String descriptor;
  684. if (target instanceof Channel) {
  685. descriptor = target.getName();
  686. } else if (target instanceof Query) {
  687. final Parser parser = target.getConnection().getParser();
  688. descriptor = parser.getClient(((PrivateChat) target).getHost()).getNickname();
  689. } else {
  690. // Unknown component
  691. return false;
  692. }
  693. final String log = getLogFile(descriptor);
  694. if (!new File(log).exists()) {
  695. // File doesn't exist
  696. return false;
  697. }
  698. final ReverseFileReader reader;
  699. try {
  700. reader = new ReverseFileReader(log);
  701. } catch (IOException | SecurityException ex) {
  702. return false;
  703. }
  704. final HistoryWindow window = new HistoryWindow("History", reader, target, urlBuilder,
  705. eventBus, colourManagerFactory, historyLines);
  706. windowManager.addWindow(target, window);
  707. return true;
  708. }
  709. /** Updates cached settings. */
  710. public void setCachedSettings() {
  711. networkfolders = config.getOptionBool(domain, "general.networkfolders");
  712. filenamehash = config.getOptionBool(domain, "advanced.filenamehash");
  713. addtime = config.getOptionBool(domain, "general.addtime");
  714. stripcodes = config.getOptionBool(domain, "general.stripcodes");
  715. channelmodeprefix = config.getOptionBool(domain, "general.channelmodeprefix");
  716. autobackbuffer = config.getOptionBool(domain, "backbuffer.autobackbuffer");
  717. backbufferTimestamp = config.getOptionBool(domain, "backbuffer.timestamp");
  718. usedate = config.getOptionBool(domain, "advanced.usedate");
  719. timestamp = config.getOption(domain, "general.timestamp");
  720. usedateformat = config.getOption(domain, "advanced.usedateformat");
  721. historyLines = config.getOptionInt(domain, "history.lines");
  722. colour = config.getOption(domain, "backbuffer.colour");
  723. backbufferLines = config.getOptionInt(domain, "backbuffer.lines");
  724. }
  725. /** Open File. */
  726. private static class OpenFile {
  727. /** Last used time. */
  728. public long lastUsedTime = System.currentTimeMillis();
  729. /** Open file's writer. */
  730. public final BufferedWriter writer;
  731. /**
  732. * Creates a new open file.
  733. *
  734. * @param writer Writer that has file open
  735. */
  736. protected OpenFile(final BufferedWriter writer) {
  737. this.writer = writer;
  738. }
  739. }
  740. }