/*
* $Id$
*
* Copyright (c) 2000-2008 by Rodney Kinney, Joel Uckelman
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License (LGPL) as published by the Free Software Foundation.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, copies are available
* at http://www.opensource.org.
*/
package VASSAL.chat;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.SortedMap;
import java.util.TreeMap;
import org.apache.commons.lang.math.LongRange;
import VASSAL.i18n.Resources;
import VASSAL.tools.SequenceEncoder;
/**
* Queries a known URL to get historical status of the chat room server.
*
* @author rkinney
*/
public class CgiServerStatus implements ServerStatus {
private static final long DAY = 24L * 3600L * 1000L;
public static final String LAST_DAY = "Server.last_24_hours"; //$NON-NLS-1$
public static final String LAST_WEEK = "Server.last_week"; //$NON-NLS-1$
public static final String LAST_MONTH = "Server.last_month"; //$NON-NLS-1$
private static final Map<String,Long> timeRanges = new HashMap<String,Long>();
private static final String[] times = new String[]{
Resources.getString(LAST_DAY),
Resources.getString(LAST_WEEK),
Resources.getString(LAST_MONTH)
};
private HttpRequestWrapper request;
public CgiServerStatus() {
request = new HttpRequestWrapper("http://www.vassalengine.org/util/"); //$NON-NLS-1$
timeRanges.put(Resources.getString(LAST_DAY), DAY);
timeRanges.put(Resources.getString(LAST_WEEK), DAY * 7);
timeRanges.put(Resources.getString(LAST_MONTH), DAY * 30);
}
public ServerStatus.ModuleSummary[] getStatus() {
final HashMap<String,ServerStatus.ModuleSummary> entries =
new HashMap<String,ServerStatus.ModuleSummary>();
try {
for (String s : request.doGet("getCurrentConnections", new Properties())) { //$NON-NLS-1$
final SequenceEncoder.Decoder st = new SequenceEncoder.Decoder(s, '\t');
try {
final String moduleName = st.nextToken();
final String roomName = st.nextToken();
final String playerName = st.nextToken();
final ServerStatus.ModuleSummary entry = entries.get(moduleName);
if (entry == null) {
entries.put(moduleName,
createEntry(moduleName, roomName, playerName));
}
else {
updateEntry(entry, roomName, playerName);
}
}
// FIXME: review error message
catch (NoSuchElementException e1) {
}
}
}
// FIXME: review error message
catch (IOException e) {
e.printStackTrace();
}
return sortEntriesByModuleName(entries);
}
public ModuleSummary[] getHistory(String timeRange) {
final Long l = timeRanges.get(timeRange);
return l != null ? getHistory(l.longValue()) : new ModuleSummary[0];
}
public String[] getSupportedTimeRanges() {
return times;
}
private SortedMap<Long,List<String[]>> records =
new TreeMap<Long,List<String[]>>();
private List<LongRange> requests = new ArrayList<LongRange>();
private ServerStatus.ModuleSummary[] getHistory(long time) {
if (time <= 0) return getStatus();
final long now = System.currentTimeMillis();
// start with new interval
final LongRange req = new LongRange(now - time, now);
final ArrayList<LongRange> toRequest = new ArrayList<LongRange>();
toRequest.add(req);
// subtract each old interval from new interval
for (LongRange y : requests) {
for (ListIterator<LongRange> i = toRequest.listIterator(); i.hasNext();) {
final LongRange x = i.next();
if (!x.overlapsRange(y)) continue; // no overlap, nothing to subtract
// otherwise, remove x and add what remains after subtracting y
i.remove();
final long xl = x.getMinimumLong();
final long xr = x.getMaximumLong();
final long yl = y.getMinimumLong();
final long yr = y.getMaximumLong();
if (xl < yl && yl <= xr) i.add(new LongRange(xl, yl));
if (xl <= yr && yr < xr) i.add(new LongRange(yr, xr));
}
}
// now toRequest contains the intervals we are missing; request those
for (LongRange i : toRequest) {
for (String s : getInterval(i)) {
final SequenceEncoder.Decoder st = new SequenceEncoder.Decoder(s, '\t');
try {
final String moduleName = st.nextToken();
final String roomName = st.nextToken();
final String playerName = st.nextToken();
final Long when = Long.valueOf(st.nextToken());
List<String[]> l = records.get(when);
if (l == null) {
l = new ArrayList<String[]>();
records.put(when, l);
}
l.add(new String[]{ moduleName, roomName, playerName });
}
// FIXME: review error message
catch (NoSuchElementException e) {
e.printStackTrace();
}
// FIXME: review error message
catch (NumberFormatException e) {
e.printStackTrace();
}
}
requests.add(i);
}
// Join intervals to minimize the number we store.
// Note: This is simple, but quadratic in the number of intervals.
// For large numbers of intervals, use an interval tree instead.
for (int i = 0; i < requests.size(); i++) {
final LongRange a = requests.get(i);
for (int j = i+1; j < requests.size(); j++) {
final LongRange b = requests.get(j);
if (a.overlapsRange(b)) {
final long al = a.getMinimumLong();
final long ar = a.getMaximumLong();
final long bl = b.getMinimumLong();
final long br = b.getMaximumLong();
requests.set(i, new LongRange(Math.min(al, bl),
Math.max(ar, br)));
requests.remove(j--);
}
}
}
// pull what we need from the records
final HashMap<String,ServerStatus.ModuleSummary> entries =
new HashMap<String,ServerStatus.ModuleSummary>();
for (List<String[]> l : records.subMap(req.getMinimumLong(),
req.getMaximumLong()).values()) {
for (String[] r : l) {
final String moduleName = r[0];
final String roomName = r[1];
final String playerName = r[2];
final ServerStatus.ModuleSummary entry = entries.get(moduleName);
if (entry == null) {
entries.put(moduleName,
createEntry(moduleName, roomName, playerName));
}
else {
updateEntry(entry, roomName, playerName);
}
}
}
return sortEntriesByModuleName(entries);
}
private ServerStatus.ModuleSummary[] sortEntriesByModuleName(
Map<String,ServerStatus.ModuleSummary> entries) {
final ServerStatus.ModuleSummary[] e = entries.values().toArray(
new ServerStatus.ModuleSummary[entries.size()]);
Arrays.sort(e, new Comparator<ServerStatus.ModuleSummary>() {
public int compare(ServerStatus.ModuleSummary a,
ServerStatus.ModuleSummary b) {
return a.getModuleName().compareTo(b.getModuleName());
}
});
return e;
}
private List<String> getInterval(LongRange i) {
final Properties p = new Properties();
p.setProperty("start", Long.toString(i.getMinimumLong())); //$NON-NLS-1$
p.setProperty("end", Long.toString(i.getMaximumLong())); //$NON-NLS-1$
try {
return request.doGet("getConnectionHistory", p); //$NON-NLS-1$
}
// FIXME: review error message
catch (IOException e) {
e.printStackTrace();
}
return null;
}
private ServerStatus.ModuleSummary updateEntry(
ServerStatus.ModuleSummary entry, String roomName, String playerName) {
SimpleRoom existingRoom = entry.getRoom(roomName);
if (existingRoom == null) {
existingRoom = new SimpleRoom(roomName);
existingRoom.setPlayers(new Player[]{new SimplePlayer(playerName)});
entry.addRoom(existingRoom);
}
else {
existingRoom.addPlayer(new SimplePlayer(playerName));
}
return entry;
}
private ServerStatus.ModuleSummary createEntry(String moduleName,
String roomName,
String playerName) {
final SimpleRoom r = new SimpleRoom(roomName);
r.setPlayers(new Player[]{new SimplePlayer(playerName)});
return new ServerStatus.ModuleSummary(moduleName, new Room[]{r});
}
}