/*
* Copyright 2002-2005 Uwyn bvba/sprl <info[remove] at uwyn dot com>
* Distributed under the terms of the GNU Lesser General Public
* License, v2.1 or later
*
* $Id: ChannelLog.java 1769 2005-04-17 15:30:44Z gbevin $
*/
package com.uwyn.drone.webui.elements.pub;
import java.util.*;
import com.uwyn.drone.DroneConfig;
import com.uwyn.drone.core.Bot;
import com.uwyn.drone.core.BotsRunner;
import com.uwyn.drone.core.Channel;
import com.uwyn.drone.modules.exceptions.LogManagerException;
import com.uwyn.drone.modules.logmanagement.DatabaseLogsFactory;
import com.uwyn.drone.modules.logmanagement.LogResultProcessor;
import com.uwyn.drone.protocol.ServerMessage;
import com.uwyn.drone.tools.LocaleProvider;
import com.uwyn.drone.tools.LocaleProviderConfig;
import com.uwyn.rife.config.Config;
import com.uwyn.rife.engine.Element;
import com.uwyn.rife.engine.exceptions.EngineException;
import com.uwyn.rife.site.FormBuilder;
import com.uwyn.rife.site.ValidationBuilder;
import com.uwyn.rife.site.ValidationError;
import com.uwyn.rife.template.Template;
import com.uwyn.rife.tools.ExceptionUtils;
import com.uwyn.rife.tools.Localization;
import com.uwyn.rife.tools.StringUtils;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
public class ChannelLog extends Element
{
public static final String IRC_ACTION = "\u0001ACTION";
public static final String[] NICK_COLORS = new String[] {"eeeeee", "eeeecc", "cceeee", "eeccee", "ccccee", "eecccc", "cceecc"};
public static final Pattern URL_HIGHLIGHT = Pattern.compile("((?:http|ftp)s?://(?:%[\\p{Digit}A-Fa-f][\\p{Digit}A-Fa-f]|[\\-_\\.!~*';\\|/?:@#&=\\+$,\\p{Alnum}])+)");
public static final Pattern EMAIL_HIGHLIGHT = Pattern.compile("([a-zA-Z0-9][_\\-\\.\\w]*@[\\w\\.\\-]+\\.[a-zA-Z]{2,4})");
public SimpleDateFormat mDateFormat;
public SimpleDateFormat mOutputTimeFormat;
public SimpleDateFormat mOutputDateFormat;
public LocaleProvider mLocaleProvider = LocaleProviderConfig.SINGLETON;
private Highlighter mHighlighter = null;
private Search.SearchBean mSearchBean = null;
private Template mTemplate = null;
static Channel validateChannel(ArrayList errors, Bot bot, String channelName)
{
if (null == channelName)
{
errors.add(new ValidationError.MANDATORY("channelname"));
}
else
{
Channel channel = bot.getJoinedChannel(channelName);
if (null == channel)
{
errors.add(new ValidationError.INVALID("channelname"));
}
else
{
return channel;
}
}
return null;
}
public void setLocaleProvider(LocaleProvider localeProvider)
{
mLocaleProvider = localeProvider;
}
public void initialize()
{
mTemplate = getHtmlTemplate("drone.pub.channel_log");
Locale locale = mLocaleProvider.retrieveLocale(this, mTemplate);
mDateFormat = new SimpleDateFormat("yyyyMMdd", locale);
mOutputTimeFormat = new SimpleDateFormat("HH:mm", locale);
mOutputDateFormat = new SimpleDateFormat("dd-MMMM-yyyy", locale);
mDateFormat.setTimeZone(TimeZone.getTimeZone(DroneConfig.getTimezone()));
mOutputTimeFormat.setTimeZone(TimeZone.getTimeZone(DroneConfig.getTimezone()));
mOutputDateFormat.setTimeZone(TimeZone.getTimeZone(DroneConfig.getTimezone()));
}
public final void processElement()
{
ArrayList errors = new ArrayList();
Bot bot = null;
Channel channel = null;
Calendar day = null;
String botname = null;
String channelname = null;
String daystring = null;
botname = getInput("botname");
channelname = getInput("channelname");
daystring = getInput("day");
String[] parts = StringUtils.splitToArray(getPathInfo(), "/");
try
{
if (null == botname &&
parts.length > 1)
{
botname = URLDecoder.decode(parts[1], "ISO-8859-1");
}
if (null == channelname &&
parts.length > 2)
{
channelname = "#"+URLDecoder.decode(parts[2], "ISO-8859-1");
}
if (null == daystring &&
parts.length > 3)
{
daystring = URLDecoder.decode(parts[3], "ISO-8859-1");
}
}
catch (UnsupportedEncodingException e)
{
Logger.getLogger("com.uwyn.drone").severe(ExceptionUtils.getExceptionStackTrace(e));
}
if (null == botname ||
null == channelname)
{
exit("back_to_list");
}
if (!channelname.startsWith("#"))
{
channelname = "#"+channelname;
}
bot = BotList.validateBotName(errors, botname);
if (bot != null)
{
channel = validateChannel(errors, bot, channelname);
}
if (null == bot ||
null == channel)
{
exit("back_to_list");
}
day = Calendar.getInstance(TimeZone.getTimeZone(DroneConfig.getTimezone()));
if (daystring != null)
{
try
{
day.setTime(mDateFormat.parse(daystring));
}
catch (ParseException e)
{
errors.add(new ValidationError.INVALID("DAY"));
}
}
processChannelLog(errors, bot, channel, day);
}
protected void processChannelLog(ArrayList errors, Bot bot, Channel channel, Calendar day)
{
initializeHighlighting();
FormBuilder form_builder = mTemplate.getBeanHandler().getFormBuilder();
form_builder.generateField(mTemplate, "q", new String[] {mSearchBean.getKeyword()}, null);
if (!hasInputValue("botname") ||
!hasInputValue("channelname"))
{
setSubmissionForm(mTemplate, "search", new String[] {"qbot", bot.getName(), "qchannel", channel.getName()});
}
if (errors.size() > 0)
{
mTemplate
.getBeanHandler()
.getFormBuilder()
.getValidationBuilder()
.generateValidationErrors(mTemplate, errors, null, null);
}
else
{
Calendar previous = Calendar.getInstance(TimeZone.getTimeZone(DroneConfig.getTimezone()));
Calendar next = Calendar.getInstance(TimeZone.getTimeZone(DroneConfig.getTimezone()));
previous.set(day.get(Calendar.YEAR), day.get(Calendar.MONTH), day.get(Calendar.DAY_OF_MONTH));
previous.add(Calendar.DAY_OF_MONTH, -1);
next.set(day.get(Calendar.YEAR), day.get(Calendar.MONTH), day.get(Calendar.DAY_OF_MONTH));
next.add(Calendar.DAY_OF_MONTH, 1);
setExitQuery(mTemplate, "show_previous", "/"+StringUtils.encodeUrl(bot.getName())+"/"+StringUtils.encodeUrl(channel.getName().substring(1))+"/"+mDateFormat.format(previous.getTime()));
setExitQuery(mTemplate, "show_next", "/"+StringUtils.encodeUrl(bot.getName())+"/"+StringUtils.encodeUrl(channel.getName().substring(1))+"/"+mDateFormat.format(next.getTime()));
mTemplate.setValue("botname", encodeHtml(bot.getName()));
mTemplate.setValue("channelname", encodeHtml(channel.getName()));
mTemplate.setValue("servername", encodeHtml(channel.getServer().getServerName()));
mTemplate.setValue("day", encodeHtml(mOutputDateFormat.format(day.getTime())));
GetLogMessages log_messages = new GetLogMessages();
try
{
if (DatabaseLogsFactory.get().getLogMessages(log_messages, bot, channel, day))
{
setOutput("botname", bot.getName());
setOutput("channelname", channel.getName());
setOutput("day", mDateFormat.format(day.getTime()));
setExitQuery(mTemplate, "download");
}
else
{
mTemplate.setValue("download", "");
}
}
catch (LogManagerException e)
{
throw new EngineException(e);
}
}
print(mTemplate);
}
private void initializeHighlighting() throws EngineException
{
Query query = null;
mSearchBean = (Search.SearchBean)getNamedInputBean("SearchBean");
if (mSearchBean != null &&
mSearchBean.getKeyword() != null &&
!mSearchBean.getKeyword().equals(""))
{
try
{
query = QueryParser.parse(mSearchBean.getKeyword(), "message", new StandardAnalyzer());
}
catch (org.apache.lucene.queryParser.ParseException e)
{
// just ignore it, nothing I can do
}
}
if (query != null)
{
try
{
Collection bots = BotsRunner.getRepInstance().getBots();
Iterator iter = bots.iterator();
String readerDir = null;
while (iter.hasNext())
{
Bot bot = (Bot)iter.next();
if (bot.getName().equals(getInput("botname")))
{
StringBuffer key = new StringBuffer();
key
.append(Config.getRepInstance().getString("LUCENE_DIR"))
.append(File.separator)
.append(bot.getName())
.append("-")
.append(bot.getServer().getServerName())
.append("-")
.append(getInput("channelname").substring(1));
readerDir = key.toString();
}
}
if (readerDir != null)
{
IndexReader reader = IndexReader.open(readerDir);
SimpleHTMLFormatter formatter = new SimpleHTMLFormatter("<span class=\"highlighted\">", "</span>");
mHighlighter = new Highlighter(formatter, new QueryScorer(query.rewrite(reader)));
}
else
{
throw new IOException("Couldn't find the correct index directory for this bot, channel, and server combination");
}
}
catch (IOException e)
{
// nothing I can do, non-essential feature
Logger
.getLogger("com.uwyn.drone.webui.elements.pub")
.severe(ExceptionUtils.getExceptionStackTrace(e));
}
}
}
public void doSearch()
{
String query = getParameter("q");
if (query != null && !query.equals(""))
{
Search.SearchBean bean = new Search.SearchBean();
bean.setKeyword(query);
//bean.setDate(Search.INPUT_DATE_FORMAT_SHORT.format(DATE_FORMAT.parse(getInput("day"))));
bean.setChannel(getInput("botname", getParameter("qbot"))+" - "+getInput("channelname", getParameter("qchannel")));
setNamedOutputBean("SearchBean", bean);
exit("search");
}
else
{
ValidationBuilder validationBuilder = mTemplate.getBeanHandler().getFormBuilder().getValidationBuilder();
List validationErrors = new ArrayList();
validationErrors.add(new ValidationError.INVALID("q"));
validationBuilder.generateErrorMarkings(mTemplate, validationErrors, null, null);
validationBuilder.generateValidationErrors(mTemplate, validationErrors, null, null);
}
processElement();
}
private class GetLogMessages extends LogResultProcessor
{
private HashMap mNickColors = new HashMap();
private int mColorCounter = 0;
public boolean gotMessage(Timestamp moment, ServerMessage serverMessage)
{
String nickname = serverMessage.getPrefix().getNickName();
String nickcolor = (String)mNickColors.get(nickname);
if (null == nickcolor)
{
nickcolor = NICK_COLORS[mColorCounter++%NICK_COLORS.length];
mNickColors.put(nickname, nickcolor);
}
mTemplate.setValue("bgcolor", nickcolor);
mTemplate.setValue("time", mOutputTimeFormat.format(moment));
mTemplate.setValue("nickname", encodeHtml(nickname));
// translate the \u0001ACTION command which corresponds to
// /me so that the user's nickname is used instead
StringBuffer message = new StringBuffer();
if (serverMessage.getPrefix() != null &&
serverMessage.getTrailing().startsWith(IRC_ACTION))
{
message.append(serverMessage.getPrefix().getNickName());
message.append(serverMessage.getTrailing().substring(IRC_ACTION.length()));
}
else
{
message.append(serverMessage.getTrailing());
}
String encoded_message = encodeHtml(message.toString());
if (mHighlighter != null)
{
try
{
TokenStream tokenStream = new StandardAnalyzer().tokenStream("message", new StringReader(encoded_message));
String highlighted = mHighlighter.getBestFragments(tokenStream, encoded_message, 25, "...");
if (!highlighted.equals(""))
{
encoded_message = highlighted;
}
}
catch (IOException e)
{
Logger
.getLogger("com.uwyn.drone.webui.elements.pub")
.severe(ExceptionUtils.getExceptionStackTrace(e));
}
}
encoded_message = convertUrl(encoded_message, URL_HIGHLIGHT);
Matcher email_matcher = EMAIL_HIGHLIGHT.matcher(encoded_message);
encoded_message = email_matcher.replaceAll("<a href=\"mailto:$1\">$1</a>");
mTemplate.setValue("message", encoded_message);
mTemplate.appendBlock("messages", "message");
return true;
}
}
public static String convertUrl(String source, Pattern pattern)
{
String result = source;
Matcher url_matcher = pattern.matcher(source);
boolean found = url_matcher.find();
if (found)
{
String visual_url = null;
String actual_url = null;
int last = 0;
StringBuffer sb = new StringBuffer();
synchronized (sb)
{
do
{
actual_url = url_matcher.group(1);
if (url_matcher.groupCount() > 1)
{
visual_url = url_matcher.group(2);
}
else
{
visual_url = actual_url;
}
sb.append(source.substring(last, url_matcher.start(0)));
sb.append("<a href=\"");
if (actual_url.startsWith("www."))
{
actual_url = "http://"+actual_url;
}
sb.append(actual_url);
sb.append("\"");
sb.append(">");
if (visual_url.length() <= 80)
{
sb.append(visual_url);
}
else
{
String ellipsis = "...";
int last_slash = visual_url.lastIndexOf("/");
int trailing_length = visual_url.length() - last_slash + ellipsis.length();
int start_slash = visual_url.indexOf("/", visual_url.indexOf("://")+3);
int previous_start_slash = start_slash;
while (start_slash+trailing_length < 80)
{
previous_start_slash = start_slash;
start_slash = visual_url.indexOf("/", previous_start_slash+1);
}
sb.append(visual_url.substring(0, previous_start_slash+1));
sb.append(ellipsis);
sb.append(visual_url.substring(last_slash));
}
sb.append("</a>");
last = url_matcher.end(0);
found = url_matcher.find();
}
while (found);
sb.append(source.substring(last));
result = sb.toString();
}
}
return result;
}
}