From fc19628695311165fe2583fb456e912833526120 Mon Sep 17 00:00:00 2001 From: Calin Crisan Date: Tue, 20 Oct 2015 22:27:06 +0300 Subject: [PATCH] fixed email notifications not being sent; reorganized imports; added settings for more subprocess timeouts --- extra/motioneye.conf.sample | 6 +++++ motioneye/cleanup.py | 13 ++++----- motioneye/config.py | 13 +++++---- motioneye/handlers.py | 12 +++++---- motioneye/mediafiles.py | 49 ++++++++++++++++++++++----------- motioneye/meyectl.py | 12 ++++----- motioneye/mjpgclient.py | 13 ++++----- motioneye/motionctl.py | 53 ++++++++++++++++++------------------ motioneye/sendmail.py | 54 +++++++++++++++++++++---------------- motioneye/server.py | 14 +++++----- motioneye/settings.py | 6 +++++ motioneye/smbctl.py | 6 ++--- motioneye/thumbnailer.py | 11 ++++---- motioneye/tzctl.py | 10 +++---- motioneye/utils.py | 15 ++++++----- motioneye/wsswitch.py | 46 ++++++++++++++++--------------- 16 files changed, 189 insertions(+), 144 deletions(-) diff --git a/extra/motioneye.conf.sample b/extra/motioneye.conf.sample index 50cb251..2503a1c 100644 --- a/extra/motioneye.conf.sample +++ b/extra/motioneye.conf.sample @@ -81,8 +81,14 @@ enable_reboot false # timeout in seconds to use when talking to the SMTP server smtp_timeout 60 +# timeout in seconds to wait media files list +list_media_timeout 120 + # timeout in seconds to wait for zip file creation zip_timeout 500 +# timeout in seconds to wait for timelapse creation +timelapse_timeout 500 + # enable adding and removing cameras from UI add_remove_cameras true diff --git a/motioneye/cleanup.py b/motioneye/cleanup.py index f635200..5f93ea3 100644 --- a/motioneye/cleanup.py +++ b/motioneye/cleanup.py @@ -20,7 +20,8 @@ import logging import multiprocessing import os import signal -import tornado + +from tornado.ioloop import IOLoop import mediafiles import settings @@ -35,8 +36,8 @@ def start(): return # schedule the first call a bit later to improve performance at startup - ioloop = tornado.ioloop.IOLoop.instance() - ioloop.add_timeout(datetime.timedelta(seconds=min(settings.CLEANUP_INTERVAL, 60)), _run_process) + io_loop = IOLoop.instance() + io_loop.add_timeout(datetime.timedelta(seconds=min(settings.CLEANUP_INTERVAL, 60)), _run_process) def stop(): @@ -63,17 +64,17 @@ def running(): def _run_process(): global _process - ioloop = tornado.ioloop.IOLoop.instance() + io_loop = IOLoop.instance() if thumbnailer.running(): # postpone if thumbnailer is currently running - ioloop.add_timeout(datetime.timedelta(seconds=60), _run_process) + io_loop.add_timeout(datetime.timedelta(seconds=60), _run_process) return else: # schedule the next call - ioloop.add_timeout(datetime.timedelta(seconds=settings.CLEANUP_INTERVAL), _run_process) + io_loop.add_timeout(datetime.timedelta(seconds=settings.CLEANUP_INTERVAL), _run_process) if not running(): # check that the previous process has finished logging.debug('running cleanup process...') diff --git a/motioneye/config.py b/motioneye/config.py index 2dc62b8..603bfab 100644 --- a/motioneye/config.py +++ b/motioneye/config.py @@ -34,8 +34,6 @@ import update import utils import v4l2ctl -from utils import OrderedDict - _CAMERA_CONFIG_FILE_NAME = 'thread-%(id)s.conf' _MAIN_CONFIG_FILE_NAME = 'motion.conf' @@ -1326,7 +1324,8 @@ def restore(content): def later(): powerctl.reboot() - IOLoop.instance().add_timeout(datetime.timedelta(seconds=2), later) + io_loop = IOLoop.instance() + io_loop.add_timeout(datetime.timedelta(seconds=2), later) else: logging.info('invalidating config cache') @@ -1428,7 +1427,7 @@ def _python_to_value(value): def _conf_to_dict(lines, list_names=[], no_convert=[]): - data = OrderedDict() + data = utils.OrderedDict() for line in lines: line = line.strip() @@ -1467,7 +1466,7 @@ def _conf_to_dict(lines, list_names=[], no_convert=[]): def _dict_to_conf(lines, data, list_names=[]): conf_lines = [] - remaining = OrderedDict(data) + remaining = utils.OrderedDict(data) processed = set() # parse existing lines and replace the values @@ -1677,7 +1676,7 @@ def get_additional_structure(camera, separators=False): 'with' if separators else 'without')) # gather sections - sections = OrderedDict() + sections = utils.OrderedDict() for func in _additional_section_funcs: result = func() if not result: @@ -1694,7 +1693,7 @@ def get_additional_structure(camera, separators=False): logging.debug('additional config section: %s' % result['name']) - configs = OrderedDict() + configs = utils.OrderedDict() for func in _additional_config_funcs: result = func() if not result: diff --git a/motioneye/handlers.py b/motioneye/handlers.py index 7f0f761..69f8912 100644 --- a/motioneye/handlers.py +++ b/motioneye/handlers.py @@ -23,8 +23,8 @@ import re import socket import subprocess -from tornado.web import RequestHandler, HTTPError, asynchronous from tornado.ioloop import IOLoop +from tornado.web import RequestHandler, HTTPError, asynchronous import config import mediafiles @@ -356,8 +356,8 @@ class ConfigHandler(BaseHandler): def call_reboot(): powerctl.reboot() - ioloop = IOLoop.instance() - ioloop.add_timeout(datetime.timedelta(seconds=2), call_reboot) + io_loop = IOLoop.instance() + io_loop.add_timeout(datetime.timedelta(seconds=2), call_reboot) return self.finish({'reload': False, 'reboot': True, 'error': None}) else: @@ -1438,10 +1438,12 @@ class PowerHandler(BaseHandler): self.reboot() def shut_down(self): - IOLoop.instance().add_timeout(datetime.timedelta(seconds=2), powerctl.shut_down) + io_loop = IOLoop.instance() + io_loop.add_timeout(datetime.timedelta(seconds=2), powerctl.shut_down) def reboot(self): - IOLoop.instance().add_timeout(datetime.timedelta(seconds=2), powerctl.reboot) + io_loop = IOLoop.instance() + io_loop.add_timeout(datetime.timedelta(seconds=2), powerctl.reboot) class VersionHandler(BaseHandler): diff --git a/motioneye/mediafiles.py b/motioneye/mediafiles.py index a275592..7523870 100644 --- a/motioneye/mediafiles.py +++ b/motioneye/mediafiles.py @@ -24,15 +24,15 @@ import logging import multiprocessing import os.path import re +import signal import stat import StringIO import subprocess import time -import tornado import zipfile from PIL import Image -from tornado import ioloop +from tornado.ioloop import IOLoop import config import settings @@ -326,17 +326,22 @@ def list_media(camera_config, media_type, callback, prefix=None): media_list.append(parent_pipe.recv()) def poll_process(): - ioloop = tornado.ioloop.IOLoop.instance() + io_loop = IOLoop.instance() if process.is_alive(): # not finished yet now = datetime.datetime.now() delta = now - started - if delta.seconds < 120: - ioloop.add_timeout(datetime.timedelta(seconds=0.5), poll_process) + if delta.seconds < settings.LIST_MEDIA_TIMEOUT: + io_loop.add_timeout(datetime.timedelta(seconds=0.5), poll_process) read_media_list() - else: # process did not finish within 2 minutes + else: # process did not finish in time logging.error('timeout waiting for the media listing process to finish') + try: + os.kill(process.pid, signal.SIGTERM) + except: + pass # nevermind + callback(None) else: # finished @@ -430,15 +435,20 @@ def get_zipped_content(camera_config, media_type, group, callback): started = datetime.datetime.now() def poll_process(): - ioloop = tornado.ioloop.IOLoop.instance() + io_loop = IOLoop.instance() if working.value: now = datetime.datetime.now() delta = now - started if delta.seconds < settings.ZIP_TIMEOUT: - ioloop.add_timeout(datetime.timedelta(seconds=0.5), poll_process) + io_loop.add_timeout(datetime.timedelta(seconds=0.5), poll_process) - else: # process did not finish within 2 minutes + else: # process did not finish in time logging.error('timeout waiting for the zip process to finish') + try: + os.kill(process.pid, signal.SIGTERM) + + except: + pass # nevermind callback(None) @@ -492,17 +502,22 @@ def make_timelapse_movie(camera_config, framerate, interval, group): media_list.append(parent_pipe.recv()) def poll_media_list_process(): - ioloop = tornado.ioloop.IOLoop.instance() + io_loop = IOLoop.instance() if _timelapse_process.is_alive(): # not finished yet now = datetime.datetime.now() delta = now - started[0] - if delta.seconds < 300: # the subprocess has 5 minutes to complete its job - ioloop.add_timeout(datetime.timedelta(seconds=0.5), poll_media_list_process) + if delta.seconds < settings.TIMELAPSE_TIMEOUT: # the subprocess has limited time to complete its job + io_loop.add_timeout(datetime.timedelta(seconds=0.5), poll_media_list_process) read_media_list() - else: # process did not finish within 2 minutes + else: # process did not finish in time logging.error('timeout waiting for the media listing process to finish') + try: + os.kill(_timelapse_process.pid, signal.SIGTERM) + except: + pass # nevermind + _timelapse_process.progress = -1 else: # finished @@ -573,9 +588,9 @@ def make_timelapse_movie(camera_config, framerate, interval, group): global _timelapse_process global _timelapse_data - ioloop = tornado.ioloop.IOLoop.instance() + io_loop = IOLoop.instance() if _timelapse_process.poll() is None: # not finished yet - ioloop.add_timeout(datetime.timedelta(seconds=0.5), functools.partial(poll_movie_process, pictures)) + io_loop.add_timeout(datetime.timedelta(seconds=0.5), functools.partial(poll_movie_process, pictures)) try: output = _timelapse_process.stdout.read() @@ -800,6 +815,8 @@ def set_prepared_cache(data): logging.warn('key "%s" was still present in the prepared cache, removed' % key) timeout = 3600 # the user has 1 hour to download the file after creation - ioloop.IOLoop.instance().add_timeout(datetime.timedelta(seconds=timeout), clear) + + io_loop = IOLoop.instance() + io_loop.add_timeout(datetime.timedelta(seconds=timeout), clear) return key diff --git a/motioneye/meyectl.py b/motioneye/meyectl.py index 5ba7f57..b785b3b 100755 --- a/motioneye/meyectl.py +++ b/motioneye/meyectl.py @@ -22,8 +22,6 @@ import os.path import pipes import sys -from tornado.httpclient import AsyncHTTPClient - # make sure motioneye is on python path sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) @@ -178,6 +176,8 @@ def configure_logging(cmd, log_to_file=False): def configure_tornado(): + from tornado.httpclient import AsyncHTTPClient + AsyncHTTPClient.configure('tornado.curl_httpclient.CurlAsyncHTTPClient', max_clients=16) @@ -242,21 +242,21 @@ def main(): command = sys.argv[1] arg_parser = make_arg_parser(command) - import relayevent - import sendmail - import server - import webhook if command in ('startserver', 'stopserver'): + import server server.main(arg_parser, sys.argv[2:], command[:-6]) elif command == 'sendmail': + import sendmail sendmail.main(arg_parser, sys.argv[2:]) elif command == 'relayevent': + import relayevent relayevent.main(arg_parser, sys.argv[2:]) elif command == 'webhook': + import webhook webhook.main(arg_parser, sys.argv[2:]) else: diff --git a/motioneye/mjpgclient.py b/motioneye/mjpgclient.py index 933df2d..949e599 100644 --- a/motioneye/mjpgclient.py +++ b/motioneye/mjpgclient.py @@ -22,7 +22,8 @@ import re import socket import time -from tornado import iostream, ioloop +from tornado.ioloop import IOLoop +from tornado.iostream import IOStream import config import motionctl @@ -30,7 +31,7 @@ import settings import utils -class MjpgClient(iostream.IOStream): +class MjpgClient(IOStream): clients = {} # dictionary of clients indexed by camera id last_jpgs = {} # dictionary of jpg contents indexed by camera id last_jpg_moment = {} # dictionary of moments of the last received jpeg indexed by camera id @@ -45,12 +46,12 @@ class MjpgClient(iostream.IOStream): self._auth_digest_state = {} s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) - iostream.IOStream.__init__(self, s) + IOStream.__init__(self, s) self.set_close_callback(self.on_close) def connect(self): - iostream.IOStream.connect(self, ('localhost', self._port), self._on_connect) + IOStream.connect(self, ('localhost', self._port), self._on_connect) MjpgClient.clients[self._camera_id] = self logging.debug('mjpg client for camera %(camera_id)s connecting on port %(port)s...' % { @@ -212,7 +213,7 @@ class MjpgClient(iostream.IOStream): def start(): # schedule the garbage collector - io_loop = ioloop.IOLoop.instance() + io_loop = IOLoop.instance() io_loop.add_timeout(datetime.timedelta(seconds=settings.MJPG_CLIENT_TIMEOUT), _garbage_collector) @@ -258,7 +259,7 @@ def close_all(invalidate=False): def _garbage_collector(): logging.debug('running garbage collector for mjpg clients...') - io_loop = ioloop.IOLoop.instance() + io_loop = IOLoop.instance() io_loop.add_timeout(datetime.timedelta(seconds=settings.MJPG_CLIENT_TIMEOUT), _garbage_collector) now = datetime.datetime.utcnow() diff --git a/motioneye/motionctl.py b/motioneye/motionctl.py index 58e8266..39265ca 100644 --- a/motioneye/motionctl.py +++ b/motioneye/motionctl.py @@ -23,16 +23,14 @@ import signal import subprocess import time -from tornado import gen -from tornado.httpclient import AsyncHTTPClient, HTTPRequest from tornado.ioloop import IOLoop import config -import mjpgclient import powerctl import settings import utils +_MOTION_CONTROL_TIMEOUT = 5 _started = False _motion_binary_cache = None @@ -73,8 +71,11 @@ def find_motion(): def start(deferred=False): + import mjpgclient + if deferred: - return IOLoop.instance().add_callback(start, deferred=False) + io_loop = IOLoop.instance() + io_loop.add_callback(start, deferred=False) global _started @@ -136,6 +137,8 @@ def start(deferred=False): def stop(invalidate=False): + import mjpgclient + global _started _started = False @@ -202,39 +205,37 @@ def started(): return _started -@gen.coroutine -def get_motion_detection(camera_id): +def get_motion_detection(camera_id, callback): + from tornado.httpclient import HTTPRequest, AsyncHTTPClient + thread_id = camera_id_to_thread_id(camera_id) if thread_id is None: - logging.error('could not find thread id for camera with id %s' % camera_id) - return + error = 'could not find thread id for camera with id %s' % camera_id + logging.error(error) + return callback(error=error) url = 'http://127.0.0.1:7999/%(id)s/detection/status' % {'id': thread_id} - request = HTTPRequest(url, connect_timeout=5, request_timeout=5) - http_client = AsyncHTTPClient() - try: - response = yield http_client.fetch(request) + def on_response(response): if response.error: - raise response.error + return callback(error=utils.pretty_http_error()) - except Exception as e: - logging.error('failed to get motion detection status for camera with id %(id)s: %(msg)s' % { - 'id': camera_id, - 'msg': unicode(e)}) - - return + enabled = bool(response.body.lower().count('active')) + + logging.debug('motion detection is %(what)s for camera with id %(id)s' % { + 'what': ['disabled', 'enabled'][enabled], + 'id': camera_id}) - enabled = bool(response.body.lower().count('active')) - - logging.debug('motion detection is %(what)s for camera with id %(id)s' % { - 'what': ['disabled', 'enabled'][enabled], - 'id': camera_id}) + callback(enabled) - raise gen.Return(enabled) + request = HTTPRequest(url, connect_timeout=_MOTION_CONTROL_TIMEOUT, request_timeout=_MOTION_CONTROL_TIMEOUT) + http_client = AsyncHTTPClient() + http_client.fetch(request, callback=on_response) def set_motion_detection(camera_id, enabled): + from tornado.httpclient import HTTPRequest, AsyncHTTPClient + thread_id = camera_id_to_thread_id(camera_id) if thread_id is None: return logging.error('could not find thread id for camera with id %s' % camera_id) @@ -262,7 +263,7 @@ def set_motion_detection(camera_id, enabled): 'what': ['disabled', 'enabled'][enabled], 'id': camera_id}) - request = HTTPRequest(url, connect_timeout=4, request_timeout=4) + request = HTTPRequest(url, connect_timeout=_MOTION_CONTROL_TIMEOUT, request_timeout=_MOTION_CONTROL_TIMEOUT) http_client = AsyncHTTPClient() http_client.fetch(request, on_response) diff --git a/motioneye/sendmail.py b/motioneye/sendmail.py index 5c64556..19c3a69 100644 --- a/motioneye/sendmail.py +++ b/motioneye/sendmail.py @@ -19,6 +19,7 @@ import datetime import logging import os import re +import signal import smtplib import socket import time @@ -27,6 +28,7 @@ from email import Encoders from email.mime.text import MIMEText from email.MIMEMultipart import MIMEMultipart from email.MIMEBase import MIMEBase + from tornado.ioloop import IOLoop import settings @@ -81,16 +83,21 @@ def send_mail(server, port, account, password, tls, to, subject, message, files) def make_message(subject, message, camera_id, moment, timespan, callback): camera_config = config.get_camera(camera_id) + # we must start the IO loop for the media list subprocess polling + io_loop = IOLoop.instance() + def on_media_files(media_files): - logging.debug('got media files') + io_loop.stop() timestamp = time.mktime(moment.timetuple()) - media_files = [m for m in media_files if abs(m['timestamp'] - timestamp) < timespan] # filter out non-recent media files - media_files.sort(key=lambda m: m['timestamp'], reverse=True) - media_files = [os.path.join(camera_config['target_dir'], re.sub('^/', '', m['path'])) for m in media_files] + if media_files: + logging.debug('got media files') + media_files = [m for m in media_files if abs(m['timestamp'] - timestamp) < timespan] # filter out non-recent media files + media_files.sort(key=lambda m: m['timestamp'], reverse=True) + media_files = [os.path.join(camera_config['target_dir'], re.sub('^/', '', m['path'])) for m in media_files] - logging.debug('selected %d pictures' % len(media_files)) + logging.debug('selected %d pictures' % len(media_files)) format_dict = { 'camera': camera_config['@name'], @@ -99,8 +106,8 @@ def make_message(subject, message, camera_id, moment, timespan, callback): } if settings.LOCAL_TIME_FILE: - format_dict['timezone'] = tzctl._get_time_zone() - + format_dict['timezone'] = tzctl.get_time_zone() + else: format_dict['timezone'] = 'local time' @@ -115,11 +122,14 @@ def make_message(subject, message, camera_id, moment, timespan, callback): if not timespan: return on_media_files([]) - - logging.debug('creating email message') - + + logging.debug('waiting for pictures to be taken') time.sleep(timespan) # give motion some time to create motion pictures + + logging.debug('creating email message') mediafiles.list_media(camera_config, media_type='picture', callback=on_media_files) + + io_loop.start() def parse_options(parser, args): @@ -140,10 +150,14 @@ def parse_options(parser, args): def main(parser, args): import meyectl + # the motion daemon overrides SIGCHLD, + # so we must restore it here, + # or otherwise media listing won't work + signal.signal(signal.SIGCHLD,signal.SIG_DFL) + options = parse_options(parser, args) meyectl.configure_logging('sendmail', options.log_to_file) - meyectl.configure_tornado() logging.debug('hello!') @@ -154,6 +168,10 @@ def main(parser, args): subject = subjects.get(options.msg_id) options.moment = datetime.datetime.strptime(options.moment, '%Y-%m-%dT%H:%M:%S') + # do not wait too long for media list, + # email notifications are critical + settings.LIST_MEDIA_TIMEOUT = 10 + logging.debug('server = %s' % options.server) logging.debug('port = %s' % options.port) logging.debug('account = %s' % options.account) @@ -170,25 +188,15 @@ def main(parser, args): to = [t.strip() for t in re.split('[,;| ]', options.to)] to = [t for t in to if t] - io_loop = IOLoop.instance() - def on_message(subject, message, files): try: send_mail(options.server, options.port, options.account, options.password, - options.tls, to, subject, message, files) + options.tls, to, subject, message, files or []) logging.info('email sent') except Exception as e: logging.error('failed to send mail: %s' % e, exc_info=True) - io_loop.stop() - - def ioloop_timeout(): - io_loop.stop() + logging.debug('bye!') make_message(subject, message, options.camera_id, options.moment, options.timespan, on_message) - - io_loop.add_timeout(datetime.timedelta(seconds=settings.SMTP_TIMEOUT), ioloop_timeout) - io_loop.start() - - logging.debug('bye!') diff --git a/motioneye/server.py b/motioneye/server.py index 19b8db7..e2a3eab 100644 --- a/motioneye/server.py +++ b/motioneye/server.py @@ -24,8 +24,8 @@ import signal import sys import time -from tornado.web import Application from tornado.ioloop import IOLoop +from tornado.web import Application import handlers import settings @@ -284,15 +284,14 @@ def test_requirements(): def start_motion(): - import tornado.ioloop import config import motionctl - ioloop = tornado.ioloop.IOLoop.instance() + io_loop = IOLoop.instance() # add a motion running checker def checker(): - if ioloop._stopped: + if io_loop._stopped: return if not motionctl.running() and motionctl.started() and config.get_enabled_local_motion_cameras(): @@ -304,11 +303,11 @@ def start_motion(): logging.error('failed to start motion: %(msg)s' % { 'msg': unicode(e)}, exc_info=True) - ioloop.add_timeout(datetime.timedelta(seconds=settings.MOTION_CHECK_INTERVAL), checker) + io_loop.add_timeout(datetime.timedelta(seconds=settings.MOTION_CHECK_INTERVAL), checker) motionctl.start() - ioloop.add_timeout(datetime.timedelta(seconds=settings.MOTION_CHECK_INTERVAL), checker) + io_loop.add_timeout(datetime.timedelta(seconds=settings.MOTION_CHECK_INTERVAL), checker) def parse_options(parser, args): @@ -367,7 +366,8 @@ def run(): application.listen(settings.PORT, settings.LISTEN) logging.info('server started') - IOLoop.instance().start() + io_loop = IOLoop.instance() + io_loop.start() logging.info('server stopped') diff --git a/motioneye/settings.py b/motioneye/settings.py index 8f593a4..5f3fb18 100644 --- a/motioneye/settings.py +++ b/motioneye/settings.py @@ -111,8 +111,14 @@ ENABLE_UPDATE = False # timeout in seconds to use when talking to the SMTP server SMTP_TIMEOUT = 60 +# timeout in seconds to wait media files list +LIST_MEDIA_TIMEOUT = 120 + # timeout in seconds to wait for zip file creation ZIP_TIMEOUT = 500 +# timeout in seconds to wait for timelapse creation +TIMELAPSE_TIMEOUT = 500 + # enable adding and removing cameras from UI ADD_REMOVE_CAMERAS = True diff --git a/motioneye/smbctl.py b/motioneye/smbctl.py index 936c04b..75f1aa7 100644 --- a/motioneye/smbctl.py +++ b/motioneye/smbctl.py @@ -22,14 +22,14 @@ import re import subprocess import time -from tornado import ioloop +from tornado.ioloop import IOLoop import config import settings def start(): - io_loop = ioloop.IOLoop.instance() + io_loop = IOLoop.instance() io_loop.add_timeout(datetime.timedelta(seconds=settings.MOUNT_CHECK_INTERVAL), _check_mounts) @@ -229,6 +229,6 @@ def _check_mounts(): if start: motionctl.start() - io_loop = ioloop.IOLoop.instance() + io_loop = IOLoop.instance() io_loop.add_timeout(datetime.timedelta(seconds=settings.MOUNT_CHECK_INTERVAL), _check_mounts) diff --git a/motioneye/thumbnailer.py b/motioneye/thumbnailer.py index e100f0f..0edd3a8 100644 --- a/motioneye/thumbnailer.py +++ b/motioneye/thumbnailer.py @@ -20,7 +20,8 @@ import logging import multiprocessing import os import signal -import tornado + +from tornado.ioloop import IOLoop import cleanup import mediafiles @@ -35,8 +36,8 @@ def start(): return # schedule the first call a bit later to improve performance at startup - ioloop = tornado.ioloop.IOLoop.instance() - ioloop.add_timeout(datetime.timedelta(seconds=min(settings.THUMBNAILER_INTERVAL, 30)), _run_process) + io_loop = IOLoop.instance() + io_loop.add_timeout(datetime.timedelta(seconds=min(settings.THUMBNAILER_INTERVAL, 30)), _run_process) def stop(): @@ -64,8 +65,8 @@ def _run_process(): global _process # schedule the next call - ioloop = tornado.ioloop.IOLoop.instance() - ioloop.add_timeout(datetime.timedelta(seconds=settings.THUMBNAILER_INTERVAL), _run_process) + io_loop = IOLoop.instance() + io_loop.add_timeout(datetime.timedelta(seconds=settings.THUMBNAILER_INTERVAL), _run_process) if not running() and not cleanup.running(): # check that the previous process has finished and that cleanup is not running logging.debug('running thumbnailer process...') diff --git a/motioneye/tzctl.py b/motioneye/tzctl.py index ff36ab7..d06ed23 100644 --- a/motioneye/tzctl.py +++ b/motioneye/tzctl.py @@ -27,6 +27,10 @@ from config import additional_config LOCAL_TIME_FILE = settings.LOCAL_TIME_FILE # @UndefinedVariable +def get_time_zone(): + return _get_time_zone_symlink() or _get_time_zone_md5() or 'UTC' + + def _get_time_zone_symlink(): file = settings.LOCAL_TIME_FILE if not file: @@ -86,10 +90,6 @@ def _get_time_zone_md5(): return time_zone -def _get_time_zone(): - return _get_time_zone_symlink() or _get_time_zone_md5() or 'UTC' - - def _set_time_zone(time_zone): time_zone = time_zone or 'UTC' @@ -134,6 +134,6 @@ def timeZone(): 'section': 'general', 'advanced': True, 'reboot': True, - 'get': _get_time_zone, + 'get': get_time_zone, 'set': _set_time_zone } diff --git a/motioneye/utils.py b/motioneye/utils.py index f88d2de..2b89225 100644 --- a/motioneye/utils.py +++ b/motioneye/utils.py @@ -33,7 +33,6 @@ from tornado.ioloop import IOLoop import settings - try: from collections import OrderedDict # @UnusedImport @@ -427,6 +426,8 @@ def test_rtsp_url(data, callback): called = [False] timeout = [None] stream = None + + io_loop = IOLoop.instance() def connect(): logging.debug('testing rtsp netcam at %s' % url) @@ -437,13 +438,13 @@ def test_rtsp_url(data, callback): stream.set_close_callback(on_close) stream.connect((data['host'], int(data['port'])), on_connect) - timeout[0] = IOLoop.instance().add_timeout(datetime.timedelta(seconds=settings.MJPG_CLIENT_TIMEOUT), + timeout[0] = io_loop.add_timeout(datetime.timedelta(seconds=settings.MJPG_CLIENT_TIMEOUT), functools.partial(on_connect, _timeout=True)) return stream def on_connect(_timeout=False): - IOLoop.instance().remove_timeout(timeout[0]) + io_loop.remove_timeout(timeout[0]) if _timeout: return handle_error('timeout connecting to rtsp netcam') @@ -468,10 +469,10 @@ def test_rtsp_url(data, callback): return stream.read_until_regex('RTSP/1.0 \d+ ', on_rtsp) - timeout[0] = IOLoop.instance().add_timeout(datetime.timedelta(seconds=settings.MJPG_CLIENT_TIMEOUT), on_rtsp) + timeout[0] = io_loop.add_timeout(datetime.timedelta(seconds=settings.MJPG_CLIENT_TIMEOUT), on_rtsp) def on_rtsp(data): - IOLoop.instance().remove_timeout(timeout[0]) + io_loop.remove_timeout(timeout[0]) if data: if data.endswith('200 '): @@ -488,10 +489,10 @@ def test_rtsp_url(data, callback): return stream.read_until_regex('Server: .*', on_server) - timeout[0] = IOLoop.instance().add_timeout(datetime.timedelta(seconds=1), on_server) + timeout[0] = io_loop.add_timeout(datetime.timedelta(seconds=1), on_server) def on_server(data=None): - IOLoop.instance().remove_timeout(timeout[0]) + io_loop.remove_timeout(timeout[0]) if data: identifier = re.findall('Server: (.*)', data)[0].strip() diff --git a/motioneye/wsswitch.py b/motioneye/wsswitch.py index 59490d0..04b9f5b 100644 --- a/motioneye/wsswitch.py +++ b/motioneye/wsswitch.py @@ -16,9 +16,10 @@ # along with this program. If not, see . import datetime +import functools import logging -from tornado import ioloop, gen +from tornado.ioloop import IOLoop import config import motionctl @@ -26,7 +27,7 @@ import utils def start(): - io_loop = ioloop.IOLoop.instance() + io_loop = IOLoop.instance() io_loop.add_timeout(datetime.timedelta(seconds=1), _check_ws) @@ -70,15 +71,32 @@ def _during_working_schedule(now, working_schedule): return True -@gen.coroutine def _check_ws(): # schedule the next call - io_loop = ioloop.IOLoop.instance() + io_loop = IOLoop.instance() io_loop.add_timeout(datetime.timedelta(seconds=10), _check_ws) if not motionctl.running(): return + def on_motion_detection_status(camera_id, must_be_enabled, working_schedule_type, enabled=None, error=None): + if error: # could not detect current status + return logging.warn('skipping motion detection status update for camera with id %(id)s' % {'id': camera_id}) + + if enabled and not must_be_enabled: + logging.debug('must disable motion detection for camera with id %(id)s (%(what)s working schedule)' % { + 'id': camera_id, + 'what': working_schedule_type}) + + motionctl.set_motion_detection(camera_id, False) + + elif not enabled and must_be_enabled: + logging.debug('must enable motion detection for camera with id %(id)s (%(what)s working schedule)' % { + 'id': camera_id, + 'what': working_schedule_type}) + + motionctl.set_motion_detection(camera_id, True) + now = datetime.datetime.now() for camera_id in config.get_camera_ids(): camera_config = config.get_camera(camera_id) @@ -98,21 +116,5 @@ def _check_ws(): now_during = _during_working_schedule(now, working_schedule) must_be_enabled = (now_during and working_schedule_type == 'during') or (not now_during and working_schedule_type == 'outside') - currently_enabled = yield motionctl.get_motion_detection(camera_id) - if currently_enabled is None: # could not detect current status - logging.warn('skipping motion detection status update for camera with id %(id)s' % {'id': camera_id}) - continue - - if currently_enabled and not must_be_enabled: - logging.debug('must disable motion detection for camera with id %(id)s (%(what)s working schedule)' % { - 'id': camera_id, - 'what': working_schedule_type}) - - motionctl.set_motion_detection(camera_id, False) - - elif not currently_enabled and must_be_enabled: - logging.debug('must enable motion detection for camera with id %(id)s (%(what)s working schedule)' % { - 'id': camera_id, - 'what': working_schedule_type}) - - motionctl.set_motion_detection(camera_id, True) + motionctl.get_motion_detection(camera_id, functools.partial( + on_motion_detection_status, camera_id, must_be_enabled, working_schedule_type)) -- 2.39.5