From dcf9a0ec18712b73d7c413209d3b231b94d500e6 Mon Sep 17 00:00:00 2001 From: Calin Crisan Date: Thu, 16 Jan 2014 18:14:10 +0200 Subject: [PATCH] fixed compatibility issue with older tornado versions (where no IOStream.error attribute is available) --- src/handlers.py | 120 +++++++++++++++++++++++++++------------------- src/mjpgclient.py | 5 +- src/remote.py | 34 ++++++------- static/js/main.js | 54 ++++++++++++++++++--- 4 files changed, 138 insertions(+), 75 deletions(-) diff --git a/src/handlers.py b/src/handlers.py index 81e86de..94adaff 100644 --- a/src/handlers.py +++ b/src/handlers.py @@ -16,6 +16,7 @@ # along with this program. If not, see . import base64 +import functools import json import logging import os @@ -189,47 +190,38 @@ class ConfigHandler(BaseHandler): self.finish_json(ui_config) @BaseHandler.auth(admin=True) - def set_config(self, camera_id, ui_config=None, no_finish=False): - if ui_config is None: - try: - ui_config = json.loads(self.request.body) - - except Exception as e: - logging.error('could not decode json: %(msg)s' % {'msg': unicode(e)}) - - raise + def set_config(self, camera_id): + try: + ui_config = json.loads(self.request.body) + + except Exception as e: + logging.error('could not decode json: %(msg)s' % {'msg': unicode(e)}) - reload = False + raise - if camera_id is not None: - if camera_id == 0: - logging.debug('setting multiple configs') - - for key, cfg in ui_config.items(): - if key == 'main': - reload = self.set_config(None, cfg, no_finish=True) or reload - - else: - reload = self.set_config(int(key), cfg, no_finish=True) or reload - - return self.finish_json({'reload': reload}) - - logging.debug('setting config for camera %(id)s' % {'id': camera_id}) + camera_ids = config.get_camera_ids() + + def set_camera_config(camera_id, ui_config, on_finish): + logging.debug('setting config for camera %(id)s...' % {'id': camera_id}) - camera_ids = config.get_camera_ids() if camera_id not in camera_ids: raise HTTPError(404, 'no such camera') local_config = config.get_camera(camera_id) if local_config['@proto'] == 'v4l2': -# ui_config.setdefault('device', local_config.get('videodevice', '')) TODO needed? -# ui_config.setdefault('proto', local_config['@proto']) -# ui_config.setdefault('enabled', local_config['@enabled']) + # overwrite some fields whose values should not be changed this way + ui_config['device_uri'] = local_config['videodevice'] + ui_config['proto'] = 'v4l2' + ui_config['host'] = '' + ui_config['port'] = '' + ui_config.setdefault('enabled', True) local_config = config.camera_ui_to_dict(ui_config) config.set_camera(camera_id, local_config) - - else: # remote camera + + on_finish(None, True) # (no error, motion needs restart) + + else: # update the camera locally local_config['@enabled'] = ui_config['enabled'] config.set_camera(camera_id, local_config) @@ -238,27 +230,20 @@ class ConfigHandler(BaseHandler): # the camera was probably disabled due to errors if ui_config.has_key('device_uri'): - # remove the fields that should not get to the remote side + # delete some fields that should not get to the remote side as they are del ui_config['device_uri'] del ui_config['proto'] del ui_config['host'] del ui_config['port'] del ui_config['enabled'] - try: - remote.set_config(local_config, ui_config) - - except Exception as e: - logging.error('failed to set remote camera config: %(msg)s' % {'msg': unicode(e)}) - - if not no_finish: - return self.finish_json({'error': unicode(e)}) - - elif not no_finish: - return self.finish_json({'error': unicode(e)}) + def on_finish_wrapper(error): + return on_finish(error, False) + + remote.set_config(local_config, ui_config, on_finish_wrapper) - else: - logging.debug('setting main config') + def set_main_config(ui_config): + logging.debug('setting main config...') old_main_config = config.get_main() old_admin_credentials = old_main_config.get('@admin_username', '') + ':' + old_main_config.get('@admin_password', '') @@ -271,14 +256,51 @@ class ConfigHandler(BaseHandler): if admin_credentials != old_admin_credentials: logging.debug('admin credentials changed, reload needed') - reload = True + return True # needs browser reload + + return False + + reload = False # indicates that browser should reload the page + restart = [False] # indicates that the local motion instance was modified and needs to be restarted + error = [None] + + def finish(): + if restart[0]: + logging.debug('motion needs to be restarted') + motionctl.restart() - motionctl.restart() # TODO should not be restarted unless a local camera was changed + self.finish({'reload': reload, 'error': error[0]}) - if not no_finish: - self.finish_json() + if camera_id is not None: + if camera_id == 0: # multiple camera configs + logging.debug('setting multiple configs') + + so_far = [0] + def check_finished(e, r): + restart[0] = restart[0] or r + error[0] = error[0] or e + so_far[0] += 1 + + if so_far[0] >= len(ui_config): # finished + finish() - return reload + for key, cfg in ui_config.items(): + if key == 'main': + reload = set_main_config(cfg) or reload + + else: + set_camera_config(int(key), cfg, check_finished) + + else: # single camera config + def on_finish(e, r): + error[0] = e + restart[0] = r + finish() + + set_camera_config(camera_id, ui_config, on_finish) + + else: # main config + reload = set_main_config(ui_config) @BaseHandler.auth(admin=True) def set_preview(self, camera_id): diff --git a/src/mjpgclient.py b/src/mjpgclient.py index 73a5d92..33609c2 100644 --- a/src/mjpgclient.py +++ b/src/mjpgclient.py @@ -70,10 +70,11 @@ class MjpgClient(iostream.IOStream): return True - if self.error is None: + error = getattr(self, 'error', None) + if error is None: return False - self._error(self.error) + self._error(error) return True diff --git a/src/remote.py b/src/remote.py index cbcde73..53a27e8 100644 --- a/src/remote.py +++ b/src/remote.py @@ -18,7 +18,7 @@ import json import logging -from tornado.httpclient import AsyncHTTPClient, HTTPClient, HTTPRequest +from tornado.httpclient import AsyncHTTPClient, HTTPRequest import settings @@ -126,7 +126,7 @@ def get_config(local_config, callback): http_client.fetch(request, on_response) -def set_config(local_config, camera_config): +def set_config(local_config, ui_config, callback): host = local_config.get('@host', local_config.get('host')) port = local_config.get('@port', local_config.get('port')) username = local_config.get('@username', local_config.get('username')) @@ -138,24 +138,24 @@ def set_config(local_config, camera_config): 'host': host, 'port': port}) - camera_config = json.dumps(camera_config) + ui_config = json.dumps(ui_config) - request = _make_request(host, port, username, password, '/config/%(id)s/set/' % {'id': camera_id}, method='POST', data=camera_config) + request = _make_request(host, port, username, password, '/config/%(id)s/set/' % {'id': camera_id}, method='POST', data=ui_config) - try: - http_client = HTTPClient() - response = http_client.fetch(request) + def on_response(response): 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' % { - 'id': camera_id, - 'host': host, - 'port': port, - 'msg': unicode(e)}) - - raise + logging.error('failed to set 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(response.error) + + callback(None) + + http_client = AsyncHTTPClient() + http_client.fetch(request, on_response) def set_preview(local_config, controls, callback): diff --git a/static/js/main.js b/static/js/main.js index a804309..ba24f1e 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -752,7 +752,7 @@ function beginProgress(cameraIds) { /* show the camera progress indicators */ if (cameraIds) { cameraIds.forEach(function (cameraId) { - $('div.camera-frame#' + cameraId + ' div.camera-progress').css('opacity', '0.5'); + $('div.camera-frame#camera' + cameraId + ' div.camera-progress').css('opacity', '0.5'); }); } else { @@ -830,7 +830,32 @@ function doApply() { return; } - beginProgress(); + /* gather the affected motion instances */ + var affectedInstances = {}; + Object.keys(pushConfigs).forEach(function (key) { + var config = pushConfigs[key]; + if (key === 'main') { + return; + } + + var instance = config.host || ''; + if (config.port) { + instance += ':' + config.port; + } + + affectedInstances[instance] = true; + }); + affectedInstances = Object.keys(affectedInstances); + + /* compute the affected camera ids */ + var cameraIdsByInstance = getCameraIdsByInstance(); + var affectedCameraIds = []; + + affectedInstances.forEach(function (instance) { + affectedCameraIds = affectedCameraIds.concat(cameraIdsByInstance[instance] || []); + }); + + beginProgress(affectedCameraIds); ajax('POST', '/config/0/set/', pushConfigs, function (data) { if (data == null || data.error) { @@ -1078,7 +1103,20 @@ function pushPreview(control) { } function getCameraIdsByInstance() { + /* a motion instance is identified by the (host, port) pair; + * the local instance has both the host and the port set to empty string */ + + var cameraIdsByInstance = {}; + $('div.camera-frame').each(function () { + var instance = this.config.host || ''; + if (this.config.port) { + instance += ':' + this.config.port; + } + + (cameraIdsByInstance[instance] = cameraIdsByInstance[instance] || []).push(this.config.id); + }); + return cameraIdsByInstance; } @@ -1632,16 +1670,18 @@ function runMediaDialog(cameraId, mediaType) { /* camera frames */ -function addCameraFrameUi(cameraId, cameraName, framerate) { +function addCameraFrameUi(cameraConfig, framerate) { var pageContainer = $('div.page-container'); - if (cameraId == null) { + if (cameraConfig == null) { var cameraFrameDivPlaceHolder = $('
'); pageContainer.append(cameraFrameDivPlaceHolder); return; } + var cameraId = cameraConfig.id; + var cameraFrameDiv = $( '
' + '
' + @@ -1650,7 +1690,6 @@ function addCameraFrameUi(cameraId, cameraName, framerate) { '
' + '
' + '
' + -// '
' + '
' + '
' + '
' + @@ -1678,7 +1717,8 @@ function addCameraFrameUi(cameraId, cameraName, framerate) { cameraFrameDiv.attr('id', 'camera' + cameraId); cameraFrameDiv[0].framerate = framerate; cameraFrameDiv[0].refreshDivider = 0; - nameSpan.html(cameraName); + cameraFrameDiv[0].config = cameraConfig; + nameSpan.html(cameraConfig.name); progressImg.attr('src', staticUrl + 'img/camera-progress.gif'); cameraProgress.click(function () { @@ -1789,7 +1829,7 @@ function recreateCameraFrames(cameras) { /* add camera frames */ for (i = 0; i < cameras.length; i++) { camera = cameras[i]; - addCameraFrameUi(camera.id, camera.name, Math.min(camera.streaming_framerate, camera.framerate)); + addCameraFrameUi(camera, Math.min(camera.streaming_framerate, camera.framerate)); } if ($('#videoDeviceSelect').find('option').length < 2 && user === 'admin' && $('#motionEyeSwitch')[0].checked) { -- 2.39.5