Not so atomic commit:

- Fixed map display of wind direction
- Showing ANCHOR in top when competitor is anchoring
- Improved logging (included timestamp, ...)
- Improved design and handling of listener threads
This commit is contained in:
Simon Pamies committed 2011-06-12 21:26:07 +02:00
1 parent f45ffa4d00
commit 4c382b21e8
7 files changed
+111 -76

No files matched your search

@@ -1,5 +1,5 @@
import cjson
import cjson, datetime
import urllib, urllib2
from sailing.web.util import get_abspath
@@ -67,7 +67,7 @@ class URIConfigurator(object):
def trigger(self, request=None):
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()
def jsonByUrl(configurator):
@@ -80,7 +80,7 @@ def jsonByUrl(configurator):
handle.close()
else:
data = configurator.trigger().read()
data = configurator.trigger()
if data.find('Exception')>0:
raise Exception, 'Java triggered exception: %s' % data[:550]
@@ -53,7 +53,7 @@ class LiveDataReceiver(threading.Thread):
# blocking call by updatecount setting
conf.setParameters(dict(eventname=self.eventname, racename=self.racename, sinceupdate=self.updatecount))
conf.setLogging(False)
conf.setLogging(True)
try:
# 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, ...)
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
time.sleep(2)
@@ -85,6 +91,9 @@ def eventConfiguration(configurator):
events = jsonByUrl(configurator)
for event in events:
if not event.has_key('boatclass'):
continue
dbevent = model.EventImpl.queryOneBy(name=event['name'])
if not dbevent:
dbevent = model.EventImpl()
@@ -123,7 +132,9 @@ def eventConfiguration(configurator):
# including all legs where something ends into model
# in this case we get the last mark that equals the
# 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)
@@ -133,7 +144,7 @@ def eventConfiguration(configurator):
for cp in cobjects:
races = [0.0 for r in range(raceindex)]
marks = cp.marks; values = cp.values
marks = cp.marks; values = cp.values; upordown = cp.upordownwind
# update races and marks - support add operation
# removal not really supported - needs complete refresh
@@ -144,6 +155,9 @@ def eventConfiguration(configurator):
if len(marks) < r+1:
marks.append([])
if len(upordown) < r+1:
upordown.append([])
if len(values) < r+1:
values.append([])
@@ -151,17 +165,20 @@ def eventConfiguration(configurator):
if len(marks[r]) < m+1:
marks[r].append(0.0)
if len(upordown[r]) < m+1:
upordown[r].append(False)
if len(values[r]) < m+1:
values[r].append([])
# 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))
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))
cp.update(dict(races=races, marks=marks))
cp.update(dict(races=races, marks=marks, upordownwind=upordown))
# finally update event
dbevent.update(dict(name=event['name'],
@@ -188,17 +205,19 @@ def liveRaceInformation(configurator):
# it is important to know which position the race has
# so we look it up in the Event itself
event = model.EventImpl.queryOneBy(name=eventname)
raceindex = event.races.index(racename)
# check that legs still match the expected value
legcount = len(data['legs'])
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:
# length of saved legs and incoming legs does not match
# 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.setCommand(config.LIST_EVENTS)
eventConfiguration(configurator)
@@ -207,10 +226,10 @@ def liveRaceInformation(configurator):
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))
raceindex = event.races.index(racename)
lcount = 0; current_legs = {}
for leg in data['legs']:
leg_id = '%s,%s' % (leg['fromwaypointid'], leg['towaypointid'])
for competitor in leg['competitors']:
comp = model.CompetitorImpl.queryOneBy(name=competitor['name'], event=eventname)
if not comp:
@@ -65,10 +65,13 @@ class Competitor(BaseDocument):
'event': unicode,
'nationality': unicode,
'leg_indexes': {}, # holds association between leg position and unique id <position in marks>:<unique id>
'total': float,
'current_rank': int, # current rank for given time
'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
'values' : [[[float, ]]]
}
@@ -40,6 +40,10 @@ def startListenerThreads(conf, eventlist):
with lock:
for event in eventlist:
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:
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)
eventlist = provider.eventConfiguration(conf)
if model.EventImpl.queryCount() == 0:
return view.yieldMessage('Seems that there are no events that can be shown! Listener not started?')
if eventlist == [] or eventlist[0].get('races', []) == []:
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)
view.session['listener-started'] = True
@@ -463,7 +467,7 @@ def adminLiveData(context, request):
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 += '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)
results += '\n'
# sort competitors by rank in current leg
@@ -474,7 +478,10 @@ def adminLiveData(context, request):
results += '%s %s' % (str(c.races[racepos]).ljust(7), str(c.marks[racepos][legpos]).ljust(7))
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)
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">
<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">
<span>URL to event configuration</span><br/>
<input class="large" type="text" name="eventJSONURL" value=""/>
<fieldset>
<legend>1. Listener Configuration</legend>
<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())">
<option value=""></option>
<option value="http://germanmaster.traclive.dk/events/event_20110505_SailingTea/jsonservice.php">Weymouth</option>
</select>
<pre style="background-color: #ececec; font-size: 12px;padding: 2px;">
<tal:for repeat="eventurl view.eventUrlHistory()"> ${eventurl}</tal:for></pre>
</div>
<fieldset>
<legend>2. Event Configuration</legend>
<div class="field">
<span>URL to event configuration</span><br/>
<input class="large" type="text" name="eventJSONURL" value=""/>
<div class="field">
<span>URL to race configuration</span><br/>
<input class="large" type="text" name="paramURL" value=""/>
<select name="paramURLselect" onchange="$('input[name=paramURL]').val($('select[name=paramURLselect] option:selected').val())">
<option value=""></option>
<option value="http://sapsimulation.tracdev.dk/simulateconf/j80race12.txt">J80 Race 12</option>
</select>
<pre style="background-color: #ececec; font-size: 12px;padding: 2px;">
<tal:for repeat="eventurl view.raceUrlHistory()"> ${eventurl}</tal:for></pre>
</div>
<select name="eventJSONURLselect" onchange="$('input[name=eventJSONURL]').val($('select[name=eventJSONURLselect] option:selected').val())">
<option value=""></option>
<option value="http://germanmaster.traclive.dk/events/event_20110505_SailingTea/jsonservice.php">Weymouth</option>
</select>
<pre style="background-color: #ececec; font-size: 12px;padding: 2px;">
<tal:for repeat="eventurl view.eventUrlHistory()"> ${eventurl}</tal:for></pre>
</div>
<div class="field">
<span>Live-Connection (host:port):</span><br/>
<input class="large" type="text" name="liveURI" value="" />
<select name="liveURIselect" onchange="$('input[name=liveURI]').val($('select[name=liveURIselect] option:selected').val())">
<option value=""></option>
<option value="tcp://sapsimulation.tracdev.dk:4420">J80 Race 12</option>
<option value="tcp://germanmaster.traclive.dk:4400">Weymouth</option>
</select>
</div>
<div class="field">
<span>URL to race configuration</span><br/>
<input class="large" type="text" name="paramURL" value=""/>
<select name="paramURLselect" onchange="$('input[name=paramURL]').val($('select[name=paramURLselect] option:selected').val())">
<option value=""></option>
<option value="http://sapsimulation.tracdev.dk/simulateconf/j80race12.txt">J80 Race 12</option>
</select>
<pre style="background-color: #ececec; font-size: 12px;padding: 2px;">
<tal:for repeat="eventurl view.raceUrlHistory()"> ${eventurl}</tal:for></pre>
</div>
<div class="field">
<span>Data-Connection (host:port):</span><br/>
<input class="large" type="text" name="storedURI" value="" />
<select name="storedURIselect" onchange="$('input[name=storedURI]').val($('select[name=storedURIselect] option:selected').val())">
<option value=""></option>
<option value="tcp://sapsimulation.tracdev.dk:4421">J80 Race 12</option>
<option value="tcp://germanmaster.traclive.dk:4401">Weymouth</option>
</select>
</div>
<div class="field">
<span>Live-Connection (host:port):</span><br/>
<input class="large" type="text" name="liveURI" value="" />
<select name="liveURIselect" onchange="$('input[name=liveURI]').val($('select[name=liveURIselect] option:selected').val())">
<option value=""></option>
<option value="tcp://sapsimulation.tracdev.dk:4420">J80 Race 12</option>
<option value="tcp://germanmaster.traclive.dk:4400">Weymouth</option>
</select>
</div>
<div class="field">
<span>Persistent store where to persist wind tracks</span><br/>
<select name="windstore">
<option value="empty">Do not persist wind</option>
<option value="mongo">MongoDB Persister</option>
</select>
</div>
<div class="field">
<span>Data-Connection (host:port):</span><br/>
<input class="large" type="text" name="storedURI" value="" />
<select name="storedURIselect" onchange="$('input[name=storedURI]').val($('select[name=storedURIselect] option:selected').val())">
<option value=""></option>
<option value="tcp://sapsimulation.tracdev.dk:4421">J80 Race 12</option>
<option value="tcp://germanmaster.traclive.dk:4401">Weymouth</option>
</select>
</div>
<div class="field">
<input type="checkbox" name="drop_db" />&nbsp;
<span>Drop Database before connect</span><br/>
</div>
<div class="field">
<span>Store where to persist wind tracks</span><br/>
<select name="windstore">
<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 condition="view.listenerStarted() or request.params.get('debug')">
@@ -66,6 +66,7 @@
// also show wind directions
GET('determineWindDirection', {at : at}, function(data) {
map.clearPaths();
if (typeof data == 'string') {
alert('Error: ' + data);
return;
@@ -20,7 +20,6 @@
<div style="text-align: right;margin: 13px 1px 13px 1px;">
<form method="POST" action="/++/configureListener">
<tal:if condition="view.listenerStarted()">
<div id="listeners-container">
<select id="listeners">
<option>Loading....</option>
@@ -36,7 +35,6 @@
<input type="button" value="Stop" onclick="GET('stopListener', {tid: $('#listeners option:selected').val()}, loadListeners)"/>
&nbsp;&nbsp;
</div>
</tal:if>
</form>
</div>