sys.path.append(os.path.join(getattr(settings, 'PROJECT_PATH', os.path.dirname(sys.argv[0])), 'src'))
+import smbctl
+
VERSION = '0.12'
set_default_setting('LOG_LEVEL', logging.INFO)
set_default_setting('LISTEN', '0.0.0.0')
set_default_setting('PORT', 8765)
- set_default_setting('SYS_SETTINGS', False)
+ set_default_setting('SMB_SHARES', False)
+ set_default_setting('MOUNT_CHECK_INTERVAL', 300)
set_default_setting('MOTION_CHECK_INTERVAL', 10)
set_default_setting('CLEANUP_INTERVAL', 43200)
set_default_setting('THUMBNAILER_INTERVAL', 60)
def _test_requirements():
- if settings.SYS_SETTINGS and os.geteuid() != 0:
- print('SYS_SETTINGS require root privileges')
- return False
-
+ if os.geteuid() != 0:
+ if settings.SMB_SHARES:
+ print('SMB_SHARES require root privileges')
+ return False
+
try:
import tornado # @UnusedImport
tornado = True
import v4l2ctl
v4lutils = v4l2ctl.find_v4l2_ctl() is not None
- import smbctl
mount_cifs = smbctl.find_mount_cifs() is not None
ok = True
print('please install v4l-utils')
ok = False
- if settings.SYS_SETTINGS and not mount_cifs:
+ if settings.SMB_SHARES and not mount_cifs:
print('please install cifs-utils')
ok = False
def _configure_signals():
def bye_handler(signal, frame):
- import cleanup
- import motionctl
- import thumbnailer
import tornado.ioloop
logging.info('interrupt signal received, shutting down...')
# shut down the IO loop if it has been started
ioloop = tornado.ioloop.IOLoop.instance()
ioloop.stop()
- logging.info('server stopped')
-
- if thumbnailer.running():
- thumbnailer.stop()
- logging.info('thumbnailer stopped')
-
- if cleanup.running():
- cleanup.stop()
- logging.info('cleanup stopped')
-
- if motionctl.running():
- motionctl.stop()
- logging.info('motion stopped')
def child_handler(signal, frame):
# this is required for the multiprocessing mechanism to work
print('')
-def _start_server():
+def _run_server():
+ import cleanup
+ import motionctl
+ import thumbnailer
import tornado.ioloop
import server
tornado.ioloop.IOLoop.instance().start()
+ logging.info('server stopped')
+
+ if thumbnailer.running():
+ thumbnailer.stop()
+ logging.info('thumbnailer stopped')
+
+ if cleanup.running():
+ cleanup.stop()
+ logging.info('cleanup stopped')
+
+ if motionctl.running():
+ motionctl.stop()
+ logging.info('motion stopped')
+
+ if settings.SMB_SHARES:
+ smbctl.umount_all()
+ logging.info('SMB shares unmounted')
+
def _start_motion():
import tornado.ioloop
_configure_signals()
_configure_logging()
+ if settings.SMB_SHARES:
+ smbctl.update_mounts()
+
_start_motion()
_start_cleanup()
if settings.THUMBNAILER_INTERVAL:
_start_thumbnailer()
- _start_server()
+ _run_server()
# change the port according to your requirements/restrictions
PORT = 8765
-# enable system settings that require root (SMB shares, WiFI setup)
-SYS_SETTINGS = False
+# enable SMB shares (requires root)
+SMB_SHARES = False
+
+# 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
return bool([c for c in cameras if c['@enabled'] and c['@proto'] == 'v4l2'])
+def get_network_shares():
+ if not get_main().get('@enabled'):
+ return []
+
+ camera_ids = get_camera_ids()
+ cameras = [get_camera(camera_id) for camera_id in camera_ids]
+
+ mounts = []
+ for camera in cameras:
+ if camera['@storage_device'] != 'network-share':
+ continue
+
+ mounts.append({
+ 'server': camera['@network_server'],
+ 'share': camera['@network_share_name'],
+ 'username': camera['@network_username'],
+ 'password': camera['@network_password'],
+
+ })
+
+ return mounts
+
+
def get_camera(camera_id, as_lines=False):
global _camera_config_cache
else:
data['hue'] = max(1, int(round(int(ui['hue']) * 2.55)))
- if ui['storage_device'] == 'network-share':
+ if (ui['storage_device'] == 'network-share') and settings.SMB_SHARES:
mount_point = smbctl.make_mount_point(ui['network_server'], ui['network_share_name'], ui['network_username'])
if ui['root_directory'].startswith('/'):
ui['root_directory'] = ui['root_directory'][1:]
else:
ui['hue'] = 50
- if data['@storage_device'] == 'network-share':
+ if (data['@storage_device'] == 'network-share') and settings.SMB_SHARES:
mount_point = smbctl.make_mount_point(data['@network_server'], data['@network_share_name'], data['@network_username'])
ui['root_directory'] = data['target_dir'][len(mount_point):]
import motionctl
import remote
import settings
+import smbctl
import template
import update
import utils
class MainHandler(BaseHandler):
@BaseHandler.auth()
def get(self):
- self.render('main.html', sys_settings=getattr(settings, 'SYS_SETTINGS', False))
+ self.render('main.html', smb_shares=settings.SMB_SHARES)
class ConfigHandler(BaseHandler):
def finish():
if restart[0]:
+ if settings.SMB_SHARES:
+ logging.debug('updating SMB mounts')
+ smbctl.update_mounts()
+
logging.debug('motion needs to be restarted')
motionctl.restart()
camera_config['@id'] = camera_id
if proto == 'v4l2':
+ if settings.SMB_SHARES:
+ smbctl.update_mounts()
+
motionctl.restart()
ui_config = config.camera_dict_to_ui(camera_config)
# 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 datetime
import logging
import os
import re
import subprocess
import time
+import config
+import settings
+
+from tornado import ioloop
+
def find_mount_cifs():
try:
line = line.strip()
if not line:
continue
-
parts = line.split()
if len(parts) < 4:
continue
mount_point = make_mount_point(server, share, username)
logging.debug('mounting "//%s/%s" at "%s"' % (server, share, mount_point))
- logging.debug('making sure mount point "%s" exists' % mount_point)
- os.makedirs(mount_point)
+ logging.debug('making sure mount point "%s" exists' % mount_point)
+
+ if not os.path.exists(mount_point):
+ os.makedirs(mount_point)
if username:
opts = 'username=%s,password=%s' % (username, password)
logging.error('failed to unmount smb share "//%s/%s" from "%s"' % (server, share, mount_point))
return False
+
+
+def update_mounts():
+ network_shares = config.get_network_shares()
+
+ mounts = list_mounts()
+ mounts = dict(((m['server'], m['share'], m['username'] or ''), False) for m in mounts)
+
+ for network_share in network_shares:
+ key = (network_share['server'], network_share['share'], network_share['username'] or '')
+ if key in mounts: # found
+ mounts[key] = True
+
+ else: # needs to be mounted
+ mount(network_share['server'], network_share['share'], network_share['username'], network_share['password'])
+
+ # unmount the no longer necessary mounts
+ for (network_share['server'], network_share['share'], network_share['username']), required in mounts.items():
+ if not required:
+ umount(network_share['server'], network_share['share'], network_share['username'])
+
+
+def umount_all():
+ for mount in list_mounts():
+ umount(mount['server'], mount['share'], mount['username'])
+
+
+def _check_mounts():
+ logging.debug('checking SMB mounts...')
+
+ update_mounts()
+
+ io_loop = ioloop.IOLoop.instance()
+ io_loop.add_timeout(datetime.timedelta(seconds=settings.MOUNT_CHECK_INTERVAL), _check_mounts)
+
+
+if settings.SMB_SHARES:
+ # schedule the mount checker
+ io_loop = ioloop.IOLoop.instance()
+ io_loop.add_timeout(datetime.timedelta(seconds=settings.MOUNT_CHECK_INTERVAL), _check_mounts)
<div class="settings-section-title advanced-setting">File Storage</div>
<table class="settings advanced-setting">
- <tr class="settings-item advanced-setting {% if not sys_settings %}hidden{% endif %}">
+ <tr class="settings-item advanced-setting {% if not smb_shares %}hidden{% endif %}">
<td class="settings-item-label"><span class="settings-item-label">Storage Device</span></td>
<td class="settings-item-value">
<select class="styled storage" id="storageDeviceSelect">
</td>
<td><span class="help-mark" title="indicates the storage device where the image and video files will be saved">?</span></td>
</tr>
- <tr class="settings-item advanced-setting {% if not sys_settings %}hidden{% endif %}">
+ <tr class="settings-item advanced-setting {% if not smb_shares %}hidden{% endif %}">
<td class="settings-item-label"><span class="settings-item-label">Network Server</span></td>
<td class="settings-item-value"><input type="text" class="styled storage" id="networkServerEntry"></td>
<td><span class="help-mark" title="the address of the network server (IP address or hostname)">?</span></td>
</tr>
- <tr class="settings-item advanced-setting {% if not sys_settings %}hidden{% endif %}">
+ <tr class="settings-item advanced-setting {% if not smb_shares %}hidden{% endif %}">
<td class="settings-item-label"><span class="settings-item-label">Share Name</span></td>
<td class="settings-item-value"><input type="text" class="styled storage" id="networkShareNameEntry"></td>
<td><span class="help-mark" title="the name of the network share">?</span></td>
</tr>
- <tr class="settings-item advanced-setting {% if not sys_settings %}hidden{% endif %}">
+ <tr class="settings-item advanced-setting {% if not smb_shares %}hidden{% endif %}">
<td class="settings-item-label"><span class="settings-item-label">Share Username</span></td>
<td class="settings-item-value"><input type="text" class="styled storage" id="networkUsernameEntry"></td>
<td><span class="help-mark" title="the username to be supplied when accessing the network share (leave empty if no username is required)">?</span></td>
</tr>
- <tr class="settings-item advanced-setting {% if not sys_settings %}hidden{% endif %}">
+ <tr class="settings-item advanced-setting {% if not smb_shares %}hidden{% endif %}">
<td class="settings-item-label"><span class="settings-item-label">Share Password</span></td>
<td class="settings-item-value"><input type="password" class="styled storage" id="networkPasswordEntry"></td>
<td><span class="help-mark" title="the password required by the network share (leave empty if no password is required)">?</span></td>