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