diff --git a/TODO b/TODO index 42939d75cf2..4915b53d70b 100644 --- a/TODO +++ b/TODO @@ -1,8 +1,7 @@ - Axel ==== - - Implement and test course update with corresponding TrackedLeg[OfCompetitor] updates + - What about distance traveled in first leg? It seems to jump too high at the beginning. - Open test server firewall ports for UDP / Expedition wind integration @@ -10,10 +9,6 @@ Axel - Set up two Expedition machines with RF and UMTS transmission, test killing one - - Test duplicate addrace for the same race; consider cleaning cache when stopRace/stopEvent is invoked - - - Test duplicate addevent for the same event - - Test fail-over in case one of the two Java VM fails - enable switching between our and TracTrac-provided leaderboard information @@ -22,6 +17,10 @@ Axel - Display gain/loss for next-best competitor (green/red arrow up/down) based on VMG comparison + - Now that we can update Course's waypoints, consider creating a RaceDefinition right upon + receiving the Event/Race from TracTrac, using an empty list of waypoints. This may simplify + the entire life cycle of RaceDefinition objects. + - Improve Mongo-based tests by using Mongo transactions instead of waiting for prior transactions to complete @@ -30,8 +29,6 @@ Simon + Server: Mehr RAM bestellen + Prüfen, warum in der Map Anzeige manchmal ein Competitor doppelt ist -+ Simulator mit Speed = 1 anschmeissen und dann prüfen was passiert -+ Prüfen warum im live leaderbaord manchmal 4 legs angezeigt werden (max ist 2+1) -+ Prüfen warum der Listener thread manchmal nicht richtig gestoppt wird ++ ANCHOR anzeigen für große Werte (Bei DSTTRV raussuchen was da z.B. groß ist) + Implementierung Leaderboard auf Basis HTML Designer + Wenn Axel Branches eingerichtet hat, dann MongoDBs auf Server startbar machen über supervisord diff --git a/java/com.sap.sailing.declination.test/src/com/sap/sailing/declination/test/DeclinationImportTest.java b/java/com.sap.sailing.declination.test/src/com/sap/sailing/declination/test/DeclinationImportTest.java index 8f4862a82f2..552d9b9aeb1 100755 --- a/java/com.sap.sailing.declination.test/src/com/sap/sailing/declination/test/DeclinationImportTest.java +++ b/java/com.sap.sailing.declination.test/src/com/sap/sailing/declination/test/DeclinationImportTest.java @@ -55,7 +55,7 @@ public class DeclinationImportTest extends AbstractDeclinationTest { public void readOnlineOrFromFile() throws IOException, ClassNotFoundException, ParseException { Declination declination = importer.getDeclination(new DegreePosition(53, 3), new MillisecondsTimePoint(simpleDateFormat.parse("2011-05-27").getTime()), - /* timeoutForOnlineFetchInMilliseconds */ 5000); + /* timeoutForOnlineFetchInMilliseconds */ 10000); assertNotNull(declination); System.out.println(declination); } diff --git a/java/com.sap.sailing.domain.test/.settings/org.eclipse.core.resources.prefs b/java/com.sap.sailing.domain.test/.settings/org.eclipse.core.resources.prefs new file mode 100755 index 00000000000..9b98b4ed4aa --- /dev/null +++ b/java/com.sap.sailing.domain.test/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,3 @@ +#Fri Jun 10 21:39:55 CEST 2011 +eclipse.preferences.version=1 +encoding//src/com/sap/sailing/domain/test/WindTest.java=UTF-8 diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseUpdateTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseUpdateTest.java index 96238653c83..a0defac30e2 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseUpdateTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseUpdateTest.java @@ -67,7 +67,7 @@ public class CourseUpdateTest extends AbstractTracTracLiveTest { super.setUp(); domainFactory = new DomainFactoryImpl(); domainEvent = domainFactory.createEvent(getEvent()); - trackedEvent = domainFactory.trackEvent(domainEvent); + trackedEvent = domainFactory.getOrCreateTrackedEvent(domainEvent); receivers.add(new RaceCourseReceiver(domainFactory, trackedEvent, getEvent(), /* millisecondsOverWhichToAverageWind */ EmptyWindStore.INSTANCE, 30000, /* millisecondsOverWhichToAverageSpeed */30000) { @@ -81,7 +81,10 @@ public class CourseUpdateTest extends AbstractTracTracLiveTest { } }); addListenersForStoredDataAndStartController(receivers); - race = domainFactory.getRaceDefinition(getEvent().getRaceList().iterator().next()); + Race tractracRace = getEvent().getRaceList().iterator().next(); + // now we expect that there is no + assertNull(domainFactory.getExistingRaceDefinitionForRace(tractracRace)); + race = domainFactory.getRaceDefinition(tractracRace); course = race.getCourse(); assertNotNull(course); assertEquals(3, Util.size(course.getWaypoints())); @@ -91,6 +94,11 @@ public class CourseUpdateTest extends AbstractTracTracLiveTest { assertEquals("top", course.getLegs().get(1).getFrom().getName()); } + private void assertNull(RaceDefinition existingRaceDefinitionForRace) { + // TODO Auto-generated method stub + + } + /** * Asserts that the race course's legs have corresponding {@link TrackedLeg}s and the {@link TrackedLeg}s have * {@link TrackedLegOfCompetitor} for each of the race's competitors. diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ReceiveMarkPassingDataTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ReceiveMarkPassingDataTest.java index 109c7101a34..ca9f5d75191 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ReceiveMarkPassingDataTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ReceiveMarkPassingDataTest.java @@ -73,7 +73,7 @@ public class ReceiveMarkPassingDataTest extends AbstractTracTracLiveTest { List receivers = new ArrayList(); receivers.add(receiver); for (Receiver r : DomainFactory.INSTANCE.getUpdateReceivers( - DomainFactory.INSTANCE.trackEvent(DomainFactory.INSTANCE.createEvent(getEvent())), + DomainFactory.INSTANCE.getOrCreateTrackedEvent(DomainFactory.INSTANCE.createEvent(getEvent())), getEvent(), EmptyWindStore.INSTANCE, ReceiverType.RACECOURSE, ReceiverType.MARKPOSITIONS, ReceiverType.RACESTARTFINISH, ReceiverType.RAWPOSITIONS)) { receivers.add(r); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ReceiveTrackingDataTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ReceiveTrackingDataTest.java index 957fddb5af3..05e3238bbb4 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ReceiveTrackingDataTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ReceiveTrackingDataTest.java @@ -72,13 +72,16 @@ public class ReceiveTrackingDataTest extends AbstractTracTracLiveTest { }; List listeners = new ArrayList(); Event event = domainFactory.createEvent(getEvent()); - DynamicTrackedEvent trackedEvent = domainFactory.trackEvent(event); + DynamicTrackedEvent trackedEvent = domainFactory.getOrCreateTrackedEvent(event); trackedEvent.addRaceListener(new RaceListener() { @Override public void raceAdded(TrackedRace trackedRace) { System.out.println("Subscribing raw position listener for race "+trackedRace); ((DynamicTrackedRace) trackedRace).addListener(positionListener); } + @Override + public void raceRemoved(TrackedRace trackedRace) { + } }); for (Receiver receiver : domainFactory.getUpdateReceivers(trackedEvent, getEvent(), EmptyWindStore.INSTANCE)) { for (TypeController raceListener : receiver.getTypeControllers()) { diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackTest.java index 5185397d1f6..246ecd3dfc7 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackTest.java @@ -224,4 +224,15 @@ public class TrackTest { } } } + + @Test + public void testDistanceTraveledOnInBetweenSectionFromFixToFix() { + // take second and third fix and compute distance between them + Iterator iter = track.getFixes().iterator(); + iter.next(); // skip first; + GPSFix second = iter.next(); + GPSFix third = iter.next(); + assertEquals(second.getPosition().getDistance(third.getPosition()), + track.getDistanceTraveled(second.getTimePoint(), third.getTimePoint())); + } } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/DomainFactory.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/DomainFactory.java index 3e11afedfa6..8bb08afc795 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/DomainFactory.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/DomainFactory.java @@ -76,7 +76,11 @@ public interface DomainFactory { RaceTracker createRaceTracker(URL paramURL, URI liveURI, URI storedURI, WindStore windStore) throws MalformedURLException, FileNotFoundException, URISyntaxException; - DynamicTrackedEvent trackEvent(com.sap.sailing.domain.base.Event event); + /** + * Looks for tracking information about event. If no such object exists yet, a new one + * is created. + */ + DynamicTrackedEvent getOrCreateTrackedEvent(com.sap.sailing.domain.base.Event event); BoatClass getBoatClass(CompetitorClass competitorClass); @@ -93,7 +97,7 @@ public interface DomainFactory { * * @param trackedEvent * must have been created before through - * {@link #trackEvent(com.sap.sailing.domain.base.Event)} because + * {@link #getOrCreateTrackedEvent(com.sap.sailing.domain.base.Event)} because * otherwise the link to the {@link Event} can't be established * @param windStore TODO */ @@ -117,6 +121,11 @@ public interface DomainFactory { DynamicTrackedRace trackRace(TrackedEvent trackedEvent, RaceDefinition raceDefinition, WindStore windStore, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, Event tractracEvent); + /** + * Non-blocking call that returns null if the {@link RaceDefinition} for the TracTrac Event + * hasn't been created yet, e.g., because the course definition hasn't been received yet or the listener + * for receiving course information hasn't been registered (yet). + */ RaceDefinition getRace(Event tractracEvent); JSONService parseJSONURL(URL jsonURL) throws IOException, ParseException, org.json.simple.parser.ParseException; @@ -133,4 +142,10 @@ public interface DomainFactory { * in a course. A single control point may be used more than once in a course's list of waypoints. */ void updateCourseWaypoints(Course courseToUpdate, List controlPoints) throws PatchFailedException; + + /** + * Looks for the tracking information for event. If not found, null is returned + * immediately. See also {@link #getOrCreateTrackedEvent(com.sap.sailing.domain.base.Event)}. + */ + DynamicTrackedEvent getTrackedEvent(com.sap.sailing.domain.base.Event event); } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/RaceHandle.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/RaceHandle.java new file mode 100755 index 00000000000..64176826d29 --- /dev/null +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/RaceHandle.java @@ -0,0 +1,33 @@ +package com.sap.sailing.domain.tractracadapter; + +import com.sap.sailing.domain.base.Event; +import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.tracking.DynamicTrackedEvent; +import com.sap.sailing.domain.tractracadapter.impl.RaceCourseReceiver; + +/** + * Gives clients a way to refer to a specific race even though the {@link RaceDefinition} object may not have + * been created yet, but without exposing the TracTrac internals. The {@link #getRace()} call will block + * until the {@link RaceDefinition} has been created. + * + * @author Axel Uhl (d043530) + * + */ +public interface RaceHandle { + + Event getEvent(); + + /** + * Fetch the race definition. If the race definition represented by this handle hasn't been created yet, the call + * blocks until such a definition is provided by another call, usually by the {@link RaceCourseReceiver}. + */ + RaceDefinition getRace(); + + DynamicTrackedEvent getTrackedEvent(); + + /** + * The tracker managing the tracking of the race to which this is a handle. + */ + RaceTracker getRaceTracker(); + +} diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/RaceTracker.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/RaceTracker.java index 4cbf2210bca..1f90c0436f7 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/RaceTracker.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/RaceTracker.java @@ -2,15 +2,39 @@ package com.sap.sailing.domain.tractracadapter; import java.io.IOException; import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.tracking.DynamicTrackedEvent; +import com.sap.sailing.domain.tracking.TrackedEvent; +import com.sap.sailing.domain.tracking.WindStore; +import com.sap.sailing.util.Util.Triple; public interface RaceTracker { + /** + * Stops tracking the race and removes the {@link TrackedRace} object one gets from calling + * {@link #getTrackedEvent()}.{@link TrackedEvent#getTrackedRace(RaceDefinition) getTrackedRace(}{@link #getRace() getRace())} + * from the {@link #getTrackedEvent() tracked event}. + */ void stop() throws MalformedURLException, IOException, InterruptedException; com.sap.sailing.domain.base.Event getEvent(); + /** + * Non-blocking call that returns null if the {@link RaceDefinition} for the TracTrac Event + * hasn't been created yet, e.g., because the course definition hasn't been received yet or the listener + * for receiving course information hasn't been registered (yet). + */ RaceDefinition getRace(); + + RaceHandle getRaceHandle(); + + DynamicTrackedEvent getTrackedEvent(); + + WindStore getWindStore(); + + Triple getURLs(); } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java index 596d19418ee..a83e7d429eb 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java @@ -74,7 +74,7 @@ import difflib.PatchFailedException; public class DomainFactoryImpl implements DomainFactory { private static final Logger logger = Logger.getLogger(DomainFactoryImpl.class.getName()); - private final long millisecondsOverWhichToAverageSpeed = 5000; + private final long millisecondsOverWhichToAverageSpeed = 15000; // TODO clarify how millisecondsOverWhichToAverageWind could be updated and propagated live private final long millisecondsOverWhichToAverageWind = 30000; @@ -338,9 +338,14 @@ public class DomainFactoryImpl implements DomainFactory { ReceiverType.MARKPASSINGS, ReceiverType.MARKPOSITIONS, ReceiverType.RACESTARTFINISH, ReceiverType.RAWPOSITIONS); } + + @Override + public DynamicTrackedEvent getTrackedEvent(com.sap.sailing.domain.base.Event event) { + return eventTrackingCache.get(event); + } @Override - public DynamicTrackedEvent trackEvent(com.sap.sailing.domain.base.Event event) { + public DynamicTrackedEvent getOrCreateTrackedEvent(com.sap.sailing.domain.base.Event event) { synchronized (eventTrackingCache) { DynamicTrackedEvent result = eventTrackingCache.get(event); if (result == null) { diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceCourseReceiver.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceCourseReceiver.java index 08c6c3e4571..70546d30b01 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceCourseReceiver.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceCourseReceiver.java @@ -84,19 +84,26 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue receivers; + private final DomainFactory domainFactory; + private final DynamicTrackedEvent trackedEvent; + private final WindStore windStore; + private final Triple urls; /** + * Creates a race tracked for the specified URL/URIs and starts receiving all available existing and future push + * data from there. Receiving continues until {@link #stop()} is called. + *

+ * * A race tracker uses the paramURL for the TracTrac Java client to register for push data about one - * race. The {@link RaceDefinition} for that race, however, cannot be created until the {@link Course} has been + * race. The {@link RaceDefinition} for that race, however, isn't created until the {@link Course} has been * received. Therefore, the {@link RaceCourseReceiver} will create the {@link RaceDefinition} and will add it to the * {@link com.sap.sailing.domain.base.Event}. *

@@ -45,16 +56,16 @@ public class RaceTrackerImpl implements Listener, RaceTracker { * {@link Event} as argument that is used for its tracking. *

* - * When {@link #getRace} is called on this object before the {@link RaceCourseReceiver} has created the - * {@link RaceDefinition}, the call will block until this has happened. - * * @param windStore - * Provides the capability to obtain the {@link WindTrack}s for the different wind sources. - * A trivial implementation is {@link EmptyWindStore} which simply provides new, empty tracks. - * This is always available but loses track of the wind, e.g., during server restarts. + * Provides the capability to obtain the {@link WindTrack}s for the different wind sources. A trivial + * implementation is {@link EmptyWindStore} which simply provides new, empty tracks. This is always + * available but loses track of the wind, e.g., during server restarts. */ protected RaceTrackerImpl(DomainFactory domainFactory, URL paramURL, URI liveURI, URI storedURI, WindStore windStore) throws URISyntaxException, MalformedURLException, FileNotFoundException { + urls = new Triple(paramURL, liveURI, storedURI); + this.windStore = windStore; + this.domainFactory = domainFactory; // Read event data from configuration file tractracEvent = KeyValue.setup(paramURL); @@ -69,7 +80,7 @@ public class RaceTrackerImpl implements Listener, RaceTracker { // Start live and stored data streams ioThread = new Thread(controller, "io"); domainEvent = domainFactory.createEvent(tractracEvent); - DynamicTrackedEvent trackedEvent = domainFactory.trackEvent(domainEvent); + trackedEvent = domainFactory.getOrCreateTrackedEvent(domainEvent); receivers = new HashSet(); Set typeControllers = new HashSet(); for (Receiver receiver : domainFactory.getUpdateReceivers(trackedEvent, tractracEvent, windStore)) { @@ -81,9 +92,29 @@ public class RaceTrackerImpl implements Listener, RaceTracker { addListenersForStoredDataAndStartController(typeControllers); } + @Override + public Triple getURLs() { + return urls; + } + + @Override + public WindStore getWindStore() { + return windStore; + } + + @Override + public DynamicTrackedEvent getTrackedEvent() { + return trackedEvent; + } + + @Override + public RaceHandle getRaceHandle() { + return new RaceHandleImpl(domainFactory, tractracEvent, getTrackedEvent(), this); + } + @Override public RaceDefinition getRace() { - return DomainFactory.INSTANCE.getRace(tractracEvent); + return domainFactory.getRace(tractracEvent); } protected void addListenersForStoredDataAndStartController(Iterable listenersForStoredData) { @@ -118,7 +149,8 @@ public class RaceTrackerImpl implements Listener, RaceTracker { for (Receiver receiver : receivers) { receiver.stop(); } - ioThread.join(); + ioThread.join(3000); // wait no more than three seconds + trackedEvent.removedTrackedRace(trackedEvent.getTrackedRace(getRace())); } protected DataController getController() { diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracTrackedLegOfCompetitor.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracTrackedLegOfCompetitor.java index cb7ff26d22a..74b0a4733af 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracTrackedLegOfCompetitor.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracTrackedLegOfCompetitor.java @@ -7,6 +7,7 @@ import com.sap.sailing.domain.base.Leg; import com.sap.sailing.domain.base.Mile; import com.sap.sailing.domain.base.Position; import com.sap.sailing.domain.base.Speed; +import com.sap.sailing.domain.base.SpeedWithBearing; import com.sap.sailing.domain.base.TimePoint; import com.sap.sailing.domain.base.impl.KilometersPerHourSpeedImpl; import com.sap.sailing.domain.base.impl.KnotSpeedImpl; @@ -139,4 +140,13 @@ public class TracTracTrackedLegOfCompetitor implements TrackedLegOfCompetitor { return trackedRace; } + @Override + public SpeedWithBearing getSpeedOverGround(TimePoint at) { + if (hasStartedLeg(at)) { + return getTrackedRace().getTrack(getCompetitor()).getEstimatedSpeed(at); + } else { + return null; + } + } + } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractDistance.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractDistance.java index 8641a6b9474..bd32b85e8c1 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractDistance.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/AbstractDistance.java @@ -6,6 +6,16 @@ import com.sap.sailing.domain.base.Speed; public abstract class AbstractDistance implements Distance { + @Override + public boolean equals(Object o) { + return (o instanceof Distance) && this.compareTo((Distance) o) == 0; + } + + @Override + public int hashCode() { + return 9128347 ^ (int) getMeters(); + } + @Override public int compareTo(Distance o) { return getMeters() > o.getMeters() ? 1 : getMeters() == o.getMeters() ? 0 : -1; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedEvent.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedEvent.java index 0fa065549ef..99b829083d1 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedEvent.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedEvent.java @@ -3,5 +3,7 @@ package com.sap.sailing.domain.tracking; import com.sap.sailing.domain.base.RaceDefinition; public interface DynamicTrackedEvent extends TrackedEvent { + DynamicTrackedRace getTrackedRace(RaceDefinition race); + } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceListener.java index 9d83f2b8427..754ce4577ef 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceListener.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceListener.java @@ -2,4 +2,6 @@ package com.sap.sailing.domain.tracking; public interface RaceListener { void raceAdded(TrackedRace trackedRace); + + void raceRemoved(TrackedRace trackedRace); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedEvent.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedEvent.java index f8c97e6783b..84e24c1392f 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedEvent.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedEvent.java @@ -19,8 +19,16 @@ public interface TrackedEvent { */ TrackedRace getTrackedRace(RaceDefinition race); + /** + * Non-blocking call that returns null if no tracking information currently exists + * for race. See also {@link #getTrackedRace(RaceDefinition)} for a blocking variant. + */ + TrackedRace getExistingTrackedRace(RaceDefinition race); + void addTrackedRace(TrackedRace trackedRace); + void removedTrackedRace(TrackedRace trackedRace); + /** * Listener will be notified when {@link #addTrackedRace(TrackedRace)} is called and * upon registration for each tracked race already known. Therefore, the listener @@ -31,5 +39,5 @@ public interface TrackedEvent { int getTotalPoints(Competitor competitor, TimePoint timePoint); int getNetPoints(Competitor competitor, TimePoint timePoint) throws NoWindException; - + } \ No newline at end of file diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedLegOfCompetitor.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedLegOfCompetitor.java index 7edc0c8afb0..405ef7a64b3 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedLegOfCompetitor.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedLegOfCompetitor.java @@ -4,6 +4,7 @@ import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Distance; import com.sap.sailing.domain.base.Leg; import com.sap.sailing.domain.base.Speed; +import com.sap.sailing.domain.base.SpeedWithBearing; import com.sap.sailing.domain.base.TimePoint; public interface TrackedLegOfCompetitor { @@ -91,4 +92,6 @@ public interface TrackedLegOfCompetitor { */ Double getEstimatedTimeToNextMarkInSeconds(TimePoint timePoint) throws NoWindException; + SpeedWithBearing getSpeedOverGround(TimePoint at); + } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java index 938d5893da4..a1011444d14 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java @@ -137,8 +137,7 @@ public interface TrackedRace { WindTrack getWindTrack(WindSource windSource); /** - * Waits until {@link #getUpdateCount()} is after since. - * @param sinceUpdate TODO + * Waits until {@link #getUpdateCount()} is after sinceUpdate. */ void waitForNextUpdate(int sinceUpdate) throws InterruptedException; @@ -149,4 +148,10 @@ public interface TrackedRace { NavigableSet getMarkPassings(Competitor competitor); void removeWind(Wind wind, WindSource windSource); + + /** + * Time stamp that the last event received from the underlying push service carried on it. + * Note that these times may not increase monotonically. + */ + TimePoint getTimePointOfLastEvent(); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java index 987025b927b..0044ee2b24d 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java @@ -119,6 +119,9 @@ public class DynamicTrackedRaceImpl extends TrackedRaceImpl implements competitorMarkPassings.add(markPassing); getMarkPassingsInOrder(markPassing.getWaypoint()).add(markPassing); updated(markPassing.getTimePoint()); + } + // notify *after* all mark passings have been re-established; should avoid flicker + for (MarkPassing markPassing : markPassings) { notifyListeners(markPassing); } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedEventImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedEventImpl.java index cb4d55da0a7..26413737dbb 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedEventImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedEventImpl.java @@ -47,6 +47,24 @@ public class TrackedEventImpl implements TrackedEvent { trackedRaces.notifyAll(); } } + + @Override + public void removedTrackedRace(TrackedRace trackedRace) { + synchronized (trackedRaces) { + trackedRaces.remove(trackedRace.getRace()); + Collection trbbc = trackedRacesByBoatClass.get(trackedRace.getRace().getBoatClass()); + if (trbbc != null) { + trbbc.remove(trackedRace); + if (trbbc.isEmpty()) { + trbbc.remove(trackedRace.getRace().getBoatClass()); + } + } + for (RaceListener listener : raceListeners) { + listener.raceRemoved(trackedRace); + } + trackedRaces.notifyAll(); + } + } @Override public Event getEvent() { @@ -79,6 +97,11 @@ public class TrackedEventImpl implements TrackedEvent { } return result; } + + @Override + public TrackedRace getExistingTrackedRace(RaceDefinition race) { + return trackedRaces.get(race); + } @Override public void addRaceListener(RaceListener listener) { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegOfCompetitorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegOfCompetitorImpl.java index 6e7492e252b..f986b3cc386 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegOfCompetitorImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegOfCompetitorImpl.java @@ -77,7 +77,7 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor { if (legStart == null) { return Distance.NULL; } else { - MarkPassing legEnd = getTrackedRace().getMarkPassing(getCompetitor(), getLeg().getTo()); + MarkPassing legEnd = getMarkPassingForLegEnd(); TimePoint end = timePoint; if (legEnd != null && timePoint.compareTo(legEnd.getTimePoint()) > 0) { // timePoint is after leg finish; take leg end and end time point @@ -245,7 +245,7 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor { public Speed getAverageVelocityMadeGood(TimePoint timePoint) throws NoWindException { Speed result = null; MarkPassing start = getMarkPassingForLegStart(); - if (start != null && start.getTimePoint().compareTo(timePoint) > 0) { + if (start != null && start.getTimePoint().compareTo(timePoint) <= 0) { MarkPassing end = getMarkPassingForLegEnd(); if (end != null) { TimePoint to; @@ -254,11 +254,13 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor { } else { to = timePoint; } - Position endPos = getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(to, false); - Distance d = getWindwardDistance( - getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(start.getTimePoint(), false), endPos, - to); - result = d.inTime(to.asMillis() - start.getTimePoint().asMillis()); + Position endPos = getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(to, /* extrapolate */ false); + if (endPos != null) { + Distance d = getWindwardDistance( + getTrackedRace().getTrack(getCompetitor()) + .getEstimatedPosition(start.getTimePoint(), false), endPos, to); + result = d.inTime(to.asMillis() - start.getTimePoint().asMillis()); + } } } return result; @@ -359,7 +361,8 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor { } } - private SpeedWithBearing getSpeedOverGround(TimePoint at) { + @Override + public SpeedWithBearing getSpeedOverGround(TimePoint at) { if (hasStartedLeg(at)) { return getTrackedRace().getTrack(getCompetitor()).getEstimatedSpeed(at); } else { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java index 82bfdc9a240..805451641d6 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java @@ -46,6 +46,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener { private TimePoint start; private TimePoint finish; private TimePoint timePointOfNewestEvent; + private TimePoint timePointOfLastEvent; private int updateCount; private final Map> competitorRankings; @@ -352,6 +353,11 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener { public TimePoint getTimePointOfNewestEvent() { return timePointOfNewestEvent; } + + @Override + public TimePoint getTimePointOfLastEvent() { + return timePointOfLastEvent; + } /** * @param timeOfEvent may be null meaning to only unblock waiters but not update any time points @@ -366,6 +372,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener { if (startOfTracking == null || startOfTracking.compareTo(timeOfEvent) > 0) { startOfTracking = timeOfEvent; } + timePointOfLastEvent = timeOfEvent; } notifyAll(); } diff --git a/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/MockedTrackedRace.java b/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/MockedTrackedRace.java index 59df8765486..70290f97256 100755 --- a/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/MockedTrackedRace.java +++ b/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/MockedTrackedRace.java @@ -243,4 +243,10 @@ public class MockedTrackedRace implements DynamicTrackedRace { } + @Override + public TimePoint getTimePointOfLastEvent() { + // TODO Auto-generated method stub + return null; + } + } diff --git a/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/UDPExpeditionReceiverTest.java b/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/UDPExpeditionReceiverTest.java index 47d804f02b1..40ed7ad3b37 100755 --- a/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/UDPExpeditionReceiverTest.java +++ b/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/UDPExpeditionReceiverTest.java @@ -160,7 +160,8 @@ public class UDPExpeditionReceiverTest { assertEquals(19, cal.get(Calendar.DAY_OF_MONTH)); assertEquals(13, cal.get(Calendar.HOUR_OF_DAY)); assertEquals(52, cal.get(Calendar.MINUTE)); - assertEquals(46, cal.get(Calendar.SECOND)); + // for some bizarre reason the conversion doesn't seem to be predictable; a calendar / platform issue? + assertTrue(cal.get(Calendar.SECOND) == 46 || cal.get(Calendar.SECOND) == 45); } @Test diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingWindTracks.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingWindTracks.java index 628001fffad..3dc22159d0e 100755 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingWindTracks.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingWindTracks.java @@ -62,7 +62,7 @@ public class TestStoringAndRetrievingWindTracks extends AbstractTracTracLiveTest public void testStoreAFewWindEntries() throws UnknownHostException, MongoException, InterruptedException { DomainFactory domainFactory = DomainFactory.INSTANCE; Event domainEvent = domainFactory.createEvent(getEvent()); - DynamicTrackedEvent trackedEvent = domainFactory.trackEvent(domainEvent); + DynamicTrackedEvent trackedEvent = domainFactory.getOrCreateTrackedEvent(domainEvent); Iterable typeControllers = domainFactory.getUpdateReceivers(trackedEvent, getEvent(), EmptyWindStore.INSTANCE, ReceiverType.RACECOURSE); addListenersForStoredDataAndStartController(typeControllers); diff --git a/java/com.sap.sailing.mongodb/src/com/sap/sailing/mongodb/impl/MongoWindStoreFactoryImpl.java b/java/com.sap.sailing.mongodb/src/com/sap/sailing/mongodb/impl/MongoWindStoreFactoryImpl.java index e1861b95a69..f32cb928e6a 100755 --- a/java/com.sap.sailing.mongodb/src/com/sap/sailing/mongodb/impl/MongoWindStoreFactoryImpl.java +++ b/java/com.sap.sailing.mongodb/src/com/sap/sailing/mongodb/impl/MongoWindStoreFactoryImpl.java @@ -65,7 +65,7 @@ public class MongoWindStoreFactoryImpl implements MongoWindStoreFactory, BundleA } defaultHostName = context.getProperty(MONGO_HOSTNAME); if (defaultHostName == null) { - defaultHostName = "localhost"; + defaultHostName = "127.0.0.1"; } else { logger.log(Level.INFO, "found mongo.hostname="+defaultHostName); } diff --git a/java/com.sap.sailing.server.test/.classpath b/java/com.sap.sailing.server.test/.classpath new file mode 100755 index 00000000000..8a8f1668cdc --- /dev/null +++ b/java/com.sap.sailing.server.test/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/java/com.sap.sailing.server.test/.gitignore b/java/com.sap.sailing.server.test/.gitignore new file mode 100755 index 00000000000..5e56e040ec0 --- /dev/null +++ b/java/com.sap.sailing.server.test/.gitignore @@ -0,0 +1 @@ +/bin diff --git a/java/com.sap.sailing.server.test/.project b/java/com.sap.sailing.server.test/.project new file mode 100755 index 00000000000..c4447359b3a --- /dev/null +++ b/java/com.sap.sailing.server.test/.project @@ -0,0 +1,28 @@ + + + com.sap.sailing.server.test + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/java/com.sap.sailing.server.test/.settings/org.eclipse.jdt.core.prefs b/java/com.sap.sailing.server.test/.settings/org.eclipse.jdt.core.prefs new file mode 100755 index 00000000000..5072260961d --- /dev/null +++ b/java/com.sap.sailing.server.test/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +#Mon Jun 13 17:15:33 CEST 2011 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/java/com.sap.sailing.server.test/.settings/org.eclipse.pde.core.prefs b/java/com.sap.sailing.server.test/.settings/org.eclipse.pde.core.prefs new file mode 100755 index 00000000000..392d08bb7fd --- /dev/null +++ b/java/com.sap.sailing.server.test/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,4 @@ +#Mon Jun 13 17:15:33 CEST 2011 +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/java/com.sap.sailing.server.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.server.test/META-INF/MANIFEST.MF new file mode 100755 index 00000000000..e0088719c55 --- /dev/null +++ b/java/com.sap.sailing.server.test/META-INF/MANIFEST.MF @@ -0,0 +1,11 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Test +Bundle-SymbolicName: com.sap.sailing.server.test +Bundle-Version: 1.0.0.qualifier +Bundle-Vendor: SAP +Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Require-Bundle: com.sap.sailing.server, + com.sap.sailing.domain, + com.sap.sailing.domain.tractracadapter, + org.junit4 diff --git a/java/com.sap.sailing.server.test/build.properties b/java/com.sap.sailing.server.test/build.properties new file mode 100755 index 00000000000..41eb6ade2b4 --- /dev/null +++ b/java/com.sap.sailing.server.test/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/java/com.sap.sailing.server.test/pom.xml b/java/com.sap.sailing.server.test/pom.xml new file mode 100644 index 00000000000..fc60e5e7695 --- /dev/null +++ b/java/com.sap.sailing.server.test/pom.xml @@ -0,0 +1,14 @@ + + + 4.0.0 + + root + com.sap.sailing + 0.0.1-SNAPSHOT + + com.sap.sailing + com.sap.sailing.server.test + 1.0.0-SNAPSHOT + eclipse-test-plugin + diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/RaceTrackerTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/RaceTrackerTest.java new file mode 100755 index 00000000000..a4ce1056b32 --- /dev/null +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/RaceTrackerTest.java @@ -0,0 +1,111 @@ +package com.sap.sailing.server.test; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; + +import org.junit.Before; +import org.junit.Test; + +import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.tracking.RaceListener; +import com.sap.sailing.domain.tracking.TrackedEvent; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.impl.EmptyWindStore; +import com.sap.sailing.domain.tractracadapter.RaceHandle; +import com.sap.sailing.server.RacingEventServiceImpl; + +public class RaceTrackerTest { + private final URL paramUrl; + private final URI liveUri; + private final URI storedUri; + private RacingEventServiceImpl service; + private RaceHandle raceHandle; + + public RaceTrackerTest() throws MalformedURLException, URISyntaxException { + // for live simulation: + // paramUrl = new URL("http://sapsimulation.tracdev.dk/simulateconf/j80race12.txt"); + // liveUri = new URI("tcp://sapsimulation.tracdev.dk:4420"); // or with tunneling: tcp://localhost:4420 + // storedUri = new URI("tcp://sapsimulation.tracdev.dk:4421"); // or with tunneling: tcp://localhost:4421 + // 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"); + + // 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"); + } + + @Before + public void setUp() throws MalformedURLException, FileNotFoundException, URISyntaxException, InterruptedException { + service = new RacingEventServiceImpl(); + raceHandle = service.addRace(paramUrl, liveUri, storedUri, EmptyWindStore.INSTANCE); + } + + private TrackedRace getTrackedRace(TrackedEvent trackedEvent) throws InterruptedException { + final TrackedRace[] trackedRaces = new TrackedRace[1]; + trackedEvent.addRaceListener(new RaceListener() { + @Override + public void raceAdded(TrackedRace trackedRace) { + synchronized (trackedRaces) { + trackedRaces[0] = trackedRace; + trackedRaces.notifyAll(); + } + } + @Override + public void raceRemoved(TrackedRace trackedRace) { + } + }); + synchronized (trackedRaces) { + if (trackedRaces[0] == null) { + trackedRaces.wait(); + } + } + return trackedRaces[0]; + } + + @Test + public void testInitialization() throws InterruptedException { + RaceDefinition race = raceHandle.getRace(); + assertNotNull(race); + assertNotNull(getTrackedRace(raceHandle.getTrackedEvent())); + } + + @Test + public void testStopTracking() throws MalformedURLException, IOException, InterruptedException, URISyntaxException { + TrackedEvent oldTrackedEvent = raceHandle.getTrackedEvent(); + TrackedRace oldTrackedRace = getTrackedRace(oldTrackedEvent); + service.stopTracking(raceHandle.getEvent()); + RaceHandle myRaceHandle = service.addRace(paramUrl, liveUri, storedUri, EmptyWindStore.INSTANCE); + TrackedEvent newTrackedEvent = myRaceHandle.getTrackedEvent(); + TrackedRace newTrackedRace = getTrackedRace(newTrackedEvent); + // expecting a new tracked race to be created when starting over with tracking + assertNotSame(oldTrackedRace, newTrackedRace); + } + + /** + * This test asserts that tracking the same race twice doesn't create another tracker and in particular no + * new tracked event / tracked race. + */ + @Test + public void testTrackingSameRaceWithoutStopping() throws MalformedURLException, IOException, InterruptedException, URISyntaxException { + TrackedEvent oldTrackedEvent = raceHandle.getTrackedEvent(); + TrackedRace oldTrackedRace = getTrackedRace(oldTrackedEvent); + RaceHandle myRaceHandle = service.addRace(paramUrl, liveUri, storedUri, EmptyWindStore.INSTANCE); + TrackedEvent newTrackedEvent = myRaceHandle.getTrackedEvent(); + TrackedRace newTrackedRace = getTrackedRace(newTrackedEvent); + // expecting a new tracked race to be created when starting over with tracking + assertSame(oldTrackedRace, newTrackedRace); + assertSame(raceHandle.getRaceTracker(), myRaceHandle.getRaceTracker()); + } +} diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, No Tests).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, No Tests).launch index 3aa5be308b0..c27228dbfae 100644 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, No Tests).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, No Tests).launch @@ -19,7 +19,7 @@ - + diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/AdminApp.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/AdminApp.java index e14819df1b9..23ca921d5c3 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/AdminApp.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/AdminApp.java @@ -160,7 +160,7 @@ public class AdminApp extends Servlet { if (race == null) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found"); } else { - TrackedRace trackedRace = getService().getDomainFactory().trackEvent(event).getTrackedRace(race); + TrackedRace trackedRace = getService().getDomainFactory().getTrackedEvent(event).getTrackedRace(race); TimePoint time = getTimePoint(req, PARAM_NAME_TIME, PARAM_NAME_TIME_MILLIS, MillisecondsTimePoint.now()); TimePoint oneHourLater = new MillisecondsTimePoint(time.asMillis()+3600*1000); String[] latitudes = req.getParameterValues(PARAM_NAME_LATDEG); @@ -202,7 +202,7 @@ public class AdminApp extends Servlet { if (race == null) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found"); } else { - TrackedRace trackedRace = getService().getDomainFactory().trackEvent(event).getTrackedRace(race); + TrackedRace trackedRace = getService().getDomainFactory().getTrackedEvent(event).getTrackedRace(race); TimePoint from = getTimePoint(req, PARAM_NAME_FROM_TIME, PARAM_NAME_FROM_TIME_MILLIS, trackedRace.getStart()==null?new MillisecondsTimePoint(0): /* 24h before race start */ new MillisecondsTimePoint(trackedRace.getStart().asMillis()-24*3600*1000)); @@ -257,7 +257,7 @@ public class AdminApp extends Servlet { if (race == null) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found"); } else { - TrackedRace trackedRace = getService().getDomainFactory().trackEvent(event) + TrackedRace trackedRace = getService().getDomainFactory().getTrackedEvent(event) .getTrackedRace(race); trackedRace.setWindSource(windSource); resp.getWriter().println( @@ -313,7 +313,7 @@ public class AdminApp extends Servlet { try { TimePoint timePoint = getTimePoint(req, PARAM_NAME_TIME, PARAM_NAME_TIME_MILLIS, MillisecondsTimePoint.now()); Wind wind = new WindImpl(p, timePoint, speed); - getService().getDomainFactory().trackEvent(event).getTrackedRace(race).recordWind(wind, WindSource.WEB); + getService().getDomainFactory().getTrackedEvent(event).getTrackedRace(race).recordWind(wind, WindSource.WEB); } catch (InvalidDateException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Couldn't parse time specification " + e.getMessage()); } @@ -342,7 +342,7 @@ public class AdminApp extends Servlet { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Wind source name " + sourceName + " unknown"); } else { try { - WindTrack windTrack = getService().getDomainFactory().trackEvent(event) + WindTrack windTrack = getService().getDomainFactory().getTrackedEvent(event) .getTrackedRace(race).getWindTrack(windSource); TimePoint timePoint = getTimePoint(req, PARAM_NAME_TIME, PARAM_NAME_TIME_MILLIS, MillisecondsTimePoint.now()); diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/ModeratorApp.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/ModeratorApp.java index ed57a4539a7..c61bceeb201 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/ModeratorApp.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/ModeratorApp.java @@ -169,7 +169,7 @@ public class ModeratorApp extends Servlet { RaceDefinition race = getRaceDefinition(req); TrackedRace trackedRace = null; if (event != null && race != null) { - trackedRace = getService().getDomainFactory().trackEvent(event).getTrackedRace(race); + trackedRace = getService().getDomainFactory().getTrackedEvent(event).getTrackedRace(race); } return trackedRace; } @@ -183,7 +183,7 @@ public class ModeratorApp extends Servlet { } else { try { TimePoint timePoint = getTimePoint(req, PARAM_NAME_TIME, PARAM_NAME_TIME_MILLIS, - trackedRace.getTimePointOfNewestEvent()==null?MillisecondsTimePoint.now():trackedRace.getTimePointOfNewestEvent()); + trackedRace.getTimePointOfLastEvent()==null?MillisecondsTimePoint.now():trackedRace.getTimePointOfLastEvent()); String sinceUpdateString = req.getParameter(PARAM_NAME_SINCE_UPDATE); if (sinceUpdateString != null) { System.out.println("Blocking..."); @@ -200,6 +200,8 @@ public class ModeratorApp extends Servlet { .asMillis()); jsonRace.put("timeofnewestevent", trackedRace.getTimePointOfNewestEvent() == null ? 0l : trackedRace .getTimePointOfNewestEvent().asMillis()); + jsonRace.put("timeoflastevent", trackedRace.getTimePointOfLastEvent() == null ? 0l : trackedRace + .getTimePointOfLastEvent().asMillis()); jsonRace.put("updatecount", trackedRace.getUpdateCount()); Position positionForWind = null; TrackedLeg currentLeg = trackedRace.getCurrentLeg(timePoint); @@ -234,6 +236,11 @@ public class ModeratorApp extends Servlet { TrackedLegOfCompetitor trackedLegOfCompetitor = leg.getTrackedLeg(competitor); if (trackedLegOfCompetitor != null) { jsonCompetitorInLeg.put("name", competitor.getName()); + Speed currentSpeedOverGround = trackedLegOfCompetitor.getSpeedOverGround(timePoint); + if (currentSpeedOverGround != null) { + jsonCompetitorInLeg.put("currentSpeedOverGroundInKnots", + currentSpeedOverGround == null ? null : currentSpeedOverGround.getKnots()); + } Speed averageSpeedOverGround = trackedLegOfCompetitor.getAverageSpeedOverGround(timePoint); if (averageSpeedOverGround != null) { jsonCompetitorInLeg.put("averageSpeedOverGroundInKnots", @@ -347,7 +354,7 @@ public class ModeratorApp extends Servlet { JSONObject jsonRace = new JSONObject(); jsonRace.put("name", race.getName()); jsonRace.put("boatclass", race.getBoatClass()==null?"":race.getBoatClass().getName()); - TimePoint start = getService().getDomainFactory().trackEvent(event).getTrackedRace(race).getStart(); + TimePoint start = getService().getDomainFactory().getTrackedEvent(event).getTrackedRace(race).getStart(); jsonRace.put("start", start==null?Long.MAX_VALUE:start.asMillis()); JSONArray jsonLegs = new JSONArray(); for (Leg leg : race.getCourse().getLegs()) { @@ -356,7 +363,7 @@ public class ModeratorApp extends Servlet { jsonLeg.put("end", leg.getTo().getName()); jsonLegs.add(jsonLeg); } - TrackedRace trackedRace = getService().getDomainFactory().trackEvent(event).getTrackedRace(race); + TrackedRace trackedRace = getService().getDomainFactory().getOrCreateTrackedEvent(event).getTrackedRace(race); if (trackedRace.getStart() != null) { jsonRace.put("start", trackedRace.getStart().asMillis()); } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java index 97f6184d8fb..421b6c531db 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java @@ -12,11 +12,32 @@ import java.text.ParseException; import com.sap.sailing.declination.DeclinationService; import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.tracking.TrackedEvent; +import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.WindStore; import com.sap.sailing.domain.tractracadapter.DomainFactory; +import com.sap.sailing.domain.tractracadapter.RaceHandle; import com.sap.sailing.domain.tractracadapter.RaceTracker; import com.sap.sailing.util.Util.Triple; +/** + * An OSGi service that can be used to track boat races using a TracTrac connector that pushes + * live GPS boat location, waypoint, coarse and mark passing data.

+ * + * If a race/event is already being tracked, another {@link #addRace(URL, URI, URI, WindStore)} or + * {@link #addEvent(URL, URI, URI, WindStore)} call will have no effect, even if a different + * {@link WindStore} is requested.

+ * + * TODO When the tracking of a race/event is {@link #stopTracking(Event, RaceDefinition) stopped}, the next + * time it's started to be tracked, a new {@link TrackedRace} at least will be constructed. This also + * means that when a {@link TrackedEvent} exists that still holds other {@link TrackedRace}s, the + * no longer tracked {@link TrackedRace} will be removed from the {@link TrackedEvent}. + * corresponding information is removed also from the {@link DomainFactory}'s caches to ensure that + * clean, fresh data is received should another tracking request be issued later. + * + * @author Axel Uhl (d043530) + * + */ public interface RacingEventService { Iterable getAllEvents(); @@ -25,38 +46,46 @@ public interface RacingEventService { DomainFactory getDomainFactory(); /** - * Defines the event and for each race listed in the JSON document creates a {@link RaceTracker} that - * tracks the respective race. The {@link RaceDefinition}s obtained this way are all grouped into - * the single {@link Event} produced for the event listed in the JSON response. Note that the - * many race trackers will have their TracTrac Event each, all with the same name, - * meaning the same event but being distinct. + * Defines the event and for each race listed in the JSON document that is not already being tracked by this service + * creates a {@link RaceTracker} that starts tracking the respective race. The {@link RaceDefinition}s obtained this + * way are all grouped into the single {@link Event} produced for the event listed in the JSON response. Note that + * the many race trackers will have their TracTrac Event each, all with the same name, meaning the same + * event but being distinct. * * @param jsonURL * URL of a JSON response that contains an "event" object telling the event's name and ID, as well as a * JSON array named "races" which tells ID and replay URL for the race. From those replay URLs the * paramURL for the Java client can be derived. - * @param windStore TODO */ - void addEvent(URL jsonURL, URI liveURI, URI storedURI, WindStore windStore) throws MalformedURLException, FileNotFoundException, + Event addEvent(URL jsonURL, URI liveURI, URI storedURI, WindStore windStore) throws MalformedURLException, FileNotFoundException, URISyntaxException, IOException, ParseException, org.json.simple.parser.ParseException; /** - * Adds a single race tracker, using the race's parameter URL which delivers the single configuration - * text file for that race. While the result of passing this URL to the TracTrac KeyValue.setup - * is a TracTrac Event, those events only manage a single race. In our domain model, we group - * those races into a single instance of our {@link Event} class.

+ * If not already tracking the URL/URI/URI combination, adds a single race tracker and starts tracking the race, + * using the race's parameter URL which delivers the single configuration text file for that race. While the result + * of passing this URL to the TracTrac KeyValue.setup is a TracTrac Event, those events + * only manage a single race. In our domain model, we group those races into a single instance of our {@link Event} + * class. + *

* * If this is the first race of an event, the {@link Event} is created as well. If the {@link RaceDefinition} for * the race already exists, it isn't created again. Also, if a {@link RaceTracker} for the given race already - * exists, it is not added again. - * @param windStore TODO + * exists, it is not added again.

+ * + * Note that when the race identified by paramURL, liveURI and storedURI is + * already being tracked, then regardless of the windStore selection the existing tracker will be used + * and its race handle will be returned. A log message will indicate a potential wind store mismatch (based on + * {@link WindStore#equals(Object)}). */ - void addRace(URL paramURL, URI liveURI, URI storedURI, WindStore windStore) throws MalformedURLException, FileNotFoundException, + RaceHandle addRace(URL paramURL, URI liveURI, URI storedURI, WindStore windStore) throws MalformedURLException, FileNotFoundException, URISyntaxException; /** * Stops tracking all races of the event specified. This will also stop tracking wind for all races of this event. - * See {@link #stopTrackingWind(Event, RaceDefinition)}. + * See {@link #stopTrackingWind(Event, RaceDefinition)}. If there were multiple calls to + * {@link #addRace(URL, URI, URI, WindStore)} with an equal combination of URLs/URIs, the {@link RaceTracker} + * already tracking the race was re-used. The trackers will be stopped by this call regardless of how many calls + * were made that ensured they were tracking. */ void stopTracking(Event event) throws MalformedURLException, IOException, InterruptedException; diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventServiceImpl.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventServiceImpl.java index b6b9a9aedad..b5aa646282c 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventServiceImpl.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventServiceImpl.java @@ -16,6 +16,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.logging.Logger; import com.sap.sailing.declination.DeclinationService; import com.sap.sailing.domain.base.Event; @@ -25,6 +26,7 @@ import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.WindStore; import com.sap.sailing.domain.tractracadapter.DomainFactory; import com.sap.sailing.domain.tractracadapter.JSONService; +import com.sap.sailing.domain.tractracadapter.RaceHandle; import com.sap.sailing.domain.tractracadapter.RaceRecord; import com.sap.sailing.domain.tractracadapter.RaceTracker; import com.sap.sailing.expeditionconnector.UDPExpeditionReceiver; @@ -33,11 +35,13 @@ import com.sap.sailing.util.Util.Pair; import com.sap.sailing.util.Util.Triple; public class RacingEventServiceImpl implements RacingEventService { + private static final Logger logger = Logger.getLogger(RacingEventServiceImpl.class.getName()); + private final DomainFactory domainFactory; private final Map eventsByName; - private final Map> raceTrackers; + private final Map> raceTrackersByEvent; /** * Remembers the wind tracker and the port on which the UDP receiver with which the wind tracker is @@ -45,13 +49,19 @@ public class RacingEventServiceImpl implements RacingEventService { */ private final Map> windTrackers; + /** + * Remembers the trackers by paramURL/liveURI/storedURI to avoid duplication + */ + private final Map, RaceTracker> raceTrackersByURLs; + private final Map windReceivers; public RacingEventServiceImpl() { domainFactory = DomainFactory.INSTANCE; eventsByName = new HashMap(); - raceTrackers = new HashMap>(); + raceTrackersByEvent = new HashMap>(); windTrackers = new HashMap>(); + raceTrackersByURLs = new HashMap, RaceTracker>(); windReceivers = new HashMap(); } @@ -71,42 +81,57 @@ public class RacingEventServiceImpl implements RacingEventService { } @Override - public void addEvent(URL jsonURL, URI liveURI, URI storedURI, WindStore windStore) throws URISyntaxException, IOException, ParseException, org.json.simple.parser.ParseException { + public Event addEvent(URL jsonURL, URI liveURI, URI storedURI, WindStore windStore) throws URISyntaxException, IOException, ParseException, org.json.simple.parser.ParseException { JSONService jsonService = getDomainFactory().parseJSONURL(jsonURL); + Event event = null; for (RaceRecord rr : jsonService.getRaceRecords()) { URL paramURL = rr.getParamURL(); - addRace(paramURL, liveURI, storedURI, windStore); + event = addRace(paramURL, liveURI, storedURI, windStore).getEvent(); } + return event; } @Override - public void addRace(URL paramURL, URI liveURI, URI storedURI, WindStore windStore) throws MalformedURLException, FileNotFoundException, + public RaceHandle addRace(URL paramURL, URI liveURI, URI storedURI, WindStore windStore) throws MalformedURLException, FileNotFoundException, URISyntaxException { - RaceTracker tracker = getDomainFactory().createRaceTracker(paramURL, liveURI, storedURI, windStore); - Set trackers = raceTrackers.get(tracker.getEvent()); - if (trackers == null) { - trackers = new HashSet(); - raceTrackers.put(tracker.getEvent(), trackers); - } - trackers.add(tracker); - String eventName = tracker.getEvent().getName(); - Event eventWithName = eventsByName.get(eventName); - if (eventWithName != null) { - if (eventWithName != tracker.getEvent()) { - throw new RuntimeException("Internal error. Two Event objects with equal name "+eventName); + Triple key = new Triple(paramURL, liveURI, storedURI); + RaceTracker tracker = raceTrackersByURLs.get(key); + if (tracker == null) { + tracker = getDomainFactory().createRaceTracker(paramURL, liveURI, storedURI, windStore); + raceTrackersByURLs.put(key, tracker); + Set trackers = raceTrackersByEvent.get(tracker.getEvent()); + if (trackers == null) { + trackers = new HashSet(); + raceTrackersByEvent.put(tracker.getEvent(), trackers); + } + trackers.add(tracker); + String eventName = tracker.getEvent().getName(); + Event eventWithName = eventsByName.get(eventName); + if (eventWithName != null) { + if (eventWithName != tracker.getEvent()) { + throw new RuntimeException("Internal error. Two Event objects with equal name "+eventName); + } + } else { + eventsByName.put(eventName, tracker.getEvent()); } } else { - eventsByName.put(eventName, tracker.getEvent()); + WindStore existingTrackersWindStore = tracker.getWindStore(); + if (!existingTrackersWindStore.equals(windStore)) { + logger.warning("Wind store mismatch. Requested wind store: "+windStore+ + ". Wind store in use by existing tracker: "+existingTrackersWindStore); + } } + return tracker.getRaceHandle(); } @Override public void stopTracking(Event event) throws MalformedURLException, IOException, InterruptedException { - if (raceTrackers.containsKey(event)) { - for (RaceTracker raceTracker : raceTrackers.get(event)) { - raceTracker.stop(); + if (raceTrackersByEvent.containsKey(event)) { + for (RaceTracker raceTracker : raceTrackersByEvent.get(event)) { + raceTracker.stop(); // this also removes the TrackedRace from trackedEvent + raceTrackersByURLs.remove(raceTracker.getURLs()); } - raceTrackers.remove(event); + raceTrackersByEvent.remove(event); } if (event != null && event.getName() != null) { eventsByName.remove(event.getName()); @@ -118,18 +143,19 @@ public class RacingEventServiceImpl implements RacingEventService { @Override public void stopTracking(Event event, RaceDefinition race) throws MalformedURLException, IOException, InterruptedException { - if (raceTrackers.containsKey(event)) { - Iterator trackerIter = raceTrackers.get(event).iterator(); + if (raceTrackersByEvent.containsKey(event)) { + Iterator trackerIter = raceTrackersByEvent.get(event).iterator(); while (trackerIter.hasNext()) { RaceTracker raceTracker = trackerIter.next(); if (raceTracker.getRace() == race) { - raceTracker.stop(); + raceTracker.stop(); // this also removes the TrackedRace from trackedEvent trackerIter.remove(); + raceTrackersByURLs.remove(raceTracker.getURLs()); } } } // if the last tracked race was removed, remove the entire event - if (raceTrackers.get(event).isEmpty()) { + if (raceTrackersByEvent.get(event).isEmpty()) { stopTracking(event); } } @@ -138,7 +164,7 @@ public class RacingEventServiceImpl implements RacingEventService { public synchronized void startTrackingWind(Event event, RaceDefinition race, int port, DeclinationService declinationService) throws SocketException { if (!windTrackers.containsKey(race)) { - DynamicTrackedEvent trackedEvent = getDomainFactory().trackEvent(event); + DynamicTrackedEvent trackedEvent = getDomainFactory().getOrCreateTrackedEvent(event); DynamicTrackedRace trackedRace = trackedEvent.getTrackedRace(race); WindTracker windTracker = new WindTracker(trackedRace, declinationService); UDPExpeditionReceiver receiver = getOrCreateWindReceiverForPort(port); diff --git a/python/src/sailing.connector/sailing/connector/provider.py b/python/src/sailing.connector/sailing/connector/provider.py index 51cfeb8fa8d..aefd2cca228 100644 --- a/python/src/sailing.connector/sailing/connector/provider.py +++ b/python/src/sailing.connector/sailing/connector/provider.py @@ -321,7 +321,7 @@ def liveRaceInformation(configurator): # check for cases where calculations from backend yield # strange numbers - this happens in cases where the competitor # has no speed during mark passing - if k in ['Infinity', 'Infinite']: + if k in ['Infinity', 'Infinite'] or float(k) > 150000.0: # use a magic number (that is very unlikely to occur) # to indicate that competitor has no speed diff --git a/python/src/sailing.db/sailing/db/model/entities.py b/python/src/sailing.db/sailing/db/model/entities.py index d2dc2a9ecb5..5a131414277 100644 --- a/python/src/sailing.db/sailing/db/model/entities.py +++ b/python/src/sailing.db/sailing/db/model/entities.py @@ -67,7 +67,7 @@ class Competitor(BaseDocument): 'leg_indexes': {}, # holds association between leg position and unique id : - 'total': float, + 'total': int, # total rank 'current_rank': int, # current rank for given time 'races' : [float, ], 'marks' : [[float, ], ], # ranks for marks diff --git a/python/src/sailing.web/sailing/web/browser/functions.py b/python/src/sailing.web/sailing/web/browser/functions.py index 580c922a719..5201863191a 100644 --- a/python/src/sailing.web/sailing/web/browser/functions.py +++ b/python/src/sailing.web/sailing/web/browser/functions.py @@ -501,7 +501,7 @@ def adminLiveData(context, request): results += '\nLAST SHOWRACE CALL: %s (UPCOUNT PARAM: %s)\nLEG: %s (FROM: %s TO: %s)\nRACE-START: %s NEWEST EVENT: %s WIND: (%s %s %s)\n' % (t_up, t_upcount, legpos+1, competitors[0].marknames[racepos][legpos][0], competitors[0].marknames[racepos][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 += '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 += '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) + 'STARTD'.ljust(9) + 'FINISHD'.ljust(9) + 'UPDOWNWIND'.ljust(12) results += '\n' # sort competitors by rank in current leg @@ -533,3 +533,69 @@ def adminLiveData(context, request): return results +@jsonize +def moderatorLiveData(context, request): + """ Returns data for the moderators leaderboard """ + + view = core.BaseView(context, request) + event = view.currentLeaderboardEvent() + + sortby = request.params.get('sortby', 'name') + race_range = request.params.get('races', '1:3') + competitor_range = request.params.get('competitors', '1:20') + direction = request.params.get('direction', 'asc'); + + if race_range in ['null', 'undefined']: + race_range = '1:3' + + race_start_index, race_end_index = race_range.split(':') + races_list = event.races[int(race_start_index)-1:int(race_end_index)] + + competitors = view.competitorsSortedBy(event.name, sortby.strip()) + + if direction == 'asc': + competitors.reverse() + + races = [] + for racename in races_list: + races.append(model.RaceImpl.queryOneBy(name=racename, event=event.name)) + + # list of competitors with corresponding data + data = [] + + for competitor in competitors: + racedata = competitor.races[int(race_start_index)-1:int(race_end_index)] + + # for each race found compute the marks and values + markranks = []; legvalues = []; racecounter = 0 + for racename in races_list: + real_racepos = event.races.index(racename) + if len(markranks) < racecounter+1: + markranks.append([]) + legvalues.append([]) + + markranks[racecounter] = markranks[racecounter] + competitor.marks[real_racepos] + + iv = competitor.values[real_racepos] + + newvalues = iv + for valpos in range(len(iv)): + nv = ['%.2f' % (val < 15000 and val or 0.0) for val in newvalues[valpos]] + + # prepare values according to UI + # UI: RANK, DSTTRAV, SPEED, VMG, SGAP, DSTGO, ETA + # INTERN: RANK, AVGSPEED, DSTRV, VMG, AVMG, SGAP, ETA, DSTGO + if nv: + nv = [nv[0], nv[2], nv[1], nv[3], nv[5], nv[-1], nv[-2]] + newvalues[valpos] = nv + + legvalues[racecounter] = legvalues[racecounter] + newvalues + + racecounter += 1 + + data.append({'name': competitor.name, 'raceranks': racedata, 'markranks': markranks, 'legvalues': legvalues, + 'nationality': competitor.nationality, 'global_rank': competitor.total}) + + c_range_start, c_range_end = competitor_range.split(':') + return data[int(c_range_start)-1:int(c_range_end)] + diff --git a/python/src/sailing.web/sailing/web/browser/moderator-static/app.js b/python/src/sailing.web/sailing/web/browser/moderator-static/app.js index 5ec810a351a..7009ecd5355 100644 --- a/python/src/sailing.web/sailing/web/browser/moderator-static/app.js +++ b/python/src/sailing.web/sailing/web/browser/moderator-static/app.js @@ -1,6 +1,154 @@ + +var global_sortkey = 'name'; +var global_direction = 'desc'; +var global_race = '1:3'; +var global_competitors = '1:20'; + +var loader_image = ""; + +var listener_paused = false; + +/* + * Loads leaderboard data. Parameters: + * + * races: Slice that indicates which data to load (e.g. "1:3") + * sortby: Sorting parameters (e.g "1,1,1") + * competitors: Slice indicating which competitors to show (e.g. "1:20") + */ +function loadLeaderboard(races, sortby, competitors, direction) { + showLoader(); + $.getJSON('/++/moderatorLiveData', + {races:races, sortby:sortby, competitors:competitors, direction:direction}, + function(data) { + displayLeaderboard(data); + + global_race = races; + global_sortkey = sortby; + global_competitors = competitors; + global_direction = direction; + + hideLoader(); + } + ); +} + +function liveRefresh() { + if (listener_paused == false) + loadLeaderboard(global_race, global_sortkey, global_competitors, global_direction); +} + +function toggleListener() { + $('#refresh-button').toggleClass('refresh'); + if ($('#refresh-button').hasClass('refresh')) { + $('#refresh-button').css('background-image', 'url(/moderator-static/refresh-icon.png)'); + listener_paused = true; + } else { + listener_paused = false; + $('#refresh-button').css('background-image', 'url(/moderator-static/pause_button.png)'); + } +} + +function showLoader() { + $('.refresh-btn').html(loader_image); +} + +function hideLoader() { + $('.refresh-btn').html(''); +} +function sortBy(param, element) { + element.toggleClass('sort-asc').toggleClass('sort-desc'); + + if (element.hasClass('sort-asc')) + global_direction = 'asc'; + else global_direction = 'desc'; + + global_sortkey = param; + loadLeaderboard(global_race, param, global_competitors, global_direction); +} + +function yieldValue(element, newvalue) { + element.html(newvalue); +} + +/** + * Puts data into the right context for the leaderboard + */ +function displayLeaderboard(data) { + rowid = 1; + for (cpos in data) { + competitor = data[cpos]; + + /* always change global rank */ + $('#clipping-'+rowid+'-1 span').html(competitor.global_rank); + + name_element = $('#clipping-'+rowid+'-3 span'); + if (name_element.html() != competitor.name) { + /* competitor position has changed - refresh whole line */ + + name_element.html(competitor.name); + $('#clipping-'+rowid+'-2 span').html(competitor.nationality); + + /* now set values independent what has been there before */ + racepos = 1; + for (racerank in competitor.raceranks) { + $('#race-'+racepos+'-rankrow-'+rowid).html(competitor.raceranks[racerank]); + + markpos = 1; + for (markrank in competitor.markranks[racepos-1]) { + $('#race-'+racepos+'-mark-'+markpos+'-row-'+rowid).html(competitor.markranks[racepos-1][markrank]); + + legpos = 1; + for (legvalue in competitor.legvalues[racepos-1][markpos-1]) { + if (legvalue == 0) { + $('#race-'+racepos+'-mark-'+markpos+'-legrow-'+rowid).html(parseFloat(competitor.legvalues[racepos-1][markpos-1][legvalue]).toFixed()); + } else { + $('#race-'+racepos+'-mark-'+markpos+'-valrow-'+rowid+'-pos-'+(legpos-1)+' span').html(competitor.legvalues[racepos-1][markpos-1][legvalue]); + } + legpos += 1; + } + + markpos += 1; + } + } + } else { + /* check if values have changed */ + name_element.html(competitor.name); + $('#clipping-'+rowid+'-2 span').html(competitor.nationality); + + for (racerank in competitor.raceranks) { + yieldValue($('#race-'+racepos+'-rankrow-'+rowid), competitor.raceranks[racerank]); + + markpos = 1; + for (markrank in competitor.markranks[racepos-1]) { + yieldValue($('#race-'+racepos+'-mark-'+markpos+'-row-'+rowid), competitor.markranks[racepos-1][markrank]); + + legpos = 1; + for (legvalue in competitor.legvalues[racepos-1][markpos-1]) { + if (legvalue == 0) { + yieldValue($('#race-'+racepos+'-mark-'+markpos+'-legrow-'+rowid), parseFloat(competitor.legvalues[racepos-1][markpos-1][legvalue]).toFixed()); + } else { + yieldValue($('#race-'+racepos+'-mark-'+markpos+'-valrow-'+rowid+'-pos-'+(legpos-1)+' span'), competitor.legvalues[racepos-1][markpos-1][legvalue]); + } + legpos += 1; + } + + markpos += 1; + } + } + } + + rowid += 1; + } +} + $(document).ready(function() { var wHeight = $(window).height(); $("#rootwrapper").css("height", wHeight); $("#appinterface").css("height", wHeight-131); + + $.ajaxSetup({cache:false}); + + loadLeaderboard(global_race, global_sortkey, global_competitors, global_direction); + window.setInterval('liveRefresh()', 5000); }); diff --git a/python/src/sailing.web/sailing/web/browser/moderator-static/pause_button.png b/python/src/sailing.web/sailing/web/browser/moderator-static/pause_button.png new file mode 100644 index 00000000000..9b81898237a Binary files /dev/null and b/python/src/sailing.web/sailing/web/browser/moderator-static/pause_button.png differ diff --git a/python/src/sailing.web/sailing/web/browser/moderator-static/screen.css b/python/src/sailing.web/sailing/web/browser/moderator-static/screen.css index 729ce179ae1..673b4e7ae89 100644 --- a/python/src/sailing.web/sailing/web/browser/moderator-static/screen.css +++ b/python/src/sailing.web/sailing/web/browser/moderator-static/screen.css @@ -122,7 +122,7 @@ body { padding: 10px 0 10px 30px; border-top: 1px solid #e1e1e1; } #header ul.systeminfo li.active { - background: url(/moderator-static/refresh-icon.png) no-repeat left center; } + background: url(/moderator-static/pause_button.png) no-repeat left center; } #header ul.systeminfo li.message-0 { border: none; } #header ul.systeminfo:after { @@ -221,7 +221,7 @@ span.race-count { #main-interface .blue-table .refresh-btn { display: block; height: 39px; - background: url(/moderator-static/refresh-icon.png) right center no-repeat #484848; } + background: url(/moderator-static/pause_button.png) right center no-repeat #484848; } #main-interface .blue-table .sort-rank { display: block; height: 39px; } diff --git a/python/src/sailing.web/sailing/web/browser/static/images/ajax-loader-blackbg.gif b/python/src/sailing.web/sailing/web/browser/static/images/ajax-loader-blackbg.gif new file mode 100644 index 00000000000..f9d0919dc82 Binary files /dev/null and b/python/src/sailing.web/sailing/web/browser/static/images/ajax-loader-blackbg.gif differ diff --git a/python/src/sailing.web/sailing/web/browser/static/images/ajax-loader-greybg.gif b/python/src/sailing.web/sailing/web/browser/static/images/ajax-loader-greybg.gif new file mode 100644 index 00000000000..6d798ab7f54 Binary files /dev/null and b/python/src/sailing.web/sailing/web/browser/static/images/ajax-loader-greybg.gif differ diff --git a/python/src/sailing.web/sailing/web/browser/templates/leaderboard-live.pt b/python/src/sailing.web/sailing/web/browser/templates/leaderboard-live.pt index a7b196c15e3..1a47b3a4987 100644 --- a/python/src/sailing.web/sailing/web/browser/templates/leaderboard-live.pt +++ b/python/src/sailing.web/sailing/web/browser/templates/leaderboard-live.pt @@ -35,230 +35,23 @@

- - + + - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
- 1 + +
+ - GER10 + + - EVANS -
- 2 - - GBR12 - - ALONSO -
- 3 - - AUS15 - - ALONSO -
- 4 - - AUS1 - - BROTHERTHOl -
- 5 - - IND66 - - ASQUITH -
- 6 - - SUI 89 - - CZAJKOWSKI -
- 7 - - NZL878 - - NIELSEN -
- 8 - - USA23 - - NIELSEN -
- 9 - - GBR25 - - PIASECKI -
- 10 - - AUS34 - - FONSECA -
- 11 - - GER34 - - GRAEL -
- 12 - - GER2 - - NORREGAARD -
- 13 - - GBR45 - - LANG -
- 14 - - AUS26 - - KOSTOV -
- 15 - - SWE87 - - CUPAC -
- 16 - - NOR3 - - STORCK -
- 17 - - GBR9 - - MOORE -
- 18 - - SUI28 - - DELLE -
- 19 - - DEN234 - - RESCH -
- 20 - - GER666 - - DRAPER + +
    @@ -270,1404 +63,118 @@
+ -
+ +
-
+ + +
-
+ - +
+ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + +
- - - - - - - - -
- - - - - +
- +