""" MIT License Copyright (C) 2023 ROCKY4546 https://github.com/rocky4546 This file is part of Cabernet Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. """ import gc import argparse import logging import os import platform import sys import time from multiprocessing import Queue, Process try: from pip._internal import main as pip try: import cryptography except ImportError: pip(['install', 'cryptography']) except ModuleNotFoundError: print('Unable to install required cryptography module') try: import httpx except ImportError: pip(['install', 'httpx[http2]']) except ModuleNotFoundError: print('Unable to install required httpx[http2] module') except (ImportError, ModuleNotFoundError): print('Unable to load pip module to install required modules') import lib.clients.hdhr.hdhr_server as hdhr_server import lib.clients.web_tuner as web_tuner import lib.clients.web_admin as web_admin import lib.common.utils as utils import lib.plugins.plugin_handler as plugin_handler import lib.clients.ssdp.ssdp_server as ssdp_server import lib.db.datamgmt.backups as backups import lib.updater.updater as updater import lib.config.user_config as user_config from lib.db.db_scheduler import DBScheduler from lib.db.db_temp import DBTemp from lib.common.utils import clean_exit from lib.common.pickling import Pickling from lib.schedule.scheduler import Scheduler from lib.common.decorators import getrequest from lib.web.pages.templates import web_templates import lib.updater.patcher as patcher from lib.streams.stream import Stream RESTART_REQUESTED = None LOGGER = None if sys.version_info.major == 2 or sys.version_info < (3, 7): print('Error: Cabernet requires python 3.7+.') sys.exit(1) def get_args(): parser = argparse.ArgumentParser(description='Fetch online streams', epilog='') parser.add_argument('-c', '--config_file', dest='cfg', type=str, default=None, help='config.ini location') parser.add_argument('-r', '--restart', help='Restart process') return parser.parse_args() def restart_cabernet(_plugins): global RESTART_REQUESTED RESTART_REQUESTED = True while RESTART_REQUESTED: time.sleep(0.10) return True @getrequest.route('/api/restart') 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')) def main(script_dir): """ main startup method for app """ global RESTART_REQUESTED global LOGGER hdhr_serverx = None ssdp_serverx = None webadmin = None tuner = None # Gather args args = get_args() # Get Operating system opersystem = platform.system() config_obj = None scheduler = None terminate_queue = None try: RESTART_REQUESTED = False config_obj = user_config.get_config(script_dir, opersystem, args) config = config_obj.data LOGGER = logging.getLogger(__name__) # reduce logging for httpx modules logging.getLogger("hpack").setLevel(logging.WARNING) logging.getLogger("httpx").setLevel(logging.WARNING) logging.getLogger("httpcore").setLevel(logging.WARNING) LOGGER.warning('#########################################') LOGGER.warning('MIT License, Copyright (C) 2021 ROCKY4546') LOGGER.notice('Cabernet v{}'.format(utils.get_version_str())) except KeyboardInterrupt: if LOGGER: LOGGER.warning('^C received, shutting down the server') return try: # use this until 0.9.3 due to maintenance mode not being enabled in 0.9.1 if config['main']['maintenance_mode']: LOGGER.info('In maintenance mode, applying patches') patcher.patch_upgrade(config_obj, utils.VERSION) time.sleep(0.01) config_obj.write('main', 'maintenance_mode', False) utils.cleanup_web_temp(config) dbtemp = DBTemp(config) dbtemp.cleanup_temp(None, None) plugins = init_plugins(config_obj) config_obj.defn_json = None init_versions(plugins) if opersystem in ['Windows']: pickle_it = Pickling(config) pickle_it.to_pickle(plugins) backups.scheduler_tasks(config) terminate_queue = Queue() hdhr_queue = Queue() sched_queue = Queue() webadmin = init_webadmin(config, plugins, hdhr_queue, terminate_queue, sched_queue) tuner = init_tuner(config, plugins, hdhr_queue, terminate_queue) scheduler = init_scheduler(config, plugins, sched_queue) time.sleep(0.1) ssdp_serverx = init_ssdp(config) hdhr_serverx = init_hdhr(config, hdhr_queue) if opersystem in ['Windows']: time.sleep(2) pickle_it.delete_pickle(plugins.__class__.__name__) LOGGER.notice('Cabernet is now online.') RESTART_REQUESTED = False while not RESTART_REQUESTED: time.sleep(5) terminate_queue.put('shutdown') LOGGER.notice('Shutting Down and Restarting...') RESTART_REQUESTED = False time.sleep(3) terminate_processes(config, hdhr_serverx, ssdp_serverx, webadmin, tuner, scheduler, config_obj) except KeyboardInterrupt: if LOGGER: LOGGER.warning('^C received, shutting down the server') shutdown(config, hdhr_serverx, ssdp_serverx, webadmin, tuner, scheduler, config_obj, terminate_queue) def scheduler_tasks(_config): scheduler_db = DBScheduler(_config) scheduler_db.save_task( 'Applications', 'Restart', 'internal', None, 'lib.main.restart_cabernet', 20, 'inline', 'Restarts Cabernet' ) def init_plugins(_config_obj): LOGGER.info('Getting Plugins...') plugins = plugin_handler.PluginHandler(_config_obj) plugins.initialize_plugins() return plugins def init_versions(_plugins): updater_obj = updater.Updater(_plugins) updater_obj.scheduler_tasks() def init_webadmin(_config, _plugins, _hdhr_queue, _terminate_queue, _sched_queue): LOGGER.notice('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, _terminate_queue, _sched_queue)) webadmin.start() time.sleep(0.1) return webadmin def init_tuner(_config, _plugins, _hdhr_queue, _terminate_queue): LOGGER.notice('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, _terminate_queue,)) tuner.start() time.sleep(0.1) return tuner def init_scheduler(_config, _plugins, _sched_queue): scheduler_tasks(_config) return Scheduler(_plugins, _sched_queue) def init_ssdp(_config): if not _config['ssdp']['disable_ssdp']: LOGGER.notice('Starting SSDP service on port 1900') ssdp_serverx = Process(target=ssdp_server.ssdp_process, args=(_config,)) ssdp_serverx.daemon = True ssdp_serverx.start() time.sleep(0.1) return ssdp_serverx return None def init_hdhr(_config, _hdhr_queue): if not _config['hdhomerun']['disable_hdhr']: LOGGER.notice('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) return hdhr_serverx return None def shutdown(_config, _hdhr_serverx, _ssdp_serverx, _webadmin, _tuner, _scheduler, _config_obj, _terminate_queue): if _terminate_queue: _terminate_queue.put('shutdown') time.sleep(0.01) terminate_processes(_config, _hdhr_serverx, _ssdp_serverx, _webadmin, _tuner, _scheduler, _config_obj) LOGGER.debug('main process terminated') clean_exit() def terminate_processes(_config, _hdhr_serverx, _ssdp_serverx, _webadmin, _tuner, _scheduler, _config_obj): if not _config['hdhomerun']['disable_hdhr'] and _hdhr_serverx: _hdhr_serverx.terminate() _hdhr_serverx.join() del _hdhr_serverx if not _config['ssdp']['disable_ssdp'] and _ssdp_serverx: _ssdp_serverx.terminate() _ssdp_serverx.join() del _ssdp_serverx if _scheduler: _scheduler.terminate() del _scheduler if _webadmin: _webadmin.terminate() _webadmin.join() del _webadmin if _tuner: _tuner.terminate() _tuner.join() del _tuner if _config_obj and _config_obj.defn_json: _config_obj.defn_json.terminate() del _config_obj time.sleep(0.5)