]> www.vanbest.org Git - motioneye-debian/commitdiff
refactored for single script (meyectl)
authorCalin Crisan <ccrisan@gmail.com>
Sun, 23 Aug 2015 18:20:31 +0000 (21:20 +0300)
committerCalin Crisan <ccrisan@gmail.com>
Sun, 23 Aug 2015 19:07:50 +0000 (22:07 +0300)
extra/motioneye.conf.sample [new file with mode: 0644]
motioneye/config.py
motioneye/eventrelay.py [deleted file]
motioneye/meyectl.py [new file with mode: 0755]
motioneye/mjpgclient.py
motioneye/relayevent.py [new file with mode: 0644]
motioneye/sendmail.py [changed mode: 0755->0644]
motioneye/server.py
motioneye/settings.py
motioneye/webhook.py [changed mode: 0755->0644]

diff --git a/extra/motioneye.conf.sample b/extra/motioneye.conf.sample
new file mode 100644 (file)
index 0000000..98b3b4d
--- /dev/null
@@ -0,0 +1,81 @@
+
+# static files (.css, .js etc) are served at this root url;
+# change this if you run motionEye behind a reverse proxy (e.g. nginx),
+# and you want static files to be served directly by it
+#static_url '/static/'
+
+# path to the configuration directory (must be writable by motionEye)
+conf_path /etc/motioneye
+
+# path to the directory where pid files go (must be writable by motionEye)
+#run_path /var/run
+
+# path to the directory where log files go (must be writable by motionEye)
+#log_path /var/log
+
+# default output path for media files (must be writable by motionEye)
+#media_path /var/lib/motioneye
+
+# path to the motion binary to use (automatically detected by default)
+#motion_binary /usr/bin/motion
+
+# the log level (use quiet, error, warning, info or debug)
+#log_level info
+
+# the IP address to listen on
+# (0.0.0.0 for all interfaces, 127.0.0.1 for localhost)
+#listen 0.0.0.0
+
+# the TCP port to listen on
+#port 8765
+
+# interval in seconds at which motionEye checks the SMB mounts
+#mount_check_interval 300
+
+# interval in seconds at which motionEye checks if motion is running
+#motion_check_interval 10
+
+# interval in seconds at which the janitor is called
+# to remove old pictures and movies
+#cleanup_interval 43200
+
+# interval in seconds at which the thumbnail mechanism runs
+# (set to 0 to disable) 
+#thumbnailer_interval 60
+
+# timeout in seconds to wait for response from a remote motionEye server
+#remote_request_timeout 10
+
+# timeout in seconds to wait for mjpg data from the motion daemon
+#mjpg_client_timeout 10
+
+# timeout in seconds after which an idle mjpg client is removed
+# (set to 0 to disable)
+#mjpg_client_idle_timeout 10
+
+# enable SMB shares (requires motionEye to run as root) 
+#smb_shares false
+
+# the directory where the SMB mount points will be created
+#smb_mount_root /media
+
+# path to the wpa_supplicant.conf file
+# (enable this to configure wifi settings from the UI)
+#wpa_supplicant_conf /etc/wpa_supplicant.conf
+
+# path to the localtime file
+# (enable this to configure the system time zone from the UI)
+#local_time_file /etc/localtime
+
+# enables shutdown and rebooting after changing system settings
+# (such as wifi settings or time zone)
+#enable_reboot false
+
+# timeout in seconds to use when talking to the SMTP server
+#smtp_timeout 60
+
+# timeout in seconds to wait for zip file creation
+#zip_timeout 500
+
+# enable adding and removing cameras from UI
+#add_remove_cameras true
index 235d763e920782ff98d9cd274e1243dfa6df0acf..6d9c6711acac4126f8fb22caaa9c697e42a56e75 100644 (file)
@@ -507,7 +507,7 @@ def add_camera(device_details):
         if device_details['username']:
             camera_config['netcam_userpass'] = device_details['username'] + ':' + device_details['password']
         
-        camera_config['netcam_keepalive'] = device_details.get('keep_alive')
+        camera_config['netcam_keepalive'] = device_details.get('keep_alive', False)
         camera_config['netcam_tolerant_check'] = True
 
         if device_details.get('camera_index') == 'udp':
@@ -609,6 +609,7 @@ def main_dict_to_ui(data):
 
 
 def motion_camera_ui_to_dict(ui, old_config=None):
+    import meyectl
     import smbctl
     
     old_config = dict(old_config or {})
@@ -833,17 +834,14 @@ def motion_camera_ui_to_dict(ui, old_config=None):
         data['@working_schedule_type'] = ui['working_schedule_type']
     
     # event start
-    event_relay_path = os.path.join(settings.PROJECT_PATH, 'eventrelay.py')
-    event_relay_path = os.path.abspath(event_relay_path)
-        
-    on_event_start = ['%(script)s start %%t' % {'script': event_relay_path}]
+    on_event_start = ['%(script)s start %%t' % {'script': meyectl.find_command('relayevent')}]
     if ui['email_notifications_enabled']:
         send_mail_path = os.path.join(settings.PROJECT_PATH, 'sendmail.py')
         send_mail_path = os.path.abspath(send_mail_path)
         emails = re.sub('\\s', '', ui['email_notifications_addresses'])
         
         on_event_start.append("%(script)s '%(server)s' '%(port)s' '%(account)s' '%(password)s' '%(tls)s' '%(to)s' 'motion_start' '%%t' '%%Y-%%m-%%dT%%H:%%M:%%S' '%(timespan)s'" % {
-                'script': send_mail_path,
+                'script': meyectl.find_command('sendmail'),
                 'server': ui['email_notifications_smtp_server'],
                 'port': ui['email_notifications_smtp_port'],
                 'account': ui['email_notifications_smtp_account'],
@@ -853,12 +851,10 @@ def motion_camera_ui_to_dict(ui, old_config=None):
                 'timespan': ui['email_notifications_picture_time_span']})
 
     if ui['web_hook_notifications_enabled']:
-        web_hook_path = os.path.join(settings.PROJECT_PATH, 'webhook.py')
-        web_hook_path = os.path.abspath(web_hook_path)
         url = re.sub('\\s', '+', ui['web_hook_notifications_url'])
 
         on_event_start.append("%(script)s '%(method)s' '%(url)s'" % {
-                'script': web_hook_path,
+                'script': meyectl.find_command('webhook'),
                 'method': ui['web_hook_notifications_http_method'],
                 'url': url})
 
@@ -869,7 +865,7 @@ def motion_camera_ui_to_dict(ui, old_config=None):
     data['on_event_start'] = '; '.join(on_event_start)
 
     # event end
-    on_event_end = ['%(script)s stop %%t' % {'script': event_relay_path}]
+    on_event_end = ['%(script)s stop %%t' % {'script': meyectl.find_command('relayevent')}]
     
     data['on_event_end'] = '; '.join(on_event_end)
     
@@ -1156,34 +1152,36 @@ def motion_camera_dict_to_ui(data):
     ui['email_notifications_picture_time_span'] = 0
     command_notifications = []
     for e in on_event_start:
-        if e.count('sendmail.py') and e.count('motion_start'):
+        if e.count('sendmail'):
             e = shlex.split(e)
+
             if len(e) < 10:
                 continue
-
+            
             ui['email_notifications_enabled'] = True 
-            ui['email_notifications_smtp_server'] = e[1]
-            ui['email_notifications_smtp_port'] = e[2]
-            ui['email_notifications_smtp_account'] = e[3]
-            ui['email_notifications_smtp_password'] = e[4]
-            ui['email_notifications_smtp_tls'] = e[5].lower() == 'true'
-            ui['email_notifications_addresses'] = e[6]
+            ui['email_notifications_smtp_server'] = e[-10]
+            ui['email_notifications_smtp_port'] = e[-9]
+            ui['email_notifications_smtp_account'] = e[-8]
+            ui['email_notifications_smtp_password'] = e[-7]
+            ui['email_notifications_smtp_tls'] = e[-6].lower() == 'true'
+            ui['email_notifications_addresses'] = e[-5]
             try:
-                ui['email_notifications_picture_time_span'] = int(e[10])
+                ui['email_notifications_picture_time_span'] = int(e[-1])
                 
             except:
                 ui['email_notifications_picture_time_span'] = 0
 
-        elif e.count('webhook.py'):
+        elif e.count('webhook'):
             e = shlex.split(e)
-            if len(e) != 3:
+
+            if len(e) < 3:
                 continue
 
             ui['web_hook_notifications_enabled'] = True 
-            ui['web_hook_notifications_http_method'] = e[1]
-            ui['web_hook_notifications_url'] = e[2]
+            ui['web_hook_notifications_http_method'] = e[-1]
+            ui['web_hook_notifications_url'] = e[-1]
         
-        elif e.count('eventrelay.py'):
+        elif e.count('relayevent') or e.count('eventrelay.py'):
             continue # ignore internal relay script
 
         else: # custom command
diff --git a/motioneye/eventrelay.py b/motioneye/eventrelay.py
deleted file mode 100755 (executable)
index 42c02f3..0000000
+++ /dev/null
@@ -1,147 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright (c) 2013 Calin Crisan
-# This file is part of motionEye.
-#
-# motionEye is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-# 
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-# 
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>. 
-
-import errno
-import json
-import logging
-import os.path
-import sys
-import urllib
-
-sys.path.append(os.path.join(os.path.dirname(sys.argv[0]),'src'))
-
-import settings
-import utils
-
-from motioneye import _configure_settings, _configure_logging
-
-
-_configure_settings()
-_configure_logging(module='eventrelay')
-
-
-def print_usage():
-    print 'Usage: eventrelay.py <event> <thread_id>'
-
-
-def get_admin_credentials():
-    # this shortcut function is a bit faster than using the config module functions
-    config_file_path = os.path.join(settings.CONF_PATH, 'motion.conf')
-    
-    logging.debug('reading main config from file %(path)s...' % {'path': config_file_path}) 
-
-    lines = None
-    try:
-        file = open(config_file_path, 'r')
-    
-    except IOError as e:
-        if e.errno == errno.ENOENT:  # file does not exist
-            logging.info('main config file %(path)s does not exist, using default values' % {'path': config_file_path})
-            
-            lines = []
-        
-        else:
-            logging.error('could not open main config file %(path)s: %(msg)s' % {
-                    'path': config_file_path, 'msg': unicode(e)})
-            
-            raise
-
-    if lines is None:
-        try:
-            lines = [l[:-1] for l in file.readlines()]
-        
-        except Exception as e:
-            logging.error('could not read main config file %(path)s: %(msg)s' % {
-                    'path': config_file_path, 'msg': unicode(e)})
-            
-            raise
-        
-        finally:
-            file.close()
-    
-    admin_username = 'admin'
-    admin_password = ''
-    for line in lines:
-        line = line.strip()
-        if not line.startswith('#'):
-            continue
-        
-        line = line[1:].strip()
-        if line.startswith('@admin_username'):
-            parts = line.split(' ', 1)
-            admin_username = parts[1] if len(parts) > 1 else ''
-            
-            continue
-        
-        if line.startswith('@admin_password'):
-            parts = line.split(' ', 1)
-            admin_password = parts[1] if len(parts) > 1 else ''
-
-            continue
-    
-    return admin_username, admin_password
-
-
-# def compute_signature(method, uri, body, key):
-#     parts = list(urlparse.urlsplit(uri))
-#     query = [q for q in urlparse.parse_qsl(parts[3]) if (q[0] != 'signature')]
-#     query.sort(key=lambda q: q[0])
-#     query = urllib.urlencode(query)
-#     parts[0] = parts[1] = ''
-#     parts[3] = query
-#     uri = urlparse.urlunsplit(parts)
-#     
-#     return hashlib.sha1('%s:%s:%s:%s' % (method, uri, body or '', key)).hexdigest().lower()
-
-
-if __name__ == '__main__':
-    if len(sys.argv) < 3:
-        print_usage()
-        sys.exit(-1)
-    
-    event = sys.argv[1] 
-    thread_id = sys.argv[2]
-
-    logging.debug('hello!')
-    logging.debug('event = %s' % event)
-    logging.debug('thread_id = %s' % thread_id)
-    
-    admin_username, admin_password = get_admin_credentials()
-
-    uri = '/_relay_event/?event=%(event)s&thread_id=%(thread_id)s&_username=%(username)s' % {
-            'username': admin_username,
-            'thread_id': thread_id,
-            'event': event}
-    
-    signature = utils.compute_signature('POST', uri, '', admin_password)
-    
-    url = 'http://127.0.0.1:%(port)s' + uri + '&_signature=' + signature
-    url = url % {'port': settings.PORT}
-    
-    try:
-        response = urllib.urlopen(url, data='')
-        response = json.load(response)
-        if response.get('error'):
-            raise Exception(response['error'])
-        
-        logging.debug('event successfully relayed')
-    
-    except Exception as e:
-        logging.error('failed to relay event: %s' % e)
-
-    logging.debug('bye!')
diff --git a/motioneye/meyectl.py b/motioneye/meyectl.py
new file mode 100755 (executable)
index 0000000..cfa3320
--- /dev/null
@@ -0,0 +1,222 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2013 Calin Crisan
+# This file is part of motionEye.
+#
+# motionEye is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+# 
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import argparse
+import logging
+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__))))
+
+import relayevent
+import sendmail
+import server
+import settings
+import webhook
+
+
+_LOG_FILE = 'motioneye.log'
+
+
+def find_command(command):
+    cmd = os.path.abspath(sys.argv[0])
+    cmd += ' %s ' % command
+    cmd += ' '.join([pipes.quote(arg) for arg in sys.argv[2:]])
+    
+    return cmd
+
+
+def load_settings():
+    # parse common comnand line argumentss
+    config_file = None
+    debug = False
+
+    for i in xrange(1, len(sys.argv)):
+        arg = sys.argv[i]
+        next_arg = i < len(sys.argv) - 1 and sys.argv[i + 1]
+        if arg == '-c':
+            config_file = next_arg
+            
+        elif arg == '-d':
+            debug = True
+
+    def parse_conf_line(line):
+        line = line.strip()
+        if not line or line.startswith('#'):
+            return
+
+        parts = line.split(' ', 1)
+        if len(parts) != 2:
+            raise Exception('invalid configuration line: %s' % line)
+
+        name, value = parts
+        upper_name = name.upper().replace('-', '_')
+
+        if hasattr(settings, upper_name):
+            curr_value = getattr(settings, upper_name)
+
+            if upper_name == 'LOG_LEVEL':
+                if value == 'quiet':
+                    value = 100
+
+                else:
+                    value = getattr(logging, value.upper(), logging.DEBUG)
+
+            elif value.lower() == 'true':
+                value = True
+
+            elif value.lower() == 'false':
+                value = False
+
+            elif isinstance(curr_value, int):
+                value = int(value)
+
+            elif isinstance(curr_value, float):
+                value = float(value)
+
+            setattr(settings, upper_name, value)
+
+        else:
+            raise Exception('unknown configuration option: %s' % name)
+
+    if config_file:
+        try:
+            with open(config_file) as f:
+                for line in f:
+                    parse_conf_line(line)
+            
+        except Exception as e:
+            logging.fatal('failed to read settings from "%s": %s' % (config_file, e))
+            sys.exit(-1)
+            
+    else:
+        logging.info('no configuration file given, using built-in defaults')
+
+    if debug:
+        settings.LOG_LEVEL = logging.DEBUG
+
+
+def configure_logging(cmd, log_to_file=False):
+    format = '%(asctime)s: [{cmd}] %(levelname)s: %(message)s'.format(cmd=cmd)
+
+    for h in logging.getLogger().handlers:
+        logging.getLogger().removeHandler(h)
+        
+    try:
+        if log_to_file:
+            log_file = os.path.join(settings.LOG_PATH, _LOG_FILE)
+            
+        else:
+            log_file = None
+
+        logging.basicConfig(filename=log_file, level=settings.LOG_LEVEL,
+                format=format, datefmt='%Y-%m-%d %H:%M:%S')
+    
+    except Exception as e:
+        sys.stderr.write('failed to configure logging: %s\n' % e)
+        sys.exit(-1)
+
+    logging.getLogger('tornado').setLevel(logging.WARN)
+
+
+def configure_tornado():
+    AsyncHTTPClient.configure('tornado.curl_httpclient.CurlAsyncHTTPClient', max_clients=16)
+
+
+def make_arg_parser(command=None):
+    if command:
+        usage = description = epilog = None
+        
+    else:
+        usage = '%(prog)s [command] [-c CONFIG_FILE] [-d] [-v] [command options...]\n\n'
+        
+        description = 'available commands:\n'
+        description += '  startserver\n'
+        description += '  stopserver\n'
+        description += '  relayevent\n'
+        description += '  sendmail\n'
+        description += '  webhook\n\n'
+
+        epilog = 'type "%(prog)s [command] -h" for help on a specific command\n\n'
+        
+    parser = argparse.ArgumentParser(prog='meyectl%s' % ((' ' + command) if command else ''),
+            usage=usage, description=description, epilog=epilog,
+            add_help=False, formatter_class=argparse.RawTextHelpFormatter)
+
+    parser.add_argument('-c', help='use a config file instead of built-in defaults',
+            type=str, dest='config_file')
+    parser.add_argument('-d', help='enable debugging, overriding log level from config file',
+            action='store_true', dest='debug')
+    parser.add_argument('-h', help='print this help and exit',
+            action='help', default=argparse.SUPPRESS)
+    parser.add_argument('-v', help='print program version and exit',
+            action='version', default=argparse.SUPPRESS)
+
+    return parser
+
+
+def print_usage_and_exit(code):
+    parser = make_arg_parser()
+    parser.print_help(sys.stderr)
+
+    sys.exit(code)
+
+
+def print_version_and_exit():
+    import motioneye
+
+    sys.stderr.write('motionEye %s\n' % motioneye.VERSION)
+    sys.exit()
+
+
+def main():
+    for a in sys.argv:
+        if a == '-v':
+            print_version_and_exit()
+    
+    if len(sys.argv) < 2 or sys.argv[1] == '-h':
+        print_usage_and_exit(0)
+    
+    load_settings()
+
+    command = sys.argv[1]
+    arg_parser = make_arg_parser(command)
+
+    if command in ('startserver', 'stopserver'):
+        server.main(arg_parser, sys.argv[2:], command[:-6])
+
+    elif command == 'sendmail':
+        sendmail.main(arg_parser, sys.argv[2:])
+    
+    elif command == 'relayevent':
+        relayevent.main(arg_parser, sys.argv[2:])
+
+    elif command == 'webhook':
+        webhook.main(arg_parser, sys.argv[2:])
+
+    else:
+        sys.stderr.write('unknown command "%s"\n\n' % command)
+        print_usage_and_exit(-1)
+
+
+if __name__ == '__main__':
+    main()
index 088a9624448e507cb3048b1920f8e4a8a3456b95..093c3cdab0ae5a083b495dea7ed1c7ed7b44ff46 100644 (file)
@@ -77,6 +77,9 @@ class MjpgClient(iostream.IOStream):
                 motionctl.start(deferred=True)
  
             MjpgClient.last_erroneous_close_time = now
+        
+        # remove the cached picture
+        MjpgClient.last_jpgs.pop(self._camera_id, None)
 
     def _check_error(self):
         if self.socket is None:
diff --git a/motioneye/relayevent.py b/motioneye/relayevent.py
new file mode 100644 (file)
index 0000000..2a91ba5
--- /dev/null
@@ -0,0 +1,131 @@
+
+# Copyright (c) 2013 Calin Crisan
+# This file is part of motionEye.
+#
+# motionEye is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+# 
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>. 
+
+import errno
+import json
+import logging
+import os.path
+import sys
+import urllib
+
+sys.path.append(os.path.join(os.path.dirname(sys.argv[0]),'src'))
+
+import settings
+import utils
+
+
+def get_admin_credentials():
+    # this shortcut function is a bit faster than using the config module functions
+    config_file_path = os.path.join(settings.CONF_PATH, 'motion.conf')
+    
+    logging.debug('reading main config from file %(path)s...' % {'path': config_file_path}) 
+
+    lines = None
+    try:
+        file = open(config_file_path, 'r')
+    
+    except IOError as e:
+        if e.errno == errno.ENOENT:  # file does not exist
+            logging.info('main config file %(path)s does not exist, using default values' % {'path': config_file_path})
+            
+            lines = []
+        
+        else:
+            logging.error('could not open main config file %(path)s: %(msg)s' % {
+                    'path': config_file_path, 'msg': unicode(e)})
+            
+            raise
+
+    if lines is None:
+        try:
+            lines = [l[:-1] for l in file.readlines()]
+        
+        except Exception as e:
+            logging.error('could not read main config file %(path)s: %(msg)s' % {
+                    'path': config_file_path, 'msg': unicode(e)})
+            
+            raise
+        
+        finally:
+            file.close()
+    
+    admin_username = 'admin'
+    admin_password = ''
+    for line in lines:
+        line = line.strip()
+        if not line.startswith('#'):
+            continue
+        
+        line = line[1:].strip()
+        if line.startswith('@admin_username'):
+            parts = line.split(' ', 1)
+            admin_username = parts[1] if len(parts) > 1 else ''
+            
+            continue
+        
+        if line.startswith('@admin_password'):
+            parts = line.split(' ', 1)
+            admin_password = parts[1] if len(parts) > 1 else ''
+
+            continue
+    
+    return admin_username, admin_password
+
+
+def parse_options(parser, args):
+    parser.add_argument('event', help='the name of the event to relay')
+    parser.add_argument('thread_id', help='the id of the thread')
+
+    return parser.parse_args(args)
+    
+
+def main(parser, args):
+    import meyectl
+    
+    options = parse_options(parser, args)
+    
+    meyectl.configure_logging('relayevent')
+    meyectl.configure_tornado()
+
+    logging.debug('hello!')
+    logging.debug('event = %s' % options.event)
+    logging.debug('thread_id = %s' % options.thread_id)
+    
+    admin_username, admin_password = get_admin_credentials()
+
+    uri = '/_relay_event/?event=%(event)s&thread_id=%(thread_id)s&_username=%(username)s' % {
+            'username': admin_username,
+            'thread_id': options.thread_id,
+            'event': options.event}
+    
+    signature = utils.compute_signature('POST', uri, '', admin_password)
+    
+    url = 'http://127.0.0.1:%(port)s' + uri + '&_signature=' + signature
+    url = url % {'port': settings.PORT}
+    
+    try:
+        response = urllib.urlopen(url, data='')
+        response = json.load(response)
+        if response.get('error'):
+            raise Exception(response['error'])
+        
+        logging.debug('event successfully relayed')
+    
+    except Exception as e:
+        logging.error('failed to relay event: %s' % e)
+
+    logging.debug('bye!')
old mode 100755 (executable)
new mode 100644 (file)
index 4844c08..df59222
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
 
 # Copyright (c) 2013 Calin Crisan
 # This file is part of motionEye.
@@ -22,7 +21,6 @@ import os
 import re
 import smtplib
 import socket
-import sys
 import time
 
 from email import Encoders
@@ -33,12 +31,6 @@ from tornado.ioloop import IOLoop
 
 import settings
 
-from motioneye import _configure_settings, _configure_logging, _configure_signals
-
-_configure_settings()
-_configure_signals()
-_configure_logging(module='sendmail')
-
 import config
 import mediafiles
 import tzctl
@@ -54,7 +46,7 @@ subjects = {
 
 
 def send_mail(server, port, account, password, tls, to, subject, message, files):
-    conn = smtplib.SMTP(server, port, timeout=getattr(settings, 'SMTP_TIMEOUT', 60))
+    conn = smtplib.SMTP(server, port, timeout=settings.SMTP_TIMEOUT)
     if tls:
         conn.starttls()
     
@@ -130,67 +122,62 @@ def make_message(subject, message, camera_id, moment, timespan, callback):
     mediafiles.list_media(camera_config, media_type='picture', callback=on_media_files)
 
 
-def print_usage():
-    print 'Usage: sendmail.py <server> <port> <account> <password> <tls> <to> <msg_id> <camera_id> <moment> <frames> [timespan]'
-
+def parse_options(parser, args):
+    parser.add_argument('server', help='address of the SMTP server')
+    parser.add_argument('port', help='port for the SMTP connection')
+    parser.add_argument('account', help='SMTP account name (username)')
+    parser.add_argument('password', help='SMTP account password')
+    parser.add_argument('tls', help='"true" to use TLS')
+    parser.add_argument('to', help='the email recipient(s)')
+    parser.add_argument('msg_id', help='the identifier of the message')
+    parser.add_argument('camera_id', help='the id of the camera')
+    parser.add_argument('moment', help='the moment in ISO-8601 format')
+    parser.add_argument('timespan', help='picture collection time span')
 
-if __name__ == '__main__':
-    if len(sys.argv) < 10:
-        print_usage()
-        sys.exit(-1)
+    return parser.parse_args(args)
     
-    server = sys.argv[1]
-    port = int(sys.argv[2]) 
-    account = sys.argv[3]
-    password = sys.argv[4]
-    tls = sys.argv[5].lower() == 'true'
-    to = sys.argv[6]
-    msg_id = sys.argv[7]
-    camera_id = sys.argv[8]
-    moment = sys.argv[9]
-    try:
-        timespan = int(sys.argv[10])
 
-    except:
-        timespan = 0
+def main(parser, args):
+    import meyectl
+    
+    options = parse_options(parser, args)
+    
+    meyectl.configure_logging('sendmail')
+    meyectl.configure_tornado()
 
     logging.debug('hello!')
 
-    message = messages.get(msg_id)
-    subject = subjects.get(msg_id)
-    if not message or not subject:
-        logging.error('unknown message id')
-        sys.exit(-1)
+    options.port = int(options.port) 
+    options.tls = options.tls.lower() == 'true'
+    options.timespan = int(options.timespan)
+    message = messages.get(options.msg_id)
+    subject = subjects.get(options.msg_id)
+    options.moment = datetime.datetime.strptime(options.moment, '%Y-%m-%dT%H:%M:%S')
     
-    moment = datetime.datetime.strptime(moment, '%Y-%m-%dT%H:%M:%S')
-    
-    logging.debug('server = %s' % server)
-    logging.debug('port = %s' % port)
-    logging.debug('account = %s' % account)
+    logging.debug('server = %s' % options.server)
+    logging.debug('port = %s' % options.port)
+    logging.debug('account = %s' % options.account)
     logging.debug('password = ******')
-    logging.debug('server = %s' % server)
-    logging.debug('tls = %s' % tls)
-    logging.debug('to = %s' % to)
-    logging.debug('msg_id = %s' % msg_id)
-    logging.debug('camera_id = %s' % camera_id)
-    logging.debug('moment = %s' % moment.strftime('%Y-%m-%d %H:%M:%S'))
+    logging.debug('server = %s' % options.server)
+    logging.debug('tls = %s' % str(options.tls).lower())
+    logging.debug('to = %s' % options.to)
+    logging.debug('msg_id = %s' % options.msg_id)
+    logging.debug('camera_id = %s' % options.camera_id)
+    logging.debug('moment = %s' % options.moment.strftime('%Y-%m-%d %H:%M:%S'))
     logging.debug('smtp timeout = %d' % settings.SMTP_TIMEOUT)
-    logging.debug('timespan = %d' % timespan)
+    logging.debug('timespan = %d' % options.timespan)
     
-    if not to:
-        logging.info('no email address specified')
-        sys.exit(0)
-
-    to = [t.strip() for t in re.split('[,;| ]', to)]
+    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(server, port, account, password, tls, to, subject, message, files)
+            send_mail(options.server, options.port, options.account, options.password,
+                    options.tls, options.to, subject, message, files)
             logging.info('email sent')
-        
+
         except Exception as e:
             logging.error('failed to send mail: %s' % e, exc_info=True)
 
@@ -199,9 +186,9 @@ if __name__ == '__main__':
     def ioloop_timeout():
         io_loop.stop()
     
-    make_message(subject, message, camera_id, moment, timespan, on_message)
+    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!')
\ No newline at end of file
+    logging.debug('bye!')
index 76b385bbb49d3ca9490541a0dbc9eda9260450c9..3913873d03b6e5824838ae43337498a58cd2d0a1 100644 (file)
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+import argparse
+import atexit
 import datetime
+import logging
 import multiprocessing
-import os.path
+import os
 import signal
-import sys 
+import sys
+import time
 
-from tornado.httpclient import AsyncHTTPClient
 from tornado.web import Application
 
 import handlers
-import logging
 import settings
 import template
 
 
-def load_settings():
-    # TODO use optparse
-#     length = len(sys.argv) - 1
-#     for i in xrange(length):
-#         arg = sys.argv[i + 1]
-#         
-#         if not arg.startswith('--'):
-#             continue
-#         
-#         next_arg = None
-#         if i < length - 1:
-#             next_arg = sys.argv[i + 2]
-#         
-#         name = arg[2:].upper().replace('-', '_')
-#         
-#         if hasattr(settings, name):
-#             curr_value = getattr(settings, name)
-#             
-#             if next_arg.lower() == 'debug':
-#                 next_arg = logging.DEBUG
-#             
-#             elif next_arg.lower() == 'info':
-#                 next_arg = logging.INFO
-#             
-#             elif next_arg.lower() == 'warn':
-#                 next_arg = logging.WARN
-#             
-#             elif next_arg.lower() == 'error':
-#                 next_arg = logging.ERROR
-#             
-#             elif next_arg.lower() == 'fatal':
-#                 next_arg = logging.FATAL
-#             
-#             elif next_arg.lower() == 'true':
-#                 next_arg = True
-#             
-#             elif next_arg.lower() == 'false':
-#                 next_arg = False
-#             
-#             elif isinstance(curr_value, int):
-#                 next_arg = int(next_arg)
-#             
-#             elif isinstance(curr_value, float):
-#                 next_arg = float(next_arg)
-# 
-#             setattr(settings, name, next_arg)
-#         
-#         else:
-#             return arg[2:]
+_PID_FILE = 'motioneye.pid'
 
-    if not os.path.exists(settings.CONF_PATH):
-        logging.fatal('config directory "%s" does not exist' % settings.CONF_PATH)
-        sys.exit(-1)
+
+class Daemon(object):
+    def __init__(self, pid_file, run_callback=None):
+        self.pid_file = pid_file
+        self.run_callback = run_callback
+
+    def daemonize(self):
+        # first fork
+        try: 
+            if os.fork() > 0: # parent
+                sys.exit(0)
+
+        except OSError, e: 
+            sys.stderr.write('fork() failed: %s\n' % e.strerror)
+            sys.exit(-1)
+
+        # separate from parent
+        os.chdir('/') 
+        os.setsid()
+        os.umask(0) 
+
+        # second fork
+        try: 
+            if os.fork() > 0: # parent 
+                sys.exit(0) 
+        
+        except OSError, e: 
+            sys.stderr.write('fork() failed: %s\n' % e.strerror)
+            sys.exit(-1) 
+
+        # redirect standard file descriptors
+        sys.stdout.flush()
+        sys.stderr.flush()
+        si = file('/dev/null', 'r')
+        so = file('/dev/null', 'a+')
+        se = file('/dev/null', 'a+', 0)
+        os.dup2(si.fileno(), sys.stdin.fileno())
+        os.dup2(so.fileno(), sys.stdout.fileno())
+        os.dup2(se.fileno(), sys.stderr.fileno())
+
+        # pid file
+        atexit.register(self.del_pid)
+        with open(self.pid_file, 'w') as f:
+            f.write('%s\n' % os.getpid())
+
+    def del_pid(self):
+        try:
+            os.remove(self.pid_file)
+        
+        except:
+            pass
     
-    if not os.path.exists(settings.RUN_PATH):
-        logging.fatal('pid directory "%s" does not exist' % settings.RUN_PATH)
-        sys.exit(-1)
+    def running(self):
+        try:
+            with open(self.pid_file) as f:
+                pid = int(f.read().strip())
 
-    if not os.path.exists(settings.LOG_PATH):
-        logging.fatal('log directory "%s" does not exist' % settings.LOG_PATH)
-        sys.exit(-1)
+        except:
+            return None
 
-    if not os.path.exists(settings.MEDIA_PATH):
-        logging.fatal('media directory "%s" does not exist' % settings.MEDIA_PATH)
-        sys.exit(-1)
+        try:
+            os.kill(pid, 0)
+            return pid
+        
+        except:
+            return None
+
+    def start(self):
+        if self.running():
+            sys.stderr.write('server is already running\n')
+            sys.exit(-1)
+
+        self.daemonize()
+        sys.stdout.write('server started\n')
+        self.run_callback()
+
+    def stop(self):
+        pid = self.running()
+        if not pid:
+            sys.stderr.write('server is not running\n')
+            sys.exit(-1)
+
+        try:
+            os.kill(pid, signal.SIGTERM)
+        
+        except Exception as e:
+            sys.stderr.write('failed to terminate server: %s\n' % e)
+
+        for i in xrange(50):  # @UnusedVariable
+            try:
+                os.kill(pid, 0)
+                time.sleep(0.1)
+
+            except OSError as e:
+                if str(e).count('No such process'):
+                    self.del_pid()
+                    sys.stdout.write('server stopped\n')
+                    break
+
+                else:
+                    sys.stderr.write('failed to terminate server: %s\n' % e)
+                    sys.exit(-11)
+        
+        else:
+            sys.stderr.write('server failed to stop, killing it\n')
+            try:
+                os.kill(pid, signal.SIGKILL)
+
+            except:
+                pass
+
+
+def _log_request(handler):
+    if handler.get_status() < 400:
+        log_method = logging.debug
+    
+    elif handler.get_status() < 500:
+        log_method = logging.warning
+    
+    else:
+        log_method = logging.error
+    
+    request_time = 1000.0 * handler.request.request_time()
+    log_method("%d %s %.2fms", handler.get_status(),
+               handler._request_summary(), request_time)
+
+application = Application(
+    [
+        (r'^/$', handlers.MainHandler),
+        (r'^/config/main/(?P<op>set|get)/?$', handlers.ConfigHandler),
+        (r'^/config/(?P<camera_id>\d+)/(?P<op>get|set|rem|set_preview)/?$', handlers.ConfigHandler),
+        (r'^/config/(?P<op>add|list|backup|restore)/?$', handlers.ConfigHandler),
+        (r'^/picture/(?P<camera_id>\d+)/(?P<op>current|list|frame)/?$', handlers.PictureHandler),
+        (r'^/picture/(?P<camera_id>\d+)/(?P<op>download|preview|delete)/(?P<filename>.+?)/?$', handlers.PictureHandler),
+        (r'^/picture/(?P<camera_id>\d+)/(?P<op>zipped|timelapse|delete_all)/(?P<group>.+?)/?$', handlers.PictureHandler),
+        (r'^/movie/(?P<camera_id>\d+)/(?P<op>list)/?$', handlers.MovieHandler),
+        (r'^/movie/(?P<camera_id>\d+)/(?P<op>download|preview|delete)/(?P<filename>.+?)/?$', handlers.MovieHandler),
+        (r'^/movie/(?P<camera_id>\d+)/(?P<op>delete_all)/(?P<group>.+?)/?$', handlers.MovieHandler),
+        (r'^/_relay_event/?$', handlers.RelayEventHandler),
+        (r'^/log/(?P<name>\w+)/?$', handlers.LogHandler),
+        (r'^/update/?$', handlers.UpdateHandler),
+        (r'^/power/(?P<op>shutdown|reboot)/?$', handlers.PowerHandler),
+        (r'^/version/?$', handlers.VersionHandler),
+        (r'^/login/?$', handlers.LoginHandler),
+        (r'^.*$', handlers.NotFoundHandler),
+    ],
+    debug=False,
+    log_function=_log_request,
+    static_path=settings.STATIC_PATH,
+    static_url_prefix=settings.STATIC_URL
+)
+
+template.add_context('STATIC_URL', settings.STATIC_URL)
 
 
 def configure_signals():
@@ -116,24 +208,23 @@ def configure_signals():
     signal.signal(signal.SIGCHLD, child_handler)
 
 
-def configure_logging(module=None):
-    if module:
-        format = '%(asctime)s: [{module}] %(levelname)s: %(message)s'.format(module=module)
-        
-    else:
-        format = '%(asctime)s: %(levelname)s: %(message)s'
-
-    logging.basicConfig(filename=None, level=settings.LOG_LEVEL,
-            format=format, datefmt='%Y-%m-%d %H:%M:%S')
-
-    logging.getLogger('tornado').setLevel(logging.WARN)
-
+def test_requirements():
+    if not os.access(settings.CONF_PATH, os.W_OK):
+        logging.fatal('config directory "%s" does not exist or is not writable' % settings.CONF_PATH)
+        sys.exit(-1)
+    
+    if not os.access(settings.RUN_PATH, os.W_OK):
+        logging.fatal('pid directory "%s" does not exist or is not writable' % settings.RUN_PATH)
+        sys.exit(-1)
 
-def configure_tornado():
-    AsyncHTTPClient.configure('tornado.curl_httpclient.CurlAsyncHTTPClient', max_clients=16)
+    if not os.access(settings.LOG_PATH, os.W_OK):
+        logging.fatal('log directory "%s" does not exist or is not writable' % settings.LOG_PATH)
+        sys.exit(-1)
 
+    if not os.access(settings.MEDIA_PATH, os.W_OK):
+        logging.fatal('media directory "%s" does not exist or is not writable' % settings.MEDIA_PATH)
+        sys.exit(-1)
 
-def test_requirements():
     if os.geteuid() != 0:
         if settings.SMB_SHARES:
             print('SMB_SHARES require root privileges')
@@ -244,12 +335,40 @@ def start_thumbnailer():
     logging.info('thumbnailer started')
 
 
-def run_server():
+def parse_options(parser, args):
+    parser.add_argument('-b', help='start the server in background (daemonize)',
+            action='store_true', dest='background', default=False)
+
+    return parser.parse_args(args)
+
+
+def run():
     import cleanup
     import motionctl
+    import motioneye
+    import smbctl
     import thumbnailer
     import tornado.ioloop
-    import smbctl
+
+    configure_signals()
+    test_requirements()
+
+    logging.info('hello! this is motionEye server %s' % motioneye.VERSION)
+
+    if settings.SMB_SHARES:
+
+        stop, start = smbctl.update_mounts()  # @UnusedVariable
+        if start:
+            start_motion()
+
+    else:
+        start_motion()
+
+    start_cleanup()
+    start_wsswitch()
+
+    if settings.THUMBNAILER_INTERVAL:
+        start_thumbnailer()
 
     application.listen(settings.PORT, settings.LISTEN)
     logging.info('server started')
@@ -274,78 +393,27 @@ def run_server():
         smbctl.umount_all()
         logging.info('SMB shares unmounted')
 
-
-def log_request(handler):
-    if handler.get_status() < 400:
-        log_method = logging.debug
-    
-    elif handler.get_status() < 500:
-        log_method = logging.warning
-    
-    else:
-        log_method = logging.error
-    
-    request_time = 1000.0 * handler.request.request_time()
-    log_method("%d %s %.2fms", handler.get_status(),
-               handler._request_summary(), request_time)
-
-
-application = Application(
-    [
-        (r'^/$', handlers.MainHandler),
-        (r'^/config/main/(?P<op>set|get)/?$', handlers.ConfigHandler),
-        (r'^/config/(?P<camera_id>\d+)/(?P<op>get|set|rem|set_preview)/?$', handlers.ConfigHandler),
-        (r'^/config/(?P<op>add|list|backup|restore)/?$', handlers.ConfigHandler),
-        (r'^/picture/(?P<camera_id>\d+)/(?P<op>current|list|frame)/?$', handlers.PictureHandler),
-        (r'^/picture/(?P<camera_id>\d+)/(?P<op>download|preview|delete)/(?P<filename>.+?)/?$', handlers.PictureHandler),
-        (r'^/picture/(?P<camera_id>\d+)/(?P<op>zipped|timelapse|delete_all)/(?P<group>.+?)/?$', handlers.PictureHandler),
-        (r'^/movie/(?P<camera_id>\d+)/(?P<op>list)/?$', handlers.MovieHandler),
-        (r'^/movie/(?P<camera_id>\d+)/(?P<op>download|preview|delete)/(?P<filename>.+?)/?$', handlers.MovieHandler),
-        (r'^/movie/(?P<camera_id>\d+)/(?P<op>delete_all)/(?P<group>.+?)/?$', handlers.MovieHandler),
-        (r'^/_relay_event/?$', handlers.RelayEventHandler),
-        (r'^/log/(?P<name>\w+)/?$', handlers.LogHandler),
-        (r'^/update/?$', handlers.UpdateHandler),
-        (r'^/power/(?P<op>shutdown|reboot)/?$', handlers.PowerHandler),
-        (r'^/version/?$', handlers.VersionHandler),
-        (r'^/login/?$', handlers.LoginHandler),
-        (r'^.*$', handlers.NotFoundHandler),
-    ],
-    debug=False,
-    log_function=log_request,
-    static_path=settings.STATIC_PATH,
-    static_url_prefix=settings.STATIC_URL
-)
-
-template.add_context('STATIC_URL', settings.STATIC_URL)
+    logging.info('bye!')
 
 
-def main():
-    import motioneye
+def main(parser, args, command):
+    import meyectl
     
-    load_settings()
-    configure_signals()
-    configure_logging()
-    test_requirements()
-    configure_tornado()
-
-    logging.info('hello! this is motionEye %s' % motioneye.VERSION)
+    options = parse_options(parser, args)
     
-    if settings.SMB_SHARES:
-        import smbctl
-
-        stop, start = smbctl.update_mounts()  # @UnusedVariable
-        if start:
-            start_motion()
-
-    else:
-        start_motion()
-
-    start_cleanup()
-    start_wsswitch()
-
-    if settings.THUMBNAILER_INTERVAL:
-        start_thumbnailer()
-
-    run_server()
+    meyectl.configure_logging('motioneye', options.background)
+    meyectl.configure_tornado()
+
+    if command == 'start':
+        if options.background:
+            daemon = Daemon(
+                    pid_file=os.path.join(settings.RUN_PATH, _PID_FILE),
+                    run_callback=run)
+            daemon.start()
+            
+        else:
+            run()
 
-    logging.info('bye!')
+    elif command == 'stop':
+        daemon = Daemon(pid_file=os.path.join(settings.RUN_PATH, _PID_FILE))
+        daemon.stop()
index 9e7f68b8d2056f962ed9a7a014ddd209a294324b..1a5fa83f382cc3fecc2530e567063b37bd4f7921 100644 (file)
@@ -14,13 +14,15 @@ TEMPLATE_PATH = os.path.join(PROJECT_PATH, 'templates')
 # the static files directory
 STATIC_PATH = os.path.join(PROJECT_PATH, 'static')
 
-# static files (.css, .js etc) are served at this root url
+# static files (.css, .js etc) are served at this root url;
+# change this if you run motionEye behind a reverse proxy (e.g. nginx),
+# and you want static files to be served directly by it
 STATIC_URL = '/static/'
 
-# path to the config directory; must be writable
+# path to the configuration directory (must be writable by motionEye)
 CONF_PATH = [sys.prefix, ''][sys.prefix == '/usr']  + '/etc/motioneye'
 
-# pid files go here
+# path to the directory where pid files go (must be writable by motionEye)
 for d in ['/run', '/var/run', '/tmp', '/var/tmp']:
     if os.path.exists(d):
         RUN_PATH = d
@@ -29,7 +31,7 @@ for d in ['/run', '/var/run', '/tmp', '/var/tmp']:
 else:
     RUN_PATH = PROJECT_PATH
 
-# log files go here
+# path to the directory where log files go (must be writable by motionEye)
 for d in ['/log', '/var/log', '/tmp', '/var/tmp']:
     if os.path.exists(d):
         LOG_PATH = d
@@ -38,16 +40,17 @@ for d in ['/log', '/var/log', '/tmp', '/var/tmp']:
 else:
     LOG_PATH = RUN_PATH
 
-# default output path for media files
+# default output path for media files (must be writable by motionEye)
 MEDIA_PATH = RUN_PATH
 
-# path to motion binary (automatically detected if not set)
+# path to the motion binary to use (automatically detected by default)
 MOTION_BINARY = None
 
-# the log level
+# the log level (use FATAL, ERROR, WARNING, INFO or DEBUG)
 LOG_LEVEL = logging.INFO
 
-# IP addresses to listen on
+# the IP address to listen on
+# (0.0.0.0 for all interfaces, 127.0.0.1 for localhost)
 LISTEN = '0.0.0.0'
 
 # the TCP port to listen on
@@ -59,40 +62,46 @@ MOUNT_CHECK_INTERVAL = 300
 # interval in seconds at which motionEye checks if motion is running
 MOTION_CHECK_INTERVAL = 10
 
-# interval in seconds at which the janitor is called to remove old pictures and movies
+# interval in seconds at which the janitor is called
+# to remove old pictures and movies
 CLEANUP_INTERVAL = 43200
 
-# interval in seconds at which the thumbnail mechanism runs (set to 0 to disable) 
+# interval in seconds at which the thumbnail mechanism runs
+# (set to 0 to disable) 
 THUMBNAILER_INTERVAL = 60
 
-# timeout in seconds when waiting for response from a remote motionEye server
+# timeout in seconds to wait for response from a remote motionEye server
 REMOTE_REQUEST_TIMEOUT = 10
 
-# timeout in seconds when waiting for mjpg data from the motion daemon
+# timeout in seconds to wait for mjpg data from the motion daemon
 MJPG_CLIENT_TIMEOUT = 10
 
-# timeout in seconds after which an idle mjpg client is removed (set to 0 to disable)
+# timeout in seconds after which an idle mjpg client is removed
+# (set to 0 to disable)
 MJPG_CLIENT_IDLE_TIMEOUT = 10
 
-# enable SMB shares (requires root) 
+# enable SMB shares (requires motionEye to run as root) 
 SMB_SHARES = False
 
-# the directory where the SMB mounts will be created
+# the directory where the SMB mount points will be created
 SMB_MOUNT_ROOT = '/media'
 
-# path to a wpa_supplicant.conf file if wifi settings UI is desired
+# path to the wpa_supplicant.conf file
+# (enable this to configure wifi settings from the UI)
 WPA_SUPPLICANT_CONF = None
 
-# path to a localtime file if time zone settings UI is desired
+# path to the localtime file
+# (enable this to configure the system time zone from the UI)
 LOCAL_TIME_FILE = None
 
-# enables shutdown and rebooting after changing system settings (such as wifi settings or system updates)
+# enables shutdown and rebooting after changing system settings
+# (such as wifi settings or time zone)
 ENABLE_REBOOT = False
 
-# the timeout in seconds to use when talking to a SMTP server
+# timeout in seconds to use when talking to the SMTP server
 SMTP_TIMEOUT = 60
 
-# the time to wait for zip file creation
+# timeout in seconds to wait for zip file creation
 ZIP_TIMEOUT = 500
 
 # enable adding and removing cameras from UI
old mode 100755 (executable)
new mode 100644 (file)
index 016a0db..2767c6c
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
 
 # Copyright (c) 2013 Calin Crisan
 # This file is part of motionEye.
 # along with this program.  If not, see <http://www.gnu.org/licenses/>. 
 
 import logging
-import sys
 import urllib2
 import urlparse
 
 import settings
 
-from motioneye import _configure_settings, _configure_logging
 
+def parse_options(parser, args):
+    parser.add_argument('method', help='the HTTP method to use')
+    parser.add_argument('url', help='the URL for the request')
 
-_configure_settings()
-_configure_logging()
+    return parser.parse_args(args)
 
 
-def print_usage():
-    print 'Usage: webhook.py <method> <url>'
-
-
-if __name__ == '__main__':
-    if len(sys.argv) < 3:
-        print_usage()
-        sys.exit(-1)
+def main(parser, args):
+    import meyectl
     
-    method = sys.argv[1] 
-    url = sys.argv[2]
+    options = parse_options(parser, args)
+    
+    meyectl.configure_logging('webhook')
+    meyectl.configure_tornado()
 
-    logging.debug('method = %s' % method)
-    logging.debug('url = %s' % url)
+    logging.debug('hello!')
+    logging.debug('method = %s' % options.method)
+    logging.debug('url = %s' % options.url)
     
-    if method == 'POST':
-        parts = urlparse.urlparse(url)
+    if options.method == 'POST':
+        parts = urlparse.urlparse(options.url)
         data = parts.query
 
     else:
         data = None
 
-    request = urllib2.Request(url, data)
+    request = urllib2.Request(options.url, data)
     try:
         urllib2.urlopen(request, timeout=settings.REMOTE_REQUEST_TIMEOUT)
         logging.debug('webhook successfully called')
     
     except Exception as e:
         logging.error('failed to call webhook: %s' % e)
+
+    logging.debug('bye!')