diff --git a/server/app.py b/server/app.py index 88ee923a826c08c676751a43a1604e3713c2705b..84f691c773c28b5c497bf86ffafe159298622575 100644 --- a/server/app.py +++ b/server/app.py @@ -70,7 +70,8 @@ def getViewData(request, stopId, view): return { 'arrivals': gtfs.getNextTrips(stopId, limit = 2, routeType = GtfsConstants.ROUTE_HEAD), 'gmaps_key': GOOGLE_MAPS_KEY if not app.config['DEBUG'] else GOOGLE_MAPS_TEST_KEY, - 'gmaps_region': GOOGLE_MAPS_REGION + 'gmaps_region': GOOGLE_MAPS_REGION, + 'service_status': { 'active': not locator.isStale() } } return {} @@ -307,6 +308,8 @@ def apiLocatorService(version, type = 'routed'): lastModified = data = None if g.stop_id == 'raw' or not g.stop_id: lastModified, data = locator.getLastResponse() + elif g.stop_id == 'status': + return jsonify({ 'active': not locator.isStale() }) elif type == 'routed': lastModified, data = gtfs.locateRoutedVehicles(g.stop_id) elif type == 'nearby': @@ -346,7 +349,10 @@ def apiBusInfo(version, busId = None, getShape = None): data["shapes"] = gtfs.getTripShapes(data.get('lineref', None), data.get('blockref', None)) if data: - return jsonify(data) + response = jsonify(data) + response.last_modified = lastModified + response.expires = lastModified + timedelta(seconds = 2) + return response.make_conditional(request) abort(404) diff --git a/server/locator.py b/server/locator.py index e69b31513b47d643298026008c45abe89f7c11eb..b3caf92f299395e9c044fba34b3f8325478d4d57 100644 --- a/server/locator.py +++ b/server/locator.py @@ -1,7 +1,9 @@ # coding=utf-8 import requests, threading, time, json, six, math + from datetime import datetime +from requests.exceptions import ConnectionError, HTTPError, Timeout, RequestException # This really should be written using multiprocessing instead, # because of the GIL, but setting up full 2-way communication @@ -18,6 +20,7 @@ class PollingThreadReader(object): _running = False _response = None _modified = datetime.utcfromtimestamp(0) + _stale = False def __init__(self, endpointURI, userAgent, pollFreq = 3): self.endpoint = endpointURI @@ -52,16 +55,33 @@ class PollingThreadReader(object): def run(self): while self._running: - response = requests.get(self.endpoint, headers = self.requestHeaders).content + apiResponse = None + + # FIXME: better exception handling + try: + apiResponse = requests.get(self.endpoint, headers = self.requestHeaders).content + except (ConnectionError, HTTPError, Timeout): + self._stale = True + time.sleep(self.pollTime) + continue + except RequestException: + self._stale = True + continue + with self._lock: try: - self._response = json.loads(response) + self._response = json.loads(apiResponse) self._modified = datetime.utcnow() + self._stale = False except ValueError: pass time.sleep(self.pollTime) + def isStale(self): + # not thread safe, only usable as a suggestion that the response may be stale + return self._stale + def getLastResponse(self): with self._lock: return (self._modified, self._response) diff --git a/templates/map.html b/templates/map.html index c7c2e9383daf1d011c7ab5cc25035d90a8a1759d..6e2f3cbc3ee4736cc0492df3bbaa3d714fb5955e 100644 --- a/templates/map.html +++ b/templates/map.html @@ -50,6 +50,15 @@ </div> </div> + {% if not service_status.active %} + <div class="alert alert-info alert-dismissible box-shadow text-center show" role="alert"> + {{ _("The location service is currently not active, the map may show outdated information.") }} + <!-- <button type="button" class="close" data-dismiss="alert" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> --> + </div> + {% endif %} + <!-- draw the map --> <div class="card border-foli box-shadow"> <div class="card-body p-0"> @@ -268,7 +277,9 @@ busTrackingLoop("all"); } - ); + ).fail(function() { + console.log('unable to start tracking vehicles, service unavailable'); + }); }; var drawAllBuses = function() { @@ -278,8 +289,10 @@ $.getJSON("{{ url_for('apiLocatorService', version = 1, stopId = stop_info.stop_id, type = 'nearby', _external = True) }}", function (data) { // poor man's locking - if (trackingLoop === null) + if (trackingLoop === null) { + completeMapUpdate(); return; + } // temporarily move all markers to a temp list // one by one all active markers will be moved back to the markers list @@ -310,7 +323,9 @@ completeMapUpdate(); } - ) + ).fail(function() { + completeMapUpdate(); + }); }; var initOneBusTracking = function() { @@ -339,8 +354,9 @@ { bus_id: requestFocus, get_shape: 1 }, function (data) { // poor man's locking (the A in Ajax is for Asynchronous :)) - if (requestFocus !== focusedMarker) + if (requestFocus !== focusedMarker) { return; + } focusedBus = data; console.log("found this bus info: ", focusedBus); @@ -364,18 +380,21 @@ }); updateInfoWindow(true); - }, - 'json' - ); - marker = gmarkers[focusedMarker]; - gmarkers = {}; - gmarkers[focusedMarker] = marker; + marker = gmarkers[focusedMarker]; + gmarkers = {}; + gmarkers[focusedMarker] = marker; - gmap.panTo(marker.getPosition()); - // gmap.setZoom(mapZoom + 1); Föli provided shapes do not line up well enough with Google map tiles to make this a good experience if zoomed + gmap.panTo(marker.getPosition()); + // gmap.setZoom(mapZoom + 1); Föli provided shapes do not line up well enough with Google map tiles to make this a good experience if zoomed - busTrackingLoop("one"); + busTrackingLoop("one"); + }, + 'json' + ).fail(function() { + console.log('lost track of the bus, going back to normal mode'); + mapClicked(); + }); }; var drawOneBus = function() { @@ -388,8 +407,10 @@ { bus_id: requestFocus, get_shape: 0 }, function (data) { // poor man's locking - if (trackingLoop === null || requestFocus !== focusedMarker) + if (trackingLoop === null || requestFocus !== focusedMarker) { + completeMapUpdate(); return; + } // if the focused bus disappeared from the list, just go back to the 'all' mode if ($.isEmptyObject(data) || gmarkers[focusedMarker] === undefined) { @@ -411,7 +432,12 @@ completeMapUpdate(); }, 'json' - ) + ).fail(function() { + console.log('lost track of the bus, going back to normal mode'); + mapClicked(); + + completeMapUpdate(); + }); }; // creates or updates the info window for focused bus