]> www.vanbest.org Git - motioneye-debian/commitdiff
most of the http server requests are now async
authorCalin Crisan <ccrisan@gmail.com>
Sun, 6 Oct 2013 19:19:46 +0000 (22:19 +0300)
committerCalin Crisan <ccrisan@gmail.com>
Sun, 6 Oct 2013 19:20:07 +0000 (22:20 +0300)
doc/todo.txt
src/handlers.py
src/remote.py
static/js/main.js

index d8b1a52825ba54ba45b41bd424304919e84127a9..d101fbeed2e05b9d88f832b214a18c130c50ba3c 100644 (file)
@@ -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
index 8cccbc92031c60992717c367c3250dedda04d4f9..2e0ee5f635f1986c3610c5b2b68474a157e02eb7 100644 (file)
@@ -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})
         
index a753bc0067062dfde0e2473b524cdb27f73340fa..f429648c8774c2b695084e6a09f98a3ec62b2ea5 100644 (file)
@@ -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)
index ea730cd126d7e33974b9c6f06251f2cc662b82b3..a9f927033f6b6a2d034b80de78de632d549ca955 100644 (file)
@@ -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('<img class="apply-progress" src="' + staticUrl + 'img/apply-progress.gif">');
     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');
     });
 }