From: Calin Crisan Date: Sun, 6 Oct 2013 19:19:46 +0000 (+0300) Subject: most of the http server requests are now async X-Git-Url: http://www.vanbest.org/gitweb/?a=commitdiff_plain;h=30fcedfaf35d905f9963f476f5e6fcc2bc9882e2;p=motioneye-debian most of the http server requests are now async --- diff --git a/doc/todo.txt b/doc/todo.txt index d8b1a52..d101fbe 100644 --- a/doc/todo.txt +++ b/doc/todo.txt @@ -1,10 +1,7 @@ -> authentication --> hide horrible 404 image on cameras - -> prevent Request closed errors by stopping mjpg clients before stopping motion --> make all the server http requests async -> style scroll bars -> hint text next to section titles diff --git a/src/handlers.py b/src/handlers.py index 8cccbc9..2e0ee5f 100644 --- a/src/handlers.py +++ b/src/handlers.py @@ -2,7 +2,7 @@ import json import logging -from tornado.web import RequestHandler, HTTPError +from tornado.web import RequestHandler, HTTPError, asynchronous import config import mjpgclient @@ -82,6 +82,7 @@ class ConfigHandler(BaseHandler): else: raise HTTPError(400, 'unknown operation') + @asynchronous def get_config(self, camera_id): if camera_id: logging.debug('getting config for camera %(id)s' % {'id': camera_id}) @@ -91,32 +92,32 @@ class ConfigHandler(BaseHandler): camera_config = config.get_camera(camera_id) if camera_config['@proto'] != 'v4l2': - try: - remote_ui_config = remote.get_config( - camera_config.get('@host'), - camera_config.get('@port'), - camera_config.get('@username'), - camera_config.get('@password'), - camera_config.get('@remote_camera_id')) + def on_response(remote_ui_config): + if remote_ui_config is None: + return self.finish_json({'error': True}) - except Exception as e: - return self.finish_json({'error': unicode(e)}) - - local_data = camera_config - camera_config = self._camera_ui_to_dict(remote_ui_config) - camera_config.update(local_data) + tmp_config = self._camera_ui_to_dict(remote_ui_config) + tmp_config.update(camera_config) + ui_config = self._camera_dict_to_ui(tmp_config) + ui_config['available_resolutions'] = remote_ui_config['available_resolutions'] + + self.finish_json(ui_config) - ui_config = self._camera_dict_to_ui(camera_config) + remote.get_config( + camera_config.get('@host'), + camera_config.get('@port'), + camera_config.get('@username'), + camera_config.get('@password'), + camera_config.get('@remote_camera_id'), on_response) - if camera_config['@proto'] == 'v4l2': + else: + ui_config = self._camera_dict_to_ui(camera_config) + resolutions = v4l2ctl.list_resolutions(camera_config['videodevice']) resolutions = [(str(w) + 'x' + str(h)) for (w, h) in resolutions] ui_config['available_resolutions'] = resolutions - - else: - ui_config['available_resolutions'] = remote_ui_config['available_resolutions'] - - self.finish_json(ui_config) + + self.finish_json(ui_config) else: logging.debug('getting main config') @@ -192,6 +193,7 @@ class ConfigHandler(BaseHandler): motionctl.restart() + @asynchronous def set_preview(self, camera_id): try: controls = json.loads(self.request.body) @@ -228,20 +230,26 @@ class ConfigHandler(BaseHandler): logging.debug('setting hue to %(value)s...' % {'value': value}) v4l2ctl.set_hue(device, value) + + self.finish_json({}) else: - try: - remote.set_preview( - camera_config['@host'], - camera_config['@port'], - camera_config['@username'], - camera_config['@password'], - camera_config['@remote_camera_id'], - controls) - - except Exception as e: - self.finish_json({'error': unicode(e)}) - + def on_response(response): + if response is None: + self.finish_json({'error': True}) + + else: + self.finish_json({}) + + remote.set_preview( + camera_config['@host'], + camera_config['@port'], + camera_config['@username'], + camera_config['@password'], + camera_config['@remote_camera_id'], + controls, on_response) + + @asynchronous def list_cameras(self): logging.debug('listing cameras') @@ -251,39 +259,55 @@ class ConfigHandler(BaseHandler): password = self.get_argument('password', None) if host: # remote listing - try: - cameras = remote.list_cameras(host, port, username, password) + def on_response(cameras): + if cameras is None: + self.finish_json({'error': True}) + + else: + self.finish_json({'cameras': cameras}) + + cameras = remote.list_cameras(host, port, username, password, on_response) - except Exception as e: - return self.finish_json({'error': unicode(e)}) - - else: + else: # local listing cameras = [] + + length = [len(config.get_camera_ids())] + def check_finished(): + if len(cameras) == length[0]: + self.finish_json({'cameras': cameras}) + + def on_response_builder(camera_id, camera_config): + def on_response(remote_ui_config): + if remote_ui_config is None: + length[0] -= 1 + + else: + remote_ui_config['id'] = camera_id + remote_ui_config['enabled'] = camera_config['@enabled'] # override the enabled status + cameras.append(remote_ui_config) + + check_finished() + + return on_response + for camera_id in config.get_camera_ids(): camera_config = config.get_camera(camera_id) if camera_config['@proto'] == 'v4l2': ui_config = self._camera_dict_to_ui(camera_config) + cameras.append(ui_config) + check_finished() else: # remote camera - try: - remote_ui_config = remote.get_config( - camera_config.get('@host'), - camera_config.get('@port'), - camera_config.get('@username'), - camera_config.get('@password'), - camera_config.get('@remote_camera_id')) - - except: - continue - - ui_config = remote_ui_config - ui_config['id'] = camera_id - ui_config['enabled'] = camera_config['@enabled'] # override the enabled status - - cameras.append(ui_config) + remote.get_config( + camera_config.get('@host'), + camera_config.get('@port'), + camera_config.get('@username'), + camera_config.get('@password'), + camera_config.get('@remote_camera_id'), on_response_builder(camera_id, camera_config)) + + if length[0] == 0: + self.finish_json({'cameras': []}) - self.finish_json({'cameras': cameras}) - def list_devices(self): logging.debug('listing devices') @@ -298,6 +322,7 @@ class ConfigHandler(BaseHandler): self.finish_json({'devices': devices}) + @asynchronous def add_camera(self): logging.debug('adding new camera') @@ -319,40 +344,37 @@ class ConfigHandler(BaseHandler): break camera_id, camera_config = config.add_camera(device_details) + camera_config['@id'] = camera_id if proto == 'v4l2': motionctl.restart() - - else: - try: - remote_ui_config = remote.get_config( - device_details.get('host'), - device_details.get('port'), - device_details.get('username'), - device_details.get('password'), - device_details.get('remote_camera_id')) - - except Exception as e: - return self.finish_json({'error': unicode(e)}) - - local_data = camera_config - camera_config = self._camera_ui_to_dict(remote_ui_config) - camera_config.update(local_data) - - camera_config['@id'] = camera_id - - ui_config = self._camera_dict_to_ui(camera_config) - - if camera_config['@proto'] == 'v4l2': + + ui_config = self._camera_dict_to_ui(camera_config) resolutions = v4l2ctl.list_resolutions(camera_config['videodevice']) resolutions = [(str(w) + 'x' + str(h)) for (w, h) in resolutions] ui_config['available_resolutions'] = resolutions + self.finish_json(ui_config) + else: - ui_config['available_resolutions'] = remote_ui_config['available_resolutions'] + def on_response(remote_ui_config): + if remote_ui_config is None: + self.finish_json({'error': True}) + + tmp_config = self._camera_ui_to_dict(remote_ui_config) + tmp_config.update(camera_config) + ui_config = self._camera_dict_to_ui(tmp_config) + ui_config['available_resolutions'] = remote_ui_config['available_resolutions'] + + self.finish_json(ui_config) + + remote.get_config( + device_details.get('host'), + device_details.get('port'), + device_details.get('username'), + device_details.get('password'), + device_details.get('remote_camera_id'), on_response) - self.finish_json(ui_config) - def rem_camera(self, camera_id): logging.debug('removing camera %(id)s' % {'id': camera_id}) @@ -686,6 +708,7 @@ class SnapshotHandler(BaseHandler): else: raise HTTPError(400, 'unknown operation') + @asynchronous def current(self, camera_id): camera_config = config.get_camera(camera_id) if camera_config['@proto'] == 'v4l2': @@ -697,20 +720,21 @@ class SnapshotHandler(BaseHandler): self.finish(jpg) else: - try: - jpg = remote.current_snapshot( - camera_config['@host'], - camera_config['@port'], - camera_config['@username'], - camera_config['@password'], - camera_config['@remote_camera_id']) + def on_response(jpg): + if jpg is None: + self.finish({}) + + else: + self.set_header('Content-Type', 'image/jpeg') + self.finish(jpg) + + remote.current_snapshot( + camera_config['@host'], + camera_config['@port'], + camera_config['@username'], + camera_config['@password'], + camera_config['@remote_camera_id'], on_response) - except: - return self.finish() - - self.set_header('Content-Type', 'image/jpeg') - self.finish(jpg) - def list(self, camera_id): logging.debug('listing snapshots for camera %(id)s' % {'id': camera_id}) diff --git a/src/remote.py b/src/remote.py index a753bc0..f429648 100644 --- a/src/remote.py +++ b/src/remote.py @@ -1,7 +1,8 @@ import json import logging -import urllib2 + +from tornado.httpclient import AsyncHTTPClient, HTTPClient, HTTPRequest def _compose_url(host, port, username, password, uri, query=None): @@ -17,73 +18,73 @@ def _compose_url(host, port, username, password, uri, query=None): return url -def list_cameras(host, port, username, password): +def list_cameras(host, port, username, password, callback): logging.debug('listing remote cameras on %(host)s:%(port)s' % { 'host': host, 'port': port}) url = _compose_url(host, port, username, password, '/config/list/') - request = urllib2.Request(url) - - try: - response = urllib2.urlopen(request) - except Exception as e: - logging.error('failed to list remote cameras on %(host)s:%(port)s: %(msg)s' % { - 'host': host, - 'port': port, - 'msg': unicode(e)}) + def on_response(response): + if response.error: + logging.error('failed to list remote cameras on %(host)s:%(port)s: %(msg)s' % { + 'host': host, + 'port': port, + 'msg': unicode(response.error)}) + + return callback(None) - raise - - try: - response = json.load(response) - - except Exception as e: - logging.error('failed to decode json answer from %(host)s:%(port)s: %(msg)s' % { - 'host': host, - 'port': port, - 'msg': unicode(e)}) + try: + response = json.loads(response.body) + + except Exception as e: + logging.error('failed to decode json answer from %(host)s:%(port)s: %(msg)s' % { + 'host': host, + 'port': port, + 'msg': unicode(e)}) + + return callback(None) - raise + return callback(response['cameras']) + + http_client = AsyncHTTPClient() + http_client.fetch(url, on_response) - return response['cameras'] - -def get_config(host, port, username, password, camera_id): +def get_config(host, port, username, password, camera_id, callback): logging.debug('getting config for remote camera %(id)s on %(host)s:%(port)s' % { 'id': camera_id, 'host': host, 'port': port}) url = _compose_url(host, port, username, password, '/config/%(id)s/get/' % {'id': camera_id}) - request = urllib2.Request(url) - - try: - response = urllib2.urlopen(request) - - except Exception as e: - logging.error('failed to get config for remote camera %(id)s on %(host)s:%(port)s: %(msg)s' % { - 'id': camera_id, - 'host': host, - 'port': port, - 'msg': unicode(e)}) - - raise - try: - response = json.load(response) - - except Exception as e: - logging.error('failed to decode json answer from %(host)s:%(port)s: %(msg)s' % { - 'host': host, - 'port': port, - 'msg': unicode(e)}) + def on_response(response): + if response.error: + logging.error('failed to get config for remote camera %(id)s on %(host)s:%(port)s: %(msg)s' % { + 'id': camera_id, + 'host': host, + 'port': port, + 'msg': unicode(response.error)}) + + return callback(None) + + try: + response = json.loads(response.body) - raise + except Exception as e: + logging.error('failed to decode json answer from %(host)s:%(port)s: %(msg)s' % { + 'host': host, + 'port': port, + 'msg': unicode(e)}) + + return callback(None) + + callback(response) + + http_client = AsyncHTTPClient() + http_client.fetch(url, on_response) - return response - def set_config(host, port, username, password, camera_id, data): logging.debug('setting config for remote camera %(id)s on %(host)s:%(port)s' % { @@ -94,10 +95,13 @@ def set_config(host, port, username, password, camera_id, data): data = json.dumps(data) url = _compose_url(host, port, username, password, '/config/%(id)s/set/' % {'id': camera_id}) - request = urllib2.Request(url, data=data) + request = HTTPRequest(url, method='POST', body=data) try: - urllib2.urlopen(request) + http_client = HTTPClient() + response = http_client.fetch(request) + if response.error: + raise Exception(unicode(response.error)) except Exception as e: logging.error('failed to set config for remote camera %(id)s on %(host)s:%(port)s: %(msg)s' % { @@ -109,7 +113,7 @@ def set_config(host, port, username, password, camera_id, data): raise -def set_preview(host, port, username, password, camera_id, controls): +def set_preview(host, port, username, password, camera_id, controls, callback): logging.debug('setting preview for remote camera %(id)s on %(host)s:%(port)s' % { 'id': camera_id, 'host': host, @@ -118,40 +122,42 @@ def set_preview(host, port, username, password, camera_id, controls): controls = json.dumps(controls) url = _compose_url(host, port, username, password, '/config/%(id)s/set_preview/' % {'id': camera_id}) - request = urllib2.Request(url, data=controls) - - try: - urllib2.urlopen(request) - - except Exception as e: - logging.error('failed to set preview for remote camera %(id)s on %(host)s:%(port)s: %(msg)s' % { - 'id': camera_id, - 'host': host, - 'port': port, - 'msg': unicode(e)}) + + def on_response(response): + if response.error: + logging.error('failed to set preview for remote camera %(id)s on %(host)s:%(port)s: %(msg)s' % { + 'id': camera_id, + 'host': host, + 'port': port, + 'msg': unicode(response.error)}) - raise + return callback(None) + + callback('') + + http_client = AsyncHTTPClient() + http_client.fetch(url, on_response) -def current_snapshot(host, port, username, password, camera_id): +def current_snapshot(host, port, username, password, camera_id, callback): logging.debug('getting current snapshot for remote camera %(id)s on %(host)s:%(port)s' % { 'id': camera_id, 'host': host, 'port': port}) url = _compose_url(host, port, username, password, '/snapshot/%(id)s/current/' % {'id': camera_id}) - request = urllib2.Request(url) - - try: - response = urllib2.urlopen(request) - except Exception as e: - logging.error('failed to get current snapshot for remote camera %(id)s on %(host)s:%(port)s: %(msg)s' % { - 'id': camera_id, - 'host': host, - 'port': port, - 'msg': unicode(e)}) + def on_response(response): + if response.error: + logging.error('failed to get current snapshot for remote camera %(id)s on %(host)s:%(port)s: %(msg)s' % { + 'id': camera_id, + 'host': host, + 'port': port, + 'msg': unicode(response.error)}) + + return callback(None) - raise + callback(response.body) - return response.read() + http_client = AsyncHTTPClient() + http_client.fetch(url, on_response) diff --git a/static/js/main.js b/static/js/main.js index ea730cd..a9f9270 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -28,7 +28,10 @@ function ajax(method, url, data, callback) { } function showErrorMessage(message) { - message = message || 'An error occurred. Refreshing is recommended.'; + if (message == null || message == true) { + message = 'An error occurred. Refreshing is recommended.'; + } + showPopupMessage(message, 'error'); } @@ -657,7 +660,7 @@ function showProgress() { applyButton.html(''); applyButton.css('display', 'inline-block'); applyButton.animate({'opacity': '1'}, 100); - applyButton.addClass('progress'); + applyButton.addClass('progress'); $('div.camera-progress').css('opacity', '0.5'); } @@ -770,7 +773,7 @@ function fetchCurrentConfig() { ajax('GET', '/config/list/', null, function (data) { if (data == null || data.error) { showErrorMessage(data && data.error); - return; + data = {cameras: []}; } var i, cameras = data.cameras; @@ -1141,6 +1144,7 @@ function addCameraFrameUi(cameraId, cameraName, framerate) { this.error = false; cameraImg.removeClass('error'); cameraImg.css('height', ''); + $('div.camera-progress').css('opacity', '0'); }); }