/*
This file is part of leafdigital leafChat.
leafChat is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
leafChat is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with leafChat. If not, see <http://www.gnu.org/licenses/>.
Copyright 2011 Samuel Marshall.
*/
package com.leafdigital.logs;
import java.awt.event.MouseEvent;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.*;
import org.w3c.dom.Element;
import util.PlatformUtils;
import util.xml.*;
import com.leafdigital.logs.LoggerImp.LogFileInfo;
import com.leafdigital.logs.api.Logger;
import com.leafdigital.prefs.api.*;
import com.leafdigital.ui.api.*;
import leafchat.core.api.*;
/**
* Logs tool (dialog and controls).
*/
@UIHandler({"logs", "additem"})
public class LogsTool implements SimpleTool
{
// Auto-set widgets from main page...
private PluginContext context;
/** UI: Main log view */
public TextView tvUI;
/** UI: Date list */
public ListBox datesUI;
/** UI: Item list */
public ListBox itemsUI;
/** UI: Search box */
public EditBox searchUI;
/** UI: Search button */
public Button searchButtonUI;
/** UI: Clear search button */
public Button clearSearchUI;
/** UI: Search initial/ongoing panel */
public ChoicePanel searchChoiceUI;
/** UI: Search progress bar */
public Progress searchProgressUI;
/** UI: Export button */
public Button exportUI;
// ...from settings page...
/** UI: Rollover hour edit */
public EditBox rolloverHourUI;
/** UI: Log only selected list */
public ListBox selectedUI;
/** UI: Do not log list */
public ListBox doNotLogUI;
/** UI: Add to selected */
public Button selectedAddUI;
/** UI: Delete from selected */
public Button selectedDeleteUI;
/** UI: Delete from do not log */
public Button doNotLogDeleteUI;
/** UI: Log only selected radio button */
public RadioButton logSelectedUI;
/** UI: Log everything except radio button */
public RadioButton logEverythingUI;
// ...from storage page...
/** UI: Never delete list */
public ListBox neverDeleteUI;
/** UI: Never delete button */
public Button neverDeleteDeleteUI;
/** UI: Enable archive */
public RadioButton archiveOnUI;
/** UI: Disable archive */
public RadioButton archiveOffUI;
// ...and from add item dialog
/** UI: Add item name */
public EditBox addItemUI;
/** UI: Set button */
public Button addItemSetUI;
private File startFolder;
private boolean searchCancelled;
private File displayedFile = null;
private File[] others = null;
/**
* @param context Context
* @throws GeneralException Any error
*/
public LogsTool(PluginContext context) throws GeneralException
{
this.context = context;
}
@Override
public void removed()
{
if(logWindow!=null) logWindow.close();
}
@Override
public String getLabel()
{
return "Logs";
}
@Override
public String getThemeType()
{
return "logsButton";
}
@Override
public int getDefaultPosition()
{
return 150;
}
private Window logWindow=null;
@Override
public void clicked() throws GeneralException
{
if(logWindow==null)
{
UI u=context.getSingle(UI.class);
logWindow=u.createWindow("logs", this);
initWindow();
logWindow.show(false);
}
else
{
logWindow.activate();
}
}
private LoggerImp.LogFileInfo[] logFileInfo;
private void initWindow() throws GeneralException
{
logWindow.setRemember("tool","logs");
LoggerImp li=(LoggerImp)context.getSingle(Logger.class);
// Fill date and item boxes
logFileInfo=li.getAllLogs();
Set<String> dates = new TreeSet<String>();
Set<String> items = new TreeSet<String>(new IgnoreCaseComparator());
for(int i=0;i<logFileInfo.length;i++)
{
dates.add(logFileInfo[i].getDate());
items.add(logFileInfo[i].getItem());
}
for(String item : items)
{
itemsUI.addItem(item);
}
String[] dateStrings=dates.toArray(new String[dates.size()]);
for(int i=dateStrings.length-1;i>=0;i--)
{
datesUI.addItem(li.displayDate(dateStrings[i]),dateStrings[i]);
}
// Set up textview
tvUI.setAction("file",new TextView.ActionHandler()
{
@Override
public void action(Element e,MouseEvent me) throws GeneralException
{
datesUI.clearSelection();
datesUI.setSelectedData(e.getAttribute("date"),true);
itemsUI.clearSelection();
itemsUI.setSelected(e.getAttribute("item"),true);
selectionChanged();
}
});
// Settings page
// Roll time
String value=""+((LogsPlugin)context.getPlugin()).getRollTime();
if(value.length()==1) value="0"+value;
rolloverHourUI.setValue(value);
// Selected items
Preferences p=context.getSingle(Preferences.class);
PreferencesGroup pg=p.getGroup(context.getPlugin());
if(p.toBoolean(pg.get(LogsPlugin.PREF_ONLYSELECTED,LogsPlugin.PREF_ONLYSELECTED_DEFAULT)))
logSelectedUI.setSelected();
else
logEverythingUI.setSelected();
actionLogChanged();
// Fill lists
fillSelected();
fillDoNotLog();
fillNeverDelete();
// Keep option
int retention=((LogsPlugin)context.getPlugin()).getRetentionDays();
try
{
((RadioButton)logWindow.getWidget("keep"+retention)).setSelected();
}
catch(BugException be)
{
// OK so there wasn't an appropriate radio, let's leave them all unselected
}
// Archiving
if(((LogsPlugin)context.getPlugin()).shouldArchive())
archiveOnUI.setSelected();
else
archiveOffUI.setSelected();
}
/** Fills the 'selected' list */
private void fillSelected()
{
fillListBox(LogsPlugin.PREFGROUP_SELECTED,selectedUI);
selectionSelected();
}
/** Fills the 'do not log' list */
private void fillDoNotLog()
{
fillListBox(LogsPlugin.PREFGROUP_DONOTLOG,doNotLogUI);
selectionDoNotLog();
}
/** Fills the 'never delete' list */
private void fillNeverDelete()
{
fillListBox(LogsPlugin.PREFGROUP_NEVERDELETE,neverDeleteUI);
selectionNeverDelete();
}
/**
* @param prefGroup Preferences group that items come from
* @param listBox List box to fill
*/
private void fillListBox(String prefGroup,ListBox listBox)
{
Preferences p=context.getSingle(Preferences.class);
PreferencesGroup pg=p.getGroup(context.getPlugin());
PreferencesGroup[] selected=pg.getChild( prefGroup).getAnon();
listBox.clear();
for(int i=0;i<selected.length;i++)
{
listBox.addItem(selected[i].get(LogsPlugin.PREF_ITEM),selected[i]);
}
}
/**
* Action: Change selection in dates list.
* @throws GeneralException Any error
*/
@UIAction
public void selectionDates() throws GeneralException
{
selectionChanged();
}
/**
* Action: Change selection in items list.
* @throws GeneralException Any error
*/
@UIAction
public void selectionItems() throws GeneralException
{
selectionChanged();
}
private static class IgnoreCaseComparator implements Comparator<String>
{
@Override
public int compare(String o1,String o2)
{
return o1.compareToIgnoreCase(o2);
}
}
private void selectionChanged() throws GeneralException
{
actionCancelSearch();
LoggerImp li=(LoggerImp)context.getSingle(Logger.class);
// Get required content of items list filtered by selected dates
Set<Object> selectedDates =
new HashSet<Object>(Arrays.asList(datesUI.getMultiSelectedData()));
Set<String> selectedItems = new HashSet<String>();
String[] selectedItemsArray=itemsUI.getMultiSelected();
for(int i=0;i<selectedItemsArray.length;i++)
{
selectedItems.add(selectedItemsArray[i].toUpperCase());
}
Set<String> filteredItemsSet =
new TreeSet<String>(new IgnoreCaseComparator());
if(selectedDates.size()==0)
{
for(int i=0;i<logFileInfo.length;i++)
{
filteredItemsSet.add(logFileInfo[i].getItem());
}
}
else
{
for(int i=0;i<logFileInfo.length;i++)
{
if(selectedDates.contains(logFileInfo[i].getDate()))
filteredItemsSet.add(logFileInfo[i].getItem());
}
}
String[] filteredItems=filteredItemsSet.toArray(new String[filteredItemsSet.size()]);
// Does items list need updating?
String[] currentItems=itemsUI.getItems();
if(!Arrays.equals(currentItems,filteredItems))
{
itemsUI.clear();
for(int i=0;i<filteredItems.length;i++)
{
itemsUI.addItem(filteredItems[i]);
if(selectedItems.contains(filteredItems[i].toUpperCase()))
{
itemsUI.setSelected(filteredItems[i],true);
}
}
}
// OK cool, now same for dates list
selectedItems = new HashSet<String>(); // Redo this in case any got filtered
selectedItemsArray=itemsUI.getMultiSelected();
for(int i=0;i<selectedItemsArray.length;i++)
{
selectedItems.add(selectedItemsArray[i].toUpperCase());
}
Set<String> filteredDatesSet = new TreeSet<String>();
if(selectedItems.size()==0)
{
for(int i=0;i<logFileInfo.length;i++)
{
filteredDatesSet.add(logFileInfo[i].getDate());
}
}
else
{
for(int i=0;i<logFileInfo.length;i++)
{
if(selectedItems.contains(logFileInfo[i].getItem().toUpperCase()))
filteredDatesSet.add(logFileInfo[i].getDate());
}
}
String[] filteredDates=new String[filteredDatesSet.size()];
int iPos=filteredDates.length;
for(String s : filteredDatesSet)
{
filteredDates[--iPos] = s;
}
// Does dates list need updating?
String[] currentDates=datesUI.getData(String.class);
if(!Arrays.equals(currentDates,filteredDates))
{
datesUI.clear();
for(int i=0;i<filteredDates.length;i++)
{
String displayDate=li.displayDate(filteredDates[i]);
datesUI.addItem(displayDate,filteredDates[i]);
if(selectedDates.contains(filteredDates[i]))
{
datesUI.setSelectedData(filteredDates[i],true);
}
}
selectedDates = new HashSet<Object>(Arrays.asList(datesUI.getMultiSelectedData()));
}
// Fine, now have we selected a single date/item yet?
Set<LogFileInfo> files = new HashSet<LogFileInfo>();
boolean single=false;
for(int i=0;i<logFileInfo.length;i++)
{
LogFileInfo current=logFileInfo[i];
if(selectedDates.contains(current.getDate()) && selectedItems.contains(current.getItem().toUpperCase()))
{
if(!files.isEmpty())
{
LogFileInfo other=files.iterator().next();
if(!(other.getDate().equals(current.getDate()) && other.getItem().equalsIgnoreCase(current.getItem())))
{
single=false;
break;
}
}
files.add(current);
single=true;
}
}
// Load and display that log (supposing it isn't already)
if(single)
{
if(files.size()==1)
{
displayFile(files.iterator().next().getFile());
}
else // Same item and date, two servers
{
// Sort list by date of first event
TreeMap<Long, File> tm = new TreeMap<Long, File>();
for(LogFileInfo lfi : files)
{
try
{
BufferedReader br=new BufferedReader(new InputStreamReader(new FileInputStream(lfi.getFile()),"UTF-8"));
String line=br.readLine();
Matcher m=Pattern.compile("^<e time='([0-9]+)'.*$").matcher(line);
if(m.matches())
tm.put(new Long(Long.parseLong(m.group(1))),lfi.getFile());
else
ErrorMsg.report("Unexpected content in log file "+lfi.getFile(),null);
}
catch(IOException e)
{
ErrorMsg.report("Error reading log file "+lfi.getFile(),e);
}
}
if(tm.isEmpty()) // Due to error above
{
displayFile(null);
}
else
{
// Get first file to use as unique identifier
Iterator<Map.Entry<Long, File>> i = tm.entrySet().iterator();
File first = i.next().getValue();
// Get array of other files
i.remove();
File[] others=tm.values().toArray(new File[tm.values().size()]);
displayFile(first,others);
}
}
}
else
{
displayFile(null);
}
}
/**
* Causes a new file to be displayed in the view.
* @param f File to display or null for blank
* @throws GeneralException
*/
private void displayFile(File f) throws GeneralException
{
displayFile(f,new File[0]);
}
/**
* Causes a new file to be displayed in the view.
* @param f First file to display or null for blank
* @param others Other files to display after the first, or empty array
* @throws GeneralException
*/
private void displayFile(File f,File[] others) throws GeneralException
{
if(f==null)
{
if(displayedFile!=null)
{
displayedFile=null;
tvUI.clear();
exportUI.setEnabled(false);
}
return;
}
if(f.equals(displayedFile)) return;
displayedFile=f;
this.others = others;
exportUI.setEnabled(true);
tvUI.clear();
if(others.length==0)
{
addFileToView(displayedFile,false);
}
else
{
addFileToView(displayedFile,true);
for(int i=0;i<others.length;i++)
addFileToView(others[i],true);
}
}
/**
* @param f File to display
* @param showServer If true, includes server name
* @throws GeneralException
*/
private void addFileToView(File f,boolean showServer) throws GeneralException
{
LoggerImp li=(LoggerImp)context.getSingle(Logger.class);
LinkedList<String> lines = li.readFileLines(f);
if(showServer)
{
LogFileInfo lfi=new LogFileInfo(f);
tvUI.addXML("<box><line>On server <key>"+XML.esc(lfi.getServer())+"</key></line></box>");
}
String lastTimestamp=null;
SimpleDateFormat sdf=new SimpleDateFormat("HH:mm");
while(!lines.isEmpty())
{
String line=lines.removeFirst();
String timestamp="";
long eventTime;
try
{
eventTime=Long.parseLong(line.replaceAll("^.*?time=['\"]([0-9]+).*$","$1"));
timestamp="<timestamp>"+sdf.format(new Date(eventTime))+"</timestamp>";
if(timestamp.equals(lastTimestamp))
timestamp="";
else
lastTimestamp=timestamp;
}
catch(NumberFormatException nfe)
{
}
line=line.replaceAll("(^<e[^>]+>)|(</e>$)","");
tvUI.addXML("<line>"+timestamp+line+"</line>");
}
}
private void addFileToWriter(Writer w, File f, boolean showServer)
throws IOException, GeneralException
{
LoggerImp li = (LoggerImp)context.getSingle(Logger.class);
LinkedList<String> lines = li.readFileLines(f);
String lf = "\n";
if(PlatformUtils.isWindows())
{
lf = "\r\n";
}
if(showServer)
{
LogFileInfo lfi = new LogFileInfo(f);
w.write("==" + lf + "== On server " + lfi.getServer() + lf + "==" +
lf + lf);
}
String lastTimestamp = null;
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
while(!lines.isEmpty())
{
String line = lines.removeFirst();
String timestamp = "";
long eventTime;
try
{
eventTime = Long.parseLong(
line.replaceAll("^.*?time=['\"]([0-9]+).*$", "$1"));
timestamp = sdf.format(new Date(eventTime));
if(timestamp.equals(lastTimestamp))
{
timestamp = "";
}
else
{
lastTimestamp = timestamp;
w.write("[" + timestamp + "]" + lf);
}
}
catch(NumberFormatException nfe)
{
}
line = line.replaceAll("(^<e[^>]+>)|(</e>$)","");
// Remove all XML tags from line
line = line.replaceAll("<[^>]+>", "");
// Replace (some) entities
line = XML.unesc(line);
w.write(line + lf);
}
}
/** Action: Window closed. */
@UIAction
public void windowClosed()
{
actionCancelSearch();
logWindow=null;
}
/** Action: Change search text. */
@UIAction
public synchronized void changeSearch()
{
boolean hasText = searchUI.getValue().length()>0;
searchButtonUI.setEnabled(hasText);
clearSearchUI.setEnabled(hasText);
datesUI.setEnabled(!hasText);
itemsUI.setEnabled(!hasText);
}
/** Action: Clear search field. */
@UIAction
public synchronized void clearSearch()
{
searchUI.setValue("");
changeSearch();
}
/**
* Action: Begin search.
* @throws GeneralException Any error
*/
@UIAction
public void actionSearch() throws GeneralException
{
if(!searchButtonUI.isEnabled()) return;
displayFile(null);
tvUI.clear();
datesUI.clearSelection();
itemsUI.clearSelection();
selectionChanged();
// Things to search for
final Set<String>
positiveWords=new HashSet<String>(), // All these words (AND) must be in file
negativeWords=new HashSet<String>(); // All these words (OR) must NOT be in file
List<String[]>
negativePhraseList=new LinkedList<String[]>(), // After getting candidate files, scan and reject with this phrase
positivePhraseList=new LinkedList<String[]>(); // After getting candidate files, scan and reject without this phrase
// Split search into quoted units or words and work out what this means for search
Pattern p=Pattern.compile("(?:(-)?\"(.*?)\")|(?:(-)?([^\" ]+))");
Matcher m=p.matcher(searchUI.getValue());
while(m.find())
{
String wordGroup=m.group(4)!=null ? m.group(4) : m.group(2);
boolean negative=m.group(4)!=null ? (m.group(3)!=null) : (m.group(1)!=null);
List<String> l = new LinkedList<String>();
LoggerImp.splitWords(wordGroup,l);
if(l.size()==1)
{
String word=l.get(0);
if(negative)
negativeWords.add(word);
else
positiveWords.add(word);
}
else if(l.size()>1)
{
String[] words=l.toArray(new String[l.size()]);
if(negative)
negativePhraseList.add(words);
else
{
positivePhraseList.add(words);
for(int i=0;i<words.length;i++)
{
positiveWords.add(words[i]);
}
}
}
}
final String[][] positivePhrases=positivePhraseList.toArray(
new String[positivePhraseList.size()][]);
final String[][] negativePhrases=negativePhraseList.toArray(
new String[negativePhraseList.size()][]);
if(positiveWords.size()<1)
{
tvUI.addLine("<error>You must search for at least one positive word.</error>");
return;
}
// OK, apply search
searchProgressUI.setProgress(0);
searchCancelled=false;
(new Thread(new Runnable()
{
@Override
public void run()
{
try
{
searchChoiceUI.display("searchActive");
try
{
LoggerImp l=(LoggerImp)context.getSingle(Logger.class);
SortedSet<SearchResult> results = new TreeSet<SearchResult>();
doSearch(positiveWords,negativeWords,positivePhrases,negativePhrases,l,results);
synchronized(LogsTool.this)
{
if(searchCancelled)
return;
if(results.isEmpty())
{
tvUI.addXML("<error>No matching log entries found.</error>");
}
else
{
for(SearchResult sr : results)
{
tvUI.addXML(
"<searchresult><file date='"+sr.lfi.getDate()+"' item='"+XML.esc(sr.lfi.getItem())+"'>" +
"<date>"+XML.esc(l.displayDate(sr.lfi.getDate()))+"</date> "+
(sr.lfi.getCategory().equals("chan")
? "<chan>"+XML.esc(sr.lfi.getItem())+"</chan>"
: "<nick>"+XML.esc(sr.lfi.getItem())+"</nick>"
)+" <searchserver>("+XML.esc(sr.lfi.getServer())+")</searchserver></file>"+
sr.ss.getXML()+"</searchresult>");
}
tvUI.addXML("<line>Search complete with "+results.size()+" results.</line>");
}
}
}
finally
{
searchChoiceUI.display("searchInput");
}
}
catch(GeneralException ge)
{
ErrorMsg.report("Error in search",ge);
}
}
},"Log search thread")).start();
}
/**
* @param positiveWords Words that must exist
* @param negativeWords Words that must not exist
* @param positivePhrases Phrases that must exist
* @param negativePhrases Phrases that must not exist
* @param l Logger
* @param results Set that receives results
* @throws GeneralException
*/
private void doSearch(Set<String> positiveWords, Set<String> negativeWords,
String[][] positivePhrases,String[][] negativePhrases,LoggerImp l,
SortedSet<SearchResult> results) throws GeneralException
{
Set<File> totalFound=null;
for(String positiveWord : positiveWords)
{
Set<File> found=l.findWord(positiveWord);
if(totalFound==null)
{
totalFound=found;
}
else
{
for(Iterator<File> j=totalFound.iterator();j.hasNext();)
{
File f = j.next();
if(!found.contains(f))
{
j.remove();
}
}
}
}
// totalFound now includes all files with the positive words, let's
// chuck out the negative...
for(String negativeWord : negativeWords)
{
Set<File> found = l.findWord(negativeWord);
for(File remove : found)
{
totalFound.remove(remove);
}
}
int n=0;
searchProgressUI.setRange(totalFound.size());
// Right, now we have to read each file to get summaries or check phrases
fileloop: for(Iterator<File> i=totalFound.iterator(); i.hasNext();)
{
searchProgressUI.setProgress(n++);
File f=i.next();
boolean[] gotPhrase=new boolean[positivePhrases.length];
SummarySegment currentSegment=new SummarySegment(),bestSegment=null;
int bestScore=-1;
int gotPhrases=0;
try
{
BufferedReader br=new BufferedReader(new InputStreamReader(new FileInputStream(f),"UTF-8"));
while(true)
{
if(searchCancelled) return;
String line=br.readLine();
if(line==null) break;
line=line.replaceAll("(^<e[^>]+>)|(</e>$)","");
String[] words=LoggerImp.extractWords(line);
if(matchPhrase(words,negativePhrases)!=-1)
{
i.remove();
continue fileloop;
}
if(gotPhrases!=gotPhrase.length)
{
int found=matchPhrase(words,positivePhrases);
if(found!=-1 && !gotPhrase[found])
{
gotPhrase[found]=true;
gotPhrases++;
}
}
int score=0;
for(int word=0;word<words.length;word++)
{
if(positiveWords.contains(words[word]))
{
// Check word only contains alphanumeric plus ', or outside ASCII,
// so we know it won't interfere with XML or regex.
if(!words[word].matches("[\\x00-\\x7f&&[^a-z0-9']]"))
{
Matcher highlighter=Pattern.compile(
"\\b("+words[word]+")\\b",Pattern.CASE_INSENSITIVE).matcher(line);
StringBuffer result=new StringBuffer();
int pos=0;
outerloop: while(highlighter.find())
{
// Check we're not replacing stuff inside an xml tag
for(int index=highlighter.end();index<line.length();index++)
{
switch(line.charAt(index))
{
case '<' : break;
case '>' : continue outerloop; // This must be inside a tag
}
}
// OK, add text before and replace it
result.append(line.substring(pos,highlighter.start()));
result.append("<found>");
result.append(highlighter.group(1));
result.append("</found>");
pos=highlighter.end();
}
result.append(line.substring(pos));
line=result.toString();
}
score+=10+words[word].length();
}
}
currentSegment=currentSegment.next(line,score);
if(currentSegment.getScore() > bestScore)
{
bestSegment=currentSegment;
bestScore=currentSegment.getScore();
}
}
br.close();
}
catch(IOException ioe)
{
throw new GeneralException("Failed to load log file",ioe);
}
// Didn't find all the positive phrases
if(gotPhrases < gotPhrase.length)
continue;
results.add(new SearchResult(new LoggerImp.LogFileInfo(f),bestSegment));
}
searchProgressUI.setProgress(n);
}
private static class SearchResult implements Comparable<SearchResult>
{
LoggerImp.LogFileInfo lfi;
SummarySegment ss;
@Override
public boolean equals(Object obj)
{
return (obj instanceof SearchResult) &&
lfi.getFile().equals(((SearchResult)obj).lfi.getFile());
}
@Override
public int compareTo(SearchResult other)
{
int i=other.lfi.getDate().compareTo(lfi.getDate());
if(i!=0) return i;
i=lfi.getCategory().compareTo(other.lfi.getCategory());
if(i!=0) return i;
i=lfi.getItem().compareTo(other.lfi.getItem());
if(i!=0) return i;
i=lfi.getServer().compareTo(other.lfi.getServer());
return i;
}
public SearchResult(LogFileInfo lfi,SummarySegment ss)
{
this.lfi=lfi;
this.ss=ss;
}
}
private int matchPhrase(String[] words, String[][] phrases)
{
for(int phrase=0;phrase<phrases.length;phrase++)
{
for(int word=0;word<words.length-phrases[phrase].length;word++)
{
boolean ok=true;
for(int pos=0;pos<phrases[phrase].length;pos++)
{
if(!words[word+pos].equals(phrases[phrase][pos]))
{
ok=false;
break;
}
}
if(ok)
return phrase;
}
}
return -1;
}
private static class SummarySegment
{
private final static int SUMMARYLINES=3;
private final static int MIDPOINT=SUMMARYLINES/2;
String[] lines=new String[SUMMARYLINES];
int[] scores=new int[SUMMARYLINES];
SummarySegment next(String line,int score)
{
SummarySegment ss=new SummarySegment();
for(int i=0;i<SUMMARYLINES-1;i++)
{
ss.lines[i]=lines[i+1];
ss.scores[i]=scores[i+1];
}
ss.lines[SUMMARYLINES-1]=line;
ss.scores[SUMMARYLINES-1]=score;
return ss;
}
int getScore()
{
int score=0;
for(int line=0;line<scores.length;line++)
{
// Bias scores towards centre
score+=scores[line]*(1+MIDPOINT-Math.abs(line - MIDPOINT));
}
return score;
}
String getXML()
{
StringBuffer sb=new StringBuffer();
for(int i=0;i<SUMMARYLINES;i++)
{
if(lines[i]!=null)
sb.append("<line>"+lines[i]+"</line>");
}
return sb.toString();
}
}
/**
* Action: Cancel current search.
*/
@UIAction
public void actionCancelSearch()
{
synchronized(this)
{
searchCancelled=true;
}
}
/**
* Action: Export current log
* @throws GeneralException Any error
*/
@UIAction
public void actionExport() throws GeneralException
{
UI ui = context.getSingle(UI.class);
if(startFolder == null)
{
startFolder = new File(PlatformUtils.getDocumentsFolder());
}
LogFileInfo lfi = new LogFileInfo(displayedFile);
File defaultFile = new File(startFolder,
LoggerImp.toFilePart(lfi.getItem()) + "." + lfi.getDate() + ".txt");
File target = ui.showFileSelect(logWindow, "Export displayed log", true,
startFolder, defaultFile, new String[] {".txt"}, "Plain text files");
if(target != null)
{
startFolder = target.getParentFile();
// Save log into the given file
try
{
Writer out = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(target), "UTF-8"));
if(others.length==0)
{
addFileToWriter(out, displayedFile, false);
}
else
{
addFileToWriter(out, displayedFile, true);
for(int i=0; i<others.length; i++)
{
out.write("\n");
addFileToWriter(out, others[i], true);
}
}
out.close();
}
catch(IOException e)
{
throw new GeneralException(e);
}
}
}
// Settings page
////////////////
/**
* Action: Changed log option.
* @throws GeneralException Any error
*/
@UIAction
public void actionLogChanged() throws GeneralException
{
Preferences p=context.getSingle(Preferences.class);
boolean onlySelected=logSelectedUI.isSelected();
p.getGroup(context.getPlugin()).set(LogsPlugin.PREF_ONLYSELECTED,
p.fromBoolean(onlySelected));
selectedUI.setEnabled(onlySelected);
selectedAddUI.setEnabled(onlySelected);
selectionSelected();
}
/**
* Action: Selected something in 'log only selected' list.
*/
@UIAction
public void selectionSelected()
{
selectedDeleteUI.setEnabled(logSelectedUI.isSelected() && selectedUI.getSelected()!=null);
}
/**
* Action: Add button in 'selected' list.
* @throws GeneralException Any error
*/
@UIAction
public void actionSelectedAdd() throws GeneralException
{
showAddItem(new AddItemHandler()
{
@Override
public void add(String item) throws GeneralException
{
Preferences p=context.getSingle(Preferences.class);
PreferencesGroup pg=p.getGroup(context.getPlugin());
PreferencesGroup newItem=pg.getChild(LogsPlugin.PREFGROUP_SELECTED).addAnon();
newItem.set(LogsPlugin.PREF_ITEM,item);
fillSelected();
}
});
}
/**
* Action: Delete button in 'selected' list.
* @throws GeneralException Any error
*/
@UIAction
public void actionSelectedDelete() throws GeneralException
{
((PreferencesGroup)selectedUI.getSelectedData()).remove();
fillSelected();
}
/**
* Action: Selected something in 'do not log' list.
*/
@UIAction
public void selectionDoNotLog()
{
doNotLogDeleteUI.setEnabled(doNotLogUI.getSelected()!=null);
}
/**
* Action: Add button in 'do not log' list.
* @throws GeneralException Any error
*/
@UIAction
public void actionDoNotLogAdd() throws GeneralException
{
showAddItem(new AddItemHandler()
{
@Override
public void add(String item) throws GeneralException
{
Preferences p=context.getSingle(Preferences.class);
PreferencesGroup pg=p.getGroup(context.getPlugin());
PreferencesGroup newItem=pg.getChild(LogsPlugin.PREFGROUP_DONOTLOG).addAnon();
newItem.set(LogsPlugin.PREF_ITEM,item);
fillDoNotLog();
}
});
}
/**
* Action: Delete button in 'do not log' list.
* @throws GeneralException Any error
*/
@UIAction
public void actionDoNotLogDelete() throws GeneralException
{
((PreferencesGroup)doNotLogUI.getSelectedData()).remove();
fillDoNotLog();
}
/**
* Action: Changed rollover hour.
* @throws GeneralException Any error
*/
@UIAction
public void changeRolloverHour() throws GeneralException
{
boolean ok=rolloverHourUI.getValue().matches("(0[1-9]|1[0-9]|2[0-3]|[0-9])");
rolloverHourUI.setFlag(ok ? EditBox.FLAG_NORMAL : EditBox.FLAG_ERROR);
if(ok)
{
Preferences p=context.getSingle(Preferences.class);
p.getGroup(context.getPlugin()).set(LogsPlugin.PREF_ROLLTIME,
p.fromInt(Integer.parseInt(rolloverHourUI.getValue())));
}
}
// Retention page
/////////////////
/**
* Action: Retention option changed.
* @throws GeneralException Any error
*/
@UIAction
public void actionKeepChanged() throws GeneralException
{
RadioButton selected=logWindow.getGroupSelected("keep");
if(selected==null) return; // what?
Preferences p=context.getSingle(Preferences.class);
p.getGroup(context.getPlugin()).set(LogsPlugin.PREF_RETENTION,
p.fromInt(Integer.parseInt(selected.getID().substring(4))));
}
/**
* Action: Selected item in 'never delete' list.
*/
@UIAction
public void selectionNeverDelete()
{
neverDeleteDeleteUI.setEnabled(neverDeleteUI.getSelected()!=null);
}
/**
* Action: Add button in 'never delete' list.
* @throws GeneralException Any error
*/
@UIAction
public void actionNeverDeleteAdd() throws GeneralException
{
showAddItem(new AddItemHandler()
{
@Override
public void add(String item) throws GeneralException
{
Preferences p=context.getSingle(Preferences.class);
PreferencesGroup pg=p.getGroup(context.getPlugin());
PreferencesGroup newItem=pg.getChild(LogsPlugin.PREFGROUP_NEVERDELETE).addAnon();
newItem.set(LogsPlugin.PREF_ITEM,item);
fillNeverDelete();
}
});
}
/**
* Action: Delete button in 'never delete' list.
* @throws GeneralException Any error
*/
@UIAction
public void actionNeverDeleteDelete() throws GeneralException
{
((PreferencesGroup)neverDeleteUI.getSelectedData()).remove();
fillNeverDelete();
}
/**
* Action: Turn on archive.
* @throws GeneralException Any error
*/
@UIAction
public void actionArchiveOn() throws GeneralException
{
setArchive(true);
}
/**
* Action: Turn off archive.
* @throws GeneralException Any error
*/
@UIAction
public void actionArchiveOff() throws GeneralException
{
setArchive(false);
}
private void setArchive(boolean archive)
{
Preferences p=context.getSingle(Preferences.class);
PreferencesGroup pg=p.getGroup(context.getPlugin());
pg.set(LogsPlugin.PREF_ARCHIVE,p.fromBoolean(archive));
}
// Add item dialog
//////////////////
private interface AddItemHandler
{
public void add(String item) throws GeneralException;
}
/** Handler that gets called when the add item dialog finishes */
private AddItemHandler aih;
/** Add item dialog */
private Dialog addItem;
private void showAddItem(AddItemHandler aih) throws GeneralException
{
this.aih = aih;
UI ui = context.getSingle(UI.class);
addItem = ui.createDialog("additem", this);
addItem.show(logWindow);
}
/**
* Action: Text changed in item to add.
*/
@UIAction
public void changeAddItem()
{
boolean ok=addItemUI.getValue().matches("[^ *]+\\*?");
addItemSetUI.setEnabled(ok);
addItemUI.setFlag(ok ? EditBox.FLAG_NORMAL : EditBox.FLAG_ERROR);
}
/**
* Action: Set button clicked.
* @throws GeneralException Any error
*/
@UIAction
public void actionAddItemSet() throws GeneralException
{
if(!addItemSetUI.isEnabled()) return;
aih.add(addItemUI.getValue());
actionAddItemCancel();
}
/**
* Action: Cancel button clicked.
*/
@UIAction
public void actionAddItemCancel()
{
addItem.close();
}
}