#!/usr/bin/env python # # This file is part of SickGear. # # SickGear 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. # # SickGear 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 SickGear. If not, see <http://www.gnu.org/licenses/>. # Check needed software dependencies to nudge users to fix their setup from __future__ import print_function from __future__ import with_statement import codecs import datetime import errno import getopt import os import signal import sys import shutil import time import threading import warnings warnings.filterwarnings('ignore', module=r'.*bs4_parser.*', message='.*No parser was explicitly specified.*') warnings.filterwarnings('ignore', module=r'.*Cheetah.*') warnings.filterwarnings('ignore', module=r'.*connectionpool.*', message='.*certificate verification.*') warnings.filterwarnings('ignore', module=r'.*fuzzywuzzy.*') warnings.filterwarnings('ignore', module=r'.*ssl_.*', message='.*SSLContext object.*') warnings.filterwarnings('ignore', module=r'.*zoneinfo.*', message='.*file or directory.*') warnings.filterwarnings('ignore', message='.*deprecated in cryptography.*') versions = [((3, 7, 1), (3, 8, 16)), ((3, 9, 0), (3, 9, 2)), ((3, 9, 4), (3, 9, 16)), ((3, 10, 0), (3, 11, 2))] # inclusive version ranges if not any(list(map(lambda v: v[0] <= sys.version_info[:3] <= v[1], versions))) and not int(os.environ.get('PYT', 0)): print('Python %s.%s.%s detected.' % sys.version_info[:3]) print('Sorry, SickGear requires a Python version %s' % ', '.join(map( lambda r: '%s - %s' % tuple(map(lambda v: str(v).replace(',', '.')[1:-1], r)), versions))) sys.exit(1) sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), 'lib'))) is_win = 'win' == sys.platform[0:3] try: try: py_cache_path = os.path.normpath(os.path.join(os.path.dirname(__file__), '__pycache__')) for pf in ['_cleaner.pyc', '_cleaner.pyo']: cleaner_file = os.path.normpath(os.path.join(os.path.normpath(os.path.dirname(__file__)), pf)) if os.path.isfile(cleaner_file): os.remove(cleaner_file) if os.path.isdir(py_cache_path): shutil.rmtree(py_cache_path) except (BaseException, Exception): pass import _cleaner from sickgear import piper except (BaseException, Exception): pass try: import Cheetah except (BaseException, Exception): print('The Python module Cheetah is required') if is_win: print('(1) However, this first run may have just installed it, so try to simply rerun sickgear.py again') print('(2) If this output is a rerun of (1) then open a command line prompt and manually install using...') else: print('Manually install using...') print('cd <sickgear_installed_folder>') print('python -m pip install --user -r requirements.txt') print('python sickgear.py') sys.exit(1) # Compatibility fixes for Windows if is_win: codecs.register(lambda name: codecs.lookup('utf-8') if name == 'cp65001' else None) # We only need this for compiling an EXE from multiprocessing import freeze_support from configobj import ConfigObj # noinspection PyPep8Naming from encodingKludge import EXIT_BAD_ENCODING, SYS_ENCODING from exceptions_helper import ex import sickgear from sickgear import db, logger, name_cache, network_timezones from sickgear.event_queue import Events from sickgear.tv import TVShow from sickgear.webserveInit import WebServer from six import integer_types, iteritems throwaway = datetime.datetime.strptime('20110101', '%Y%m%d') rollback_loaded = None for signal_type in [signal.SIGTERM, signal.SIGINT] + ([] if not is_win else [signal.SIGBREAK]): signal.signal(signal_type, lambda signum, void: sickgear.sig_handler(signum=signum, _=void)) class SickGear(object): def __init__(self): # system event callback for shutdown/restart sickgear.events = Events(self.shutdown) # daemon constants self.run_as_daemon = False self.create_pid = False self.pid_file = '' self.run_as_systemd = False self.console_logging = False # webserver constants self.webserver = None self.force_update = False self.forced_port = None self.no_launch = False self.web_options = None self.webhost = None self.start_port = None self.log_dir = None @staticmethod def help_message(): """ print help message for commandline options """ global is_win help_msg = [''] help_msg += ['Usage: %s <option> <another option>\n' % sickgear.MY_FULLNAME] help_msg += ['Options:\n'] help_tmpl = ' %-10s%-17s%s' for ln in [ ('-h', '--help', 'Prints this message'), ('-f', '--forceupdate', 'Force update all shows in the DB (from tvdb) on startup'), ('-q', '--quiet', 'Disables logging to console'), ('', '--nolaunch', 'Suppress launching web browser on startup') ]: help_msg += [help_tmpl % ln] if is_win: for ln in [ ('-d', '--daemon', 'Running as daemon is not supported on Windows'), ('', '', 'On Windows, --daemon is substituted with: --quiet --nolaunch') ]: help_msg += [help_tmpl % ln] else: for ln in [ ('-d', '--daemon', 'Run as double forked daemon (includes options --quiet --nolaunch)'), ('-s', '--systemd', 'Run as systemd service (includes options --quiet --nolaunch)'), ('', '--pidfile=<path>', 'Combined with --daemon creates a pidfile (full path including filename)') ]: help_msg += [help_tmpl % ln] for ln in [ ('-p <port>', '--port=<port>', 'Override default/configured port to listen on'), ('', '--datadir=<path>', 'Override folder (full path) as location for'), ('', '', 'storing database, configfile, cache, logfiles'), ('', '', 'Default: %s' % sickgear.PROG_DIR), ('', '--config=<path>', 'Override config filename (full path including filename)'), ('', '', 'to load configuration from'), ('', '', 'Default: config.ini in %s or --datadir location' % sickgear.PROG_DIR), ('', '--noresize', 'Prevent resizing of the banner/posters even if PIL is installed') ]: help_msg += [help_tmpl % ln] return '\n'.join(help_msg) @staticmethod def execute_rollback(mo, max_v, load_msg): global rollback_loaded try: if None is rollback_loaded: rollback_loaded = db.get_rollback_module() if None is not rollback_loaded: rc = rollback_loaded.__dict__[mo]() rc.load_msg = load_msg rc.run(max_v) else: print(u'ERROR: Could not download Rollback Module.') except (BaseException, Exception): pass def start(self): global is_win # do some preliminary stuff sickgear.MY_FULLNAME = os.path.normpath(os.path.abspath(__file__)) sickgear.PROG_DIR = os.path.dirname(sickgear.MY_FULLNAME) sickgear.DATA_DIR = sickgear.PROG_DIR sickgear.MY_ARGS = sys.argv[1:] if EXIT_BAD_ENCODING: print('Sorry, you MUST add the SickGear folder to the PYTHONPATH environment variable') print('or find another way to force Python to use %s for string encoding.' % SYS_ENCODING) sys.exit(1) sickgear.SYS_ENCODING = SYS_ENCODING legacy_runner = globals().get('_legacy_sickgear_runner') if not legacy_runner: import __main__ legacy_runner = 'bear' in (getattr(__main__, '__file__', False) or '').split(os.sep)[-1].lower() sickgear.MEMCACHE['DEPRECATE_SB_RUNNER'] = legacy_runner # Need console logging for sickgear.py self.console_logging = not hasattr(sys, 'frozen') # Rename the main thread threading.current_thread().name = 'MAIN' try: opts, args = getopt.getopt(sys.argv[1:], 'hfqdsp::', ['help', 'forceupdate', 'quiet', 'nolaunch', 'daemon', 'systemd', 'pidfile=', 'port=', 'datadir=', 'config=', 'noresize', 'update-restart', 'update-pkg']) except getopt.GetoptError: sys.exit(self.help_message()) for o, a in opts: # Prints help message if o in ('-h', '--help'): sys.exit(self.help_message()) # For now we'll just silence the logging if o in ('-q', '--quiet'): self.console_logging = False # Should we update (from indexer) all shows in the DB right away? if o in ('-f', '--forceupdate'): self.force_update = True # Suppress launching web browser # Needed for OSes without default browser assigned # Prevent duplicate browser window when restarting in the app if o in ('--nolaunch',): self.no_launch = True # Override default/configured port if o in ('-p', '--port'): try: self.forced_port = int(a) except ValueError: sys.exit('Port: %s is not a number. Exiting.' % a) # Run as a double forked daemon if o in ('-d', '--daemon'): self.run_as_daemon = True # When running as daemon disable console_logging and don't start browser self.console_logging = False self.no_launch = True if is_win: self.run_as_daemon = False # Run as a systemd service if o in ('-s', '--systemd') and not is_win: self.run_as_systemd = True self.run_as_daemon = False self.console_logging = False self.no_launch = True # Write a pidfile if requested if o in ('--pidfile',): self.create_pid = True self.pid_file = str(a) # If the pidfile already exists, sickgear may still be running, so exit if os.path.exists(self.pid_file): sys.exit('PID file: %s already exists. Exiting.' % self.pid_file) # Specify folder to load the config file from if o in ('--config',): sickgear.CONFIG_FILE = os.path.abspath(a) # Specify folder to use as the data dir if o in ('--datadir',): sickgear.DATA_DIR = os.path.abspath(a) # Prevent resizing of the banner/posters even if PIL is installed if o in ('--noresize',): sickgear.NO_RESIZE = True # The pidfile is only useful in daemon mode, make sure we can write the file properly if self.create_pid: if self.run_as_daemon: pid_dir = os.path.dirname(self.pid_file) if not os.access(pid_dir, os.F_OK): sys.exit(u"PID dir: %s doesn't exist. Exiting." % pid_dir) if not os.access(pid_dir, os.W_OK): sys.exit(u'PID dir: %s must be writable (write permissions). Exiting.' % pid_dir) else: if self.console_logging: print(u'Not running in daemon mode. PID file creation disabled') self.create_pid = False # If they don't specify a config file then put it in the data dir if not sickgear.CONFIG_FILE: sickgear.CONFIG_FILE = os.path.join(sickgear.DATA_DIR, 'config.ini') # Make sure that we can create the data dir if not os.access(sickgear.DATA_DIR, os.F_OK): try: os.makedirs(sickgear.DATA_DIR, 0o744) except os.error: sys.exit(u'Unable to create data directory: %s Exiting.' % sickgear.DATA_DIR) # Make sure we can write to the data dir if not os.access(sickgear.DATA_DIR, os.W_OK): sys.exit(u'Data directory: %s must be writable (write permissions). Exiting.' % sickgear.DATA_DIR) # Make sure we can write to the config file if not os.access(sickgear.CONFIG_FILE, os.W_OK): if os.path.isfile(sickgear.CONFIG_FILE): sys.exit(u'Config file: %s must be writeable (write permissions). Exiting.' % sickgear.CONFIG_FILE) elif not os.access(os.path.dirname(sickgear.CONFIG_FILE), os.W_OK): sys.exit(u'Config file directory: %s must be writeable (write permissions). Exiting' % os.path.dirname(sickgear.CONFIG_FILE)) os.chdir(sickgear.DATA_DIR) if self.console_logging: print(u'Starting up SickGear from %s' % sickgear.CONFIG_FILE) # Load the config and publish it to the sickgear package if not os.path.isfile(sickgear.CONFIG_FILE): print(u'Unable to find "%s", all settings will be default!' % sickgear.CONFIG_FILE) sickgear.CFG = ConfigObj(sickgear.CONFIG_FILE) try: stack_size = int(sickgear.CFG['General']['stack_size']) except (BaseException, Exception): stack_size = None if stack_size: try: threading.stack_size(stack_size) except (BaseException, Exception) as er: print('Stack Size %s not set: %s' % (stack_size, ex(er))) if self.run_as_daemon: self.daemonize() # Get PID sickgear.PID = os.getpid() # Initialize the config sickgear.initialize(console_logging=self.console_logging) if self.forced_port: logger.log(u'Forcing web server to port %s' % self.forced_port) self.start_port = self.forced_port else: self.start_port = sickgear.WEB_PORT if sickgear.WEB_LOG: self.log_dir = sickgear.LOG_DIR else: self.log_dir = None # sickgear.WEB_HOST is available as a configuration value in various # places but is not configurable. It is supported here for historic reasons. if sickgear.WEB_HOST and '0.0.0.0' != sickgear.WEB_HOST: self.webhost = sickgear.WEB_HOST else: self.webhost = (('0.0.0.0', '::')[sickgear.WEB_IPV6], '')[sickgear.WEB_IPV64] # web server options self.web_options = dict( host=self.webhost, port=int(self.start_port), web_root=sickgear.WEB_ROOT, data_root=os.path.join(sickgear.PROG_DIR, 'gui', sickgear.GUI_NAME), log_dir=self.log_dir, username=sickgear.WEB_USERNAME, password=sickgear.WEB_PASSWORD, handle_reverse_proxy=sickgear.HANDLE_REVERSE_PROXY, enable_https=False, https_cert=None, https_key=None, ) if sickgear.ENABLE_HTTPS: self.web_options.update(dict( enable_https=sickgear.ENABLE_HTTPS, https_cert=os.path.join(sickgear.PROG_DIR, sickgear.HTTPS_CERT), https_key=os.path.join(sickgear.PROG_DIR, sickgear.HTTPS_KEY) )) # start web server try: # used to check if existing SG instances have been started sickgear.helpers.wait_for_free_port( sickgear.WEB_IPV6 and '::1' or self.web_options['host'], self.web_options['port']) self.webserver = WebServer(options=self.web_options) self.webserver.start() # wait for server thread to be started self.webserver.wait_server_start() sickgear.started = True except (BaseException, Exception): logger.log(u'Unable to start web server, is something else running on port %d?' % self.start_port, logger.ERROR) if self.run_as_systemd: self.exit(0) if sickgear.LAUNCH_BROWSER and not self.no_launch: logger.log(u'Launching browser and exiting', logger.ERROR) sickgear.launch_browser(self.start_port) self.exit(1) # Launch browser if sickgear.LAUNCH_BROWSER and not self.no_launch: sickgear.launch_browser(self.start_port) # send pid of sg instance to ui sickgear.classes.loading_msg.set_msg_progress('Process-id', sickgear.PID) # check all db versions for d, min_v, max_v, base_v, mo in [ ('failed.db', sickgear.failed_db.MIN_DB_VERSION, sickgear.failed_db.MAX_DB_VERSION, sickgear.failed_db.TEST_BASE_VERSION, 'FailedDb'), ('cache.db', sickgear.cache_db.MIN_DB_VERSION, sickgear.cache_db.MAX_DB_VERSION, sickgear.cache_db.TEST_BASE_VERSION, 'CacheDb'), ('sickbeard.db', sickgear.mainDB.MIN_DB_VERSION, sickgear.mainDB.MAX_DB_VERSION, sickgear.mainDB.TEST_BASE_VERSION, 'MainDb') ]: cur_db_version = db.DBConnection(d).checkDBVersion() # handling of standalone TEST db versions load_msg = 'Downgrading %s to production version' % d if 100000 <= cur_db_version != max_v: sickgear.classes.loading_msg.set_msg_progress(load_msg, 'Rollback') print('Your [%s] database version (%s) is a test db version and doesn\'t match SickGear required ' 'version (%s), downgrading to production db' % (d, cur_db_version, max_v)) self.execute_rollback(mo, max_v, load_msg) cur_db_version = db.DBConnection(d).checkDBVersion() if 100000 <= cur_db_version: print(u'Rollback to production failed.') sys.exit(u'If you have used other forks, your database may be unusable due to their changes') if 100000 <= max_v and None is not base_v: max_v = base_v # set max_v to the needed base production db for test_db print(u'Rollback to production of [%s] successful.' % d) sickgear.classes.loading_msg.set_msg_progress(load_msg, 'Finished') # handling of production version higher then current base of test db if isinstance(base_v, integer_types) and max_v >= 100000 > cur_db_version > base_v: sickgear.classes.loading_msg.set_msg_progress(load_msg, 'Rollback') print('Your [%s] database version (%s) is a db version and doesn\'t match SickGear required ' 'version (%s), downgrading to production base db' % (d, cur_db_version, max_v)) self.execute_rollback(mo, base_v, load_msg) cur_db_version = db.DBConnection(d).checkDBVersion() if 100000 <= cur_db_version: print(u'Rollback to production base failed.') sys.exit(u'If you have used other forks, your database may be unusable due to their changes') if 100000 <= max_v and None is not base_v: max_v = base_v # set max_v to the needed base production db for test_db print(u'Rollback to production base of [%s] successful.' % d) sickgear.classes.loading_msg.set_msg_progress(load_msg, 'Finished') # handling of production db versions if 0 < cur_db_version < 100000: if cur_db_version < min_v: print(u'Your [%s] database version (%s) is too old to migrate from with this version of SickGear' % (d, cur_db_version)) sys.exit(u'Upgrade using a previous version of SG first,' + u' or start with no database file to begin fresh') if cur_db_version > max_v: sickgear.classes.loading_msg.set_msg_progress(load_msg, 'Rollback') print(u'Your [%s] database version (%s) has been incremented past' u' what this version of SickGear supports. Trying to rollback now. Please wait...' % (d, cur_db_version)) self.execute_rollback(mo, max_v, load_msg) if db.DBConnection(d).checkDBVersion() > max_v: print(u'Rollback failed.') sys.exit(u'If you have used other forks, your database may be unusable due to their changes') print(u'Rollback of [%s] successful.' % d) sickgear.classes.loading_msg.set_msg_progress(load_msg, 'Finished') # migrate the config if it needs it from sickgear.config import ConfigMigrator migrator = ConfigMigrator(sickgear.CFG) if migrator.config_version > migrator.expected_config_version: self.execute_rollback('ConfigFile', migrator.expected_config_version, 'Downgrading config.ini') migrator = ConfigMigrator(sickgear.CFG) migrator.migrate_config() # free memory global rollback_loaded rollback_loaded = None sickgear.classes.loading_msg.message = 'Init SickGear' # Initialize the threads and other stuff sickgear.initialize(console_logging=self.console_logging) # Check if we need to perform a restore first restore_dir = os.path.join(sickgear.DATA_DIR, 'restore') if os.path.exists(restore_dir): sickgear.classes.loading_msg.message = 'Restoring files' if self.restore(restore_dir, sickgear.DATA_DIR): logger.log(u'Restore successful...') else: logger.log_error_and_exit(u'Restore FAILED!') # refresh network timezones sickgear.classes.loading_msg.message = 'Checking network timezones' network_timezones.update_network_dict() update_arg = '--update-restart' manual_update_arg = '--update-pkg' if update_arg not in sickgear.MY_ARGS and sickgear.UPDATES_TODO \ and (manual_update_arg in sickgear.MY_ARGS or sickgear.UPDATE_PACKAGES_AUTO): sickgear.MEMCACHE['update_restart'] = piper.pip_update( sickgear.classes.loading_msg, sickgear.UPDATES_TODO, sickgear.DATA_DIR) sickgear.UPDATES_TODO = dict() sickgear.save_config() if manual_update_arg in sickgear.MY_ARGS: sickgear.MY_ARGS.remove(manual_update_arg) if not sickgear.MEMCACHE.get('update_restart'): # Build from the DB to start with sickgear.classes.loading_msg.message = 'Loading shows from db' sickgear.indexermapper.indexer_list = [i for i in sickgear.TVInfoAPI().all_sources if sickgear.TVInfoAPI(i).config.get('show_url') and True is not sickgear.TVInfoAPI(i).config.get('people_only')] self.load_shows_from_db() sickgear.MEMCACHE['history_tab'] = sickgear.webserve.History.menu_tab( sickgear.MEMCACHE['history_tab_limit']) if not db.DBConnection().has_flag('ignore_require_cleaned'): from sickgear.show_updater import clean_ignore_require_words sickgear.classes.loading_msg.message = 'Cleaning ignore/require words lists' clean_ignore_require_words() db.DBConnection().set_flag('ignore_require_cleaned') # Fire up threads sickgear.classes.loading_msg.message = 'Starting threads' sickgear.start() if sickgear.MEMCACHE.get('update_restart'): sickgear.MY_ARGS.append(update_arg) sickgear.classes.loading_msg.message = 'Restarting SickGear after update' time.sleep(3) sickgear.restart(soft=False) # restart wait loop while True: time.sleep(1) if update_arg in sickgear.MY_ARGS: sickgear.MY_ARGS.remove(update_arg) # Build internal name cache sickgear.classes.loading_msg.message = 'Build name cache' name_cache.buildNameCache() # load all ids from xem sickgear.classes.loading_msg.message = 'Loading xem data' startup_background_tasks = threading.Thread(name='XEMUPDATER', target=sickgear.scene_exceptions.get_xem_ids) startup_background_tasks.start() sickgear.classes.loading_msg.message = 'Checking history' # check history snatched_proper update if not db.DBConnection().has_flag('history_snatch_proper'): # noinspection PyUnresolvedReferences history_snatched_proper_task = threading.Thread(name='UPGRADE-HISTORY-ACTION', target=sickgear.history.history_snatched_proper_fix) history_snatched_proper_task.start() if not db.DBConnection().has_flag('kodi_nfo_default_removed'): sickgear.metadata.kodi.remove_default_attr() if not db.DBConnection().has_flag('kodi_nfo_rebuild_uniqueid'): sickgear.metadata.kodi.rebuild_nfo() my_db = db.DBConnection() sql_result = my_db.select('SELECT * FROM tv_src_switch WHERE status = 0') if sql_result: switching = True l_msg = 'Adding shows that are switching tv source to queue' total_result = len(sql_result) def q_switch(switch): try: _show_obj = sickgear.helpers.find_show_by_id({switch['new_indexer']: switch['new_indexer_id']}) if _show_obj: sickgear.show_queue_scheduler.action.switch_show( show_obj=_show_obj, new_tvid=switch['new_indexer'], new_prodid=switch['new_indexer_id'], force_id=bool(switch['force_id']), uid=switch['uid'], set_pause=bool(switch['set_pause']), mark_wanted=bool(switch['mark_wanted']), resume=True, old_tvid=switch['old_indexer'], old_prodid=switch['old_indexer_id'] ) except (BaseException, Exception): pass sickgear.classes.loading_msg.set_msg_progress(l_msg, '0/%s' % total_result) for i, cur_switch in enumerate(sql_result, 1): sickgear.classes.loading_msg.set_msg_progress(l_msg, '%s/%s' % (i, total_result)) try: show_obj = sickgear.helpers.find_show_by_id( {cur_switch['old_indexer']: cur_switch['old_indexer_id']}) except (BaseException, Exception): if cur_switch['new_indexer_id']: # show id was already switched, but not finished updated, so queue as update q_switch(cur_switch) continue if show_obj: try: sickgear.show_queue_scheduler.action.switch_show( show_obj=show_obj, new_tvid=cur_switch['new_indexer'], new_prodid=cur_switch['new_indexer_id'], force_id=bool(cur_switch['force_id']), uid=cur_switch['uid'], set_pause=bool(cur_switch['set_pause']), mark_wanted=bool(cur_switch['mark_wanted']) ) except (BaseException, Exception): continue elif cur_switch['new_indexer_id']: # show id was already switched, but not finished updated, so resume q_switch(cur_switch) else: switching = False # Start an update if we're supposed to if not switching and (self.force_update or sickgear.UPDATE_SHOWS_ON_START): sickgear.classes.loading_msg.message = 'Starting a forced show update' background_start_forced_show_update = threading.Thread(name='STARTUP-FORCE-SHOW-UPDATE', target=sickgear.show_update_scheduler.action.run) background_start_forced_show_update.start() sickgear.classes.loading_msg.message = 'Switching to default web server' time.sleep(2) self.webserver.switch_handlers() # main loop while 1: time.sleep(1) def daemonize(self): """ Fork off as a daemon """ # pylint: disable=E1101 # Make a non-session-leader child process try: pid = os.fork() # only available in UNIX if 0 != pid: self.exit(0) except OSError as er: sys.stderr.write('fork #1 failed: %d (%s)\n' % (er.errno, er.strerror)) sys.exit(1) os.setsid() # only available in UNIX # Make sure I can read my own files and shut out others prev = os.umask(0) os.umask(prev and int('077', 8)) # Make the child a session-leader by detaching from the terminal try: pid = os.fork() # only available in UNIX if 0 != pid: self.exit(0) except OSError as er: sys.stderr.write('fork #2 failed: %d (%s)\n' % (er.errno, er.strerror)) sys.exit(1) # Write pid if self.create_pid: pid = str(os.getpid()) logger.log(u'Writing PID: %s to %s' % (pid, self.pid_file)) try: os.fdopen(os.open(self.pid_file, os.O_CREAT | os.O_WRONLY, 0o644), 'w').write('%s\n' % pid) except (BaseException, Exception) as er: logger.log_error_and_exit('Unable to write PID file: %s Error: %s [%s]' % ( self.pid_file, er.strerror, er.errno)) # Redirect all output sys.stdout.flush() sys.stderr.flush() devnull = getattr(os, 'devnull', '/dev/null') stdin = open(devnull, 'r') stdout = open(devnull, 'a+') stderr = open(devnull, 'a+') os.dup2(stdin.fileno(), sys.stdin.fileno()) os.dup2(stdout.fileno(), sys.stdout.fileno()) os.dup2(stderr.fileno(), sys.stderr.fileno()) @staticmethod def remove_pid_file(pidfile): try: if os.path.exists(pidfile): os.remove(pidfile) except (IOError, OSError): return False return True @staticmethod def load_shows_from_db(): """ Populates the showList with shows from the database """ logger.log(u'Loading initial show list') my_db = db.DBConnection(row_type='dict') sql_result = my_db.select( """ SELECT tv_shows.indexer AS tv_id, tv_shows.indexer_id AS prod_id, tv_shows.*, ii.akas AS ii_akas, ii.certificates AS ii_certificates, ii.countries AS ii_countries, ii.country_codes AS ii_country_codes, ii.genres AS ii_genres, ii.imdb_id AS ii_imdb_id, ii.indexer AS ii_indexer, ii.indexer_id AS ii_indexer_id, ii.last_update AS ii_ii_last_update, ii.rating AS ii_rating, ii.runtimes AS ii_runtimes, ii.is_mini_series AS ii_is_mini_series, ii.episode_count AS ii_episode_count, ii.title AS ii_title, ii.votes AS ii_votes, ii.year AS ii_year, tsnf.fail_count AS tsnf_fail_count, tsnf.indexer AS tsnf_indexer, tsnf.indexer_id AS tsnf_indexer_id, tsnf.last_check AS tsnf_last_check, tsnf.last_success AS tsnf_last_success FROM tv_shows LEFT JOIN imdb_info ii ON tv_shows.indexer = ii.indexer AND tv_shows.indexer_id = ii.indexer_id LEFT JOIN tv_shows_not_found tsnf ON tv_shows.indexer = tsnf.indexer AND tv_shows.indexer_id = tsnf.indexer_id """) sickgear.showList = [] sickgear.showDict = {} for cur_result in sql_result: try: tv_id = int(cur_result['tv_id']) prod_id = int(cur_result['prod_id']) if cur_result['ii_indexer_id']: imdb_info_sql = {_fk.replace('ii_', ''): _fv for _fk, _fv in iteritems(cur_result) if _fk.startswith('ii_')} else: imdb_info_sql = None show_obj = TVShow(tv_id, prod_id, show_result=cur_result, imdb_info_result=imdb_info_sql) if cur_result['tsnf_indexer_id']: failed_result = {_fk.replace('tsnf_', ''): _fv for _fk, _fv in iteritems(cur_result) if _fk.startswith('tsnf_')} show_obj.helper_load_failed_db(sql_result=failed_result) sickgear.showList.append(show_obj) sickgear.showDict[show_obj.sid_int] = show_obj _ = show_obj.ids except (BaseException, Exception) as err: logger.log('There was an error creating the show in %s: %s' % ( cur_result['location'], ex(err)), logger.ERROR) sickgear.webserve.Home.make_showlist_unique_names() @staticmethod def restore(src_dir, dst_dir): try: for filename in os.listdir(src_dir): src_file = os.path.join(src_dir, filename) dst_file = os.path.join(dst_dir, filename) bak_file = os.path.join(dst_dir, '%s.bak' % filename) shutil.move(dst_file, bak_file) shutil.move(src_file, dst_file) os.rmdir(src_dir) return True except (BaseException, Exception): return False def shutdown(self, ev_type): if sickgear.started: # stop all tasks sickgear.halt() # save all shows to DB sickgear.save_all() # shutdown web server if self.webserver: logger.log('Shutting down Tornado') self.webserver.shut_down() try: self.webserver.join(10) except (BaseException, Exception): pass # if run as daemon delete the pidfile if self.run_as_daemon and self.create_pid: self.remove_pid_file(self.pid_file) if sickgear.events.SystemEvent.RESTART == ev_type: popen_list = [] if sickgear.update_software_scheduler.action.install_type in ('git', 'source'): popen_list = [sys.executable, sickgear.MY_FULLNAME] if popen_list: popen_list += sickgear.MY_ARGS if self.run_as_systemd: logger.log(u'Restarting SickGear with exit(1) handler and %s' % popen_list) logger.close() self.exit(1) if '--nolaunch' not in popen_list: popen_list += ['--nolaunch'] logger.log(u'Restarting SickGear with %s' % popen_list) logger.close() from _23 import Popen with Popen(popen_list, cwd=os.getcwd()): self.exit(0) # system exit self.exit(0) @staticmethod def exit(code): # noinspection PyProtectedMember os._exit(code) if '__main__' == __name__: freeze_support() try: try: # start SickGear SickGear().start() except IOError as e: if e.errno != errno.EINTR: raise except SystemExit as e: print('%s' % ex(e)) except (BaseException, Exception) as e: import traceback print(traceback.format_exc()) logger.log('SickGear.Start() exception caught %s: %s' % (ex(e), traceback.format_exc()))