+-> implement file preserving
+
 -> style scroll bars
 -> hint text next to section titles
 -> clickable hints
 -> add a previewer for snapshots
 -> add a motioneye.svg icon
 
--> implement file preserving
 -> add other options applicable only to special devices (rpi): wifi settings, notifications
 -> group @config rules to top
 
 -> browser compatibility test
 -> requirements test
 
--> add a motion running status indicator (and maybe a start/stop button)
 -> other todos
 
             logging.info('motion started')
 
         ioloop = tornado.ioloop.IOLoop.instance()
-        ioloop.add_timeout(datetime.timedelta(seconds=10), checker)
+        ioloop.add_timeout(datetime.timedelta(seconds=settings.MOTION_CHECK_INTERVAL), checker)
     
     checker()
 
 
+def _start_cleanup():
+    import cleanup
+
+    def do_cleanup():
+        cleanup.cleanup_images()
+        cleanup.cleanup_movies()
+        
+        ioloop = tornado.ioloop.IOLoop.instance()
+        ioloop.add_timeout(datetime.timedelta(seconds=settings.CLEANUP_INTERVAL), do_cleanup)
+
+    do_cleanup()
+
+
 if __name__ == '__main__':
     _configure_settings()
     _configure_signals()
     _configure_logging()
     _start_motion()
+    _start_cleanup()
     _start_server()
 
 
 LISTEN = '0.0.0.0'
 PORT = 8765
+
+MOTION_CHECK_INTERVAL = 10
+CLEANUP_INTERVAL = 10
 
--- /dev/null
+
+import datetime
+import logging
+import os
+
+import config
+
+
+def _remove_older_files(dir, moment):
+    for name in os.listdir(dir):
+        full_path = os.path.join(dir, name)
+        if not os.path.isfile(full_path):
+            continue
+        
+        file_moment = datetime.datetime.fromtimestamp(os.path.getmtime(file))
+        if file_moment < moment:
+            logging.debug('removing file %(path)s...' % {
+                    'path': full_path})
+            
+            #os.remove(full_path)
+
+
+def cleanup_images():
+    logging.debug('cleaning up images...')
+    
+    for camera_id in config.get_camera_ids():
+        camera_config = config.get_camera(camera_id)
+        if camera_config.get('@proto') != 'v4l2':
+            continue
+        
+        preserve_images = camera_config.get('@preserve_images')
+        if preserve_images == 0:
+            return # preserve forever
+        
+        preserve_moment = datetime.datetime.now() - datetime.timedelta(days=preserve_images)
+            
+        target_dir = camera_config.get('target_dir')
+        snapshot_filename = camera_config.get('snapshot_filename')
+        jpeg_filename = camera_config.get('snapshot_jpeg')
+        
+        if snapshot_filename:
+            snapshot_path = os.path.join(target_dir, snapshot_filename)
+            snapshot_path = os.path.dirname(snapshot_path)
+            _remove_older_files(dir, preserve_moment)
+    
+        if jpeg_filename:
+            snapshot_path = os.path.join(target_dir, jpeg_filename)
+            snapshot_path = os.path.dirname(snapshot_path)
+            _remove_older_files(dir, preserve_moment)
+
+
+def cleanup_movies():
+    logging.debug('cleaning up movies...')
+    
+    for camera_id in config.get_camera_ids():
+        camera_config = config.get_camera(camera_id)
+        if camera_config.get('@proto') != 'v4l2':
+            continue
+        
+        preserve_movies = camera_config.get('@preserve_movies')
+        if preserve_movies == 0:
+            return # preserve forever
+        
+        preserve_moment = datetime.datetime.now() - datetime.timedelta(days=preserve_movies)
+            
+        target_dir = camera_config.get('target_dir')
+        movie_filename = camera_config.get('movie_filename')
+        
+        if movie_filename:
+            snapshot_path = os.path.join(target_dir, movie_filename)
+            snapshot_path = os.path.dirname(snapshot_path)
+            _remove_older_files(dir, preserve_moment)
 
 def get_main(as_lines=False):
     global _main_config_cache
     
-    if not as_lines and _main_config_cache:
+    if not as_lines and _main_config_cache is not None:
         return _main_config_cache
     
     config_file_path = os.path.join(settings.PROJECT_PATH, _MAIN_CONFIG_FILE_PATH)
 def get_camera_ids():
     global _camera_ids_cache
     
-    if _camera_ids_cache:
+    if _camera_ids_cache is not None:
         return _camera_ids_cache
 
     config_path = settings.CONF_PATH
 def get_camera(camera_id, as_lines=False):
     global _camera_config_cache
     
-    if not as_lines and _camera_config_cache and camera_id in _camera_config_cache:
+    if not as_lines and _camera_config_cache is not None and camera_id in _camera_config_cache:
         return _camera_config_cache[camera_id]
     
     camera_config_path = _CAMERA_CONFIG_FILE_PATH % {'id': camera_id}
 
 
 
 class SnapshotHandler(BaseHandler):
-    #@asynchronous TODO don't forget about me
+    @asynchronous
     def get(self, camera_id, op, filename=None):
         if camera_id is not None:
             camera_id = int(camera_id)
 
     
     /* text validators */
     makeTextValidator($('#adminUsernameEntry'), true);
-    makeTextValidator($('#adminPasswordEntry'), true);
     makeTextValidator($('#normalUsernameEntry'), true);
-    makeTextValidator($('#normalPasswordEntry'), true);
     makeTextValidator($('#deviceNameEntry'), true);
     makeTextValidator($('#networkServerEntry'), true);
     makeTextValidator($('#networkShareNameEntry'), true);