/* * $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}); } }