Merge branch 'master' into prod

This commit is contained in:
Axel Uhl committed 2011-06-13 01:13:13 +02:00
commit 5812eeed95
19 files changed
+309 -98

No files matched your search

@@ -45,8 +45,14 @@ public abstract class AbstractTracTracLiveTest implements Listener {
// storedUri = new URI("tcp://sapsimulation.tracdev.dk:4421"); // or with tunneling: tcp://localhost:4421 // storedUri = new URI("tcp://sapsimulation.tracdev.dk:4421"); // or with tunneling: tcp://localhost:4421
// for stored race, non-real-time simulation: // for stored race, non-real-time simulation:
paramUrl = new URL("http://germanmaster.traclive.dk/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=bd8c778e-7c65-11e0-8236-406186cbf87c"); paramUrl = new URL("http://germanmaster.traclive.dk/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=bd8c778e-7c65-11e0-8236-406186cbf87c");
liveUri = new URI("tcp://localhost:4412");
storedUri = new URI("tcp://localhost:4413"); // tunneled:
//liveUri = new URI("tcp://localhost:4412");
//storedUri = new URI("tcp://localhost:4413");
//no tunnel:
liveUri = new URI("tcp://germanmaster.traclive.dk:4400");
storedUri = new URI("tcp://germanmaster.traclive.dk:4401");
receivers = new HashSet<Receiver>(); receivers = new HashSet<Receiver>();
} }
@@ -2,12 +2,14 @@ package com.sap.sailing.domain.test;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import java.io.IOException; import java.io.IOException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@@ -15,15 +17,20 @@ import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.ControlPoint; import com.sap.sailing.domain.base.ControlPoint;
import com.sap.sailing.domain.base.Course; import com.sap.sailing.domain.base.Course;
import com.sap.sailing.domain.base.CourseListener; import com.sap.sailing.domain.base.CourseListener;
import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.Event;
import com.sap.sailing.domain.base.Leg;
import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.BuoyImpl; import com.sap.sailing.domain.base.impl.BuoyImpl;
import com.sap.sailing.domain.base.impl.WaypointImpl; import com.sap.sailing.domain.base.impl.WaypointImpl;
import com.sap.sailing.domain.tracking.DynamicTrackedEvent; import com.sap.sailing.domain.tracking.DynamicTrackedEvent;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.impl.EmptyWindStore; import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
import com.sap.sailing.domain.tractracadapter.DomainFactory; import com.sap.sailing.domain.tractracadapter.DomainFactory;
import com.sap.sailing.domain.tractracadapter.Receiver; import com.sap.sailing.domain.tractracadapter.Receiver;
@@ -42,6 +49,7 @@ import difflib.Patch;
import difflib.PatchFailedException; import difflib.PatchFailedException;
public class CourseUpdateTest extends AbstractTracTracLiveTest { public class CourseUpdateTest extends AbstractTracTracLiveTest {
private RaceDefinition race;
private Course course; private Course course;
private Event domainEvent; private Event domainEvent;
private DynamicTrackedEvent trackedEvent; private DynamicTrackedEvent trackedEvent;
@@ -73,10 +81,14 @@ public class CourseUpdateTest extends AbstractTracTracLiveTest {
} }
}); });
addListenersForStoredDataAndStartController(receivers); addListenersForStoredDataAndStartController(receivers);
RaceDefinition race = domainFactory.getRaceDefinition(getEvent().getRaceList().iterator().next()); race = domainFactory.getRaceDefinition(getEvent().getRaceList().iterator().next());
course = race.getCourse(); course = race.getCourse();
assertNotNull(course); assertNotNull(course);
assertEquals(3, Util.size(course.getWaypoints())); assertEquals(3, Util.size(course.getWaypoints()));
// make sure leg is initialized correctly in CourseImpl
assertEquals("start/finish", course.getLegs().get(0).getFrom().getName());
assertEquals("top", course.getLegs().get(1).getFrom().getName());
} }
@Test @Test
@@ -189,6 +201,47 @@ public class CourseUpdateTest extends AbstractTracTracLiveTest {
assertTrue(result[0]); assertTrue(result[0]);
} }
@Test
public void testTrackedRacesTrackedLegsUpdatedProperly() throws InterruptedException, PatchFailedException {
final boolean[] result = new boolean[1];
final com.tractrac.clientmodule.ControlPoint cp1 = new com.tractrac.clientmodule.ControlPoint(
UUID.randomUUID(), "CP1", /* hasTwo */false) {
};
waitForRouteData();
final List<com.tractrac.clientmodule.ControlPoint> controlPoints = new ArrayList<com.tractrac.clientmodule.ControlPoint>(
routeData[0].getPoints());
controlPoints.add(cp1);
course.addCourseListener(new CourseListener() {
@Override
public void waypointAdded(int zeroBasedIndex, Waypoint waypointThatGotAdded) {
System.out.println("waypointAdded " + zeroBasedIndex + " / " + waypointThatGotAdded);
ControlPoint cp = domainFactory.getControlPoint(cp1);
result[0] = zeroBasedIndex == controlPoints.size() - 1 && waypointThatGotAdded.getControlPoint() == cp;
}
@Override
public void waypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) {
System.out.println("waypointRemoved " + zeroBasedIndex + " / " + waypointThatGotRemoved);
}
});
domainFactory.updateCourseWaypoints(course, controlPoints);
assertTrue(result[0]);
TrackedRace trackedRace = trackedEvent.getTrackedRace(race);
Iterator<Leg> legIter = course.getLegs().iterator();
for (TrackedLeg trackedLeg : trackedRace.getTrackedLegs()) {
assertTrue(legIter.hasNext());
Leg leg = legIter.next();
assertSame(leg, trackedLeg.getLeg());
}
// and again, this time for the TrackedLegOfCompetitors
for (Leg leg : course.getLegs()) {
for (Competitor competitor : race.getCompetitors()) {
TrackedLegOfCompetitor trackedLegOfCompetitor = trackedRace.getTrackedLeg(competitor, leg);
assertSame(leg, trackedLegOfCompetitor.getLeg());
}
}
}
@After @After
public void tearDown() { public void tearDown() {
routeData[0] = null; routeData[0] = null;
@@ -133,6 +133,11 @@ public class DomainFactoryImpl implements DomainFactory {
Iterable<Waypoint> courseWaypoints = courseToUpdate.getWaypoints(); Iterable<Waypoint> courseWaypoints = courseToUpdate.getWaypoints();
List<Waypoint> newWaypointList = new LinkedList<Waypoint>(); List<Waypoint> newWaypointList = new LinkedList<Waypoint>();
for (ControlPoint tractracControlPoint : controlPoints) { for (ControlPoint tractracControlPoint : controlPoints) {
// The following creates new waypoints; those using equal control points (equality controlled by their UUID)
// are considered equal. Therefore, the diff will try to construct a minimal set of changes where those
// old waypoints remain the same but where the patch may alter their positions. Deleted waypoints are trivial
// regarding ID retention. Waypoints added obtain a new ID during the createWaypoint and will be inserted
// with this new ID.
Waypoint waypoint = createWaypoint(tractracControlPoint); Waypoint waypoint = createWaypoint(tractracControlPoint);
newWaypointList.add(waypoint); newWaypointList.add(waypoint);
} }
@@ -49,8 +49,8 @@ public class TracTracTrackedRaceImpl extends DynamicTrackedRaceImpl implements D
} }
@Override @Override
protected TrackedLeg createTrackedLeg(RaceDefinition race, Leg leg) { protected TrackedLeg createTrackedLeg(Leg leg) {
return new TracTracTrackedLegImpl(this, leg, race.getCompetitors()); return new TracTracTrackedLegImpl(this, leg, getRace().getCompetitors());
} }
@Override @Override
@@ -36,12 +36,15 @@ public class CourseImpl extends NamedImpl implements Course {
if (waypointIter.hasNext()) { if (waypointIter.hasNext()) {
Waypoint previous = waypointIter.next(); Waypoint previous = waypointIter.next();
this.waypoints.add(previous); this.waypoints.add(previous);
waypointIndexes.put(previous, i++); waypointIndexes.put(previous, i++);
while (waypointIter.hasNext()) { while (waypointIter.hasNext()) {
Waypoint current = waypointIter.next(); Waypoint current = waypointIter.next();
this.waypoints.add(current); this.waypoints.add(current);
int indexOfStartWaypoint = i-1;
waypointIndexes.put(current, i++); waypointIndexes.put(current, i++);
Leg leg = new LegImpl(this, i); Leg leg = new LegImpl(this, indexOfStartWaypoint);
legs.add(leg); legs.add(leg);
previous = current; previous = current;
} }
@@ -58,7 +61,13 @@ public class CourseImpl extends NamedImpl implements Course {
@Override @Override
public void addWaypoint(int zeroBasedPosition, Waypoint waypointToAdd) { public void addWaypoint(int zeroBasedPosition, Waypoint waypointToAdd) {
waypoints.add(zeroBasedPosition, waypointToAdd); waypoints.add(zeroBasedPosition, waypointToAdd);
legs.add(new LegImpl(this, zeroBasedPosition)); int legStartWaypointIndex;
if (zeroBasedPosition == waypoints.size()-1) { // added to end
legStartWaypointIndex = zeroBasedPosition-1;
} else {
legStartWaypointIndex = zeroBasedPosition;
}
legs.add(new LegImpl(this, legStartWaypointIndex));
notifyListenersWaypointAdded(zeroBasedPosition, waypointToAdd); notifyListenersWaypointAdded(zeroBasedPosition, waypointToAdd);
} }
@@ -182,7 +182,7 @@ public class DynamicTrackedRaceImpl extends TrackedRaceImpl implements
@Override @Override
protected TrackedLeg createTrackedLeg(RaceDefinition race, Leg leg) { protected TrackedLeg createTrackedLeg(Leg leg) {
return new TrackedLegImpl(this, leg, race.getCompetitors()); return new TrackedLegImpl(this, leg, getRace().getCompetitors());
} }
} }
@@ -97,7 +97,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
} }
trackedLegs = new LinkedHashMap<Leg, TrackedLeg>(); trackedLegs = new LinkedHashMap<Leg, TrackedLeg>();
for (Leg leg : race.getCourse().getLegs()) { for (Leg leg : race.getCourse().getLegs()) {
trackedLegs.put(leg, createTrackedLeg(race, leg)); trackedLegs.put(leg, createTrackedLeg(leg));
} }
markPassingsForCompetitor = new HashMap<Competitor, NavigableSet<MarkPassing>>(); markPassingsForCompetitor = new HashMap<Competitor, NavigableSet<MarkPassing>>();
tracks = new HashMap<Competitor, GPSFixTrack<Competitor, GPSFixMoving>>(); tracks = new HashMap<Competitor, GPSFixTrack<Competitor, GPSFixMoving>>();
@@ -115,9 +115,13 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
} }
currentWindSource = WindSource.EXPEDITION; currentWindSource = WindSource.EXPEDITION;
competitorRankings = new HashMap<TimePoint, TreeSet<Competitor>>(); competitorRankings = new HashMap<TimePoint, TreeSet<Competitor>>();
getRace().getCourse().addCourseListener(this);
} }
abstract protected TrackedLeg createTrackedLeg(RaceDefinition race, Leg leg); /**
* Precondition: race has already been set, e.g., in constructor before this methocd is called
*/
abstract protected TrackedLeg createTrackedLeg(Leg leg);
@Override @Override
public NavigableSet<MarkPassing> getMarkPassings(Competitor competitor) { public NavigableSet<MarkPassing> getMarkPassings(Competitor competitor) {
@@ -388,13 +392,35 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
buoyTracks.put(buoy, new DynamicTrackImpl<Buoy, GPSFix>(buoy, millisecondsOverWhichToAverageSpeed)); buoyTracks.put(buoy, new DynamicTrackImpl<Buoy, GPSFix>(buoy, millisecondsOverWhichToAverageSpeed));
} }
} }
// TODO update trackedLegs // a waypoint got added; this means that a leg got added as well; but we shouldn't claim we know where
// in the leg list of the course the leg was added; that's an implementation secret of CourseImpl. So try:
LinkedHashMap<Leg, TrackedLeg> reorderedTrackedLegs = new LinkedHashMap<Leg, TrackedLeg>();
for (Leg leg : getRace().getCourse().getLegs()) {
if (!trackedLegs.containsKey(leg)) {
// no tracked leg for leg yet:
TrackedLeg newTrackedLeg = createTrackedLeg(leg);
reorderedTrackedLegs.put(leg, newTrackedLeg);
} else {
reorderedTrackedLegs.put(leg, trackedLegs.get(leg));
}
}
// now ensure that the iteration order is in sync with the leg iteration order
trackedLegs.clear();
for (Map.Entry<Leg, TrackedLeg> entry : reorderedTrackedLegs.entrySet()) {
trackedLegs.put(entry.getKey(), entry.getValue());
}
updated(/* time point*/ null); updated(/* time point*/ null);
} }
@Override @Override
public synchronized void waypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) { public synchronized void waypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) {
// TODO update trackedLegs TrackedLeg toRemove;
if (zeroBasedIndex > trackedLegs.size()) { // last waypoint removed
toRemove = getTrackedLegFinishingAt(waypointThatGotRemoved);
} else {
toRemove = getTrackedLegStartingAt(waypointThatGotRemoved);
}
trackedLegs.remove(toRemove.getLeg());
updated(/* time point*/ null); updated(/* time point*/ null);
} }
} }
@@ -22,19 +22,30 @@ public class UDPMirror {
if (args.length == 0) { if (args.length == 0) {
usage(); usage();
} else { } else {
int listeningOnPort = Integer.valueOf(args[0]); int c = 0;
boolean verbose = false;
if (args[c].equals("-v")) {
c++;
verbose = true;
}
int listeningOnPort = Integer.valueOf(args[c++]);
byte[] buf = new byte[65536]; byte[] buf = new byte[65536];
DatagramSocket udpSocket = new DatagramSocket(listeningOnPort); DatagramSocket udpSocket = new DatagramSocket(listeningOnPort);
DatagramPacket received = new DatagramPacket(buf, buf.length); DatagramPacket received = new DatagramPacket(buf, buf.length);
DatagramSocket[] sendingSockets = new DatagramSocket[(args.length - 1) / 2]; DatagramSocket[] sendingSockets = new DatagramSocket[(args.length - 1) / 2];
DatagramPacket[] mirroredPackets = new DatagramPacket[(args.length - 1) / 2]; DatagramPacket[] mirroredPackets = new DatagramPacket[(args.length - 1) / 2];
for (int i = 1; i < args.length - 1; i += 2) { while (c < args.length - 1) {
sendingSockets[(i - 1) / 2] = new DatagramSocket(); sendingSockets[(c - 1) / 2] = new DatagramSocket();
mirroredPackets[(i - 1) / 2] = new DatagramPacket(buf, buf.length, InetAddress.getByName(args[i]), mirroredPackets[(c - 1) / 2] = new DatagramPacket(buf, buf.length, InetAddress.getByName(args[c]),
Integer.valueOf(args[i + 1])); Integer.valueOf(args[c + 1]));
c += 2;
} }
while (true) { while (true) {
udpSocket.receive(received); udpSocket.receive(received);
if (verbose) {
String packetAsString = new String(received.getData(), received.getOffset(), received.getLength()).trim();
System.out.println(packetAsString);
}
for (int i = 0; i < mirroredPackets.length; i++) { for (int i = 0; i < mirroredPackets.length; i++) {
mirroredPackets[i].setLength(received.getLength()); mirroredPackets[i].setLength(received.getLength());
sendingSockets[i].send(mirroredPackets[i]); sendingSockets[i].send(mirroredPackets[i]);
@@ -44,6 +55,7 @@ public class UDPMirror {
} }
private static void usage() { private static void usage() {
System.out.println("Usage: java "+UDPMirror.class.getName()+" <listeningport> hostname1 port1 [hostname2 port2]*"); System.out.println("Usage: java "+UDPMirror.class.getName()+" [-v] <listeningport> hostname1 port1 [hostname2 port2]*");
System.out.println(" -v\tPrint packets received to stdout");
} }
} }
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.pde.ui.EquinoxLauncher">
<booleanAttribute key="append.args" value="true"/>
<booleanAttribute key="automaticAdd" value="true"/>
<booleanAttribute key="automaticValidate" value="false"/>
<stringAttribute key="bad_container_name" value="\com.sap."/>
<stringAttribute key="bootstrap" value=""/>
<stringAttribute key="checked" value="[NONE]"/>
<booleanAttribute key="clearConfig" value="false"/>
<stringAttribute key="configLocation" value="${workspace_loc}/.metadata/.plugins/org.eclipse.pde.core/SailingServer (No Proxy, No Tests)"/>
<booleanAttribute key="default" value="true"/>
<booleanAttribute key="default_auto_start" value="true"/>
<intAttribute key="default_start_level" value="4"/>
<stringAttribute key="deselected_workspace_plugins" value="com.googlecode.java-diff-utils.test,com.sap.sailing.declination.test,com.sap.sailing.domain.test,com.sap.sailing.expeditionconnector.test,com.sap.sailing.mongodb.test"/>
<booleanAttribute key="includeOptional" value="true"/>
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -consoleLog -console"/>
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Declipse.ignoreApp=true -Dosgi.noShutdown=true -Dorg.osgi.service.http.port=8888"/>
<stringAttribute key="pde.version" value="3.3"/>
<booleanAttribute key="show_selected_only" value="false"/>
<stringAttribute key="target_bundles" value="javax.servlet.jsp@default:default,javax.servlet@default:default,org.apache.ant@default:default,org.apache.commons.el@default:default,org.apache.commons.logging@default:default,org.apache.jasper@default:default,org.eclipse.core.jobs@default:default,org.eclipse.core.runtime.compatibility.registry@default:false,org.eclipse.equinox.common@2:true,org.eclipse.equinox.http.jetty@default:default,org.eclipse.equinox.http.registry@default:default,org.eclipse.equinox.http.servlet@default:default,org.eclipse.equinox.registry@default:default,org.eclipse.equinox.util@default:default,org.eclipse.osgi.services@default:default,org.eclipse.osgi.util@default:default,org.eclipse.osgi@-1:true,org.hamcrest.core@default:default,org.mortbay.jetty.server@default:default,org.mortbay.jetty.util@default:default"/>
<booleanAttribute key="tracing" value="false"/>
<booleanAttribute key="useCustomFeatures" value="false"/>
<booleanAttribute key="useDefaultConfigArea" value="true"/>
<stringAttribute key="workspace_bundles" value="com.googlecode.java-diff-utils@default:default,com.mongodb.driver@default:default,com.sap.sailing.declination@default:default,com.sap.sailing.domain.tractracadapter@default:default,com.sap.sailing.domain@default:default,com.sap.sailing.expeditionconnector@default:default,com.sap.sailing.mongodb@default:default,com.sap.sailing.server@default:default,com.tractrac.clientmodule@default:default,com.tractrac.resultapi@default:default,org.json.simple@default:default"/>
</launchConfiguration>
@@ -219,7 +219,9 @@ public class ModeratorApp extends Servlet {
for (TrackedLeg leg : trackedRace.getTrackedLegs()) { for (TrackedLeg leg : trackedRace.getTrackedLegs()) {
JSONObject jsonLeg = new JSONObject(); JSONObject jsonLeg = new JSONObject();
jsonLeg.put("from", leg.getLeg().getFrom().getName()); jsonLeg.put("from", leg.getLeg().getFrom().getName());
jsonLeg.put("fromwaypointid", leg.getLeg().getFrom().getId());
jsonLeg.put("to", leg.getLeg().getTo().getName()); jsonLeg.put("to", leg.getLeg().getTo().getName());
jsonLeg.put("towaypointid", leg.getLeg().getTo().getId());
try { try {
jsonLeg.put("upordownwindleg", leg.isUpOrDownwindLeg(timePoint)); jsonLeg.put("upordownwindleg", leg.isUpOrDownwindLeg(timePoint));
} catch (NoWindException e) { } catch (NoWindException e) {
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sh
jar=`find plugins -name 'com.sap.sailing.expeditionconnector-*.jar' | sort | tail -1`
echo Using JAR file $jar
java -cp "$jar" com.sap.sailing.expeditionconnector.UDPMirror $*
@@ -1,5 +1,5 @@
import cjson import cjson, datetime
import urllib, urllib2 import urllib, urllib2
from sailing.web.util import get_abspath from sailing.web.util import get_abspath
@@ -67,7 +67,7 @@ class URIConfigurator(object):
def trigger(self, request=None): def trigger(self, request=None):
if self.do_log is True: if self.do_log is True:
log.info('Triggering URI %s' % self.URI()) log.info(' %s Triggering URI %s' % (datetime.datetime.now(), self.URI()))
return urllib2.urlopen(self.URI()).read() return urllib2.urlopen(self.URI()).read()
def jsonByUrl(configurator): def jsonByUrl(configurator):
@@ -80,7 +80,7 @@ def jsonByUrl(configurator):
handle.close() handle.close()
else: else:
data = configurator.trigger().read() data = configurator.trigger()
if data.find('Exception')>0: if data.find('Exception')>0:
raise Exception, 'Java triggered exception: %s' % data[:550] raise Exception, 'Java triggered exception: %s' % data[:550]
@@ -53,7 +53,7 @@ class LiveDataReceiver(threading.Thread):
# blocking call by updatecount setting # blocking call by updatecount setting
conf.setParameters(dict(eventname=self.eventname, racename=self.racename, sinceupdate=self.updatecount)) conf.setParameters(dict(eventname=self.eventname, racename=self.racename, sinceupdate=self.updatecount))
conf.setLogging(False) conf.setLogging(True)
try: try:
# need to lock because other threads could overwrite information # need to lock because other threads could overwrite information
@@ -67,6 +67,12 @@ class LiveDataReceiver(threading.Thread):
# print error but do not kill thread as this could be recoverable (connection problems, ...) # print error but do not kill thread as this could be recoverable (connection problems, ...)
log.error('Error in thread for %s %s' % (self.eventname, self.racename), exc_info=True) log.error('Error in thread for %s %s' % (self.eventname, self.racename), exc_info=True)
if str(ex).find('Race not found')>=0:
# if race is not found then pause
log.error(' Pausing listener because race is invalid.')
self.paused = True
continue
# avoid filling console with errors # avoid filling console with errors
time.sleep(2) time.sleep(2)
@@ -85,6 +91,9 @@ def eventConfiguration(configurator):
events = jsonByUrl(configurator) events = jsonByUrl(configurator)
for event in events: for event in events:
if not event.has_key('boatclass'):
continue
dbevent = model.EventImpl.queryOneBy(name=event['name']) dbevent = model.EventImpl.queryOneBy(name=event['name'])
if not dbevent: if not dbevent:
dbevent = model.EventImpl() dbevent = model.EventImpl()
@@ -123,7 +132,9 @@ def eventConfiguration(configurator):
# including all legs where something ends into model # including all legs where something ends into model
# in this case we get the last mark that equals the # in this case we get the last mark that equals the
# points for the whole race # points for the whole race
marknames.append(leg['end']) if leg.has_key('end'):
marknames.append(leg['end'])
else: marknames.append('UNKNOWN')
rcmarks.append(marknames) rcmarks.append(marknames)
@@ -133,7 +144,7 @@ def eventConfiguration(configurator):
for cp in cobjects: for cp in cobjects:
races = [0.0 for r in range(raceindex)] races = [0.0 for r in range(raceindex)]
marks = cp.marks; values = cp.values marks = cp.marks; values = cp.values; upordown = cp.upordownwind; additional = cp.additional
# update races and marks - support add operation # update races and marks - support add operation
# removal not really supported - needs complete refresh # removal not really supported - needs complete refresh
@@ -144,24 +155,36 @@ def eventConfiguration(configurator):
if len(marks) < r+1: if len(marks) < r+1:
marks.append([]) marks.append([])
if len(upordown) < r+1:
upordown.append([])
if len(values) < r+1: if len(values) < r+1:
values.append([]) values.append([])
if len(additional) < r+1:
additional.append([])
for m in range(len(rcmarks[r])): for m in range(len(rcmarks[r])):
if len(marks[r]) < m+1: if len(marks[r]) < m+1:
marks[r].append(0.0) marks[r].append(0.0)
if len(upordown[r]) < m+1:
upordown[r].append(False)
if len(values[r]) < m+1: if len(values[r]) < m+1:
values[r].append([]) values[r].append([])
if len(additional[r]) < m+1:
additional[r].append([])
# recognize if races and/or marks changed # recognize if races and/or marks changed
if cp.races and cp.races != races: if cp.races and len(cp.races) != len(races):
log.error('Difference between races for competitor %s found. Old: %s New: %s' % (cp.name, cp.races, races)) log.error('Difference between races for competitor %s found. Old: %s New: %s' % (cp.name, cp.races, races))
if cp.marks and cp.marks != marks: if cp.marks and len(cp.marks) != len(marks):
log.error('Difference between marks for competitor %s found. Old: %s New: %s' % (cp.name, cp.marks, marks)) log.error('Difference between marks for competitor %s found. Old: %s New: %s' % (cp.name, cp.marks, marks))
cp.update(dict(races=races, marks=marks)) cp.update(dict(races=races, marks=marks, upordownwind=upordown, additional=additional))
# finally update event # finally update event
dbevent.update(dict(name=event['name'], dbevent.update(dict(name=event['name'],
@@ -188,17 +211,19 @@ def liveRaceInformation(configurator):
# it is important to know which position the race has # it is important to know which position the race has
# so we look it up in the Event itself # so we look it up in the Event itself
event = model.EventImpl.queryOneBy(name=eventname) event = model.EventImpl.queryOneBy(name=eventname)
raceindex = event.races.index(racename)
# check that legs still match the expected value # check that legs still match the expected value
legcount = len(data['legs']) legcount = len(data['legs'])
if legcount: if legcount:
legcheck_competitor = model.CompetitorImpl.queryOneBy(name=data['legs'][0]['competitors'][0], eventname=eventname) legcheck_competitor = model.CompetitorImpl.queryOneBy(name=data['legs'][0]['competitors'][0]['name'], event=eventname)
known_leg_count = len(legcheck_competitor.marks) known_leg_count = len(legcheck_competitor.marks[raceindex])
if known_leg_count != legcount: if known_leg_count != legcount:
# length of saved legs and incoming legs does not match # length of saved legs and incoming legs does not match
# in this case we need to reload all events and then continue # in this case we need to reload all events and then continue
# this can lead to competitors that loose data
configurator.setContext(config.MODERATOR) configurator.setContext(config.MODERATOR)
configurator.setCommand(config.LIST_EVENTS) configurator.setCommand(config.LIST_EVENTS)
eventConfiguration(configurator) eventConfiguration(configurator)
@@ -207,10 +232,10 @@ def liveRaceInformation(configurator):
event = model.EventImpl.queryOneBy(name=eventname) event = model.EventImpl.queryOneBy(name=eventname)
log.error('Refreshed event %s information because legs (Old: %s New: %s) seem to have changed.' % (eventname, known_leg_count, legcount)) log.error('Refreshed event %s information because legs (Old: %s New: %s) seem to have changed.' % (eventname, known_leg_count, legcount))
raceindex = event.races.index(racename)
lcount = 0; current_legs = {} lcount = 0; current_legs = {}
for leg in data['legs']: for leg in data['legs']:
leg_id = '%s,%s' % (leg['fromwaypointid'], leg['towaypointid'])
for competitor in leg['competitors']: for competitor in leg['competitors']:
comp = model.CompetitorImpl.queryOneBy(name=competitor['name'], event=eventname) comp = model.CompetitorImpl.queryOneBy(name=competitor['name'], event=eventname)
if not comp: if not comp:
@@ -221,6 +246,8 @@ def liveRaceInformation(configurator):
c_marks = comp.marks c_marks = comp.marks
c_values = comp.values c_values = comp.values
c_total_rank = comp.total c_total_rank = comp.total
c_additional = comp.additional
c_upordown = comp.upordownwind
# if competitor hasn't started yet we just ignore this leg # if competitor hasn't started yet we just ignore this leg
# and don't update the competitors information for the given leg # and don't update the competitors information for the given leg
@@ -292,6 +319,14 @@ def liveRaceInformation(configurator):
c_values[raceindex][lcount][attr[0]] = k c_values[raceindex][lcount][attr[0]] = k
if len(c_additional[raceindex][lcount]) == 0:
c_additional[raceindex][lcount] = [0, 0]
c_additional[raceindex][lcount][0] = competitor['started']
c_additional[raceindex][lcount][1] = competitor['finished']
c_upordown[raceindex][lcount] = leg['upordownwindleg']
# compute total rank for competitor # compute total rank for competitor
rank = competitor.get('rank', 0) rank = competitor.get('rank', 0)
@@ -303,7 +338,7 @@ def liveRaceInformation(configurator):
rank = c['rank'] rank = c['rank']
break break
comp.update(dict(current_rank=rank,races=c_races, marks=c_marks, values=c_values, total=c_total_rank)) comp.update(dict(current_rank=rank,races=c_races, marks=c_marks, values=c_values, additional=c_additional, upordownwind=c_upordown, total=c_total_rank))
# next leg # next leg
lcount += 1 lcount += 1
@@ -21,7 +21,7 @@ class CompetitorImpl(ModelBase):
default_values = { default_values = {
'races': [], 'races': [],
'marks': [[]] 'marks': [[]],
} }
@classmethod @classmethod
@@ -65,10 +65,14 @@ class Competitor(BaseDocument):
'event': unicode, 'event': unicode,
'nationality': unicode, 'nationality': unicode,
'leg_indexes': {}, # holds association between leg position and unique id <position in marks>:<unique id>
'total': float, 'total': float,
'current_rank': int, # current rank for given time 'current_rank': int, # current rank for given time
'races' : [float, ], 'races' : [float, ],
'marks' : [[float, ], ], 'marks' : [[float, ], ], # should be named legs but refactoring is so hard in python
'upordownwind' : [[bool], ], # indicates wheter the leg is up or downwind
'additional' : [[[float]]], # additional information regarding one leg (finished, started)
'values' : [[[float, ]]] 'values' : [[[float, ]]]
} }
@@ -40,6 +40,10 @@ def startListenerThreads(conf, eventlist):
with lock: with lock:
for event in eventlist: for event in eventlist:
event_impl = model.EventImpl.queryOneBy(name=event['name']) event_impl = model.EventImpl.queryOneBy(name=event['name'])
if not event_impl:
raise Exception, 'Could not find event %s in database' % event['name']
for racename in event_impl.races: for racename in event_impl.races:
key = hash('%s-%s-%s-%s' % (conf.host, conf.port, event['name'], racename)) key = hash('%s-%s-%s-%s' % (conf.host, conf.port, event['name'], racename))
@@ -168,10 +172,10 @@ def configureListener(context, request):
conf.setCommand(config.LIST_EVENTS) conf.setCommand(config.LIST_EVENTS)
eventlist = provider.eventConfiguration(conf) eventlist = provider.eventConfiguration(conf)
if model.EventImpl.queryCount() == 0: if eventlist == [] or eventlist[0].get('races', []) == []:
return view.yieldMessage('Seems that there are no events that can be shown! Listener not started?') return view.yieldMessage('Seems that java server (listener) is not initialized correctly. Yields no events and/or races! Try to reconfigure...')
# start listener thread if this is not yet active # start listener thread if it is not yet active
startListenerThreads(conf, eventlist) startListenerThreads(conf, eventlist)
view.session['listener-started'] = True view.session['listener-started'] = True
@@ -457,13 +461,16 @@ def adminLiveData(context, request):
# always show the last leg # always show the last leg
currentlegs.insert(0, currentlegs[0]-1) currentlegs.insert(0, currentlegs[0]-1)
t_up = threaded_listener.values()[0].last_update.strftime('%H:%M:%S')
t_upcount = threaded_listener.values()[0].updatecount
results = '' results = ''
for pos in range(len(currentlegs)): for pos in range(len(currentlegs)):
legpos = currentlegs[pos] legpos = currentlegs[pos]
results += '\nLEG: %s RACE-START: %s NEWEST: %s WIND: (%s %s %s)\n' % (legpos+1, view.millisToDatetime(race.start), view.millisToDatetime(race.timeofnewestevent), race.wind_source, race.wind_bearing, race.wind_speed) results += '\nLAST SHOWRACE CALL: %s (UPCOUNT PARAM: %s)\nLEG: %s RACE-START: %s NEWEST: %s WIND: (%s %s %s)\n' % (t_up, t_upcount, legpos+1, view.millisToDatetime(race.start), view.millisToDatetime(race.timeofnewestevent), race.wind_source, race.wind_bearing, race.wind_speed)
results += 'NAME'.ljust(16) + 'TOTAL'.ljust(7) + 'CRANK'.ljust(7) + 'RRANK'.ljust(7) results += 'NAME'.ljust(16) + 'TOTAL'.ljust(7) + 'CRANK'.ljust(7) + 'RRANK'.ljust(7)
results += 'MRANK'.ljust(7) + 'LRANK'.ljust(9) + 'SPD'.ljust(9) + 'DST'.ljust(9) + 'VMG'.ljust(9) + 'AVMG'.ljust(9) + 'SGAP'.ljust(9) + 'ETA'.ljust(9) + 'DSTGO'.ljust(9) results += 'MRANK'.ljust(7) + 'LRANK'.ljust(9) + 'SPD'.ljust(9) + 'DSTTRV'.ljust(9) + 'VMG'.ljust(9) + 'AVMG'.ljust(9) + 'SGAP'.ljust(9) + 'ETA'.ljust(9) + 'DSTGO'.ljust(9) + 'FINISHD'.ljust(9) + 'STARTD'.ljust(9) + 'UPDOWNWIND'.ljust(12)
results += '\n' results += '\n'
# sort competitors by rank in current leg # sort competitors by rank in current leg
@@ -474,7 +481,19 @@ def adminLiveData(context, request):
results += '%s %s' % (str(c.races[racepos]).ljust(7), str(c.marks[racepos][legpos]).ljust(7)) results += '%s %s' % (str(c.races[racepos]).ljust(7), str(c.marks[racepos][legpos]).ljust(7))
for v in c.values[racepos][legpos]: for v in c.values[racepos][legpos]:
results += str('%.1f' % v).ljust(9) if v == 42.260426041982:
results += 'ANCHOR'.ljust(9)
else:
results += str('%.2f' % v).ljust(9)
try:
results += str(c.additional[racepos][legpos][0]).ljust(9)
results += str(c.additional[racepos][legpos][1]).ljust(9)
except:
results += "False".ljust(9)
results += "False".ljust(9)
results += str(c.upordownwind[racepos][legpos]).ljust(12)
results += '\n' results += '\n'
@@ -4,69 +4,76 @@ When <i>stopping listener</i> it will trigger two methods: If you select a race
<form method="POST" action="/++/configureListener"> <form method="POST" action="/++/configureListener">
<tal:if condition="not view.listenerStarted()"> <tal:if condition="not view.listenerStarted()">
<div class="field">
<span>Host and Port:</span><br/>
<input type="text" name="host" value="localhost" />&nbsp;<input type="text" name="port" value="8888" />
</div>
<input type="submit" name="listener-event-refresh" value="Connect to already running server" /><br/><br/>
<div class="field"> <fieldset>
<span>URL to event configuration</span><br/> <legend>1. Listener Configuration</legend>
<input class="large" type="text" name="eventJSONURL" value=""/> <div class="field">
<span>Host and Port:</span><br/>
<input type="text" name="host" value="localhost" />&nbsp;<input type="text" name="port" value="8888" />
</div>
<input type="submit" name="listener-event-refresh" value="Connect to already running server" /><br/><br/>
</fieldset>
<select name="eventJSONURLselect" onchange="$('input[name=eventJSONURL]').val($('select[name=eventJSONURLselect] option:selected').val())"> <fieldset>
<option value=""></option> <legend>2. Event Configuration</legend>
<option value="http://germanmaster.traclive.dk/events/event_20110505_SailingTea/jsonservice.php">Weymouth</option> <div class="field">
</select> <span>URL to event configuration</span><br/>
<pre style="background-color: #ececec; font-size: 12px;padding: 2px;"> <input class="large" type="text" name="eventJSONURL" value=""/>
<tal:for repeat="eventurl view.eventUrlHistory()"> ${eventurl}</tal:for></pre>
</div>
<div class="field"> <select name="eventJSONURLselect" onchange="$('input[name=eventJSONURL]').val($('select[name=eventJSONURLselect] option:selected').val())">
<span>URL to race configuration</span><br/> <option value=""></option>
<input class="large" type="text" name="paramURL" value=""/> <option value="http://germanmaster.traclive.dk/events/event_20110505_SailingTea/jsonservice.php">Weymouth</option>
<select name="paramURLselect" onchange="$('input[name=paramURL]').val($('select[name=paramURLselect] option:selected').val())"> </select>
<option value=""></option> <pre style="background-color: #ececec; font-size: 12px;padding: 2px;">
<option value="http://sapsimulation.tracdev.dk/simulateconf/j80race12.txt">J80 Race 12</option> <tal:for repeat="eventurl view.eventUrlHistory()"> ${eventurl}</tal:for></pre>
</select> </div>
<pre style="background-color: #ececec; font-size: 12px;padding: 2px;">
<tal:for repeat="eventurl view.raceUrlHistory()"> ${eventurl}</tal:for></pre>
</div>
<div class="field"> <div class="field">
<span>Live-Connection (host:port):</span><br/> <span>URL to race configuration</span><br/>
<input class="large" type="text" name="liveURI" value="" /> <input class="large" type="text" name="paramURL" value=""/>
<select name="liveURIselect" onchange="$('input[name=liveURI]').val($('select[name=liveURIselect] option:selected').val())"> <select name="paramURLselect" onchange="$('input[name=paramURL]').val($('select[name=paramURLselect] option:selected').val())">
<option value=""></option> <option value=""></option>
<option value="tcp://sapsimulation.tracdev.dk:4420">J80 Race 12</option> <option value="http://sapsimulation.tracdev.dk/simulateconf/j80race12.txt">J80 Race 12</option>
<option value="tcp://germanmaster.traclive.dk:4400">Weymouth</option> </select>
</select> <pre style="background-color: #ececec; font-size: 12px;padding: 2px;">
</div> <tal:for repeat="eventurl view.raceUrlHistory()"> ${eventurl}</tal:for></pre>
</div>
<div class="field"> <div class="field">
<span>Data-Connection (host:port):</span><br/> <span>Live-Connection (host:port):</span><br/>
<input class="large" type="text" name="storedURI" value="" /> <input class="large" type="text" name="liveURI" value="" />
<select name="storedURIselect" onchange="$('input[name=storedURI]').val($('select[name=storedURIselect] option:selected').val())"> <select name="liveURIselect" onchange="$('input[name=liveURI]').val($('select[name=liveURIselect] option:selected').val())">
<option value=""></option> <option value=""></option>
<option value="tcp://sapsimulation.tracdev.dk:4421">J80 Race 12</option> <option value="tcp://sapsimulation.tracdev.dk:4420">J80 Race 12</option>
<option value="tcp://germanmaster.traclive.dk:4401">Weymouth</option> <option value="tcp://germanmaster.traclive.dk:4400">Weymouth</option>
</select> </select>
</div> </div>
<div class="field"> <div class="field">
<span>Persistent store where to persist wind tracks</span><br/> <span>Data-Connection (host:port):</span><br/>
<select name="windstore"> <input class="large" type="text" name="storedURI" value="" />
<option value="empty">Do not persist wind</option> <select name="storedURIselect" onchange="$('input[name=storedURI]').val($('select[name=storedURIselect] option:selected').val())">
<option value="mongo">MongoDB Persister</option> <option value=""></option>
</select> <option value="tcp://sapsimulation.tracdev.dk:4421">J80 Race 12</option>
</div> <option value="tcp://germanmaster.traclive.dk:4401">Weymouth</option>
</select>
</div>
<div class="field"> <div class="field">
<input type="checkbox" name="drop_db" />&nbsp; <span>Store where to persist wind tracks</span><br/>
<span>Drop Database before connect</span><br/> <select name="windstore">
</div> <option value="empty">Do not persist wind</option>
<option value="mongo">MongoDB Persister</option>
</select>
</div>
<input type="submit" name="listener-start" value="Start listener"/>&nbsp; <div class="field">
<input type="checkbox" name="drop_db" />&nbsp;
<span>Drop Database before connect</span><br/>
</div>
<input type="submit" name="listener-start" value="Start listener"/>&nbsp;
</fieldset>
</tal:if> </tal:if>
<tal:if condition="view.listenerStarted() or request.params.get('debug')"> <tal:if condition="view.listenerStarted() or request.params.get('debug')">
@@ -66,6 +66,7 @@
// also show wind directions // also show wind directions
GET('determineWindDirection', {at : at}, function(data) { GET('determineWindDirection', {at : at}, function(data) {
map.clearPaths();
if (typeof data == 'string') { if (typeof data == 'string') {
alert('Error: ' + data); alert('Error: ' + data);
return; return;
@@ -20,7 +20,6 @@
<div style="text-align: right;margin: 13px 1px 13px 1px;"> <div style="text-align: right;margin: 13px 1px 13px 1px;">
<form method="POST" action="/++/configureListener"> <form method="POST" action="/++/configureListener">
<tal:if condition="view.listenerStarted()">
<div id="listeners-container"> <div id="listeners-container">
<select id="listeners"> <select id="listeners">
<option>Loading....</option> <option>Loading....</option>
@@ -36,7 +35,6 @@
<input type="button" value="Stop" onclick="GET('stopListener', {tid: $('#listeners option:selected').val()}, loadListeners)"/> <input type="button" value="Stop" onclick="GET('stopListener', {tid: $('#listeners option:selected').val()}, loadListeners)"/>
&nbsp;&nbsp; &nbsp;&nbsp;
</div> </div>
</tal:if>
</form> </form>
</div> </div>