Beispiel #1
0
 def __init__(self, _plugin):
     super().__init__(_plugin)
     self.auth = Authenticate(_plugin.config_obj, self.namespace.lower())
     self.scheduler_db = DBScheduler(self.config_obj.data)
     for inst in _plugin.instances:
         self.instances[inst] = LocastInstance(self, inst)
     self.scheduler_tasks()
Beispiel #2
0
def scheduler_tasks(config):
    scheduler_db = DBScheduler(config)
    if scheduler_db.save_task('Applications', 'Backup', 'internal', None,
                              'lib.db.datamgmt.backups.backup_data', 20,
                              'thread',
                              'Backs up cabernet including databases'):
        scheduler_db.save_trigger('Applications',
                                  'Backup',
                                  'weekly',
                                  dayofweek='Sunday',
                                  timeofday='02:00')
Beispiel #3
0
def reset_sched(_config, _name):
    db_scheduler = DBScheduler(_config)
    tasks = db_scheduler.get_tasks_by_name(_name)
    html = ''
    for task in tasks:
        db_scheduler.del_task(task['area'], task['title'])
        html = ''.join([
            html, '<b>', task['area'], ':', task['title'],
            '</b> deleted from Scheduler<br>'
        ])
    return ''.join(
        [html, 'Restart the app to re-populate the scheduler with defaults'])
Beispiel #4
0
def restart_api(_webserver):
    scheduler_db = DBScheduler(_webserver.config)
    tasks = scheduler_db.get_tasks('Applications', 'Restart')
    if len(tasks) == 1:
        _webserver.sched_queue.put({
            'cmd': 'runtask',
            'taskid': tasks[0]['taskid']
        })
        _webserver.do_mime_response(200, 'text/html', 'Restarting Cabernet')
    else:
        _webserver.do_mime_response(
            404, 'text/html',
            web_templates['htmlError'].format('404 - Request Not Found'))
Beispiel #5
0
 def select_reset_sched(self):
     db_sched = DBScheduler(self.config)
     plugins_sched = db_sched.get_task_names()
     html_option = ''.join([
         '<td nowrap>Plugin: <select id="name" name="name"</select>',
         '<option value="">ALL</option>',
     ])
     for name in plugins_sched:
         html_option = ''.join([
             html_option,
             '<option value="',
             name['namespace'],
             '">',
             name['namespace'],
             '</option>',
         ])
     return ''.join([html_option, '</select></td></tr>'])
    def __init__(self, _plugins, _queue):
        Thread.__init__(self)
        self.logger = logging.getLogger(__name__)
        self.plugins = _plugins
        self.queue = _queue
        self.config = _plugins.config_obj.data
        self.scheduler_db = DBScheduler(self.config)
        self.scheduler_db.reset_activity()
        self.schedule = lib.schedule.schedule
        self.daemon = True
        self.stop_thread = False
        Scheduler.scheduler_obj = self

        def _queue_thread():
            while not self.stop_thread:
                queue_item = self.queue.get(True)
                self.process_queue(queue_item)

        _q_thread = Thread(target=_queue_thread, args=())
        _q_thread.start()
        self.start()
Beispiel #7
0
def main(script_dir):
    """ main startup method for app """
    global RESTART_REQUESTED
    hdhr_serverx = None
    ssdp_serverx = None
    webadmin = None
    tuner = None

    # Gather args
    args = get_args()
    if args.restart:
        time.sleep(0.01)

    # Get Operating system
    opersystem = platform.system()
    config_obj = None
    try:
        RESTART_REQUESTED = False

        config_obj = user_config.get_config(script_dir, opersystem, args)
        config = config_obj.data
        logger = logging.getLogger(__name__)

        logger.warning('#########################################')
        logger.warning('MIT License, Copyright (C) 2021 ROCKY4546')
        logger.info('Initiating Cabernet v{}'.format(utils.get_version_str()))

        utils.cleanup_web_temp(config)
        logger.info('Getting Plugins...')
        plugins = plugin_handler.PluginHandler(config_obj)
        plugins.initialize_plugins()
        config_obj.defn_json = None

        scheduler_db = DBScheduler(config)
        scheduler_db.save_task('Applications', 'Restart', 'internal', None,
                               'lib.main.restart_cabernet', 20, 'inline',
                               'Restarts Cabernet')

        if opersystem in ['Windows']:
            pickle_it = Pickling(config)
            pickle_it.to_pickle(plugins)

        backups.scheduler_tasks(config)
        hdhr_queue = Queue()
        sched_queue = Queue()
        logger.info('Starting admin website on {}:{}'.format(
            config['web']['plex_accessible_ip'],
            config['web']['web_admin_port']))
        webadmin = Process(target=web_admin.start,
                           args=(plugins, hdhr_queue, sched_queue))
        webadmin.start()
        time.sleep(0.1)

        logger.info('Starting streaming tuner website on {}:{}'.format(
            config['web']['plex_accessible_ip'],
            config['web']['plex_accessible_port']))
        tuner = Process(target=web_tuner.start, args=(
            plugins,
            hdhr_queue,
        ))
        tuner.start()
        time.sleep(0.1)

        scheduler = Scheduler(plugins, sched_queue)
        time.sleep(0.1)

        if not config['ssdp']['disable_ssdp']:
            logger.info('Starting SSDP service on port 1900')
            ssdp_serverx = Process(target=ssdp_server.ssdp_process,
                                   args=(config, ))
            ssdp_serverx.daemon = True
            ssdp_serverx.start()

        if not config['hdhomerun']['disable_hdhr']:
            logger.info('Starting HDHR service on port 65001')
            hdhr_serverx = Process(target=hdhr_server.hdhr_process,
                                   args=(
                                       config,
                                       hdhr_queue,
                                   ))
            hdhr_serverx.start()
        time.sleep(0.1)

        if opersystem in ['Windows']:
            time.sleep(2)
            pickle_it.delete_pickle(plugins.__class__.__name__)
        logger.info('Cabernet is now online.')

        RESTART_REQUESTED = False
        while not RESTART_REQUESTED:
            time.sleep(5)
        RESTART_REQUESTED = False
        logger.info('Shutting Down...')
        time.sleep(1)
        terminate_processes(config, hdhr_serverx, ssdp_serverx, webadmin,
                            tuner, scheduler, config_obj)

    except KeyboardInterrupt:
        logger.info('^C received, shutting down the server')
        shutdown(config, hdhr_serverx, ssdp_serverx, webadmin, tuner,
                 scheduler, config_obj)
class Scheduler(Thread):
    """
    Assumed to be a singleton
    triggers are associated with a task in the database and define when a task runs
    jobs are listed in the Schedule object and run as cron jobs. Triggers with
    their associated tasks define jobs.
    Calls are from the sched_queue to run, delete or add triggers/jobs.
    Tasks are not managed by this class.
    Only one trigger/job can run from within a task at any point in time.
    """
    scheduler_obj = None

    def __init__(self, _plugins, _queue):
        Thread.__init__(self)
        self.logger = logging.getLogger(__name__)
        self.plugins = _plugins
        self.queue = _queue
        self.config = _plugins.config_obj.data
        self.scheduler_db = DBScheduler(self.config)
        self.scheduler_db.reset_activity()
        self.schedule = lib.schedule.schedule
        self.daemon = True
        self.stop_thread = False
        Scheduler.scheduler_obj = self

        def _queue_thread():
            while not self.stop_thread:
                queue_item = self.queue.get(True)
                self.process_queue(queue_item)

        _q_thread = Thread(target=_queue_thread, args=())
        _q_thread.start()
        self.start()

    def run(self):
        """
        Thread run method for the class.
        - Executes all startup tasks
        - Sets up the Schedule/Job objects based on database
        - Loops getting queue events and runs any pending triggers
        """
        triggers = self.scheduler_db.get_triggers_by_type('startup')
        for trigger in triggers:
            self.exec_trigger(trigger)
        self.setup_triggers()
        while not self.stop_thread:
            self.schedule.run_pending()
            for i in range(30):
                if self.stop_thread:
                    break
                time.sleep(1)

    def terminate(self):
        self.stop_thread = True
        self.queue.put({'cmd': 'noop'})

    def exec_trigger(self, _trigger):
        """
        Main entry for the Schedule Job to run a task/event
        """
        if self.scheduler_db.get_active_status(_trigger['taskid']):
            self.logger.debug(
                'Task currently running, ignored request {}:{}'.format(
                    _trigger['area'], _trigger['title']))
            return

        self.scheduler_db.start_task(_trigger['area'], _trigger['title'])
        if _trigger['threadtype'] == 'thread':
            self.logger.info('Running threaded task {}:{}'.format(
                _trigger['area'], _trigger['title']))
            t_event = Thread(target=self.call_trigger, args=(_trigger, ))
            t_event.start()
        elif _trigger['threadtype'] == 'process':
            self.logger.info('Running process task {}:{}'.format(
                _trigger['area'], _trigger['title']))
            p_event = Process(target=self.call_trigger, args=(_trigger, ))
            p_event.start()
        else:
            self.logger.info('Running inline task {}:{}'.format(
                _trigger['area'], _trigger['title']))
            self.call_trigger(_trigger)

    def call_trigger(self, _trigger):
        """
        Calls the trigger function and times the result
        """
        start = time.time()
        if _trigger['namespace'] == 'internal':
            mod_name, func_name = _trigger['funccall'].rsplit('.', 1)
            mod = importlib.import_module(mod_name)
            call_f = getattr(mod, func_name)
            call_f(self.plugins)
        else:
            plugin_obj = self.plugins.plugins[_trigger['namespace']].plugin_obj
            if plugin_obj is None:
                self.logger.debug('{} scheduled tasks ignored. plugin disabled' \
                    .format(_trigger['namespace']))
                pass
            elif _trigger['instance'] is None:
                call_f = getattr(plugin_obj, _trigger['funccall'])
                call_f()
            else:
                call_f = getattr(plugin_obj.instances[_trigger['instance']],
                                 _trigger['funccall'])
                call_f()
        end = time.time()
        duration = int(end - start)
        time.sleep(0.2)
        self.scheduler_db.finish_task(_trigger['area'], _trigger['title'],
                                      duration)

    def setup_triggers(self):
        """
        Assumes the trigger is already in the database and adds the job
        to the Schedule object
        """
        triggers = self.scheduler_db.get_triggers_by_type('daily')
        for trigger_data in triggers:
            self.add_job(trigger_data)

        triggers = self.scheduler_db.get_triggers_by_type('weekly')
        for trigger_data in triggers:
            self.add_job(trigger_data)

        triggers = self.scheduler_db.get_triggers_by_type('interval')
        for trigger_data in triggers:
            self.add_job(trigger_data)

    def add_job(self, _trigger):
        """
        Adds a job to the schedule object using the trigger dict from the database
        """
        if _trigger['timetype'] == 'daily':
            self.schedule.every().day.at(_trigger['timeofday']).do(
                self.exec_trigger, _trigger) \
                .tag(_trigger['uuid'])
        elif _trigger['timetype'] == 'weekly':
            getattr(self.schedule.every(), _trigger['dayofweek'].lower()) \
                .at(_trigger['timeofday']).do(
                self.exec_trigger, _trigger) \
                .tag(_trigger['uuid'])
        elif _trigger['timetype'] == 'interval':
            if _trigger['randdur'] < 0:
                self.schedule.every(_trigger['interval']).minutes.do(
                    self.exec_trigger, _trigger) \
                    .tag(_trigger['uuid'])
            else:
                self.schedule.every(_trigger['interval']) \
                    .to(_trigger['interval'] + _trigger['randdur']) \
                    .minutes.do(self.exec_trigger, _trigger) \
                    .tag(_trigger['uuid'])
        elif _trigger['timetype'] == 'startup':
            pass
        else:
            self.logger.warning(
                'Bad trigger timetype called {}'.format(_trigger))

        # Need to add UNTIL method to trigger when provided
        # database has timelimit in minutes and by default is set to -1.
        # until does not work that way.  Use a second trigger to clear the first if it is still running.
        # but only works when the randum generator is not used.
        # also it won't work for inline triggers since a second trigger cannot run.

    def process_queue(self, _queue_item):
        """
        cmd: run_job, arg: uuid
        cmd: del_job, arg: uuid
        cmd: add_job, arg: trigger data without uuid
        """
        try:
            if _queue_item['cmd'] == 'run':
                self.run_trigger(_queue_item['uuid'])
            elif _queue_item['cmd'] == 'runtask':
                self.run_task(_queue_item['taskid'])
            elif _queue_item['cmd'] == 'del':
                self.delete_trigger(_queue_item['uuid'])
            elif _queue_item['cmd'] == 'add':
                self.add_trigger(_queue_item['trigger'])
            elif _queue_item['cmd'] == 'noop':
                pass
            else:
                self.logger.warning(
                    'UNKNOWN Scheduler cmd from queue: {}'.format(_queue_item))
        except KeyError as e:
            self.logger.warning('Badly formed scheduled request {} {}'.format(
                _queue_item, repr(e)))

    def delete_trigger(self, _uuid):
        self.logger.info('Deleting trigger {}'.format(_uuid))
        jobs = self.schedule.get_jobs(_uuid)
        for job in jobs:
            self.schedule.cancel_job(job)
        self.scheduler_db.del_trigger(_uuid)

    def run_trigger(self, _uuid):
        jobs = self.schedule.get_jobs(_uuid)
        if len(jobs) == 0:
            self.logger.info('Invalid uuid for run request')
        else:
            for job in jobs:
                job.run()

    def add_trigger(self, trigger):
        if trigger['timetype'] == 'startup':
            self.create_trigger(trigger['area'], trigger['title'],
                                trigger['timetype'])
        elif trigger['timetype'] == 'daily':
            self.create_trigger(trigger['area'],
                                trigger['title'],
                                trigger['timetype'],
                                timeofday=trigger['timeofday'])
        elif trigger['timetype'] == 'daily':
            self.create_trigger(trigger['area'],
                                trigger['title'],
                                trigger['timetype'],
                                timeofday=trigger['timeofday'])
        elif trigger['timetype'] == 'weekly':
            self.create_trigger(trigger['area'],
                                trigger['title'],
                                trigger['timetype'],
                                timeofday=trigger['timeofday'],
                                dayofweek=trigger['dayofweek'])
        elif trigger['timetype'] == 'interval':
            self.create_trigger(trigger['area'],
                                trigger['title'],
                                trigger['timetype'],
                                interval=trigger['interval'],
                                randdur=trigger['randdur'])

    def create_trigger(self,
                       _area,
                       _title,
                       _timetype,
                       timeofday=None,
                       dayofweek=None,
                       interval=-1,
                       timelimit=-1,
                       randdur=-1):
        self.logger.info('Creating trigger {}:{}:{}'.format(
            _area, _title, _timetype))
        uuid = self.scheduler_db.save_trigger(_area, _title, _timetype,
                                              timeofday, dayofweek, interval,
                                              timelimit, randdur)
        trigger = self.scheduler_db.get_trigger(uuid)
        self.add_job(trigger)

    def run_task(self, _taskid):
        triggers = self.scheduler_db.get_triggers(_taskid)
        if len(triggers) == 0:
            # check if the task has no triggers
            task = self.scheduler_db.get_task(_taskid)
            if task is not None:
                self.exec_trigger(task)
            else:
                self.logger.warning(
                    'Invalid taskid when requesting to run task')
            return None

        is_run = False
        default_trigger = None
        for trigger in triggers:
            if trigger['timetype'] == 'startup':
                continue
            elif trigger['timetype'] == 'interval':
                self.queue.put({'cmd': 'run', 'uuid': trigger['uuid']})
                is_run = True
                break
            else:
                default_trigger = trigger
        if not is_run:
            if default_trigger is not None:
                self.queue.put({'cmd': 'run', 'uuid': trigger['uuid']})
            else:
                self.logger.warning(
                    'Need at least one non-startup trigger event to run manually'
                )
        return None
Beispiel #9
0
class Locast(PluginObj):
    def __init__(self, _plugin):
        super().__init__(_plugin)
        self.auth = Authenticate(_plugin.config_obj, self.namespace.lower())
        self.scheduler_db = DBScheduler(self.config_obj.data)
        for inst in _plugin.instances:
            self.instances[inst] = LocastInstance(self, inst)
        self.scheduler_tasks()

    def refresh_channels_ext(self, _instance=None):
        """
        External request to refresh channels. Called from the plugin manager.
        All tasks are namespace based so instance is ignored. 
        This calls the scheduler to run the task.
        """
        self.web_admin_url = 'http://' + self.config_obj.data['web']['plex_accessible_ip'] + \
            ':' + str(self.config_obj.data['web']['web_admin_port'])
        task = self.scheduler_db.get_tasks('Channels',
                                           'Refresh Locast Channels')[0]
        url = (
            self.web_admin_url +
            '/pages/scheduler?action=runtask&taskid={}'.format(task['taskid']))
        req = urllib.request.Request(url)
        with urllib.request.urlopen(req) as resp:
            result = resp.read()

        # wait for the last run to update indicating the task has completed.
        while True:
            task_status = self.scheduler_db.get_task(task['taskid'])
            x = datetime.datetime.utcnow() - task_status['lastran']
            # If updated in the last 20 minutes, then ignore
            # Many media servers will request this multiple times.
            if x.total_seconds() < 1200:
                break
            time.sleep(0.5)

    def refresh_epg_ext(self, _instance=None):
        """
        External request to refresh epg. Called from the plugin manager.
        All tasks are namespace based so instance is ignored.
        This calls the scheduler to run the task.
        """
        self.web_admin_url = 'http://' + self.config_obj.data['web']['plex_accessible_ip'] + \
            ':' + str(self.config_obj.data['web']['web_admin_port'])
        task = self.scheduler_db.get_tasks('EPG', 'Refresh Locast EPG')[0]
        url = (
            self.web_admin_url +
            '/pages/scheduler?action=runtask&taskid={}'.format(task['taskid']))
        req = urllib.request.Request(url)
        with urllib.request.urlopen(req) as resp:
            result = resp.read()

        # wait for the last run to update indicating the task has completed.
        while True:
            task_status = self.scheduler_db.get_task(task['taskid'])
            x = datetime.datetime.utcnow() - task_status['lastran']
            # If updated in the last 20 minutes, then ignore
            # Many media servers will request this multiple times.
            if x.total_seconds() < 1200:
                break
            time.sleep(0.5)

    def get_channel_uri_ext(self, sid, _instance=None):
        """
        External request to return the uri for a m3u8 stream.
        Called from stream object.
        """
        return self.instances[_instance].get_channel_uri(sid)

    def is_time_to_refresh_ext(self, _last_refresh, _instance):
        """
        External request to determine if the m3u8 stream uri needs to 
        be refreshed.
        Called from stream object.
        """
        return self.instances[_instance].is_time_to_refresh(_last_refresh)

    def refresh_channels(self, _instance=None):
        """
        Called from the scheduler
        """
        if _instance is None:
            for key, instance in self.instances.items():
                instance.refresh_channels()
        else:
            self.instances[_instance].refresh_channels()

    def refresh_epg(self, _instance=None):
        """
        Called from the scheduler
        """
        if _instance is None:
            for key, instance in self.instances.items():
                instance.refresh_epg()
        else:
            self.instances[_instance].refresh_epg()

    def scheduler_tasks(self):
        if self.scheduler_db.save_task('Channels', 'Refresh Locast Channels',
                                       self.name, None, 'refresh_channels', 20,
                                       'inline',
                                       'Pulls channel lineup from Locast'):
            self.scheduler_db.save_trigger('Channels',
                                           'Refresh Locast Channels',
                                           'startup')
            self.scheduler_db.save_trigger('Channels',
                                           'Refresh Locast Channels',
                                           'daily',
                                           timeofday='22:00')
        if self.scheduler_db.save_task(
                'EPG', 'Refresh Locast EPG', self.name, None, 'refresh_epg',
                10, 'thread', 'Pulls channel program data from Locast'):
            self.scheduler_db.save_trigger('EPG', 'Refresh Locast EPG',
                                           'startup')
            self.scheduler_db.save_trigger('EPG',
                                           'Refresh Locast EPG',
                                           'interval',
                                           interval=700)

    @property
    def name(self):
        return self.namespace
 def __init__(self, _config, _queue):
     self.logger = logging.getLogger(__name__)
     self.config = _config
     self.queue = _queue
     self.scheduler_db = DBScheduler(self.config)
class ScheduleHTML:
    def __init__(self, _config, _queue):
        self.logger = logging.getLogger(__name__)
        self.config = _config
        self.queue = _queue
        self.scheduler_db = DBScheduler(self.config)

    def get(self):
        return ''.join([self.header, self.body])

    @property
    def header(self):
        return ''.join([
            '<!DOCTYPE html><html><head>',
            '<meta charset="utf-8"/><meta name="author" content="rocky4546">',
            '<meta name="description" content="schedule task management for Cabernet">',
            '<title>Scheduled Tasks</title>',
            '<meta name="viewport" content="width=device-width, ',
            'minimum-scale=1.0, maximum-scale=1.0">',
            '<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>',
            '<link rel="stylesheet" type="text/css" href="/modules/scheduler/scheduler.css">',
            '<script src="/modules/scheduler/scheduler.js"></script>'
        ])

    @property
    def body(self):
        return ''.join(
            ['<body>', self.title, self.schedule_tasks, self.task, '</body>'])

    @property
    def title(self):
        return ''.join(['<div class="container">', '<h2>Scheduled Tasks</h2>'])

    @property
    def schedule_tasks(self):
        tasks = self.scheduler_db.get_tasks()
        current_area = None

        html = ''.join([
            '<div id="schedtasks" class="schedShow">',
            '<table class="schedTable" width=95%>'
        ])
        for task_dict in tasks:
            if task_dict['area'] != current_area:
                current_area = task_dict['area']
                html = ''.join([
                    html,
                    '<tr>',
                    '<td colspan=3><div class="schedSection">',
                    current_area,
                    '</div></td>'
                    '</tr>',
                ])

            if task_dict['lastran'] is None:
                lastran_delta = 'Never'
                dur_delta = ''
            else:
                lastran_delta = datetime.datetime.utcnow(
                ) - task_dict['lastran']
                lastran_secs = int(lastran_delta.total_seconds())
                lastran_mins = lastran_secs // 60
                lastran_hrs = lastran_mins // 60
                lastran_days = lastran_hrs // 24
                if lastran_days != 0:
                    lastran_delta = str(lastran_days) + ' days'
                elif lastran_hrs != 0:
                    lastran_delta = str(lastran_hrs) + ' hours'
                elif lastran_mins != 0:
                    lastran_delta = str(lastran_mins) + ' minutes'
                else:
                    lastran_delta = str(lastran_secs) + ' seconds'

                dur_mins = task_dict['duration'] // 60
                dur_hrs = dur_mins // 60
                dur_days = dur_hrs // 24
                if dur_days != 0:
                    dur_delta = str(dur_days) + ' days'
                elif dur_hrs != 0:
                    dur_delta = str(dur_hrs) + ' hours'
                elif dur_mins != 0:
                    dur_delta = str(dur_mins) + ' minutes'
                else:
                    dur_delta = str(task_dict['duration']) + ' seconds'

            html = ''.join([
                html, '<tr>', '<td class="schedIcon">',
                '<a href="#" onclick=\'load_task_url("/pages/schedule.html?task=',
                task_dict['taskid'], '")\'>',
                '<i class="md-icon">schedule</i></a></td>',
                '<td class="schedTask">',
                '<a href="#" onclick=\'load_task_url("/pages/schedule.html?task=',
                task_dict['taskid'], '")\'>', '<div class="schedTitle">',
                task_dict['title'], '</div>', '<div>Plugin: ',
                task_dict['namespace']
            ])
            if task_dict['active']:
                html = ''.join([
                    html, ' -- Currently Running</div>'
                    '<div class="progress-line"></div>'
                ])
                play_name = ''
                play_icon = ''
            else:
                html = ''.join([
                    html, ' -- Last ran ', lastran_delta, ' ago, ', 'taking ',
                    dur_delta, '</div>'
                    '<div class=""></div>'
                ])
                play_name = '&run=1'
                play_icon = 'play_arrow'

            html = ''.join([
                html, '</a></td>', '<td class="schedIcon">',
                '<a href="#" onclick=\'load_sched_url("/pages/schedule.html?task=',
                task_dict['taskid'], play_name, '")\'>', '<i class="md-icon">',
                play_icon, '</i></a></td>', '</tr>', '<tr>',
                '<td colspan=3><hr></td>', '</tr>'
            ])
        return ''.join([html, '</table></div>'])

    @property
    def task(self):
        return ''.join(
            ['<div id="schedtask" class="schedTable schedHide"></div>'])

    def get_task(self, _id):
        task_dict = self.scheduler_db.get_task(_id)
        if task_dict is None:
            self.logger.warning('get_task: Invalid task id: {}'.format(_id))
            return ''

        html = ''.join([
            '<table style="display: contents" width=95%>',
            '<tr>',
            '<td class="schedIcon">',
            '<a href="#" onclick=\'display_tasks()\'>',
            '<div ><i class="md-icon">arrow_back</i></div></a></td>',
            '<td colspan=2 ><div class="schedSection">',
            str(task_dict['title']),
            '</div></td>',
            '</tr>',
            '<tr>',
            '<td class="schedIcon"></td>',
            '<td colspan=2>',
            str(task_dict['description']),
            '</div></td>'
            '</tr>',
            '<td class="schedIcon"></td>',
            '<td colspan=2><b>Namespace:</b> ',
            str(task_dict['namespace']),
            ' &nbsp; <b>Instance:</b> ',
            str(task_dict['instance']),
            ' &nbsp; <b>Priority:</b> ',
            str(task_dict['priority']),
            ' &nbsp; <b>Thread Type:</b> ',
            str(task_dict['threadtype']),
            '</div></td>',
            '<tr>',
            '<tr><td>&nbsp;</td></tr>',
            '<td colspan=3><div class="schedSection">Task Triggers',
            '<button class="schedIconButton" onclick=\'load_task_url(',
            '"/pages/schedule.html?task=',
            _id,
            '&trigger=1");return false;\'>',
            '<i class="schedIcon md-icon" style="padding-left: 1px; text-align: left;">add</i></button>',
            '</div></td>',
            '</tr>',
        ])

        trigger_array = self.scheduler_db.get_triggers(_id)
        for trigger_dict in trigger_array:
            if trigger_dict['timetype'] == 'startup':
                trigger_str = 'At startup'
            elif trigger_dict['timetype'] == 'daily':
                trigger_str = 'Daily at ' + trigger_dict['timeofday']
            elif trigger_dict['timetype'] == 'weekly':
                trigger_str = ''.join([
                    'Every ', trigger_dict['dayofweek'], ' at ',
                    trigger_dict['timeofday']
                ])
            elif trigger_dict['timetype'] == 'interval':
                interval_mins = trigger_dict['interval']
                remainder_hrs = interval_mins % 60
                if remainder_hrs != 0:
                    interval_str = str(interval_mins) + ' minutes'
                else:
                    interval_hrs = interval_mins // 60
                    interval_str = str(interval_hrs) + ' hours'
                trigger_str = 'Every ' + interval_str
                if trigger_dict['randdur'] != -1:
                    trigger_str += ' with random maximum added time of ' + str(
                        trigger_dict['randdur']) + ' minutes'

            else:
                trigger_str = 'UNKNOWN'

            html = ''.join([
                html, '<tr>', '<td class="schedIcon">',
                '<i class="md-icon">schedule</i></td>',
                '<td class="schedTask">', trigger_str, '</td>',
                '<td class="schedIcon">',
                '<a href="#" onclick=\'load_task_url("/pages/schedule.html?task=',
                _id, '&trigger=', trigger_dict['uuid'], '&delete=1")\'>',
                '<i class="md-icon">delete_forever</i></a></td>', '</tr>'
            ])

        return ''.join([html, '</table>'])

    def get_trigger(self, _id):
        task_dict = self.scheduler_db.get_task(_id)
        if task_dict is None:
            self.logger.warning('get_trigger: Invalid task id: {}'.format(_id))
            return ''
        if task_dict['namespace'] is None:
            namespace = ""
        else:
            namespace = task_dict['namespace']
        if task_dict['instance'] is None:
            instance = ""
        else:
            instance = task_dict['instance']

        return "".join([
            '<script src="/modules/scheduler/trigger.js"></script>',
            '<form id="triggerform" action="/pages/schedule.html" method="post">',
            '<input type="hidden" name="name" value="', namespace, '" >',
            '<input type="hidden" name="instance" value="', instance, '" >',
            '<input type="hidden" name="area" value="', task_dict['area'],
            '" >', '<table style="display: contents" width=95%>', '<tr>',
            '<td style="display: flex;">',
            '<a href="#" onclick=\'load_task_url("/pages/schedule.html?task=',
            _id, '");\'>',
            '<div ><i class="schedIcon md-icon">arrow_back</i></div></a>',
            '<div class="schedSection">', 'Add Trigger</div></td>'
            '</tr>', '<tr>', '<td><b>Task: ', task_dict['title'],
            '<input type="hidden" name="title" value="', task_dict['title'],
            '" >', '</b><br><br></td>'
            '</tr>',
            '<tr><td><label title="Interval will reset each time the task is requested">Trigger Type: &nbsp; </label>',
            '<select id="timetype" name="timetype"</select>',
            '<option value="daily">Daily</option>',
            '<option value="weekly">Weekly</option>',
            '<option value="interval">On an interval</option>',
            '<option value="startup">On Startup</option>', '</select><br><br>',
            '<script>',
            '$("#timetype").change(function(){ onChangeTimeType( this ); });',
            '</script>', '</td></tr>',
            '<tr><td><div id="divDOW" class="schedHide">Day: &nbsp; ',
            '<select name="dayofweek"</select>',
            '<option value="">Not Set</option>',
            '<option value="Sunday">Sunday</option>',
            '<option value="Monday">Monday</option>',
            '<option value="Tuesday">Tuesday</option>',
            '<option value="Wednesday">Wednesday</option>',
            '<option value="Thursday">Thursday</option>',
            '<option value="Friday">Friday</option>',
            '<option value="Saturday">Saturday</option>', '</select>',
            '<br><br>', '</div></td></tr>',
            '<tr><td><div id="divTOD" class="schedShow"><label title="Local time">Time: &nbsp; </label>',
            '<select name="timeofdayhr"</select>',
            '<option value="">Not set</option>',
            '<option value="12">12AM</option>',
            '<option value="01">1AM</option>',
            '<option value="02">2AM</option>',
            '<option value="03">3AM</option>',
            '<option value="04">4AM</option>',
            '<option value="05">5AM</option>',
            '<option value="06">6AM</option>',
            '<option value="07">7AM</option>',
            '<option value="08">8AM</option>',
            '<option value="09">9AM</option>',
            '<option value="10">10AM</option>',
            '<option value="11">11AM</option>',
            '<option value="12">12PM</option>',
            '<option value="13">1PM</option>',
            '<option value="14">2PM</option>',
            '<option value="15">3PM</option>',
            '<option value="16">4PM</option>',
            '<option value="17">5PM</option>',
            '<option value="18">6PM</option>',
            '<option value="19">7PM</option>',
            '<option value="20">8PM</option>',
            '<option value="21">9PM</option>',
            '<option value="22">10PM</option>',
            '<option value="23">11PM</option>', '</select> : ',
            '<select name="timeofdaymin"</select>',
            '<option value="">Not set</option>',
            '<option value="00">00 min</option>',
            '<option value="05">05 min</option>',
            '<option value="10">10 min</option>',
            '<option value="15">15 min</option>',
            '<option value="20">20 min</option>',
            '<option value="25">25 min</option>',
            '<option value="30">30 min</option>',
            '<option value="35">35 min</option>',
            '<option value="40">40 min</option>',
            '<option value="45">45 min</option>',
            '<option value="50">50 min</option>',
            '<option value="55">55 min</option>', '</select>', '<br><br>',
            '</td></tr>',
            '<tr><td><div id="divINTL" class="schedHide">Every: &nbsp; ',
            '<select name="interval"</select>',
            '<option value="">Not Set</option>',
            '<option value="15">15 minutes</option>',
            '<option value="30">30 minutes</option>',
            '<option value="45">45 minutes</option>',
            '<option value="60">1 hour</option>',
            '<option value="120">2 hours</option>',
            '<option value="180">3 hours</option>',
            '<option value="240">4 hours</option>',
            '<option value="330">5 hours, 30 minutes</option>',
            '<option value="355">5 hours, 55 minutes</option>',
            '<option value="360">6 hours</option>',
            '<option value="480">8 hours</option>',
            '<option value="690">11 hours, 30 minutes</option>',
            '<option value="710">11 hours, 50 minutes</option>',
            '<option value="720">12 hours</option>',
            '<option value="1410">23 hours, 30 minutes</option>',
            '<option value="1440">24 hours</option>', '</select><br><br>',
            '</td></tr>',
            '<tr><td><div id="divRND" class="schedHide">Max Random Added Time: &nbsp; ',
            '<select name="randdur"</select>',
            '<option value="-1">Not set</option>',
            '<option value="5">5 min</option>',
            '<option value="10">10 min</option>',
            '<option value="15">15 min</option>',
            '<option value="20">20 min</option>',
            '<option value="30">30 min</option>',
            '<option value="60">1 hour</option>',
            '<option value="120">2 hours</option>'
            '</select><br><br>', '</td></tr>',
            '<tr><td><button type="submit">Add</button>',
            ' &nbsp; <button onclick=\'load_task_url("/pages/schedule.html?task=',
            _id, '"); return false;\' >Cancel</button>',
            '<tr><td>&nbsp;</td></tr>', '</table></form>',
            '<section id="status"></section>'
        ])

    def post_add_trigger(self, query_data):
        if query_data['timetype'][0] == 'startup':
            self.queue.put({
                'cmd': 'add',
                'trigger': {
                    'area': query_data['area'][0],
                    'title': query_data['title'][0],
                    'timetype': query_data['timetype'][0]
                }
            })
            time.sleep(0.05)
            return 'Startup Trigger added'

        elif query_data['timetype'][0] == 'daily':
            if query_data['timeofdayhr'][0] is None or query_data[
                    'timeofdaymin'][0] is None:
                return 'Time of Day is not set and is required'
            self.queue.put({
                'cmd': 'add',
                'trigger': {
                    'area':
                    query_data['area'][0],
                    'title':
                    query_data['title'][0],
                    'timetype':
                    query_data['timetype'][0],
                    'timeofday':
                    query_data['timeofdayhr'][0] + ':' +
                    query_data['timeofdaymin'][0]
                }
            })
            time.sleep(0.05)
            return 'Daily Trigger added'

        elif query_data['timetype'][0] == 'weekly':
            if query_data['dayofweek'][0] is None:
                return 'Day of Week is not set and is required'
            if query_data['timeofdayhr'][0] is None or query_data[
                    'timeofdaymin'][0] is None:
                return 'Time of Day is not set and is required'
            self.queue.put({
                'cmd': 'add',
                'trigger': {
                    'area':
                    query_data['area'][0],
                    'title':
                    query_data['title'][0],
                    'timetype':
                    query_data['timetype'][0],
                    'timeofday':
                    query_data['timeofdayhr'][0] + ':' +
                    query_data['timeofdaymin'][0],
                    'dayofweek':
                    query_data['dayofweek'][0]
                }
            })
            time.sleep(0.05)
            return 'Weekly Trigger added'

        elif query_data['timetype'][0] == 'interval':
            if query_data['interval'][0] is None:
                return 'Interval is not set and is required'
            self.queue.put({
                'cmd': 'add',
                'trigger': {
                    'area': query_data['area'][0],
                    'title': query_data['title'][0],
                    'timetype': query_data['timetype'][0],
                    'interval': query_data['interval'][0],
                    'randdur': query_data['randdur'][0]
                }
            })
            time.sleep(0.05)
            return 'Interval Trigger added'
        return 'UNKNOWN'

    def del_trigger(self, _uuid):
        if self.scheduler_db.get_trigger(_uuid) is None:
            return None
        self.queue.put({'cmd': 'del', 'uuid': _uuid})
        time.sleep(0.05)
        return 'Interval Trigger deleted'

    def run_task(self, _taskid):
        self.queue.put({'cmd': 'runtask', 'taskid': _taskid})
        return None