Ejemplo n.º 1
0
    def start(self):
        """
        Start SickChill
        """
        # do some preliminary stuff
        settings.MY_FULLNAME = os.path.normpath(os.path.abspath(__file__))
        settings.MY_NAME = os.path.basename(settings.MY_FULLNAME)

        settings.DATA_DIR = os.path.dirname(settings.PROG_DIR)
        profile_path = str(Path.home().joinpath("sickchill").absolute())
        if check_installed():
            settings.DATA_DIR = profile_path

        if settings.DATA_DIR != profile_path:
            checks = ["sickbeard.db", "sickchill.db", "config.ini"]
            if not any([
                    os.path.isfile(os.path.join(settings.DATA_DIR, check))
                    for check in checks
            ]):
                settings.DATA_DIR = profile_path

        settings.MY_ARGS = sys.argv[1:]

        # Rename the main thread
        threading.currentThread().name = "MAIN"

        args = SickChillArgumentParser(settings.DATA_DIR).parse_args()

        if args.force_update:
            result = self.force_update()
            sys.exit(int(not result))  # Ok -> 0 , Error -> 1

        settings.NO_RESIZE = args.noresize
        self.console_logging = not (hasattr(sys, "frozen") or args.quiet
                                    or args.daemon)
        self.no_launch = args.nolaunch or args.daemon
        self.forced_port = args.port
        self.run_as_daemon = args.daemon and platform.system() != "Windows"

        # The pid file is only useful in daemon mode, make sure we can write the file properly
        if bool(args.pidfile) and not self.run_as_daemon:
            if self.console_logging:
                sys.stdout.write(
                    "Not running in daemon mode. PID file creation disabled.\n"
                )

        settings.DATA_DIR = os.path.abspath(
            args.datadir) if args.datadir else settings.DATA_DIR
        settings.CONFIG_FILE = os.path.abspath(
            args.config) if args.config else os.path.join(
                settings.DATA_DIR, "config.ini")

        # Make sure that we can create the data dir
        if not os.access(settings.DATA_DIR, os.F_OK):
            try:
                os.makedirs(settings.DATA_DIR, 0o744)
            except os.error:
                raise SystemExit("Unable to create data directory: {0}".format(
                    settings.DATA_DIR))

        # Make sure we can write to the data dir
        if not os.access(settings.DATA_DIR, os.W_OK):
            raise SystemExit("Data directory must be writeable: {0}".format(
                settings.DATA_DIR))

        # Make sure we can write to the config file
        if not os.access(settings.CONFIG_FILE, os.W_OK):
            if os.path.isfile(settings.CONFIG_FILE):
                raise SystemExit("Config file must be writeable: {0}".format(
                    settings.CONFIG_FILE))
            elif not os.access(os.path.dirname(settings.CONFIG_FILE), os.W_OK):
                raise SystemExit(
                    "Config file root dir must be writeable: {0}".format(
                        os.path.dirname(settings.CONFIG_FILE)))

        os.chdir(settings.DATA_DIR)

        # Check if we need to perform a restore first
        restore_dir = os.path.join(settings.DATA_DIR, "restore")
        if os.path.exists(restore_dir):
            success = self.restore_db(restore_dir, settings.DATA_DIR)
            if self.console_logging:
                sys.stdout.write(
                    "Restore: restoring DB and config.ini {0}!\n".format(
                        ("FAILED", "SUCCESSFUL")[success]))

        # Load the config and publish it to the oldbeard package
        if self.console_logging and not os.path.isfile(settings.CONFIG_FILE):
            sys.stdout.write(
                "Unable to find {0}, all settings will be default!\n".format(
                    settings.CONFIG_FILE))

        settings.CFG = ConfigObj(settings.CONFIG_FILE,
                                 encoding="UTF-8",
                                 indent_type="  ")

        # Initialize the config and our threads
        sickchill.start.initialize(consoleLogging=self.console_logging)

        # Get PID
        settings.PID = os.getpid()

        # Build from the DB to start with
        self.load_shows_from_db()

        logger.info("Starting SickChill [{branch}] using '{config}'".format(
            branch=settings.BRANCH, config=settings.CONFIG_FILE))

        self.clear_cache()

        if settings.DEVELOPER:
            settings.movie_list = MovieList()

        web_options = {}
        if self.forced_port:
            logger.info("Forcing web server to port {port}".format(
                port=self.forced_port))
            self.start_port = self.forced_port
            web_options.update({
                "port": int(self.start_port),
            })
        else:
            self.start_port = settings.WEB_PORT

        # start web server
        self.web_server = SRWebServer(web_options)
        self.web_server.start()

        # Fire up all our threads
        sickchill.start.start()

        # Build internal name cache
        name_cache.build_name_cache()

        # Pre-populate network timezones, it isn't thread safe
        network_timezones.update_network_dict()

        # sure, why not?
        if settings.USE_FAILED_DOWNLOADS:
            failed_history.trimHistory()

        # Check for metadata indexer updates for shows (sets the next aired ep!)
        # oldbeard.showUpdateScheduler.forceRun()

        # Launch browser
        if settings.LAUNCH_BROWSER and not self.no_launch:
            sickchill.start.launchBrowser(
                "https" if settings.ENABLE_HTTPS else "http", self.start_port,
                settings.WEB_ROOT)

        # main loop
        while True:
            time.sleep(1)
Ejemplo n.º 2
0
class SickChill(object):
    """
    Main SickChill module
    """
    def __init__(self):
        # system event callback for shutdown/restart
        settings.events = Events(self.shutdown)

        # daemon constants
        self.run_as_daemon = False

        # web server constants
        self.web_server = None
        self.forced_port = None
        self.no_launch = False

        self.start_port = settings.WEB_PORT

        self.console_logging = True

    @staticmethod
    def clear_cache():
        """
        Remove the Mako cache directory
        """
        try:
            cache_folder = os.path.join(settings.CACHE_DIR, "mako")
            if os.path.isdir(cache_folder):
                shutil.rmtree(cache_folder)
        except Exception:
            logger.warning("Unable to remove the cache/mako directory!")

    def start(self):
        """
        Start SickChill
        """
        # do some preliminary stuff
        settings.MY_FULLNAME = os.path.normpath(os.path.abspath(__file__))
        settings.MY_NAME = os.path.basename(settings.MY_FULLNAME)

        settings.DATA_DIR = os.path.dirname(settings.PROG_DIR)
        profile_path = str(Path.home().joinpath("sickchill").absolute())
        if check_installed():
            settings.DATA_DIR = profile_path

        if settings.DATA_DIR != profile_path:
            checks = ["sickbeard.db", "sickchill.db", "config.ini"]
            if not any([
                    os.path.isfile(os.path.join(settings.DATA_DIR, check))
                    for check in checks
            ]):
                settings.DATA_DIR = profile_path

        settings.MY_ARGS = sys.argv[1:]

        # Rename the main thread
        threading.currentThread().name = "MAIN"

        args = SickChillArgumentParser(settings.DATA_DIR).parse_args()

        if args.force_update:
            result = self.force_update()
            sys.exit(int(not result))  # Ok -> 0 , Error -> 1

        settings.NO_RESIZE = args.noresize
        self.console_logging = not (hasattr(sys, "frozen") or args.quiet
                                    or args.daemon)
        self.no_launch = args.nolaunch or args.daemon
        self.forced_port = args.port
        self.run_as_daemon = args.daemon and platform.system() != "Windows"

        # The pid file is only useful in daemon mode, make sure we can write the file properly
        if bool(args.pidfile) and not self.run_as_daemon:
            if self.console_logging:
                sys.stdout.write(
                    "Not running in daemon mode. PID file creation disabled.\n"
                )

        settings.DATA_DIR = os.path.abspath(
            args.datadir) if args.datadir else settings.DATA_DIR
        settings.CONFIG_FILE = os.path.abspath(
            args.config) if args.config else os.path.join(
                settings.DATA_DIR, "config.ini")

        # Make sure that we can create the data dir
        if not os.access(settings.DATA_DIR, os.F_OK):
            try:
                os.makedirs(settings.DATA_DIR, 0o744)
            except os.error:
                raise SystemExit("Unable to create data directory: {0}".format(
                    settings.DATA_DIR))

        # Make sure we can write to the data dir
        if not os.access(settings.DATA_DIR, os.W_OK):
            raise SystemExit("Data directory must be writeable: {0}".format(
                settings.DATA_DIR))

        # Make sure we can write to the config file
        if not os.access(settings.CONFIG_FILE, os.W_OK):
            if os.path.isfile(settings.CONFIG_FILE):
                raise SystemExit("Config file must be writeable: {0}".format(
                    settings.CONFIG_FILE))
            elif not os.access(os.path.dirname(settings.CONFIG_FILE), os.W_OK):
                raise SystemExit(
                    "Config file root dir must be writeable: {0}".format(
                        os.path.dirname(settings.CONFIG_FILE)))

        os.chdir(settings.DATA_DIR)

        # Check if we need to perform a restore first
        restore_dir = os.path.join(settings.DATA_DIR, "restore")
        if os.path.exists(restore_dir):
            success = self.restore_db(restore_dir, settings.DATA_DIR)
            if self.console_logging:
                sys.stdout.write(
                    "Restore: restoring DB and config.ini {0}!\n".format(
                        ("FAILED", "SUCCESSFUL")[success]))

        # Load the config and publish it to the oldbeard package
        if self.console_logging and not os.path.isfile(settings.CONFIG_FILE):
            sys.stdout.write(
                "Unable to find {0}, all settings will be default!\n".format(
                    settings.CONFIG_FILE))

        settings.CFG = ConfigObj(settings.CONFIG_FILE,
                                 encoding="UTF-8",
                                 indent_type="  ")

        # Initialize the config and our threads
        sickchill.start.initialize(consoleLogging=self.console_logging)

        # Get PID
        settings.PID = os.getpid()

        # Build from the DB to start with
        self.load_shows_from_db()

        logger.info("Starting SickChill [{branch}] using '{config}'".format(
            branch=settings.BRANCH, config=settings.CONFIG_FILE))

        self.clear_cache()

        if settings.DEVELOPER:
            settings.movie_list = MovieList()

        web_options = {}
        if self.forced_port:
            logger.info("Forcing web server to port {port}".format(
                port=self.forced_port))
            self.start_port = self.forced_port
            web_options.update({
                "port": int(self.start_port),
            })
        else:
            self.start_port = settings.WEB_PORT

        # start web server
        self.web_server = SRWebServer(web_options)
        self.web_server.start()

        # Fire up all our threads
        sickchill.start.start()

        # Build internal name cache
        name_cache.build_name_cache()

        # Pre-populate network timezones, it isn't thread safe
        network_timezones.update_network_dict()

        # sure, why not?
        if settings.USE_FAILED_DOWNLOADS:
            failed_history.trimHistory()

        # Check for metadata indexer updates for shows (sets the next aired ep!)
        # oldbeard.showUpdateScheduler.forceRun()

        # Launch browser
        if settings.LAUNCH_BROWSER and not self.no_launch:
            sickchill.start.launchBrowser(
                "https" if settings.ENABLE_HTTPS else "http", self.start_port,
                settings.WEB_ROOT)

        # main loop
        while True:
            time.sleep(1)

    @staticmethod
    def load_shows_from_db():
        """
        Populates the showList with shows from the database
        """
        logger.debug("Loading initial show list")

        main_db_con = db.DBConnection()
        sql_results = main_db_con.select(
            "SELECT indexer, indexer_id, location FROM tv_shows;")

        settings.showList = []
        for sql_show in sql_results:
            try:
                cur_show = TVShow(sql_show["indexer"], sql_show["indexer_id"])
                cur_show.nextEpisode()
                settings.showList.append(cur_show)
            except Exception as error:
                logger.exception(
                    "There was an error creating the show in {0}: Error {1}".
                    format(sql_show["location"], error))
                logger.debug(traceback.format_exc())

    @staticmethod
    def restore_db(src_dir, dst_dir):
        """
        Restore the Database from a backup

        :param src_dir: Directory containing backup
        :param dst_dir: Directory to restore to
        :return:
        """
        try:
            files_list = [
                "sickbeard.db", "sickchill.db", "config.ini", "failed.db",
                "cache.db"
            ]
            for filename in files_list:
                src_file = os.path.join(src_dir, filename)
                dst_file = os.path.join(dst_dir, filename)
                bak_file = os.path.join(
                    dst_dir, "{0}.bak-{1}".format(
                        filename,
                        datetime.datetime.now().strftime("%Y%m%d_%H%M%S")))
                sickchill_db = os.path.join(dst_dir, "sickchill.db")
                sickbeard_db = os.path.join(src_dir, "sickbeard.db")
                if os.path.isfile(src_file):
                    if src_file == sickbeard_db:
                        dst_file = sickchill_db

                    if os.path.isfile(dst_file):
                        shutil.move(dst_file, bak_file)
                    shutil.move(src_file, dst_file)

            sickbeard_db = os.path.join(dst_dir, "sickbeard.db")
            sickchill_db = os.path.join(dst_dir, "sickchill.db")
            if os.path.isfile(
                    sickbeard_db) and not os.path.isfile(sickchill_db):
                shutil.move(sickbeard_db, sickchill_db)

            return True
        except Exception:
            return False

    def shutdown(self, event):
        """
        Shut down SickChill

        :param event: Type of shutdown event, used to see if restart required
        """
        if settings.started:
            sickchill.start.halt()  # stop all tasks
            sickchill.start.saveAll()  # save all shows to DB

            # shutdown web server
            if self.web_server:
                logger.info("Shutting down Tornado")
                self.web_server.shutdown()

                try:
                    self.web_server.join(10)
                except Exception:
                    pass

            self.clear_cache()  # Clean cache

            # if run as daemon delete the pid file
            remove_pid_file()

            if event == sickchill.oldbeard.event_queue.Events.SystemEvent.RESTART:
                popen_list = [sys.executable, settings.MY_FULLNAME]
                if popen_list and not settings.NO_RESTART:
                    popen_list += settings.MY_ARGS
                    if "--nolaunch" not in popen_list:
                        popen_list += ["--nolaunch"]
                    logger.info("Restarting SickChill with {options}".format(
                        options=popen_list))
                    # shutdown the logger to make sure it's released the logfile BEFORE it restarts SC.
                    logger.shutdown()
                    subprocess.Popen(popen_list,
                                     cwd=os.getcwd(),
                                     universal_newlines=True)

        # Make sure the logger has stopped, just in case
        logger.shutdown()
        os._exit(0)

    @staticmethod
    def force_update():
        """
        Forces SickChill to update to the latest version and exit.

        :return: True if successful, False otherwise
        """
        def update_with_git():
            def run_git(updater, cmd):
                stdout_, stderr_, exit_status = updater._run_git(
                    updater._git_path, cmd)
                if not exit_status == 0:
                    print("Failed to run command: {0} {1}".format(
                        updater._git_path, cmd))
                    return False
                else:
                    return True

            updater = GitUpdateManager()
            if not run_git(
                    updater,
                    "config remote.origin.url https://github.com/SickChill/SickChill.git"
            ):
                return False
            if not run_git(updater, "fetch origin --prune"):
                return False
            if not run_git(updater, "checkout master"):
                return False
            if not run_git(updater, "reset --hard origin/master"):
                return False

            return True

        if os.path.isdir(
                os.path.join(os.path.dirname(settings.PROG_DIR),
                             ".git")):  # update with git
            print("Forcing SickChill to update using git...")
            result = update_with_git()
            if result:
                print(
                    "Successfully updated to latest commit. You may now run SickChill normally."
                )
                return True
            else:
                print("Error while trying to force an update using git.")

        print("Forcing SickChill to update using source...")
        if not SourceUpdateManager().update():
            print("Failed to force an update.")
            return False

        print(
            "Successfully updated to latest commit. You may now run SickChill normally."
        )
        return True
Ejemplo n.º 3
0
class SickChill(object):
    """
    Main SickChill module
    """
    def __init__(self):
        # system event callback for shutdown/restart
        sickbeard.events = Events(self.shutdown)

        # daemon constants
        self.run_as_daemon = False
        self.create_pid = False
        self.pid_file = ''

        # web server constants
        self.web_server = None
        self.forced_port = None
        self.no_launch = False

        self.web_host = '0.0.0.0'
        self.start_port = sickbeard.WEB_PORT
        self.web_options = {}

        self.log_dir = None
        self.console_logging = True

    @staticmethod
    def clear_cache():
        """
        Remove the Mako cache directory
        """
        try:
            cache_folder = ek(os.path.join, sickbeard.CACHE_DIR, 'mako')
            if os.path.isdir(cache_folder):
                shutil.rmtree(cache_folder)
        except Exception:
            logger.log('Unable to remove the cache/mako directory!',
                       logger.WARNING)

    @staticmethod
    def help_message():
        """
        Print help message for commandline options
        """
        help_msg = __doc__
        help_msg = help_msg.replace('SickBeard.py', sickbeard.MY_FULLNAME)
        help_msg = help_msg.replace('SickChill directory', sickbeard.PROG_DIR)

        return help_msg

    def start(self):
        """
        Start SickChill
        """
        # do some preliminary stuff
        sickbeard.MY_FULLNAME = ek(os.path.normpath,
                                   ek(os.path.abspath, __file__))
        sickbeard.MY_NAME = ek(os.path.basename, sickbeard.MY_FULLNAME)
        sickbeard.PROG_DIR = ek(os.path.dirname, sickbeard.MY_FULLNAME)
        sickbeard.LOCALE_DIR = ek(os.path.join, sickbeard.PROG_DIR, 'locale')
        sickbeard.DATA_DIR = sickbeard.PROG_DIR
        sickbeard.MY_ARGS = sys.argv[1:]

        try:
            locale.setlocale(locale.LC_ALL, '')
            sickbeard.SYS_ENCODING = locale.getpreferredencoding()
        except (locale.Error, IOError):
            sickbeard.SYS_ENCODING = 'UTF-8'


        if not sickbeard.SYS_ENCODING or sickbeard.SYS_ENCODING.lower() in ('ansi_x3.4-1968', 'us-ascii', 'ascii', 'charmap') or \
                (sys.platform.startswith('win') and sys.getwindowsversion()[0] >= 6 and str(getattr(sys.stdout, 'device', sys.stdout).encoding).lower() in ('cp65001', 'charmap')):
            sickbeard.SYS_ENCODING = 'UTF-8'

        # TODO: Continue working on making this unnecessary, this hack creates all sorts of hellish problems
        if not hasattr(sys, 'setdefaultencoding'):
            reload_module(sys)

        try:
            # On non-unicode builds this will raise an AttributeError, if encoding type is not valid it throws a LookupError
            sys.setdefaultencoding(sickbeard.SYS_ENCODING)
        except (AttributeError, LookupError):
            sys.exit(
                'Sorry, you MUST add the SickChill folder to the PYTHONPATH environment variable\n'
                'or find another way to force Python to use {} for string encoding.'
                .format(sickbeard.SYS_ENCODING))

        # Rename the main thread
        threading.currentThread().name = 'MAIN'

        args = SickChillArgumentParser(sickbeard.PROG_DIR).parse_args()

        if args.force_update:
            result = self.force_update()
            sys.exit(int(not result))  # Ok -> 0 , Error -> 1

        # Need console logging for SickBeard.py and SickBeard-console.exe
        sickbeard.NO_RESIZE = args.noresize
        self.console_logging = (not hasattr(sys, 'frozen')) or (
            sickbeard.MY_NAME.lower().find('-console') > 0) and not args.quiet
        self.no_launch = args.nolaunch
        self.forced_port = args.port
        if args.daemon:
            self.run_as_daemon = platform.system() != 'Windows'
            self.console_logging = False
            self.no_launch = True

        self.create_pid = bool(args.pidfile)
        self.pid_file = args.pidfile
        if self.pid_file and ek(os.path.exists, self.pid_file):
            # If the pid file already exists, SickChill may still be running, so exit
            raise SystemExit('PID file: {0} already exists. Exiting.'.format(
                self.pid_file))

        sickbeard.DATA_DIR = ek(
            os.path.abspath,
            args.datadir) if args.datadir else sickbeard.DATA_DIR
        sickbeard.CONFIG_FILE = ek(
            os.path.abspath, args.config) if args.config else ek(
                os.path.join, sickbeard.DATA_DIR, 'config.ini')

        # The pid file 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 = ek(os.path.dirname, self.pid_file)
                if not ek(os.access, pid_dir, os.F_OK):
                    sys.exit('PID dir: {0} doesn\'t exist. Exiting.'.format(
                        pid_dir))
                if not ek(os.access, pid_dir, os.W_OK):
                    raise SystemExit(
                        'PID dir: {0} must be writable (write permissions). Exiting.'
                        .format(pid_dir))
            else:
                if self.console_logging:
                    sys.stdout.write(
                        'Not running in daemon mode. PID file creation disabled.\n'
                    )
                self.create_pid = False

        # Make sure that we can create the data dir
        if not ek(os.access, sickbeard.DATA_DIR, os.F_OK):
            try:
                ek(os.makedirs, sickbeard.DATA_DIR, 0o744)
            except os.error:
                raise SystemExit('Unable to create data directory: {0}'.format(
                    sickbeard.DATA_DIR))

        # Make sure we can write to the data dir
        if not ek(os.access, sickbeard.DATA_DIR, os.W_OK):
            raise SystemExit('Data directory must be writeable: {0}'.format(
                sickbeard.DATA_DIR))

        # Make sure we can write to the config file
        if not ek(os.access, sickbeard.CONFIG_FILE, os.W_OK):
            if ek(os.path.isfile, sickbeard.CONFIG_FILE):
                raise SystemExit('Config file must be writeable: {0}'.format(
                    sickbeard.CONFIG_FILE))
            elif not ek(os.access, ek(os.path.dirname, sickbeard.CONFIG_FILE),
                        os.W_OK):
                raise SystemExit(
                    'Config file root dir must be writeable: {0}'.format(
                        ek(os.path.dirname, sickbeard.CONFIG_FILE)))

        ek(os.chdir, sickbeard.DATA_DIR)

        # Check if we need to perform a restore first
        restore_dir = ek(os.path.join, sickbeard.DATA_DIR, 'restore')
        if ek(os.path.exists, restore_dir):
            success = self.restore_db(restore_dir, sickbeard.DATA_DIR)
            if self.console_logging:
                sys.stdout.write(
                    'Restore: restoring DB and config.ini {0}!\n'.format(
                        ('FAILED', 'SUCCESSFUL')[success]))

        # Load the config and publish it to the sickbeard package
        if self.console_logging and not ek(os.path.isfile,
                                           sickbeard.CONFIG_FILE):
            sys.stdout.write(
                'Unable to find {0}, all settings will be default!\n'.format(
                    sickbeard.CONFIG_FILE))

        sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE, encoding='UTF-8')

        # Initialize the config and our threads
        sickbeard.initialize(consoleLogging=self.console_logging)

        if self.run_as_daemon:
            self.daemonize()

        # Get PID
        sickbeard.PID = os.getpid()

        # Build from the DB to start with
        self.load_shows_from_db()

        logger.log('Starting SickChill [{branch}] using \'{config}\''.format(
            branch=sickbeard.BRANCH, config=sickbeard.CONFIG_FILE))

        self.clear_cache()

        if self.forced_port:
            logger.log('Forcing web server to port {port}'.format(
                port=self.forced_port))
            self.start_port = self.forced_port
        else:
            self.start_port = sickbeard.WEB_PORT

        if sickbeard.WEB_LOG:
            self.log_dir = sickbeard.LOG_DIR
        else:
            self.log_dir = None

        # sickbeard.WEB_HOST is available as a configuration value in various
        # places but is not configurable. It is supported here for historic reasons.
        if sickbeard.WEB_HOST and sickbeard.WEB_HOST != '0.0.0.0':
            self.web_host = sickbeard.WEB_HOST
        else:
            self.web_host = '' if sickbeard.WEB_IPV6 else '0.0.0.0'

        # web server options
        self.web_options = {
            'port':
            int(self.start_port),
            'host':
            self.web_host,
            'data_root':
            ek(os.path.join, sickbeard.PROG_DIR, 'gui', sickbeard.GUI_NAME),
            'web_root':
            sickbeard.WEB_ROOT,
            'log_dir':
            self.log_dir,
            'username':
            sickbeard.WEB_USERNAME,
            'password':
            sickbeard.WEB_PASSWORD,
            'enable_https':
            sickbeard.ENABLE_HTTPS,
            'handle_reverse_proxy':
            sickbeard.HANDLE_REVERSE_PROXY,
            'https_cert':
            ek(os.path.join, sickbeard.PROG_DIR, sickbeard.HTTPS_CERT),
            'https_key':
            ek(os.path.join, sickbeard.PROG_DIR, sickbeard.HTTPS_KEY),
        }

        # start web server
        self.web_server = SRWebServer(self.web_options)
        self.web_server.start()

        # Fire up all our threads
        sickbeard.start()

        # Build internal name cache
        name_cache.buildNameCache()

        # Pre-populate network timezones, it isn't thread safe
        network_timezones.update_network_dict()

        # sure, why not?
        if sickbeard.USE_FAILED_DOWNLOADS:
            failed_history.trimHistory()

        # Check for metadata indexer updates for shows (sets the next aired ep!)
        # sickbeard.showUpdateScheduler.forceRun()

        # Launch browser
        if sickbeard.LAUNCH_BROWSER and not (self.no_launch
                                             or self.run_as_daemon):
            sickbeard.launchBrowser(
                'https' if sickbeard.ENABLE_HTTPS else 'http', self.start_port,
                sickbeard.WEB_ROOT)

        # main loop
        while True:
            time.sleep(1)

    def daemonize(self):
        """
        Fork off as a daemon
        """

        # An object is accessed for a non-existent member.
        # Access to a protected member of a client class
        # Make a non-session-leader child process
        try:
            pid = os.fork()  # @UndefinedVariable - only available in UNIX
            if pid != 0:
                os._exit(0)
        except OSError as error:
            sys.stderr.write(
                'fork #1 failed: {error_num}: {error_message}\n'.format(
                    error_num=error.errno, error_message=error.strerror))
            sys.exit(1)

        os.setsid()  # @UndefinedVariable - only available in UNIX

        # https://github.com/SickChill/SickChill/issues/2969
        # http://www.microhowto.info/howto/cause_a_process_to_become_a_daemon_in_c.html#idp23920
        # https://www.safaribooksonline.com/library/view/python-cookbook/0596001673/ch06s08.html
        # Previous code simply set the umask to whatever it was because it was ANDing instead of OR-ing
        # Daemons traditionally run with umask 0 anyways and this should not have repercussions
        os.umask(0)

        # Make the child a session-leader by detaching from the terminal
        try:
            pid = os.fork()  # @UndefinedVariable - only available in UNIX
            if pid != 0:
                os._exit(0)
        except OSError as error:
            sys.stderr.write(
                'fork #2 failed: Error {error_num}: {error_message}\n'.format(
                    error_num=error.errno, error_message=error.strerror))
            sys.exit(1)

        # Write pid
        if self.create_pid:
            pid = os.getpid()
            logger.log('Writing PID: {pid} to {filename}'.format(
                pid=pid, filename=self.pid_file))

            try:
                with os.fdopen(
                        os.open(self.pid_file, os.O_CREAT | os.O_WRONLY,
                                0o644), 'w') as f_pid:
                    f_pid.write('{0}\n'.format(pid))
            except EnvironmentError as error:
                logger.log_error_and_exit(
                    'Unable to write PID file: {filename} Error {error_num}: {error_message}'
                    .format(filename=self.pid_file,
                            error_num=error.errno,
                            error_message=error.strerror))

        # Redirect all output
        sys.stdout.flush()
        sys.stderr.flush()

        devnull = getattr(os, 'devnull', '/dev/null')
        stdin = open(devnull)
        stdout = open(devnull, 'a+')
        stderr = open(devnull, 'a+')

        os.dup2(stdin.fileno(),
                getattr(sys.stdin, 'device', sys.stdin).fileno())
        os.dup2(stdout.fileno(),
                getattr(sys.stdout, 'device', sys.stdout).fileno())
        os.dup2(stderr.fileno(),
                getattr(sys.stderr, 'device', sys.stderr).fileno())

    @staticmethod
    def remove_pid_file(pid_file):
        """
        Remove pid file

        :param pid_file: to remove
        :return:
        """
        try:
            if ek(os.path.exists, pid_file):
                ek(os.remove, pid_file)
        except EnvironmentError:
            return False

        return True

    @staticmethod
    def load_shows_from_db():
        """
        Populates the showList with shows from the database
        """
        logger.log('Loading initial show list', logger.DEBUG)

        main_db_con = db.DBConnection()
        sql_results = main_db_con.select(
            'SELECT indexer, indexer_id, location FROM tv_shows;')

        sickbeard.showList = []
        for sql_show in sql_results:
            try:
                cur_show = TVShow(sql_show[b'indexer'],
                                  sql_show[b'indexer_id'])
                cur_show.nextEpisode()
                sickbeard.showList.append(cur_show)
            except Exception as error:
                logger.log(
                    'There was an error creating the show in {0}: Error {1}'.
                    format(sql_show[b'location'], error), logger.ERROR)
                logger.log(traceback.format_exc(), logger.DEBUG)

    @staticmethod
    def restore_db(src_dir, dst_dir):
        """
        Restore the Database from a backup

        :param src_dir: Directory containing backup
        :param dst_dir: Directory to restore to
        :return:
        """
        try:
            files_list = [
                'sickbeard.db', 'config.ini', 'failed.db', 'cache.db'
            ]

            for filename in files_list:
                src_file = ek(os.path.join, src_dir, filename)
                dst_file = ek(os.path.join, dst_dir, filename)
                bak_file = ek(
                    os.path.join, dst_dir, '{0}.bak-{1}'.format(
                        filename,
                        datetime.datetime.now().strftime('%Y%m%d_%H%M%S')))
                if ek(os.path.isfile, dst_file):
                    shutil.move(dst_file, bak_file)
                shutil.move(src_file, dst_file)
            return True
        except Exception:
            return False

    def shutdown(self, event):
        """
        Shut down SickChill

        :param event: Type of shutdown event, used to see if restart required
        """
        if sickbeard.started:
            sickbeard.halt()  # stop all tasks
            sickbeard.saveAll()  # save all shows to DB

            # shutdown web server
            if self.web_server:
                logger.log('Shutting down Tornado')
                self.web_server.shutdown()

                try:
                    self.web_server.join(10)
                except Exception:
                    pass

            self.clear_cache()  # Clean cache

            # if run as daemon delete the pid file
            if self.run_as_daemon and self.create_pid:
                self.remove_pid_file(self.pid_file)

            if event == sickbeard.event_queue.Events.SystemEvent.RESTART:
                install_type = sickbeard.versionCheckScheduler.action.install_type

                popen_list = []

                if install_type in ('git', 'source'):
                    popen_list = [sys.executable, sickbeard.MY_FULLNAME]
                elif install_type == 'win':
                    logger.log(
                        'You are using a binary Windows build of SickChill. '
                        'Please switch to using git.', logger.ERROR)

                if popen_list and not sickbeard.NO_RESTART:
                    popen_list += sickbeard.MY_ARGS
                    if '--nolaunch' not in popen_list:
                        popen_list += ['--nolaunch']
                    logger.log('Restarting SickChill with {options}'.format(
                        options=popen_list))
                    # shutdown the logger to make sure it's released the logfile BEFORE it restarts SR.
                    logger.shutdown()
                    subprocess.Popen(popen_list, cwd=os.getcwd())

        # Make sure the logger has stopped, just in case
        logger.shutdown()
        os._exit(0)

    @staticmethod
    def force_update():
        """
        Forces SickChill to update to the latest version and exit.

        :return: True if successful, False otherwise
        """
        def update_with_git():
            def run_git(updater, cmd):
                stdout_, stderr_, exit_status = updater._run_git(
                    updater._git_path, cmd)
                if not exit_status == 0:
                    print('Failed to run command: {0} {1}'.format(
                        updater._git_path, cmd))
                    return False
                else:
                    return True

            updater = GitUpdateManager()
            if not run_git(
                    updater,
                    'config remote.origin.url https://github.com/SickChill/SickChill.git'
            ):
                return False
            if not run_git(updater, 'fetch origin --prune'):
                return False
            if not run_git(updater, 'checkout master'):
                return False
            if not run_git(updater, 'reset --hard origin/master'):
                return False

            return True

        if ek(os.path.isdir, ek(os.path.join, sickbeard.PROG_DIR,
                                '.git')):  # update with git
            print('Forcing SickChill to update using git...')
            result = update_with_git()
            if result:
                print(
                    'Successfully updated to latest commit. You may now run SickChill normally.'
                )
                return True
            else:
                print('Error while trying to force an update using git.')

        print('Forcing SickChill to update using source...')
        if not SourceUpdateManager().update():
            print('Failed to force an update.')
            return False

        print(
            'Successfully updated to latest commit. You may now run SickChill normally.'
        )
        return True
Ejemplo n.º 4
0
    def start(self):
        """
        Start SickChill
        """
        # do some preliminary stuff
        sickbeard.MY_FULLNAME = ek(os.path.normpath,
                                   ek(os.path.abspath, __file__))
        sickbeard.MY_NAME = ek(os.path.basename, sickbeard.MY_FULLNAME)
        sickbeard.PROG_DIR = ek(os.path.dirname, sickbeard.MY_FULLNAME)
        sickbeard.LOCALE_DIR = ek(os.path.join, sickbeard.PROG_DIR, 'locale')
        sickbeard.DATA_DIR = sickbeard.PROG_DIR
        sickbeard.MY_ARGS = sys.argv[1:]

        try:
            locale.setlocale(locale.LC_ALL, '')
            sickbeard.SYS_ENCODING = locale.getpreferredencoding()
        except (locale.Error, IOError):
            sickbeard.SYS_ENCODING = 'UTF-8'


        if not sickbeard.SYS_ENCODING or sickbeard.SYS_ENCODING.lower() in ('ansi_x3.4-1968', 'us-ascii', 'ascii', 'charmap') or \
                (sys.platform.startswith('win') and sys.getwindowsversion()[0] >= 6 and str(getattr(sys.stdout, 'device', sys.stdout).encoding).lower() in ('cp65001', 'charmap')):
            sickbeard.SYS_ENCODING = 'UTF-8'

        # TODO: Continue working on making this unnecessary, this hack creates all sorts of hellish problems
        if not hasattr(sys, 'setdefaultencoding'):
            reload_module(sys)

        try:
            # On non-unicode builds this will raise an AttributeError, if encoding type is not valid it throws a LookupError
            sys.setdefaultencoding(sickbeard.SYS_ENCODING)
        except (AttributeError, LookupError):
            sys.exit(
                'Sorry, you MUST add the SickChill folder to the PYTHONPATH environment variable\n'
                'or find another way to force Python to use {} for string encoding.'
                .format(sickbeard.SYS_ENCODING))

        # Rename the main thread
        threading.currentThread().name = 'MAIN'

        args = SickChillArgumentParser(sickbeard.PROG_DIR).parse_args()

        if args.force_update:
            result = self.force_update()
            sys.exit(int(not result))  # Ok -> 0 , Error -> 1

        # Need console logging for SickBeard.py and SickBeard-console.exe
        sickbeard.NO_RESIZE = args.noresize
        self.console_logging = (not hasattr(sys, 'frozen')) or (
            sickbeard.MY_NAME.lower().find('-console') > 0) and not args.quiet
        self.no_launch = args.nolaunch
        self.forced_port = args.port
        if args.daemon:
            self.run_as_daemon = platform.system() != 'Windows'
            self.console_logging = False
            self.no_launch = True

        self.create_pid = bool(args.pidfile)
        self.pid_file = args.pidfile
        if self.pid_file and ek(os.path.exists, self.pid_file):
            # If the pid file already exists, SickChill may still be running, so exit
            raise SystemExit('PID file: {0} already exists. Exiting.'.format(
                self.pid_file))

        sickbeard.DATA_DIR = ek(
            os.path.abspath,
            args.datadir) if args.datadir else sickbeard.DATA_DIR
        sickbeard.CONFIG_FILE = ek(
            os.path.abspath, args.config) if args.config else ek(
                os.path.join, sickbeard.DATA_DIR, 'config.ini')

        # The pid file 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 = ek(os.path.dirname, self.pid_file)
                if not ek(os.access, pid_dir, os.F_OK):
                    sys.exit('PID dir: {0} doesn\'t exist. Exiting.'.format(
                        pid_dir))
                if not ek(os.access, pid_dir, os.W_OK):
                    raise SystemExit(
                        'PID dir: {0} must be writable (write permissions). Exiting.'
                        .format(pid_dir))
            else:
                if self.console_logging:
                    sys.stdout.write(
                        'Not running in daemon mode. PID file creation disabled.\n'
                    )
                self.create_pid = False

        # Make sure that we can create the data dir
        if not ek(os.access, sickbeard.DATA_DIR, os.F_OK):
            try:
                ek(os.makedirs, sickbeard.DATA_DIR, 0o744)
            except os.error:
                raise SystemExit('Unable to create data directory: {0}'.format(
                    sickbeard.DATA_DIR))

        # Make sure we can write to the data dir
        if not ek(os.access, sickbeard.DATA_DIR, os.W_OK):
            raise SystemExit('Data directory must be writeable: {0}'.format(
                sickbeard.DATA_DIR))

        # Make sure we can write to the config file
        if not ek(os.access, sickbeard.CONFIG_FILE, os.W_OK):
            if ek(os.path.isfile, sickbeard.CONFIG_FILE):
                raise SystemExit('Config file must be writeable: {0}'.format(
                    sickbeard.CONFIG_FILE))
            elif not ek(os.access, ek(os.path.dirname, sickbeard.CONFIG_FILE),
                        os.W_OK):
                raise SystemExit(
                    'Config file root dir must be writeable: {0}'.format(
                        ek(os.path.dirname, sickbeard.CONFIG_FILE)))

        ek(os.chdir, sickbeard.DATA_DIR)

        # Check if we need to perform a restore first
        restore_dir = ek(os.path.join, sickbeard.DATA_DIR, 'restore')
        if ek(os.path.exists, restore_dir):
            success = self.restore_db(restore_dir, sickbeard.DATA_DIR)
            if self.console_logging:
                sys.stdout.write(
                    'Restore: restoring DB and config.ini {0}!\n'.format(
                        ('FAILED', 'SUCCESSFUL')[success]))

        # Load the config and publish it to the sickbeard package
        if self.console_logging and not ek(os.path.isfile,
                                           sickbeard.CONFIG_FILE):
            sys.stdout.write(
                'Unable to find {0}, all settings will be default!\n'.format(
                    sickbeard.CONFIG_FILE))

        sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE, encoding='UTF-8')

        # Initialize the config and our threads
        sickbeard.initialize(consoleLogging=self.console_logging)

        if self.run_as_daemon:
            self.daemonize()

        # Get PID
        sickbeard.PID = os.getpid()

        # Build from the DB to start with
        self.load_shows_from_db()

        logger.log('Starting SickChill [{branch}] using \'{config}\''.format(
            branch=sickbeard.BRANCH, config=sickbeard.CONFIG_FILE))

        self.clear_cache()

        if self.forced_port:
            logger.log('Forcing web server to port {port}'.format(
                port=self.forced_port))
            self.start_port = self.forced_port
        else:
            self.start_port = sickbeard.WEB_PORT

        if sickbeard.WEB_LOG:
            self.log_dir = sickbeard.LOG_DIR
        else:
            self.log_dir = None

        # sickbeard.WEB_HOST is available as a configuration value in various
        # places but is not configurable. It is supported here for historic reasons.
        if sickbeard.WEB_HOST and sickbeard.WEB_HOST != '0.0.0.0':
            self.web_host = sickbeard.WEB_HOST
        else:
            self.web_host = '' if sickbeard.WEB_IPV6 else '0.0.0.0'

        # web server options
        self.web_options = {
            'port':
            int(self.start_port),
            'host':
            self.web_host,
            'data_root':
            ek(os.path.join, sickbeard.PROG_DIR, 'gui', sickbeard.GUI_NAME),
            'web_root':
            sickbeard.WEB_ROOT,
            'log_dir':
            self.log_dir,
            'username':
            sickbeard.WEB_USERNAME,
            'password':
            sickbeard.WEB_PASSWORD,
            'enable_https':
            sickbeard.ENABLE_HTTPS,
            'handle_reverse_proxy':
            sickbeard.HANDLE_REVERSE_PROXY,
            'https_cert':
            ek(os.path.join, sickbeard.PROG_DIR, sickbeard.HTTPS_CERT),
            'https_key':
            ek(os.path.join, sickbeard.PROG_DIR, sickbeard.HTTPS_KEY),
        }

        # start web server
        self.web_server = SRWebServer(self.web_options)
        self.web_server.start()

        # Fire up all our threads
        sickbeard.start()

        # Build internal name cache
        name_cache.buildNameCache()

        # Pre-populate network timezones, it isn't thread safe
        network_timezones.update_network_dict()

        # sure, why not?
        if sickbeard.USE_FAILED_DOWNLOADS:
            failed_history.trimHistory()

        # Check for metadata indexer updates for shows (sets the next aired ep!)
        # sickbeard.showUpdateScheduler.forceRun()

        # Launch browser
        if sickbeard.LAUNCH_BROWSER and not (self.no_launch
                                             or self.run_as_daemon):
            sickbeard.launchBrowser(
                'https' if sickbeard.ENABLE_HTTPS else 'http', self.start_port,
                sickbeard.WEB_ROOT)

        # main loop
        while True:
            time.sleep(1)
Ejemplo n.º 5
0
    def start(self):
        """
        Start SickChill
        """
        # do some preliminary stuff
        settings.MY_FULLNAME = os.path.normpath(os.path.abspath(__file__))
        settings.MY_NAME = os.path.basename(settings.MY_FULLNAME)

        settings.DATA_DIR = os.path.dirname(settings.PROG_DIR)
        profile_path = str(Path.home().joinpath('sickchill').absolute())
        if check_installed():
            settings.DATA_DIR = profile_path

        if settings.DATA_DIR != profile_path:
            checks = [
                'sickbeard.db',
                'sickchill.db',
                'config.ini'
            ]
            if not any([os.path.isfile(os.path.join(settings.DATA_DIR, check)) for check in checks]):
                settings.DATA_DIR = profile_path

        settings.MY_ARGS = sys.argv[1:]

        # Rename the main thread
        threading.currentThread().name = 'MAIN'

        args = SickChillArgumentParser(settings.DATA_DIR).parse_args()

        if args.force_update:
            result = self.force_update()
            sys.exit(int(not result))  # Ok -> 0 , Error -> 1

        # Need console logging for SickChill.py and SickChill-console.exe
        settings.NO_RESIZE = args.noresize
        self.console_logging = (not hasattr(sys, 'frozen')) or (settings.MY_NAME.lower().find('-console') > 0) and not args.quiet
        self.no_launch = args.nolaunch
        self.forced_port = args.port
        if args.daemon:
            self.run_as_daemon = platform.system() != 'Windows'
            self.console_logging = False
            self.no_launch = True

        self.create_pid = bool(args.pidfile)
        self.pid_file = args.pidfile
        if self.pid_file and os.path.exists(self.pid_file):
            # If the pid file already exists, SickChill may still be running, so exit
            raise SystemExit('PID file: {0} already exists. Exiting.'.format(self.pid_file))

        settings.DATA_DIR = os.path.abspath(args.datadir) if args.datadir else settings.DATA_DIR
        settings.CONFIG_FILE = os.path.abspath(args.config) if args.config else os.path.join(settings.DATA_DIR, 'config.ini')

        # The pid file 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('PID dir: {0} doesn\'t exist. Exiting.'.format(pid_dir))
                if not os.access(pid_dir, os.W_OK):
                    raise SystemExit('PID dir: {0} must be writable (write permissions). Exiting.'.format(pid_dir))
            else:
                if self.console_logging:
                    sys.stdout.write('Not running in daemon mode. PID file creation disabled.\n')
                self.create_pid = False

        # Make sure that we can create the data dir
        if not os.access(settings.DATA_DIR, os.F_OK):
            try:
                os.makedirs(settings.DATA_DIR, 0o744)
            except os.error:
                raise SystemExit('Unable to create data directory: {0}'.format(settings.DATA_DIR))

        # Make sure we can write to the data dir
        if not os.access(settings.DATA_DIR, os.W_OK):
            raise SystemExit('Data directory must be writeable: {0}'.format(settings.DATA_DIR))

        # Make sure we can write to the config file
        if not os.access(settings.CONFIG_FILE, os.W_OK):
            if os.path.isfile(settings.CONFIG_FILE):
                raise SystemExit('Config file must be writeable: {0}'.format(settings.CONFIG_FILE))
            elif not os.access(os.path.dirname(settings.CONFIG_FILE), os.W_OK):
                raise SystemExit('Config file root dir must be writeable: {0}'.format(os.path.dirname(settings.CONFIG_FILE)))

        os.chdir(settings.DATA_DIR)

        # Check if we need to perform a restore first
        restore_dir = os.path.join(settings.DATA_DIR, 'restore')
        if os.path.exists(restore_dir):
            success = self.restore_db(restore_dir, settings.DATA_DIR)
            if self.console_logging:
                sys.stdout.write('Restore: restoring DB and config.ini {0}!\n'.format(('FAILED', 'SUCCESSFUL')[success]))

        # Load the config and publish it to the oldbeard package
        if self.console_logging and not os.path.isfile(settings.CONFIG_FILE):
            sys.stdout.write('Unable to find {0}, all settings will be default!\n'.format(settings.CONFIG_FILE))

        settings.CFG = ConfigObj(settings.CONFIG_FILE, encoding='UTF-8', indent_type='  ')

        if self.run_as_daemon:
            self.daemonize()

        # Initialize the config and our threads
        sickchill.start.initialize(consoleLogging=self.console_logging)

        # Get PID
        settings.PID = os.getpid()

        # Build from the DB to start with
        self.load_shows_from_db()

        logger.info('Starting SickChill [{branch}] using \'{config}\''.format(branch=settings.BRANCH, config=settings.CONFIG_FILE))

        self.clear_cache()

        if settings.DEVELOPER:
            settings.movie_list = MovieList()

        web_options = {}
        if self.forced_port:
            logger.info('Forcing web server to port {port}'.format(port=self.forced_port))
            self.start_port = self.forced_port
            web_options.update({
                'port': int(self.start_port),
            })
        else:
            self.start_port = settings.WEB_PORT

        # start web server
        self.web_server = SRWebServer(web_options)
        self.web_server.start()

        # Fire up all our threads
        sickchill.start.start()

        # Build internal name cache
        name_cache.buildNameCache()

        # Pre-populate network timezones, it isn't thread safe
        network_timezones.update_network_dict()

        # sure, why not?
        if settings.USE_FAILED_DOWNLOADS:
            failed_history.trimHistory()

        # Check for metadata indexer updates for shows (sets the next aired ep!)
        # oldbeard.showUpdateScheduler.forceRun()

        # Launch browser
        if settings.LAUNCH_BROWSER and not (self.no_launch or self.run_as_daemon):
            sickchill.start.launchBrowser('https' if settings.ENABLE_HTTPS else 'http', self.start_port, settings.WEB_ROOT)

        # main loop
        while True:
            time.sleep(1)
Ejemplo n.º 6
0
class SickChill(object):

    """
    Main SickChill module
    """

    def __init__(self):
        # system event callback for shutdown/restart
        settings.events = Events(self.shutdown)

        # daemon constants
        self.run_as_daemon = False
        self.create_pid = False
        self.pid_file = ''

        # web server constants
        self.web_server = None
        self.forced_port = None
        self.no_launch = False

        self.start_port = settings.WEB_PORT

        self.console_logging = True

    @staticmethod
    def clear_cache():
        """
        Remove the Mako cache directory
        """
        try:
            cache_folder = os.path.join(settings.CACHE_DIR, 'mako')
            if os.path.isdir(cache_folder):
                shutil.rmtree(cache_folder)
        except Exception:
            logger.warning('Unable to remove the cache/mako directory!')

    def start(self):
        """
        Start SickChill
        """
        # do some preliminary stuff
        settings.MY_FULLNAME = os.path.normpath(os.path.abspath(__file__))
        settings.MY_NAME = os.path.basename(settings.MY_FULLNAME)

        settings.DATA_DIR = os.path.dirname(settings.PROG_DIR)
        profile_path = str(Path.home().joinpath('sickchill').absolute())
        if check_installed():
            settings.DATA_DIR = profile_path

        if settings.DATA_DIR != profile_path:
            checks = [
                'sickbeard.db',
                'sickchill.db',
                'config.ini'
            ]
            if not any([os.path.isfile(os.path.join(settings.DATA_DIR, check)) for check in checks]):
                settings.DATA_DIR = profile_path

        settings.MY_ARGS = sys.argv[1:]

        # Rename the main thread
        threading.currentThread().name = 'MAIN'

        args = SickChillArgumentParser(settings.DATA_DIR).parse_args()

        if args.force_update:
            result = self.force_update()
            sys.exit(int(not result))  # Ok -> 0 , Error -> 1

        # Need console logging for SickChill.py and SickChill-console.exe
        settings.NO_RESIZE = args.noresize
        self.console_logging = (not hasattr(sys, 'frozen')) or (settings.MY_NAME.lower().find('-console') > 0) and not args.quiet
        self.no_launch = args.nolaunch
        self.forced_port = args.port
        if args.daemon:
            self.run_as_daemon = platform.system() != 'Windows'
            self.console_logging = False
            self.no_launch = True

        self.create_pid = bool(args.pidfile)
        self.pid_file = args.pidfile
        if self.pid_file and os.path.exists(self.pid_file):
            # If the pid file already exists, SickChill may still be running, so exit
            raise SystemExit('PID file: {0} already exists. Exiting.'.format(self.pid_file))

        settings.DATA_DIR = os.path.abspath(args.datadir) if args.datadir else settings.DATA_DIR
        settings.CONFIG_FILE = os.path.abspath(args.config) if args.config else os.path.join(settings.DATA_DIR, 'config.ini')

        # The pid file 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('PID dir: {0} doesn\'t exist. Exiting.'.format(pid_dir))
                if not os.access(pid_dir, os.W_OK):
                    raise SystemExit('PID dir: {0} must be writable (write permissions). Exiting.'.format(pid_dir))
            else:
                if self.console_logging:
                    sys.stdout.write('Not running in daemon mode. PID file creation disabled.\n')
                self.create_pid = False

        # Make sure that we can create the data dir
        if not os.access(settings.DATA_DIR, os.F_OK):
            try:
                os.makedirs(settings.DATA_DIR, 0o744)
            except os.error:
                raise SystemExit('Unable to create data directory: {0}'.format(settings.DATA_DIR))

        # Make sure we can write to the data dir
        if not os.access(settings.DATA_DIR, os.W_OK):
            raise SystemExit('Data directory must be writeable: {0}'.format(settings.DATA_DIR))

        # Make sure we can write to the config file
        if not os.access(settings.CONFIG_FILE, os.W_OK):
            if os.path.isfile(settings.CONFIG_FILE):
                raise SystemExit('Config file must be writeable: {0}'.format(settings.CONFIG_FILE))
            elif not os.access(os.path.dirname(settings.CONFIG_FILE), os.W_OK):
                raise SystemExit('Config file root dir must be writeable: {0}'.format(os.path.dirname(settings.CONFIG_FILE)))

        os.chdir(settings.DATA_DIR)

        # Check if we need to perform a restore first
        restore_dir = os.path.join(settings.DATA_DIR, 'restore')
        if os.path.exists(restore_dir):
            success = self.restore_db(restore_dir, settings.DATA_DIR)
            if self.console_logging:
                sys.stdout.write('Restore: restoring DB and config.ini {0}!\n'.format(('FAILED', 'SUCCESSFUL')[success]))

        # Load the config and publish it to the oldbeard package
        if self.console_logging and not os.path.isfile(settings.CONFIG_FILE):
            sys.stdout.write('Unable to find {0}, all settings will be default!\n'.format(settings.CONFIG_FILE))

        settings.CFG = ConfigObj(settings.CONFIG_FILE, encoding='UTF-8', indent_type='  ')

        if self.run_as_daemon:
            self.daemonize()

        # Initialize the config and our threads
        sickchill.start.initialize(consoleLogging=self.console_logging)

        # Get PID
        settings.PID = os.getpid()

        # Build from the DB to start with
        self.load_shows_from_db()

        logger.info('Starting SickChill [{branch}] using \'{config}\''.format(branch=settings.BRANCH, config=settings.CONFIG_FILE))

        self.clear_cache()

        if settings.DEVELOPER:
            settings.movie_list = MovieList()

        web_options = {}
        if self.forced_port:
            logger.info('Forcing web server to port {port}'.format(port=self.forced_port))
            self.start_port = self.forced_port
            web_options.update({
                'port': int(self.start_port),
            })
        else:
            self.start_port = settings.WEB_PORT

        # start web server
        self.web_server = SRWebServer(web_options)
        self.web_server.start()

        # Fire up all our threads
        sickchill.start.start()

        # Build internal name cache
        name_cache.build_name_cache()

        # Pre-populate network timezones, it isn't thread safe
        network_timezones.update_network_dict()

        # sure, why not?
        if settings.USE_FAILED_DOWNLOADS:
            failed_history.trimHistory()

        # Check for metadata indexer updates for shows (sets the next aired ep!)
        # oldbeard.showUpdateScheduler.forceRun()

        # Launch browser
        if settings.LAUNCH_BROWSER and not (self.no_launch or self.run_as_daemon):
            sickchill.start.launchBrowser('https' if settings.ENABLE_HTTPS else 'http', self.start_port, settings.WEB_ROOT)

        # main loop
        while True:
            time.sleep(1)

    def daemonize(self):
        """
        Fork off as a daemon
        """

        # An object is accessed for a non-existent member.
        # Access to a protected member of a client class
        # Make a non-session-leader child process
        try:
            pid = os.fork()  # @UndefinedVariable - only available in UNIX
            if pid != 0:
                os._exit(0)
        except OSError as error:
            sys.stderr.write('fork #1 failed: {error_num}: {error_message}\n'.format
                             (error_num=error.errno, error_message=error.strerror))
            sys.exit(1)

        os.setsid()  # @UndefinedVariable - only available in UNIX

        # https://github.com/SickChill/SickChill/issues/2969
        # http://www.microhowto.info/howto/cause_a_process_to_become_a_daemon_in_c.html#idp23920
        # https://www.safaribooksonline.com/library/view/python-cookbook/0596001673/ch06s08.html
        # Previous code simply set the umask to whatever it was because it was ANDing instead of OR-ing
        # Daemons traditionally run with umask 0 anyways and this should not have repercussions
        os.umask(0)

        # Make the child a session-leader by detaching from the terminal
        try:
            pid = os.fork()  # @UndefinedVariable - only available in UNIX
            if pid != 0:
                os._exit(0)
        except OSError as error:
            sys.stderr.write('fork #2 failed: Error {error_num}: {error_message}\n'.format
                             (error_num=error.errno, error_message=error.strerror))
            sys.exit(1)

        # Write pid
        if self.create_pid:
            pid = os.getpid()
            logger.info('Writing PID: {pid} to {filename}'.format(pid=pid, filename=self.pid_file))

            try:
                with os.fdopen(os.open(self.pid_file, os.O_CREAT | os.O_WRONLY, 0o644), 'w') as f_pid:
                    f_pid.write('{0}\n'.format(pid))
            except EnvironmentError as error:
                logger.log_error_and_exit('Unable to write PID file: {filename} Error {error_num}: {error_message}'.format
                                          (filename=self.pid_file, error_num=error.errno, error_message=error.strerror))

        # Redirect all output
        sys.stdout.flush()
        sys.stderr.flush()


        devnull = getattr(os, 'devnull', '/dev/null')
        stdin = open(devnull)
        stdout = open(devnull, 'a+')
        stderr = open(devnull, 'a+')

        os.dup2(stdin.fileno(), getattr(sys.stdin, 'device', sys.stdin).fileno())
        os.dup2(stdout.fileno(), getattr(sys.stdout, 'device', sys.stdout).fileno())
        os.dup2(stderr.fileno(), getattr(sys.stderr, 'device', sys.stderr).fileno())

    @staticmethod
    def remove_pid_file(pid_file):
        """
        Remove pid file

        :param pid_file: to remove
        :return:
        """
        try:
            if os.path.exists(pid_file):
                os.remove(pid_file)
        except EnvironmentError:
            return False

        return True

    @staticmethod
    def load_shows_from_db():
        """
        Populates the showList with shows from the database
        """
        logger.debug('Loading initial show list')

        main_db_con = db.DBConnection()
        sql_results = main_db_con.select('SELECT indexer, indexer_id, location FROM tv_shows;')

        settings.showList = []
        for sql_show in sql_results:
            try:
                cur_show = TVShow(sql_show['indexer'], sql_show['indexer_id'])
                cur_show.nextEpisode()
                settings.showList.append(cur_show)
            except Exception as error:
                logger.exception('There was an error creating the show in {0}: Error {1}'.format
                           (sql_show['location'], error))
                logger.debug(traceback.format_exc())

    @staticmethod
    def restore_db(src_dir, dst_dir):
        """
        Restore the Database from a backup

        :param src_dir: Directory containing backup
        :param dst_dir: Directory to restore to
        :return:
        """
        try:
            files_list = ['sickbeard.db', 'sickchill.db', 'config.ini', 'failed.db', 'cache.db']
            for filename in files_list:
                src_file = os.path.join(src_dir, filename)
                dst_file = os.path.join(dst_dir, filename)
                bak_file = os.path.join(dst_dir, '{0}.bak-{1}'.format(filename, datetime.datetime.now().strftime('%Y%m%d_%H%M%S')))
                sickchill_db = os.path.join(dst_dir, 'sickchill.db')
                sickbeard_db = os.path.join(src_dir, 'sickbeard.db')
                if os.path.isfile(src_file):
                    if src_file == sickbeard_db:
                        dst_file = sickchill_db

                    if os.path.isfile(dst_file):
                        shutil.move(dst_file, bak_file)
                    shutil.move(src_file, dst_file)

            sickbeard_db = os.path.join(dst_dir, 'sickbeard.db')
            sickchill_db = os.path.join(dst_dir, 'sickchill.db')
            if os.path.isfile(sickbeard_db) and not os.path.isfile(sickchill_db):
                shutil.move(sickbeard_db, sickchill_db)

            return True
        except Exception:
            return False

    def shutdown(self, event):
        """
        Shut down SickChill

        :param event: Type of shutdown event, used to see if restart required
        """
        if settings.started:
            sickchill.start.halt()  # stop all tasks
            sickchill.start.saveAll()  # save all shows to DB

            # shutdown web server
            if self.web_server:
                logger.info('Shutting down Tornado')
                self.web_server.shutdown()

                try:
                    self.web_server.join(10)
                except Exception:
                    pass

            self.clear_cache()  # Clean cache

            # if run as daemon delete the pid file
            if self.run_as_daemon and self.create_pid:
                self.remove_pid_file(self.pid_file)

            if event == sickchill.oldbeard.event_queue.Events.SystemEvent.RESTART:
                install_type = settings.versionCheckScheduler.action.install_type

                popen_list = []

                if install_type in ('git', 'source'):
                    popen_list = [sys.executable, settings.MY_FULLNAME]
                elif install_type == 'win':
                    logger.exception('You are using a binary Windows build of SickChill. '
                               'Please switch to using git.')

                if popen_list and not settings.NO_RESTART:
                    popen_list += settings.MY_ARGS
                    if '--nolaunch' not in popen_list:
                        popen_list += ['--nolaunch']
                    logger.info('Restarting SickChill with {options}'.format(options=popen_list))
                    # shutdown the logger to make sure it's released the logfile BEFORE it restarts SR.
                    logger.shutdown()
                    subprocess.Popen(popen_list, cwd=os.getcwd(), universal_newlines=True)

        # Make sure the logger has stopped, just in case
        logger.shutdown()
        os._exit(0)

    @staticmethod
    def force_update():
        """
        Forces SickChill to update to the latest version and exit.

        :return: True if successful, False otherwise
        """

        def update_with_git():
            def run_git(updater, cmd):
                stdout_, stderr_, exit_status = updater._run_git(updater._git_path, cmd)
                if not exit_status == 0:
                    print('Failed to run command: {0} {1}'.format(updater._git_path, cmd))
                    return False
                else:
                    return True

            updater = GitUpdateManager()
            if not run_git(updater, 'config remote.origin.url https://github.com/SickChill/SickChill.git'):
                return False
            if not run_git(updater, 'fetch origin --prune'):
                return False
            if not run_git(updater, 'checkout master'):
                return False
            if not run_git(updater, 'reset --hard origin/master'):
                return False

            return True

        if os.path.isdir(os.path.join(os.path.dirname(settings.PROG_DIR), '.git')):  # update with git
            print('Forcing SickChill to update using git...')
            result = update_with_git()
            if result:
                print('Successfully updated to latest commit. You may now run SickChill normally.')
                return True
            else:
                print('Error while trying to force an update using git.')

        print('Forcing SickChill to update using source...')
        if not SourceUpdateManager().update():
            print('Failed to force an update.')
            return False

        print('Successfully updated to latest commit. You may now run SickChill normally.')
        return True