Example #1
0
    def run(self, force=False):
        updated = None
        if self.check_for_new_version():
            if sickbeard.AUTO_UPDATE:
                logger.log(
                    u"New update found for SickRage, starting auto-updater ..."
                )
                updated = sickbeard.versionCheckScheduler.action.update()
                if updated:
                    logger.log(
                        u"Update was successfull, restarting SickRage ...")

                    # do a soft restart
                    threading.Timer(2, sickbeard.invoke_restart,
                                    [False]).start()

        if not updated:
            # refresh scene exceptions too
            scene_exceptions.retrieve_exceptions()

            # refresh network timezones
            network_timezones.update_network_dict()

            # sure, why not?
            if sickbeard.USE_FAILED_DOWNLOADS:
                failed_history.trimHistory()
Example #2
0
    def run(self, force=False):

        self.amActive = True

        update_datetime = datetime.datetime.now()
        update_date = update_datetime.date()

        # refresh network timezones
        network_timezones.update_network_dict()

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

        logger.log(u"Doing full update on all shows")

        # select 10 'Ended' tv_shows updated more than 90 days ago to include in this update
        stale_should_update = []
        stale_update_date = (update_date -
                             datetime.timedelta(days=90)).toordinal()

        # last_update_date <= 90 days, sorted ASC because dates are ordinal
        myDB = db.DBConnection()
        sql_result = myDB.select(
            "SELECT indexer_id FROM tv_shows WHERE status = 'Ended' AND last_update_indexer <= ? ORDER BY last_update_indexer ASC LIMIT 10;",
            [stale_update_date])

        for cur_result in sql_result:
            stale_should_update.append(int(cur_result['indexer_id']))

        # start update process
        piList = []
        for curShow in sickbeard.showList:

            try:
                # get next episode airdate
                curShow.nextEpisode()

                # if should_update returns True (not 'Ended') or show is selected stale 'Ended' then update, otherwise just refresh
                if curShow.should_update(
                        update_date=update_date
                ) or curShow.indexerid in stale_should_update:
                    try:
                        piList.append(
                            sickbeard.showQueueScheduler.action.updateShow(
                                curShow, True))  # @UndefinedVariable
                    except CantUpdateShowException as e:
                        logger.log("Unable to update show: {0}".format(str(e)),
                                   logger.DEBUG)
                else:
                    logger.log(
                        u"Not updating episodes for show " + curShow.name +
                        " because it's marked as ended and last/next episode is not within the grace period.",
                        logger.DEBUG)
                    piList.append(
                        sickbeard.showQueueScheduler.action.refreshShow(
                            curShow, True))  # @UndefinedVariable

            except (CantUpdateShowException, CantRefreshShowException), e:
                logger.log(u"Automatic update failed: " + ex(e), logger.ERROR)
Example #3
0
    def _change_missing_episodes():
        if not network_timezones.network_dict:
            network_timezones.update_network_dict()

        if network_timezones.network_dict:
            cur_date = (datetime.date.today() + datetime.timedelta(days=1)).toordinal()
        else:
            cur_date = (datetime.date.today() - datetime.timedelta(days=2)).toordinal()

        cur_time = datetime.datetime.now(network_timezones.sb_timezone)

        my_db = db.DBConnection()
        sql_results = my_db.select(
            'SELECT * FROM tv_episodes'
            ' WHERE status = ? AND season > 0 AND airdate <= ? AND airdate > 1'
            ' ORDER BY showid', [common.UNAIRED, cur_date])

        sql_l = []
        show = None
        wanted = False

        for sqlEp in sql_results:
            try:
                if not show or show.indexerid != int(sqlEp['showid']):
                    show = helpers.findCertainShow(sickbeard.showList, int(sqlEp['showid']))

                # for when there is orphaned series in the database but not loaded into our showlist
                if not show:
                    continue

            except exceptions.MultipleShowObjectsException:
                logger.log(u'ERROR: expected to find a single show matching %s' % sqlEp['showid'])
                continue

            try:
                end_time = (network_timezones.parse_date_time(sqlEp['airdate'], show.airs, show.network) +
                            datetime.timedelta(minutes=helpers.tryInt(show.runtime, 60)))
                # filter out any episodes that haven't aired yet
                if end_time > cur_time:
                    continue
            except (StandardError, Exception):
                # if an error occurred assume the episode hasn't aired yet
                continue

            ep = show.getEpisode(int(sqlEp['season']), int(sqlEp['episode']))
            with ep.lock:
                # Now that it is time, change state of UNAIRED show into expected or skipped
                ep.status = (common.WANTED, common.SKIPPED)[ep.show.paused]
                result = ep.get_sql()
                if None is not result:
                    sql_l.append(result)
                    wanted |= (False, True)[common.WANTED == ep.status]
        else:
            logger.log(u'No unaired episodes marked wanted')

        if 0 < len(sql_l):
            my_db = db.DBConnection()
            my_db.mass_action(sql_l)
            if wanted:
                logger.log(u'Found new episodes marked wanted')
Example #4
0
    def run(self):
        self.check_for_new_version()

        # refresh scene exceptions too
        scene_exceptions.retrieve_exceptions()

        # refresh network timezones
        network_timezones.update_network_dict()
Example #5
0
 def run(self):
     self.check_for_new_version()
     
     # refresh scene exceptions too
     scene_exceptions.retrieve_exceptions()
     
     # refresh network timezones
     network_timezones.update_network_dict()
Example #6
0
    def _change_missing_episodes():
        if not network_timezones.network_dict:
            network_timezones.update_network_dict()

        if network_timezones.network_dict:
            cur_date = (datetime.date.today() + datetime.timedelta(days=1)).toordinal()
        else:
            cur_date = (datetime.date.today() - datetime.timedelta(days=2)).toordinal()

        cur_time = datetime.datetime.now(network_timezones.sb_timezone)

        my_db = db.DBConnection()
        sql_results = my_db.select('SELECT * FROM tv_episodes WHERE status = ? AND season > 0 AND airdate <= ? AND airdate > 1',
                                   [common.UNAIRED, cur_date])

        sql_l = []
        show = None
        wanted = False

        for sqlEp in sql_results:
            try:
                if not show or show.indexerid != int(sqlEp['showid']):
                    show = helpers.findCertainShow(sickbeard.showList, int(sqlEp['showid']))

                # for when there is orphaned series in the database but not loaded into our showlist
                if not show:
                    continue

            except exceptions.MultipleShowObjectsException:
                logger.log(u'ERROR: expected to find a single show matching %s' % sqlEp['showid'])
                continue

            try:
                end_time = (network_timezones.parse_date_time(sqlEp['airdate'], show.airs, show.network) +
                            datetime.timedelta(minutes=helpers.tryInt(show.runtime, 60)))
                # filter out any episodes that haven't aired yet
                if end_time > cur_time:
                    continue
            except:
                # if an error occurred assume the episode hasn't aired yet
                continue

            ep = show.getEpisode(int(sqlEp['season']), int(sqlEp['episode']))
            with ep.lock:
                # Now that it is time, change state of UNAIRED show into expected or skipped
                ep.status = (common.WANTED, common.SKIPPED)[ep.show.paused]
                result = ep.get_sql()
                if None is not result:
                    sql_l.append(result)
                    wanted |= (False, True)[common.WANTED == ep.status]
        else:
            logger.log(u'No unaired episodes marked wanted')

        if 0 < len(sql_l):
            my_db = db.DBConnection()
            my_db.mass_action(sql_l)
            if wanted:
                logger.log(u'Found new episodes marked wanted')
Example #7
0
    def run(self, force=False):
        logger.log('ShowUpdater for tvdb Api V3 starting')
        if self.amActive:
            return

        self.amActive = True

        cache_db_con = db.DBConnection('cache.db')
        for index, provider in sickchill.indexer:
            database_result = cache_db_con.select('SELECT `time` FROM lastUpdate WHERE provider = ?', [provider.name])
            last_update = int(database_result[0][0]) if database_result else 0
            network_timezones.update_network_dict()
            update_timestamp = int(time.time())
            updated_shows = []

            if last_update:
                logger.log('Last update: {}'.format(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(last_update))))
                # We query tvdb for updates starting from the last update time from the cache until now with increments of 7 days
                for fromTime in range(last_update, update_timestamp - self.seven_days, self.seven_days):  # increments of 604800 sec = 7*24*60*60
                    try:
                        TvdbData = sickchill.indexer[1].updates(fromTime=fromTime, toTime=fromTime + self.seven_days)
                        TvdbData.series()
                        updated_shows.extend([d['id'] for d in TvdbData.series])

                    except Exception as error:
                        logger.log(str(error))
            else:
                logger.log(_('No last update time from the cache, so we do a full update for all shows'))

            pi_list = []
            for cur_show in sickbeard.showList:
                try:
                    cur_show.nextEpisode()

                    # Skip ended shows until interval is met
                    if cur_show.status == 'Ended' and sickbeard.ENDED_SHOWS_UPDATE_INTERVAL != 0:  # 0 is always
                        if sickbeard.ENDED_SHOWS_UPDATE_INTERVAL == -1:  # Never
                            continue
                        if (datetime.datetime.today() - datetime.datetime.fromordinal(cur_show.last_update_indexer or 1)).days < \
                            sickbeard.ENDED_SHOWS_UPDATE_INTERVAL:
                            continue

                    # When last_update is not set from the cache or the show was in the tvdb updated list we update the show
                    if not last_update or cur_show.indexerid in updated_shows:
                        pi_list.append(sickbeard.showQueueScheduler.action.update_show(cur_show, True))
                    else:
                        pi_list.append(sickbeard.showQueueScheduler.action.refresh_show(cur_show, force))
                except (CantUpdateShowException, CantRefreshShowException) as error:
                    logger.log(_('Automatic update failed: {0}').format(ex(error)))

            ui.ProgressIndicators.setIndicator('dailyUpdate', ui.QueueProgressIndicator('Daily Update', pi_list))

            if database_result:
                cache_db_con.action('UPDATE lastUpdate SET `time` = ? WHERE provider = ?', [str(update_timestamp), provider.name])
            else:
                cache_db_con.action('INSERT INTO lastUpdate (time, provider) VALUES (?, ?)', [str(update_timestamp), provider.name])

        self.amActive = False
Example #8
0
    def run(self, force=False):

        self.amActive = True

        update_datetime = datetime.datetime.now()
        update_date = update_datetime.date()

        # refresh network timezones
        network_timezones.update_network_dict()

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

        logger.log(u"Doing full update on all shows")

        # select 10 'Ended' tv_shows updated more than 90 days ago to include in this update
        stale_should_update = []
        stale_update_date = (update_date - datetime.timedelta(days=90)).toordinal()

        # last_update_date <= 90 days, sorted ASC because dates are ordinal
        myDB = db.DBConnection()
        sql_result = myDB.select(
            "SELECT indexer_id FROM tv_shows WHERE status = 'Ended' AND last_update_indexer <= ? ORDER BY last_update_indexer ASC LIMIT 10;",
            [stale_update_date])

        for cur_result in sql_result:
            stale_should_update.append(int(cur_result['indexer_id']))

        # start update process
        piList = []
        for curShow in sickbeard.showList:

            try:
                # get next episode airdate
                curShow.nextEpisode()

                # if should_update returns True (not 'Ended') or show is selected stale 'Ended' then update, otherwise just refresh
                if curShow.should_update(update_date=update_date) or curShow.indexerid in stale_should_update:
                    try:
                        piList.append(sickbeard.showQueueScheduler.action.updateShow(curShow, True))  # @UndefinedVariable
                    except CantUpdateShowException as e:
                        logger.log(u"Unable to update show: {0}".format(str(e)),logger.DEBUG)
                else:
                    logger.log(
                        u"Not updating episodes for show " + curShow.name + " because it's marked as ended and last/next episode is not within the grace period.",
                        logger.DEBUG)
                    piList.append(sickbeard.showQueueScheduler.action.refreshShow(curShow, True))  # @UndefinedVariable

            except (CantUpdateShowException, CantRefreshShowException) as e:
                logger.log(u"Automatic update failed: {}".format(ex(e)), logger.ERROR)

        ui.ProgressIndicators.setIndicator('dailyUpdate', ui.QueueProgressIndicator("Daily Update", piList))

        logger.log(u"Completed full update on all shows")

        self.amActive = False
 def test_timezone(self):
     network_timezones.update_network_dict()
     network_timezones.sb_timezone = tz.gettz('CET', zoneinfo_priority=True)
     d = datetime.date(2018, 9, 2).toordinal()
     t = 'Monday 9:00 PM'
     network = 'NBC'
     r = network_timezones.parse_date_time(d, t, network)
     local_date = datetime.datetime(2018, 9, 3, 3, 0, 0).replace(tzinfo=tz.gettz('CET', zoneinfo_priority=True))
     self.assertEqual(r, local_date)
Example #10
0
    def run(self, force=False):  # pylint: disable=unused-argument, too-many-locals, too-many-branches, too-many-statements

        if self.amActive:
            return

        self.amActive = True

        update_timestamp = time.mktime(datetime.datetime.now().timetuple())
        cache_db_con = db.DBConnection('cache.db')
        result = cache_db_con.select('SELECT `time` FROM lastUpdate WHERE provider = ?', ['theTVDB'])
        if result:
            last_update = int(result[0][0])
        else:
            last_update = int(time.mktime(datetime.datetime.min.timetuple()))
            cache_db_con.action('INSERT INTO lastUpdate (provider, `time`) VALUES (?, ?)', ['theTVDB', last_update])

        network_timezones.update_network_dict()

        url = 'http://thetvdb.com/api/Updates.php?type=series&time={0}'.format(last_update)
        data = helpers.getURL(url, session=self.session, returns='text', hooks={'response': self.request_hook})
        if not data:
            logger.log('Could not get the recently updated show data from {0}. Retrying later. Url was: {1}'.format(sickbeard.indexerApi(INDEXER_TVDB).name, url))
            self.amActive = False
            return

        updated_shows = set()
        try:
            tree = etree.fromstring(data)
            for show in tree.findall('Series'):
                updated_shows.add(int(show.text))
        except SyntaxError:
            update_timestamp = last_update

        pi_list = []
        for cur_show in sickbeard.showList:
            if int(cur_show.indexer) in [INDEXER_TVRAGE]:
                logger.log('Indexer is no longer available for show [{0}] '.format(cur_show.name), logger.WARNING)
                continue

            try:
                cur_show.nextEpisode()
                if sickbeard.indexerApi(cur_show.indexer).name == 'theTVDB':
                    if cur_show.indexerid in updated_shows:
                        pi_list.append(sickbeard.showQueueScheduler.action.update_show(cur_show, True))
                    else:
                        pi_list.append(sickbeard.showQueueScheduler.action.refresh_show(cur_show, False))
            except (CantUpdateShowException, CantRefreshShowException) as error:
                logger.log('Automatic update failed: {0}'.format(ex(error)), logger.DEBUG)

        ui.ProgressIndicators.setIndicator('dailyUpdate', ui.QueueProgressIndicator('Daily Update', pi_list))

        cache_db_con.action('UPDATE lastUpdate SET `time` = ? WHERE provider=?', [update_timestamp, 'theTVDB'])

        self.amActive = False
Example #11
0
    def run(self, force=False):  # pylint: disable=unused-argument, too-many-locals, too-many-branches, too-many-statements

        if self.amActive:
            return

        self.amActive = True

        update_timestamp = time.mktime(datetime.datetime.now().timetuple())
        cache_db_con = db.DBConnection('cache.db')
        result = cache_db_con.select('SELECT `time` FROM lastUpdate WHERE provider = ?', ['theTVDB'])
        if result:
            last_update = int(result[0][0])
        else:
            last_update = int(time.mktime(datetime.datetime.min.timetuple()))
            cache_db_con.action('INSERT INTO lastUpdate (provider, `time`) VALUES (?, ?)', ['theTVDB', last_update])

        network_timezones.update_network_dict()

        url = 'http://thetvdb.com/api/Updates.php?type=series&time={0}'.format(last_update)
        data = helpers.getURL(url, session=self.session, returns='text', hooks={'response': self.request_hook})
        if not data:
            logger.log('Could not get the recently updated show data from {0}. Retrying later. Url was: {1}'.format(sickbeard.indexerApi(INDEXER_TVDB).name, url))
            self.amActive = False
            return

        updated_shows = set()
        try:
            tree = etree.fromstring(data)
            for show in tree.findall('Series'):
                updated_shows.add(int(show.text))
        except SyntaxError:
            update_timestamp = last_update

        pi_list = []
        for cur_show in sickbeard.showList:
            if int(cur_show.indexer) in [INDEXER_TVRAGE]:
                logger.log('Indexer is no longer available for show [{0}] '.format(cur_show.name), logger.WARNING)
                continue

            try:
                cur_show.nextEpisode()
                if sickbeard.indexerApi(cur_show.indexer).name == 'theTVDB':
                    if cur_show.indexerid in updated_shows:
                        pi_list.append(sickbeard.showQueueScheduler.action.update_show(cur_show, True))
                    else:
                        pi_list.append(sickbeard.showQueueScheduler.action.refresh_show(cur_show, False))
            except (CantUpdateShowException, CantRefreshShowException) as error:
                logger.log('Automatic update failed: {0}'.format(ex(error)), logger.DEBUG)

        ui.ProgressIndicators.setIndicator('dailyUpdate', ui.QueueProgressIndicator('Daily Update', pi_list))

        cache_db_con.action('UPDATE lastUpdate SET `time` = ? WHERE provider=?', [update_timestamp, 'theTVDB'])

        self.amActive = False
Example #12
0
 def test_timezone(self):
     network_timezones.update_network_dict()
     network_timezones.sb_timezone = tz.gettz('CET', zoneinfo_priority=True)
     d = datetime.date(2018, 9, 2).toordinal()
     t = 'Monday 9:00 PM'
     network = 'NBC'
     r = network_timezones.parse_date_time(d, t, network)
     local_date = datetime.datetime(
         2018, 9, 3, 3, 0,
         0).replace(tzinfo=tz.gettz('CET', zoneinfo_priority=True))
     self.assertEqual(r, local_date)
Example #13
0
    def run(self):
        self.check_for_new_version()

        # refresh scene exceptions too
        scene_exceptions.retrieve_exceptions()

        # refresh network timezones
        network_timezones.update_network_dict()

        # sure, why not?
        if sickbeard.USE_FAILED_DOWNLOADS:
            failed_history.trimHistory()
    def run(self):
        self.check_for_new_version()

        # refresh scene exceptions too
        scene_exceptions.retrieve_exceptions()

        # refresh network timezones
        network_timezones.update_network_dict()

        # sure, why not?
        if sickbeard.USE_FAILED_DOWNLOADS:
            failed_history.trimHistory()
Example #15
0
    def run(self, force=False):

        update_datetime = datetime.datetime.now()
        update_date = update_datetime.date()

        # refresh network timezones
        network_timezones.update_network_dict()

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

        logger.log(u"Doing full update on all shows")

        # clean out cache directory, remove everything > 12 hours old
        sickbeard.helpers.clearCache()

        # select 10 'Ended' tv_shows updated more than 90 days ago to include in this update
        stale_should_update = []
        stale_update_date = (update_date - datetime.timedelta(days=90)).toordinal()

        # last_update_date <= 90 days, sorted ASC because dates are ordinal
        myDB = db.DBConnection()
        sql_result = myDB.select(
            "SELECT indexer_id FROM tv_shows WHERE status = 'Ended' AND last_update_indexer <= ? ORDER BY last_update_indexer ASC LIMIT 10;",
            [stale_update_date])

        for cur_result in sql_result:
            stale_should_update.append(int(cur_result['indexer_id']))

        # start update process
        piList = []
        for curShow in sickbeard.showList:

            try:
                # get next episode airdate
                curShow.nextEpisode()

                # if should_update returns True (not 'Ended') or show is selected stale 'Ended' then update, otherwise just refresh
                if curShow.should_update(update_date=update_date) or curShow.indexerid in stale_should_update:
                    curQueueItem = sickbeard.showQueueScheduler.action.updateShow(curShow, True)  # @UndefinedVariable
                else:
                    logger.log(
                        u"Not updating episodes for show " + curShow.name + " because it's marked as ended and last/next episode is not within the grace period.",
                        logger.DEBUG)
                    curQueueItem = sickbeard.showQueueScheduler.action.refreshShow(curShow, True)  # @UndefinedVariable

                piList.append(curQueueItem)

            except (exceptions.CantUpdateException, exceptions.CantRefreshException), e:
                logger.log(u"Automatic update failed: " + ex(e), logger.ERROR)
Example #16
0
    def run(self, force=False):
        self.amActive = True

        # get and update scene exceptions lists
        scene_exceptions.retrieve_exceptions()

        # refresh network timezones
        network_timezones.update_network_dict()

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

        self.amActive = False
Example #17
0
    def run(self, force=False):
        self.amActive = True

        # refresh scene exceptions too
        scene_exceptions.retrieve_exceptions()

        # refresh network timezones
        network_timezones.update_network_dict()

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

        self.amActive = False
Example #18
0
    def run(self, force=False):

        update_datetime = datetime.datetime.now()
        update_date = update_datetime.date()

        # refresh network timezones
        network_timezones.update_network_dict()

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

        logger.log(u"Doing full update on all shows")

        # clean out cache directory, remove everything > 12 hours old
        if sickbeard.CACHE_DIR:
            for indexer in sickbeard.indexerApi().indexers:
                cache_dir = sickbeard.indexerApi(indexer).cache
                logger.log(u"Trying to clean cache folder " + cache_dir)

                # Does our cache_dir exists
                if not ek.ek(os.path.isdir, cache_dir):
                    logger.log(u"Can't clean " + cache_dir + " if it doesn't exist", logger.WARNING)
                else:
                    max_age = datetime.timedelta(hours=12)
                    # Get all our cache files
                    cache_files = ek.ek(os.listdir, cache_dir)

                    for cache_file in cache_files:
                        cache_file_path = ek.ek(os.path.join, cache_dir, cache_file)

                        if ek.ek(os.path.isfile, cache_file_path):
                            cache_file_modified = datetime.datetime.fromtimestamp(
                                ek.ek(os.path.getmtime, cache_file_path)
                            )

                            if update_datetime - cache_file_modified > max_age:
                                try:
                                    ek.ek(os.remove, cache_file_path)
                                except OSError, e:
                                    logger.log(
                                        u"Unable to clean " + cache_dir + ": " + repr(e) + " / " + str(e),
                                        logger.WARNING,
                                    )
                                    break
Example #19
0
    def run(self):
        if self.check_for_new_version():
           if sickbeard.AUTO_UPDATE:
            logger.log(u"New update found for SickBeard, starting auto-updater ...")
            updated = sickbeard.versionCheckScheduler.action.update()
            if updated:
                logger.log(u"Update was successfull, restarting SickBeard ...")
                sickbeard.restart(False)

        # refresh scene exceptions too
        scene_exceptions.retrieve_exceptions()

        # refresh network timezones
        network_timezones.update_network_dict()

        # sure, why not?
        if sickbeard.USE_FAILED_DOWNLOADS:
            failed_history.trimHistory()
Example #20
0
    def run(self, force=False):
        self.amActive = True

        # clear internal name cache
        name_cache.clearCache()

        # get and update scene exceptions lists
        scene_exceptions.retrieve_exceptions()

        # build internal name cache for searches and parsing
        name_cache.buildNameCache()

        # refresh network timezones
        network_timezones.update_network_dict()

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

        self.amActive = False
    def run(self):
        if self.check_for_new_version():
            if sickbeard.AUTO_UPDATE:
                logger.log(
                    u"New update found for SickBeard, starting auto-updater ..."
                )
                updated = sickbeard.versionCheckScheduler.action.update()
                if updated:
                    logger.log(
                        u"Update was successfull, restarting SickBeard ...")
                    sickbeard.restart(False)

        # refresh scene exceptions too
        scene_exceptions.retrieve_exceptions()

        # refresh network timezones
        network_timezones.update_network_dict()

        # sure, why not?
        if sickbeard.USE_FAILED_DOWNLOADS:
            failed_history.trimHistory()
Example #22
0
    def run(self):
        updated = None
        if self.check_for_new_version():
            if sickbeard.AUTO_UPDATE:
                logger.log(u"New update found for SickRage, starting auto-updater ...")
                updated = sickbeard.versionCheckScheduler.action.update()
                if updated:
                    logger.log(u"Update was successfull, restarting SickRage ...")

                    # do a soft restart
                    threading.Timer(2, sickbeard.invoke_restart, [False]).start()

        if not updated:
            # refresh scene exceptions too
            scene_exceptions.retrieve_exceptions()

            # refresh network timezones
            network_timezones.update_network_dict()

            # sure, why not?
            if sickbeard.USE_FAILED_DOWNLOADS:
                failed_history.trimHistory()
Example #23
0
    def start(self):
        # 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.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'

        # pylint: disable=no-member
        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(sys)

        try:
            # pylint: disable=no-member
            # 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 Exception:
            sys.exit("Sorry, you MUST add the SickRage folder to the PYTHONPATH environment variable\n" +
                     "or find another way to force Python to use " + sickbeard.SYS_ENCODING + " for string encoding.")

        # Need console logging for SickBeard.py and SickBeard-console.exe
        self.consoleLogging = (not hasattr(sys, "frozen")) or (sickbeard.MY_NAME.lower().find('-console') > 0)

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

        try:
            opts, _ = getopt.getopt(
                sys.argv[1:], "hqdp::",
                ['help', 'quiet', 'nolaunch', 'daemon', 'pidfile=', 'port=', 'datadir=', 'config=', 'noresize']
            )
        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.consoleLogging = False

            # 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.noLaunch = True

            # Override default/configured port
            if o in ('-p', '--port'):
                try:
                    self.forcedPort = int(a)
                except ValueError:
                    sys.exit("Port: " + str(a) + " is not a number. Exiting.")

            # Run as a double forked daemon
            if o in ('-d', '--daemon'):
                self.runAsDaemon = True
                # When running as daemon disable consoleLogging and don't start browser
                self.consoleLogging = False
                self.noLaunch = True

                if sys.platform == 'win32' or sys.platform == 'darwin':
                    self.runAsDaemon = False

            # Write a pidfile if requested
            if o in ('--pidfile',):
                self.CREATEPID = True
                self.PIDFILE = str(a)

                # If the pidfile already exists, sickbeard may still be running, so exit
                if ek(os.path.exists, self.PIDFILE):
                    sys.exit("PID file: " + self.PIDFILE + " already exists. Exiting.")

            # Specify folder to load the config file from
            if o in ('--config',):
                sickbeard.CONFIG_FILE = ek(os.path.abspath, a)

            # Specify folder to use as the data dir
            if o in ('--datadir',):
                sickbeard.DATA_DIR = ek(os.path.abspath, a)

            # Prevent resizing of the banner/posters even if PIL is installed
            if o in ('--noresize',):
                sickbeard.NO_RESIZE = True

        # The pidfile is only useful in daemon mode, make sure we can write the file properly
        if self.CREATEPID:
            if self.runAsDaemon:
                pid_dir = ek(os.path.dirname, self.PIDFILE)
                if not ek(os.access, pid_dir, os.F_OK):
                    sys.exit("PID dir: " + pid_dir + " doesn't exist. Exiting.")
                if not ek(os.access, pid_dir, os.W_OK):
                    sys.exit("PID dir: " + pid_dir + " must be writable (write permissions). Exiting.")

            else:
                if self.consoleLogging:
                    sys.stdout.write(u"Not running in daemon mode. PID file creation disabled.\n")

                self.CREATEPID = False

        # If they don't specify a config file then put it in the data dir
        if not sickbeard.CONFIG_FILE:
            sickbeard.CONFIG_FILE = ek(os.path.join, sickbeard.DATA_DIR, "config.ini")

        # 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, 0744)
            except os.error:
                raise SystemExit("Unable to create datadir '" + 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("Datadir must be writeable '" + 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 '" + sickbeard.CONFIG_FILE + "' must be writeable.")
            elif not ek(os.access, ek(os.path.dirname, sickbeard.CONFIG_FILE), os.W_OK):
                raise SystemExit(
                    "Config file root dir '" + ek(os.path.dirname, sickbeard.CONFIG_FILE) + "' must be writeable.")

        ek(os.chdir, sickbeard.DATA_DIR)

        # Check if we need to perform a restore first
        restoreDir = ek(os.path.join, sickbeard.DATA_DIR, 'restore')
        if ek(os.path.exists, restoreDir):
            success = self.restoreDB(restoreDir, sickbeard.DATA_DIR)
            if self.consoleLogging:
                sys.stdout.write(u"Restore: restoring DB and config.ini %s!\n" % ("FAILED", "SUCCESSFUL")[success])

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

        sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE)

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

        if self.runAsDaemon:
            self.daemonize()

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

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

        if self.forcedPort:
            logger.log(u"Forcing web server to port " + str(self.forcedPort))
            self.startPort = self.forcedPort
        else:
            self.startPort = 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.webhost = sickbeard.WEB_HOST
        else:
            if sickbeard.WEB_IPV6:
                self.webhost = ''
            else:
                self.webhost = '0.0.0.0'

        # web server options
        self.web_options = {
            'port': int(self.startPort),
            'host': self.webhost,
            '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.webserver = SRWebServer(self.web_options)
        self.webserver.start()

        if self.consoleLogging:
            print "Starting up SickRage " + sickbeard.BRANCH + " from " + sickbeard.CONFIG_FILE

        # Clean up after update
        if sickbeard.GIT_NEWVER:
            toclean = ek(os.path.join, sickbeard.CACHE_DIR, 'mako')
            for root, dirs, files in ek(os.walk, toclean, topdown=False):
                for name in files:
                    ek(os.remove, ek(os.path.join, root, name))
                for name in dirs:
                    ek(os.rmdir, ek(os.path.join, root, name))
            sickbeard.GIT_NEWVER = False

        # Fire up all our threads
        sickbeard.start()

        # Build internal name cache
        name_cache.buildNameCache()

        # Prepopulate 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 (Disabled until we use api)
        # sickbeard.showUpdateScheduler.forceRun()

        # Launch browser
        if sickbeard.LAUNCH_BROWSER and not (self.noLaunch or self.runAsDaemon):
            sickbeard.launchBrowser('https' if sickbeard.ENABLE_HTTPS else 'http', self.startPort, sickbeard.WEB_ROOT)

        # main loop
        while True:
            time.sleep(1)
Example #24
0
    def start(self):
        # do some preliminary stuff
        sickbeard.MY_FULLNAME = os.path.normpath(os.path.abspath(__file__))
        sickbeard.MY_NAME = os.path.basename(sickbeard.MY_FULLNAME)
        sickbeard.PROG_DIR = os.path.dirname(sickbeard.MY_FULLNAME)
        sickbeard.DATA_DIR = sickbeard.PROG_DIR
        sickbeard.MY_ARGS = sys.argv[1:]
        sickbeard.SYS_ENCODING = None

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

        # For OSes that are poorly configured I'll just randomly force UTF-8
        if not sickbeard.SYS_ENCODING or sickbeard.SYS_ENCODING in (
                'ANSI_X3.4-1968', 'US-ASCII', 'ASCII'):
            sickbeard.SYS_ENCODING = 'UTF-8'

        if not hasattr(sys, 'setdefaultencoding'):
            moves.reload_module(sys)

        try:
            # pylint: disable=E1101
            # 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:
            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.'
                % sickbeard.SYS_ENCODING)
            sys.exit(1)

        # Need console logging for SickBeard.py and SickBeard-console.exe
        self.consoleLogging = (not hasattr(
            sys, 'frozen')) or (sickbeard.MY_NAME.lower().find('-console') > 0)

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

        try:
            opts, args = getopt.getopt(sys.argv[1:], 'hfqdp::', [
                'help', 'forceupdate', 'quiet', 'nolaunch', 'daemon',
                'pidfile=', 'port=', 'datadir=', 'config=', 'noresize'
            ])  # @UnusedVariable
        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.consoleLogging = False

            # Should we update (from indexer) all shows in the DB right away?
            if o in ('-f', '--forceupdate'):
                self.forceUpdate = 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.noLaunch = True

            # Override default/configured port
            if o in ('-p', '--port'):
                try:
                    self.forcedPort = 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.runAsDaemon = True
                # When running as daemon disable consoleLogging and don't start browser
                self.consoleLogging = False
                self.noLaunch = True

                if sys.platform == 'win32':
                    self.runAsDaemon = False

            # Write a pidfile if requested
            if o in ('--pidfile', ):
                self.CREATEPID = True
                self.PIDFILE = str(a)

                # If the pidfile already exists, sickbeard may still be running, so exit
                if os.path.exists(self.PIDFILE):
                    sys.exit('PID file: %s already exists. Exiting.' %
                             self.PIDFILE)

            # Specify folder to load the config file from
            if o in ('--config', ):
                sickbeard.CONFIG_FILE = os.path.abspath(a)

            # Specify folder to use as the data dir
            if o in ('--datadir', ):
                sickbeard.DATA_DIR = os.path.abspath(a)

            # Prevent resizing of the banner/posters even if PIL is installed
            if o in ('--noresize', ):
                sickbeard.NO_RESIZE = True

        # The pidfile is only useful in daemon mode, make sure we can write the file properly
        if self.CREATEPID:
            if self.runAsDaemon:
                pid_dir = os.path.dirname(self.PIDFILE)
                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.consoleLogging:
                    print(
                        u'Not running in daemon mode. PID file creation disabled'
                    )

                self.CREATEPID = False

        # If they don't specify a config file then put it in the data dir
        if not sickbeard.CONFIG_FILE:
            sickbeard.CONFIG_FILE = os.path.join(sickbeard.DATA_DIR,
                                                 'config.ini')

        # Make sure that we can create the data dir
        if not os.access(sickbeard.DATA_DIR, os.F_OK):
            try:
                os.makedirs(sickbeard.DATA_DIR, 0o744)
            except os.error:
                sys.exit(u'Unable to create data directory: %s Exiting.' %
                         sickbeard.DATA_DIR)

        # Make sure we can write to the data dir
        if not os.access(sickbeard.DATA_DIR, os.W_OK):
            sys.exit(
                u'Data directory: %s must be writable (write permissions). Exiting.'
                % sickbeard.DATA_DIR)

        # Make sure we can write to the config file
        if not os.access(sickbeard.CONFIG_FILE, os.W_OK):
            if os.path.isfile(sickbeard.CONFIG_FILE):
                sys.exit(
                    u'Config file: %s must be writeable (write permissions). Exiting.'
                    % sickbeard.CONFIG_FILE)
            elif not os.access(os.path.dirname(sickbeard.CONFIG_FILE),
                               os.W_OK):
                sys.exit(
                    u'Config file directory: %s must be writeable (write permissions). Exiting'
                    % os.path.dirname(sickbeard.CONFIG_FILE))
        os.chdir(sickbeard.DATA_DIR)

        if self.consoleLogging:
            print(u'Starting up SickGear from %s' % sickbeard.CONFIG_FILE)

        # Load the config and publish it to the sickbeard package
        if not os.path.isfile(sickbeard.CONFIG_FILE):
            print(u'Unable to find "%s", all settings will be default!' %
                  sickbeard.CONFIG_FILE)

        sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE)

        # check all db versions
        for d, min_v, max_v, mo in [
            ('failed.db', sickbeard.failed_db.MIN_DB_VERSION,
             sickbeard.failed_db.MAX_DB_VERSION, 'FailedDb'),
            ('cache.db', sickbeard.cache_db.MIN_DB_VERSION,
             sickbeard.cache_db.MAX_DB_VERSION, 'CacheDb'),
            ('sickbeard.db', sickbeard.mainDB.MIN_DB_VERSION,
             sickbeard.mainDB.MAX_DB_VERSION, 'MainDb')
        ]:
            cur_db_version = db.DBConnection(d).checkDBVersion()

            if cur_db_version > 0:
                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:
                    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))
                    try:
                        rollback_loaded = db.get_rollback_module()
                        if None is not rollback_loaded:
                            rollback_loaded.__dict__[mo]().run(max_v)
                        else:
                            print(
                                u'ERROR: Could not download Rollback Module.')
                    except (StandardError, Exception):
                        pass
                    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)

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

        if self.runAsDaemon:
            self.daemonize()

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

        if self.forcedPort:
            logger.log(u'Forcing web server to port %s' % self.forcedPort)
            self.startPort = self.forcedPort
        else:
            self.startPort = 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.webhost = sickbeard.WEB_HOST
        else:
            if sickbeard.WEB_IPV6:
                self.webhost = '::'
            else:
                self.webhost = '0.0.0.0'

        # web server options
        self.web_options = {
            'port':
            int(self.startPort),
            'host':
            self.webhost,
            'data_root':
            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':
            os.path.join(sickbeard.PROG_DIR, sickbeard.HTTPS_CERT),
            'https_key':
            os.path.join(sickbeard.PROG_DIR, sickbeard.HTTPS_KEY),
        }

        # start web server
        try:
            # used to check if existing SG instances have been started
            sickbeard.helpers.wait_for_free_port(self.web_options['host'],
                                                 self.web_options['port'])

            self.webserver = WebServer(self.web_options)
            self.webserver.start()
        except Exception:
            logger.log(
                u'Unable to start web server, is something else running on port %d?'
                % self.startPort, logger.ERROR)
            if sickbeard.LAUNCH_BROWSER and not self.runAsDaemon:
                logger.log(u'Launching browser and exiting', logger.ERROR)
                sickbeard.launch_browser(self.startPort)
            os._exit(1)

        # Check if we need to perform a restore first
        restoreDir = os.path.join(sickbeard.DATA_DIR, 'restore')
        if os.path.exists(restoreDir):
            if self.restore(restoreDir, sickbeard.DATA_DIR):
                logger.log(u'Restore successful...')
            else:
                logger.log_error_and_exit(u'Restore FAILED!')

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

        # Fire up all our threads
        sickbeard.start()

        # Build internal name cache
        name_cache.buildNameCache()

        # refresh network timezones
        network_timezones.update_network_dict()

        # load all ids from xem
        startup_background_tasks = threading.Thread(
            name='FETCH-XEMDATA',
            target=sickbeard.scene_exceptions.get_xem_ids)
        startup_background_tasks.start()

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

        # Start an update if we're supposed to
        if self.forceUpdate or sickbeard.UPDATE_SHOWS_ON_START:
            sickbeard.showUpdateScheduler.action.run(
                force=True)  # @UndefinedVariable

        # Launch browser
        if sickbeard.LAUNCH_BROWSER and not (self.noLaunch
                                             or self.runAsDaemon):
            sickbeard.launch_browser(self.startPort)

        # main loop
        while True:
            time.sleep(1)
Example #25
0
    def start(self):
        # do some preliminary stuff
        sickbeard.MY_FULLNAME = os.path.normpath(os.path.abspath(__file__))
        sickbeard.MY_NAME = os.path.basename(sickbeard.MY_FULLNAME)
        sickbeard.PROG_DIR = os.path.dirname(sickbeard.MY_FULLNAME)
        sickbeard.DATA_DIR = sickbeard.PROG_DIR
        sickbeard.MY_ARGS = sys.argv[1:]
        sickbeard.SYS_ENCODING = None

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

        # For OSes that are poorly configured I'll just randomly force UTF-8
        if not sickbeard.SYS_ENCODING or sickbeard.SYS_ENCODING in ('ANSI_X3.4-1968', 'US-ASCII', 'ASCII'):
            sickbeard.SYS_ENCODING = 'UTF-8'

        if not hasattr(sys, 'setdefaultencoding'):
            moves.reload_module(sys)

        try:
            # pylint: disable=E1101
            # On non-unicode builds this raises an AttributeError, if encoding type is not valid it throws a LookupError
            sys.setdefaultencoding(sickbeard.SYS_ENCODING)
        except (StandardError, Exception):
            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.' % sickbeard.SYS_ENCODING)
            sys.exit(1)

        # Need console logging for SickBeard.py and SickBeard-console.exe
        self.console_logging = (not hasattr(sys, 'frozen')) or (sickbeard.MY_NAME.lower().find('-console') > 0)

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

        try:
            opts, args = getopt.getopt(sys.argv[1:], 'hfqdsp::',
                                       ['help', 'forceupdate', 'quiet', 'nolaunch', 'daemon', 'systemd', 'pidfile=',
                                        'port=', 'datadir=', 'config=', 'noresize'])  # @UnusedVariable
        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 'win32' == sys.platform:
                    self.run_as_daemon = False

            # Run as a systemd service
            if o in ('-s', '--systemd') and 'win32' != sys.platform:
                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, sickbeard 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',):
                sickbeard.CONFIG_FILE = os.path.abspath(a)

            # Specify folder to use as the data dir
            if o in ('--datadir',):
                sickbeard.DATA_DIR = os.path.abspath(a)

            # Prevent resizing of the banner/posters even if PIL is installed
            if o in ('--noresize',):
                sickbeard.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 sickbeard.CONFIG_FILE:
            sickbeard.CONFIG_FILE = os.path.join(sickbeard.DATA_DIR, 'config.ini')

        # Make sure that we can create the data dir
        if not os.access(sickbeard.DATA_DIR, os.F_OK):
            try:
                os.makedirs(sickbeard.DATA_DIR, 0o744)
            except os.error:
                sys.exit(u'Unable to create data directory: %s Exiting.' % sickbeard.DATA_DIR)

        # Make sure we can write to the data dir
        if not os.access(sickbeard.DATA_DIR, os.W_OK):
            sys.exit(u'Data directory: %s must be writable (write permissions). Exiting.' % sickbeard.DATA_DIR)

        # Make sure we can write to the config file
        if not os.access(sickbeard.CONFIG_FILE, os.W_OK):
            if os.path.isfile(sickbeard.CONFIG_FILE):
                sys.exit(u'Config file: %s must be writeable (write permissions). Exiting.' % sickbeard.CONFIG_FILE)
            elif not os.access(os.path.dirname(sickbeard.CONFIG_FILE), os.W_OK):
                sys.exit(u'Config file directory: %s must be writeable (write permissions). Exiting'
                         % os.path.dirname(sickbeard.CONFIG_FILE))
        os.chdir(sickbeard.DATA_DIR)

        if self.console_logging:
            print(u'Starting up SickGear from %s' % sickbeard.CONFIG_FILE)

        # Load the config and publish it to the sickbeard package
        if not os.path.isfile(sickbeard.CONFIG_FILE):
            print(u'Unable to find "%s", all settings will be default!' % sickbeard.CONFIG_FILE)

        sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE)
        try:
            stack_size = int(sickbeard.CFG['General']['stack_size'])
        except (StandardError, Exception):
            stack_size = None

        if stack_size:
            try:
                threading.stack_size(stack_size)
            except (StandardError, Exception) as er:
                print('Stack Size %s not set: %s' % (stack_size, er.message))

        # check all db versions
        for d, min_v, max_v, base_v, mo in [
            ('failed.db', sickbeard.failed_db.MIN_DB_VERSION, sickbeard.failed_db.MAX_DB_VERSION,
             sickbeard.failed_db.TEST_BASE_VERSION, 'FailedDb'),
            ('cache.db', sickbeard.cache_db.MIN_DB_VERSION, sickbeard.cache_db.MAX_DB_VERSION,
             sickbeard.cache_db.TEST_BASE_VERSION, 'CacheDb'),
            ('sickbeard.db', sickbeard.mainDB.MIN_DB_VERSION, sickbeard.mainDB.MAX_DB_VERSION,
             sickbeard.mainDB.TEST_BASE_VERSION, 'MainDb')
        ]:
            cur_db_version = db.DBConnection(d).checkDBVersion()

            # handling of standalone TEST db versions
            if cur_db_version >= 100000 and cur_db_version != max_v:
                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)
                cur_db_version = db.DBConnection(d).checkDBVersion()
                if cur_db_version >= 100000:
                    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)

            # 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:
                    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)
                    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)

        # free memory
        global rollback_loaded
        rollback_loaded = None

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

        if self.run_as_daemon:
            self.daemonize()

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

        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 = 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.webhost = sickbeard.WEB_HOST
        else:
            self.webhost = (('0.0.0.0', '::')[sickbeard.WEB_IPV6], '')[sickbeard.WEB_IPV64]

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

        # start web server
        try:
            # used to check if existing SG instances have been started
            sickbeard.helpers.wait_for_free_port(
                sickbeard.WEB_IPV6 and '::1' or self.web_options['host'], self.web_options['port'])

            self.webserver = WebServer(self.web_options)
            self.webserver.start()
        except (StandardError, 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 sickbeard.LAUNCH_BROWSER and not self.no_launch:
                logger.log(u'Launching browser and exiting', logger.ERROR)
                sickbeard.launch_browser(self.start_port)
            self.exit(1)

        # Check if we need to perform a restore first
        restore_dir = os.path.join(sickbeard.DATA_DIR, 'restore')
        if os.path.exists(restore_dir):
            if self.restore(restore_dir, sickbeard.DATA_DIR):
                logger.log(u'Restore successful...')
            else:
                logger.log_error_and_exit(u'Restore FAILED!')

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

        # Fire up all our threads
        sickbeard.start()

        # Build internal name cache
        name_cache.buildNameCache()

        # refresh network timezones
        network_timezones.update_network_dict()

        # load all ids from xem
        startup_background_tasks = threading.Thread(name='FETCH-XEMDATA', target=sickbeard.scene_exceptions.get_xem_ids)
        startup_background_tasks.start()

        # 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=sickbeard.history.history_snatched_proper_fix)
            history_snatched_proper_task.start()

        if sickbeard.USE_FAILED_DOWNLOADS:
            failed_history.remove_old_history()

        # Start an update if we're supposed to
        if self.force_update or sickbeard.UPDATE_SHOWS_ON_START:
            sickbeard.showUpdateScheduler.action.run(force=True)  # @UndefinedVariable

        # Launch browser
        if sickbeard.LAUNCH_BROWSER and not self.no_launch:
            sickbeard.launch_browser(self.start_port)

        # main loop
        while True:
            time.sleep(1)
Example #26
0
    def run(self, force=False):

        self.amActive = True

        logger.log(u"Searching for new released episodes ...")

        if not network_timezones.network_dict:
            network_timezones.update_network_dict()

        if network_timezones.network_dict:
            curDate = (datetime.date.today() + datetime.timedelta(days=1)).toordinal()
        else:
            curDate = (datetime.date.today() + datetime.timedelta(days=2)).toordinal()

        curTime = datetime.datetime.now(network_timezones.sb_timezone)

        myDB = db.DBConnection()
        sqlResults = myDB.select("SELECT * FROM tv_episodes WHERE status = ? AND season > 0 AND airdate <= ?",
                                 [common.UNAIRED, curDate])

        sql_l = []
        show = None

        for sqlEp in sqlResults:
            try:
                if not show or int(sqlEp["showid"]) != show.indexerid:
                    show = helpers.findCertainShow(sickbeard.showList, int(sqlEp["showid"]))

                # for when there is orphaned series in the database but not loaded into our showlist
                if not show:
                    continue

            except exceptions.MultipleShowObjectsException:
                logger.log(u"ERROR: expected to find a single show matching " + str(sqlEp['showid']))
                continue

            try:
                end_time = network_timezones.parse_date_time(sqlEp['airdate'], show.airs,
                                                             show.network) + datetime.timedelta(
                    minutes=helpers.tryInt(show.runtime, 60))
                # filter out any episodes that haven't aried yet
                if end_time > curTime:
                    continue
            except:
                # if an error occured assume the episode hasn't aired yet
                continue

            ep = show.getEpisode(int(sqlEp["season"]), int(sqlEp["episode"]))
            with ep.lock:
                if ep.show.paused:
                    ep.status = common.SKIPPED
                else:
                    myDB = db.DBConnection()
                    sql_selection="SELECT show_name, indexer_id, season, episode, paused FROM (SELECT * FROM tv_shows s,tv_episodes e WHERE s.indexer_id = e.showid) T1 WHERE T1.paused = 0 and T1.episode_id IN (SELECT T2.episode_id FROM tv_episodes T2 WHERE T2.showid = T1.indexer_id and T2.status in (?) ORDER BY T2.season,T2.episode LIMIT 1) and airdate is not null and indexer_id = ? ORDER BY T1.show_name,season,episode"
                    results = myDB.select(sql_selection, [common.SKIPPED, sqlEp["showid"]])
                    if not sickbeard.TRAKT_USE_ROLLING_DOWNLOAD:
                        if ep.season == 0: 
                            logger.log(u"New episode " + ep.prettyName() + " airs today, setting status to SKIPPED, due to trakt integration")
                            ep.status = common.SKIPPED
                        else:  
                            logger.log(u"New episode " + ep.prettyName() + " airs today, setting status to WANTED")
                            ep.status = common.WANTED                         
                    else:
                        sn_sk = results[0]["season"]
                        ep_sk = results[0]["episode"]
                        if (int(sn_sk)*100+int(ep_sk)) < (int(sqlEp["season"])*100+int(sqlEp["episode"])):
                            logger.log(u"New episode " + ep.prettyName() + " airs today, setting status to SKIPPED, due to trakt integration")
                            ep.status = common.SKIPPED
                        else:
                            if ep.season == 0: 
                                logger.log(u"New episode " + ep.prettyName() + " airs today, setting status to SKIPPED, due to trakt integration")
                                ep.status = common.SKIPPED
                            else:
                                logger.log(u"New episode " + ep.prettyName() + " airs today, setting status to WANTED")
                                ep.status = common.WANTED

                sql_l.append(ep.get_sql())
        else:
            logger.log(u"No new released episodes found ...")

        if len(sql_l) > 0:
            myDB = db.DBConnection()
            myDB.mass_action(sql_l)

        # queue episode for daily search
        dailysearch_queue_item = sickbeard.search_queue.DailySearchQueueItem()
        sickbeard.searchQueueScheduler.action.add_item(dailysearch_queue_item)

        self.amActive = False
Example #27
0
    def run(self, force=False):
        if self.amActive:
            return

        self.amActive = True

        logger.log(u"Searching for new released episodes ...")

        if not network_timezones.network_dict:
            network_timezones.update_network_dict()

        if network_timezones.network_dict:
            curDate = (datetime.date.today() + datetime.timedelta(days=1)).toordinal()
        else:
            curDate = (datetime.date.today() + datetime.timedelta(days=2)).toordinal()

        curTime = datetime.datetime.now(network_timezones.sb_timezone)

        myDB = db.DBConnection()
        sqlResults = myDB.select("SELECT * FROM tv_episodes WHERE status = ? AND season > 0 AND airdate <= ?",
                                 [common.UNAIRED, curDate])

        sql_l = []
        show = None

        for sqlEp in sqlResults:
            try:
                if not show or int(sqlEp["showid"]) != show.indexerid:
                    show = helpers.findCertainShow(sickbeard.showList, int(sqlEp["showid"]))

                # for when there is orphaned series in the database but not loaded into our showlist
                if not show:
                    continue

            except exceptions.MultipleShowObjectsException:
                logger.log(u"ERROR: expected to find a single show matching " + str(sqlEp['showid']))
                continue

            try:
                end_time = network_timezones.parse_date_time(sqlEp['airdate'], show.airs,
                                                             show.network) + datetime.timedelta(
                    minutes=helpers.tryInt(show.runtime, 60))
                # filter out any episodes that haven't aried yet
                if end_time > curTime:
                    continue
            except:
                # if an error occured assume the episode hasn't aired yet
                continue

            UpdateWantedList = 0
            ep = show.getEpisode(int(sqlEp["season"]), int(sqlEp["episode"]))
            with ep.lock:
                if ep.show.paused:
                    ep.status = ep.show.default_ep_status
                elif ep.season == 0:
                    logger.log(u"New episode " + ep.prettyName() + " airs today, setting status to SKIPPED because is a special season")
                    ep.status = common.SKIPPED
                elif sickbeard.TRAKT_USE_ROLLING_DOWNLOAD and sickbeard.USE_TRAKT:
                    ep.status = common.SKIPPED
                    UpdateWantedList = 1
                else:
                    logger.log(u"New episode %s airs today, setting to default episode status for this show: %s" % (ep.prettyName(), common.statusStrings[ep.show.default_ep_status]))
                    ep.status = ep.show.default_ep_status

                sql_l.append(ep.get_sql())

        if len(sql_l) > 0:
            myDB = db.DBConnection()
            myDB.mass_action(sql_l)
        else:
            logger.log(u"No new released episodes found ...")

        sickbeard.traktRollingScheduler.action.updateWantedList()

        # queue episode for daily search
        dailysearch_queue_item = sickbeard.search_queue.DailySearchQueueItem()
        sickbeard.searchQueueScheduler.action.add_item(dailysearch_queue_item)

        self.amActive = False
Example #28
0
class SickRage(object):
    def __init__(self):
        # system event callback for shutdown/restart
        sickbeard.events = Events(self.shutdown)

        # daemon constants
        self.runAsDaemon = False
        self.CREATEPID = False
        self.PIDFILE = ''

        # webserver constants
        self.webserver = None
        self.forcedPort = None
        self.noLaunch = False

    def help_message(self):
        """
        print help message for commandline options
        """
        help_msg = "\n"
        help_msg += "Usage: " + sickbeard.MY_FULLNAME + " <option> <another option>\n"
        help_msg += "\n"
        help_msg += "Options:\n"
        help_msg += "\n"
        help_msg += "    -h          --help              Prints this message\n"
        help_msg += "    -q          --quiet             Disables logging to console\n"
        help_msg += "                --nolaunch          Suppress launching web browser on startup\n"

        if sys.platform == 'win32' or sys.platform == 'darwin':
            help_msg += "    -d          --daemon            Running as real daemon is not supported on Windows\n"
            help_msg += "                                    On Windows and MAC, --daemon is substituted with: --quiet --nolaunch\n"
        else:
            help_msg += "    -d          --daemon            Run as double forked daemon (includes options --quiet --nolaunch)\n"
            help_msg += "                --pidfile=<path>    Combined with --daemon creates a pidfile (full path including filename)\n"

        help_msg += "    -p <port>   --port=<port>       Override default/configured port to listen on\n"
        help_msg += "                --datadir=<path>    Override folder (full path) as location for\n"
        help_msg += "                                    storing database, configfile, cache, logfiles \n"
        help_msg += "                                    Default: " + sickbeard.PROG_DIR + "\n"
        help_msg += "                --config=<path>     Override config filename (full path including filename)\n"
        help_msg += "                                    to load configuration from \n"
        help_msg += "                                    Default: config.ini in " + sickbeard.PROG_DIR + " or --datadir location\n"
        help_msg += "                --noresize          Prevent resizing of the banner/posters even if PIL is installed\n"

        return help_msg

    def fix_clients_nonsense(self):

        files = ["sickbeard/clients/download_station.py",
                 "sickbeard/clients/utorrent.py",
                 "sickbeard/clients/qbittorrent.py",
                 "sickbeard/clients/transmission.py",
                 "sickbeard/clients/deluge.py",
                 "sickbeard/clients/deluged.py",
                 "sickbeard/clients/rtorrent.py"
                ]

        for file in files:
            file = ek.ek(os.path.join, sickbeard.PROG_DIR, file)
            try:
                if ek.ek(os.path.exists, file):
                    ek.ek(os.remove, file)
            except:
                pass
            try:
                if ek.ek(os.path.exists, file + "c"):
                    ek.ek(os.remove, file + "c")
            except:
                pass

    def start(self):
        # do some preliminary stuff
        sickbeard.MY_FULLNAME = os.path.normpath(os.path.abspath(__file__))
        sickbeard.MY_NAME = os.path.basename(sickbeard.MY_FULLNAME)
        sickbeard.PROG_DIR = os.path.dirname(sickbeard.MY_FULLNAME)
        sickbeard.DATA_DIR = sickbeard.PROG_DIR
        sickbeard.MY_ARGS = sys.argv[1:]
        sickbeard.SYS_ENCODING = None

        try:
            locale.setlocale(locale.LC_ALL, "")
            sickbeard.SYS_ENCODING = locale.getpreferredencoding()
        except (locale.Error, IOError):
            pass

        # For OSes that are poorly configured I'll just randomly force UTF-8
        if not sickbeard.SYS_ENCODING or sickbeard.SYS_ENCODING in ('ANSI_X3.4-1968', 'US-ASCII', 'ASCII'):
            sickbeard.SYS_ENCODING = 'UTF-8'

        if not hasattr(sys, "setdefaultencoding"):
            reload(sys)

        if sys.platform == 'win32':
            if sys.getwindowsversion()[0] >= 6 and sys.stdout.encoding == 'cp65001':
                sickbeard.SYS_ENCODING = 'UTF-8'

        try:
            # pylint: disable=E1101
            # 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:
            sys.exit("Sorry, you MUST add the SickRage folder to the PYTHONPATH environment variable\n" +
                     "or find another way to force Python to use " + sickbeard.SYS_ENCODING + " for string encoding.")

        # Need console logging for SickBeard.py and SickBeard-console.exe
        self.consoleLogging = (not hasattr(sys, "frozen")) or (sickbeard.MY_NAME.lower().find('-console') > 0)

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

        try:
            opts, args = getopt.getopt(sys.argv[1:], "hfqdp::",
                                       ['help', 'quiet', 'nolaunch', 'daemon', 'pidfile=', 'port=',
                                        'datadir=', 'config=', 'noresize'])  # @UnusedVariable
        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.consoleLogging = False

            # 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.noLaunch = True

            # Override default/configured port
            if o in ('-p', '--port'):
                try:
                    self.forcedPort = int(a)
                except ValueError:
                    sys.exit("Port: " + str(a) + " is not a number. Exiting.")

            # Run as a double forked daemon
            if o in ('-d', '--daemon'):
                self.runAsDaemon = True
                # When running as daemon disable consoleLogging and don't start browser
                self.consoleLogging = False
                self.noLaunch = True

                if sys.platform == 'win32' or sys.platform == 'darwin':
                    self.runAsDaemon = False

            # Write a pidfile if requested
            if o in ('--pidfile',):
                self.CREATEPID = True
                self.PIDFILE = str(a)

                # If the pidfile already exists, sickbeard may still be running, so exit
                if os.path.exists(self.PIDFILE):
                    sys.exit("PID file: " + self.PIDFILE + " already exists. Exiting.")

            # Specify folder to load the config file from
            if o in ('--config',):
                sickbeard.CONFIG_FILE = os.path.abspath(a)

            # Specify folder to use as the data dir
            if o in ('--datadir',):
                sickbeard.DATA_DIR = os.path.abspath(a)

            # Prevent resizing of the banner/posters even if PIL is installed
            if o in ('--noresize',):
                sickbeard.NO_RESIZE = True

        # The pidfile is only useful in daemon mode, make sure we can write the file properly
        if self.CREATEPID:
            if self.runAsDaemon:
                pid_dir = os.path.dirname(self.PIDFILE)
                if not os.access(pid_dir, os.F_OK):
                    sys.exit("PID dir: " + pid_dir + " doesn't exist. Exiting.")
                if not os.access(pid_dir, os.W_OK):
                    sys.exit("PID dir: " + pid_dir + " must be writable (write permissions). Exiting.")

            else:
                if self.consoleLogging:
                    sys.stdout.write("Not running in daemon mode. PID file creation disabled.\n")

                self.CREATEPID = False

        # If they don't specify a config file then put it in the data dir
        if not sickbeard.CONFIG_FILE:
            sickbeard.CONFIG_FILE = os.path.join(sickbeard.DATA_DIR, "config.ini")

        # Make sure that we can create the data dir
        if not os.access(sickbeard.DATA_DIR, os.F_OK):
            try:
                os.makedirs(sickbeard.DATA_DIR, 0744)
            except os.error, e:
                raise SystemExit("Unable to create datadir '" + sickbeard.DATA_DIR + "'")

        # Make sure we can write to the data dir
        if not os.access(sickbeard.DATA_DIR, os.W_OK):
            raise SystemExit("Datadir must be writeable '" + sickbeard.DATA_DIR + "'")

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

        os.chdir(sickbeard.DATA_DIR)

        # Check if we need to perform a restore first
        try:
            restoreDir = os.path.join(sickbeard.DATA_DIR, 'restore')
            if self.consoleLogging and os.path.exists(restoreDir):
                if self.restoreDB(restoreDir, sickbeard.DATA_DIR):
                    sys.stdout.write("Restore: restoring DB and config.ini successful...\n")
                else:
                    sys.stdout.write("Restore: restoring DB and config.ini FAILED!\n")
        except Exception as e:
            sys.stdout.write("Restore: restoring DB and config.ini FAILED!\n")

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

        sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE)

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

        if self.runAsDaemon:
            self.daemonize()

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

        # Fix clients old files
        self.fix_clients_nonsense()

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

        if self.forcedPort:
            logger.log(u"Forcing web server to port " + str(self.forcedPort))
            self.startPort = self.forcedPort
        else:
            self.startPort = 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.webhost = sickbeard.WEB_HOST
        else:
            if sickbeard.WEB_IPV6:
                self.webhost = '::'
            else:
                self.webhost = '0.0.0.0'

        # web server options
        self.web_options = {
            'port': int(self.startPort),
            'host': self.webhost,
            'data_root': 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': os.path.join(sickbeard.PROG_DIR, sickbeard.HTTPS_CERT),
            'https_key': os.path.join(sickbeard.PROG_DIR, sickbeard.HTTPS_KEY),
        }

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

        if self.consoleLogging:
            print "Starting up SickRage " + sickbeard.BRANCH + " from " + sickbeard.CONFIG_FILE

        # Fire up all our threads
        sickbeard.start()

        # Build internal name cache
        name_cache.buildNameCache()

        # Prepopulate 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 (Disabled until we use api)
        #sickbeard.showUpdateScheduler.forceRun()

        # Launch browser
        if sickbeard.LAUNCH_BROWSER and not (self.noLaunch or self.runAsDaemon):
            sickbeard.launchBrowser('https' if sickbeard.ENABLE_HTTPS else 'http', self.startPort, sickbeard.WEB_ROOT)

        # main loop
        while (True):
            time.sleep(1)
Example #29
0
    def run(self, force=False):  # pylint:disable=too-many-branches
        """
        Runs the daily searcher, queuing selected episodes for search

        :param force: Force search
        """
        if self.amActive:
            return

        self.amActive = True
        _ = force
        logger.log(u"Searching for new released episodes ...")

        if not network_timezones.network_dict:
            network_timezones.update_network_dict()

        if network_timezones.network_dict:
            curDate = (datetime.date.today() + datetime.timedelta(days=1)).toordinal()
        else:
            curDate = (datetime.date.today() + datetime.timedelta(days=2)).toordinal()

        curTime = datetime.datetime.now(network_timezones.sb_timezone)

        main_db_con = db.DBConnection()
        sql_results = main_db_con.select("SELECT showid, airdate, season, episode FROM tv_episodes WHERE status = ? AND (airdate <= ? and airdate > 1)",
                                         [common.UNAIRED, curDate])

        sql_l = []
        show = None

        for sqlEp in sql_results:
            try:
                if not show or int(sqlEp["showid"]) != show.indexerid:
                    show = Show.find(sickbeard.showList, int(sqlEp["showid"]))

                # for when there is orphaned series in the database but not loaded into our showlist
                if not show or show.paused:
                    continue

            except MultipleShowObjectsException:
                logger.log(u"ERROR: expected to find a single show matching " + str(sqlEp['showid']))
                continue

            if show.airs and show.network:
                # This is how you assure it is always converted to local time
                air_time = network_timezones.parse_date_time(sqlEp['airdate'], show.airs, show.network).astimezone(network_timezones.sb_timezone)

                # filter out any episodes that haven't started airing yet,
                # but set them to the default status while they are airing
                # so they are snatched faster
                if air_time > curTime:
                    continue

            ep = show.getEpisode(sqlEp["season"], sqlEp["episode"])
            with ep.lock:
                if ep.season == 0:
                    logger.log(u"New episode " + ep.prettyName() + " airs today, setting status to SKIPPED because is a special season")
                    ep.status = common.SKIPPED
                else:
                    logger.log(u"New episode %s airs today, setting to default episode status for this show: %s" % (ep.prettyName(), common.statusStrings[ep.show.default_ep_status]))
                    ep.status = ep.show.default_ep_status

                sql_l.append(ep.get_sql())

        if len(sql_l) > 0:
            main_db_con = db.DBConnection()
            main_db_con.mass_action(sql_l)
        else:
            logger.log(u"No new released episodes found ...")

        # queue episode for daily search
        dailysearch_queue_item = sickbeard.search_queue.DailySearchQueueItem()
        sickbeard.searchQueueScheduler.action.add_item(dailysearch_queue_item)

        self.amActive = False
Example #30
0
    def start(self):
        # Rename the main thread
        threading.currentThread().name = "MAIN"

        # initalize encoding defaults
        encodingInit()

        # 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.DATA_DIR = sickbeard.PROG_DIR
        sickbeard.MY_ARGS = sys.argv[1:]

        # Need console logging for SickBeard.py and SickBeard-console.exe
        self.consoleLogging = (not hasattr(sys, "frozen")) or (sickbeard.MY_NAME.lower().find('-console') > 0)

        # Do this before importing sickbeard, to prevent locked files and incorrect import
        oldtornado = ek(os.path.abspath, ek(os.path.join, ek(os.path.dirname, __file__), 'tornado'))
        if ek(os.path.isdir, oldtornado):
            ek(shutil.move, oldtornado, oldtornado + '_kill')
            ek(removetree, oldtornado + '_kill')

        try:
            opts, _ = getopt.getopt(
                    sys.argv[1:], "hqdp::",
                    ['help', 'quiet', 'nolaunch', 'daemon', 'pidfile=', 'port=', 'datadir=', 'config=', 'noresize']
            )
        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.consoleLogging = False

            # 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.noLaunch = True

            # Override default/configured port
            if o in ('-p', '--port'):
                try:
                    self.forcedPort = int(a)
                except ValueError:
                    sys.exit("Port: " + str(a) + " is not a number. Exiting.")

            # Run as a double forked daemon
            if o in ('-d', '--daemon'):
                self.runAsDaemon = True
                # When running as daemon disable consoleLogging and don't start browser
                self.consoleLogging = False
                self.noLaunch = True

                if sys.platform == 'win32' or sys.platform == 'darwin':
                    self.runAsDaemon = False

            # Write a pidfile if requested
            if o in ('--pidfile',):
                self.CREATEPID = True
                self.PIDFILE = str(a)

                # If the pidfile already exists, sickbeard may still be running, so exit
                if ek(os.path.exists, self.PIDFILE):
                    sys.exit("PID file: " + self.PIDFILE + " already exists. Exiting.")

            # Specify folder to load the config file from
            if o in ('--config',):
                sickbeard.CONFIG_FILE = ek(os.path.abspath, a)

            # Specify folder to use as the data dir
            if o in ('--datadir',):
                sickbeard.DATA_DIR = ek(os.path.abspath, a)

            # Prevent resizing of the banner/posters even if PIL is installed
            if o in ('--noresize',):
                sickbeard.NO_RESIZE = True

        # The pidfile is only useful in daemon mode, make sure we can write the file properly
        if self.CREATEPID:
            if self.runAsDaemon:
                pid_dir = ek(os.path.dirname, self.PIDFILE)
                if not ek(os.access, pid_dir, os.F_OK):
                    sys.exit("PID dir: " + pid_dir + " doesn't exist. Exiting.")
                if not ek(os.access, pid_dir, os.W_OK):
                    sys.exit("PID dir: " + pid_dir + " must be writable (write permissions). Exiting.")

            else:
                if self.consoleLogging:
                    sys.stdout.write("Not running in daemon mode. PID file creation disabled.\n")

                self.CREATEPID = False

        # If they don't specify a config file then put it in the data dir
        if not sickbeard.CONFIG_FILE:
            sickbeard.CONFIG_FILE = ek(os.path.join, sickbeard.DATA_DIR, "config.ini")

        # 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 datadir '" + 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("Datadir must be writeable '" + 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 '" + sickbeard.CONFIG_FILE + "' must be writeable.")
            elif not ek(os.access, ek(os.path.dirname, sickbeard.CONFIG_FILE), os.W_OK):
                raise SystemExit(
                        "Config file root dir '" + ek(os.path.dirname, sickbeard.CONFIG_FILE) + "' must be writeable.")

        ek(os.chdir, sickbeard.DATA_DIR)

        # Check if we need to perform a restore first
        restoreDir = ek(os.path.join, sickbeard.DATA_DIR, 'restore')
        if ek(os.path.exists, restoreDir):
            success = self.restoreDB(restoreDir, sickbeard.DATA_DIR)
            if self.consoleLogging:
                sys.stdout.write("Restore: restoring DB and config.ini %s!\n" % ("FAILED", "SUCCESSFUL")[success])

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

        sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE)

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

        if self.runAsDaemon:
            self.daemonize()

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

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

        if self.forcedPort:
            logging.info("Forcing web server to port " + str(self.forcedPort))
            self.startPort = self.forcedPort
        else:
            self.startPort = 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.webhost = sickbeard.WEB_HOST
        else:
            if sickbeard.WEB_IPV6:
                self.webhost = '::'
            else:
                self.webhost = '0.0.0.0'

        # start tornado web server
        self.webserver = SRWebServer({
            'port': int(self.startPort),
            'host': self.webhost,
            '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()

        # Clean up after update
        if sickbeard.GIT_NEWVER:
            toclean = ek(os.path.join, sickbeard.CACHE_DIR, 'mako')
            for root, dirs, files in ek(os.walk, toclean, topdown=False):
                for name in files:
                    ek(os.remove, ek(os.path.join, root, name))
                for name in dirs:
                    ek(os.rmdir, ek(os.path.join, root, name))
            sickbeard.GIT_NEWVER = False

        # Fire up all our threads
        logging.info("Starting SiCKRAGE BRANCH:[{}] CONFIG:[{}]".format(sickbeard.BRANCH, sickbeard.CONFIG_FILE))
        sickbeard.start()

        # Build internal name cache
        name_cache.buildNameCache()

        # Prepopulate 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 (Disabled until we use api)
        # sickbeard.showUpdateScheduler.forceRun()

        # Launch browser
        if sickbeard.LAUNCH_BROWSER and not (self.noLaunch or self.runAsDaemon):
            sickbeard.launchBrowser('https' if sickbeard.ENABLE_HTTPS else 'http', self.startPort, sickbeard.WEB_ROOT)

        # main loop
        while True:
            time.sleep(1)
Example #31
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',
                                  options={'indent_type': '  '})

        # 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)
Example #32
0
    def run(self, force=False):  # pylint:disable=too-many-branches
        """
        Runs the daily searcher, queuing selected episodes for search

        :param force: Force search
        """
        if self.amActive:
            logger.log('Daily search is still running, not starting it again', logger.DEBUG)
            return
        elif sickbeard.forcedSearchQueueScheduler.action.is_forced_search_in_progress() and not force:
            logger.log('Manual search is running. Can\'t start Daily search', logger.WARNING)
            return

        self.amActive = True

        logger.log('Searching for newly released episodes ...')

        if not network_dict:
            update_network_dict()

        cur_time = datetime.now(sb_timezone)
        cur_date = (
            date.today() + timedelta(days=1 if network_dict else 2)
        ).toordinal()

        main_db_con = DBConnection()
        episodes_from_db = main_db_con.select(
            b'SELECT showid, airdate, season, episode '
            b'FROM tv_episodes '
            b'WHERE status = ? AND (airdate <= ? and airdate > 1)',
            [common.UNAIRED, cur_date]
        )

        new_releases = []
        show = None

        for db_episode in episodes_from_db:
            try:
                show_id = int(db_episode[b'showid'])
                if not show or show_id != show.indexerid:
                    show = Show.find(sickbeard.showList, show_id)

                # for when there is orphaned series in the database but not loaded into our show list
                if not show or show.paused:
                    continue

            except MultipleShowObjectsException:
                logger.log('ERROR: expected to find a single show matching {id}'.format(id=show_id))
                continue

            if show.airs and show.network:
                # This is how you assure it is always converted to local time
                show_air_time = parse_date_time(db_episode[b'airdate'], show.airs, show.network)
                end_time = show_air_time.astimezone(sb_timezone) + timedelta(minutes=try_int(show.runtime, 60))

                # filter out any episodes that haven't finished airing yet,
                if end_time > cur_time:
                    continue

            cur_ep = show.get_episode(db_episode[b'season'], db_episode[b'episode'])
            with cur_ep.lock:
                cur_ep.status = show.default_ep_status if cur_ep.season else common.SKIPPED
                logger.log('Setting status ({status}) for show airing today: {name} {special}'.format(
                    name=cur_ep.pretty_name(),
                    status=common.statusStrings[cur_ep.status],
                    special='(specials are not supported)' if not cur_ep.season else ''
                ))
                new_releases.append(cur_ep.get_sql())

        if new_releases:
            main_db_con = DBConnection()
            main_db_con.mass_action(new_releases)
        else:
            logger.log('No newly released episodes found ...')

        # queue episode for daily search
        sickbeard.searchQueueScheduler.action.add_item(
            DailySearchQueueItem()
        )

        self.amActive = False
Example #33
0
    def run(self, force=False):  # pylint: disable=unused-argument, too-many-locals, too-many-branches, too-many-statements
        logger.log('ShowUpdater for tvdb Api V3 starting')
        if self.amActive:
            return
        self.amActive = True
        if not self._gettoken():
            self.amActive = False
            logger.log('No token from tvdb so update not possible')
            return

        cache_db_con = db.DBConnection('cache.db')
        result = cache_db_con.select(
            'SELECT `time` FROM lastUpdate WHERE provider = ?', ['theTVDB'])
        last_update = int(result[0][0]) if result else 0
        network_timezones.update_network_dict()
        update_timestamp = int(time.time())
        updated_shows = []
        if last_update:
            logger.log('Last update: %s' % time.strftime(
                '%Y-%m-%d %H:%M:%S', time.localtime(last_update)))
            # We query tvdb for updates starting from the last update time from the cache until now with increments of 7 days
            for fromTime in range(
                    last_update, update_timestamp,
                    604800):  # increments of 604800 sec = 7*24*60*60
                try:
                    resp = self.session.get(self.update + str(fromTime),
                                            timeout=self.timeout)
                    if resp.ok:
                        TvdbData = json.loads(resp.text)
                        updated_shows.extend(
                            [d['id'] for d in TvdbData.get('data', [])])
                    else:
                        raise Exception(
                            'Failed to get update from tvdb. reason: %s' %
                            resp.reason)
                except Exception as error:
                    logger.log(str(error))
        else:
            logger.log(
                'No last update time from the cache, so we do a full update for all shows'
            )
        pi_list = []
        for cur_show in sickbeard.showList:
            if int(cur_show.indexer) in [INDEXER_TVRAGE]:
                logger.log(
                    'Indexer is no longer available for show [{0}] '.format(
                        cur_show.name), logger.WARNING)
                continue
            try:
                cur_show.nextEpisode()
                if sickbeard.indexerApi(cur_show.indexer).name == 'theTVDB':
                    # When last_update is not set from the cache or the show was in the tvdb updated list we update the show
                    if not last_update or cur_show.indexerid in updated_shows:
                        pi_list.append(
                            sickbeard.showQueueScheduler.action.update_show(
                                cur_show, True))
                    else:
                        pi_list.append(
                            sickbeard.showQueueScheduler.action.refresh_show(
                                cur_show, False))
            except (CantUpdateShowException,
                    CantRefreshShowException) as error:
                logger.log('Automatic update failed: {0}'.format(ex(error)))

        ui.ProgressIndicators.setIndicator(
            'dailyUpdate', ui.QueueProgressIndicator('Daily Update', pi_list))

        cache_db_con.action(
            'UPDATE lastUpdate SET `time` = ? WHERE provider=?',
            [str(update_timestamp), 'theTVDB'])

        self.amActive = False
Example #34
0
    def run(self, force=False):  # pylint: disable=unused-argument, too-many-locals, too-many-branches, too-many-statements

        self.amActive = True

        bad_indexer = [INDEXER_TVRAGE]
        update_datetime = datetime.datetime.now()
        update_date = update_datetime.date()

        # update_timestamp = calendar.timegm(update_datetime.timetuple())
        update_timestamp = time.mktime(update_datetime.timetuple())
        cache_db_con = db.DBConnection('cache.db')
        result = cache_db_con.select("SELECT `time` FROM lastUpdate WHERE provider = 'theTVDB'")
        if result:
            last_update = int(result[0]['time'])
        else:
            last_update = update_timestamp - 86400
            cache_db_con.action("INSERT INTO lastUpdate (provider,`time`) VALUES (?, ?)", ['theTVDB', last_update])

        # refresh network timezones
        network_timezones.update_network_dict()

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

        update_delta = update_timestamp - last_update

        if update_delta >= 691200:      # 8 days ( 7 days + 1 day of buffer time)
            update_file = 'updates_month.xml'
        elif update_delta >= 90000:     # 25 hours ( 1 day + 1 hour of buffer time)
            update_file = 'updates_week.xml'
        else:
            update_file = 'updates_day.xml'

        # url = 'http://thetvdb.com/api/Updates.php?type=series&time=%s' % last_update
        url = 'http://thetvdb.com/api/{0}/updates/{1}'.format(sickbeard.indexerApi(INDEXER_TVDB).api_params['apikey'], update_file)
        data = helpers.getURL(url, session=self.session, returns='text')
        if not data:
            logger.info(u'Could not get the recently updated show data from {indexer}. Retrying later. Url was: {logurl}', indexer=sickbeard.indexerApi(INDEXER_TVDB).name, logurl=url)
            self.amActive = False
            return

        updated_shows = []
        try:
            tree = ET.fromstring(data)
            for show in tree.findall("Series"):
                updated_shows.append(int(show.find('id').text))
        except SyntaxError:
            pass

        logger.info(u'Doing full update on all shows')

        pi_list = []
        for cur_show in sickbeard.showList:

            if cur_show.indexer in bad_indexer:
                logger.warning(u'Indexer is no longer available for show [ {show} ] ', show=cur_show.name)
            else:
                indexer_name = sickbeard.indexerApi(cur_show.indexer).name

            try:
                if indexer_name == 'theTVDB':
                    if cur_show.indexerid in updated_shows:
                        # If the cur_show is not 'paused' then add to the showQueueSchedular
                        if not cur_show.paused:
                            pi_list.append(sickbeard.showQueueScheduler.action.updateShow(cur_show, True))
                        else:
                            logger.info(u'Show update skipped, show: {show} is paused.', show=cur_show.name)
                else:
                    cur_show.next_episode()

                    if cur_show.should_update(update_date=update_date):
                        try:
                            pi_list.append(sickbeard.showQueueScheduler.action.updateShow(cur_show, True))
                        except CantUpdateShowException as e:
                            logger.debug(u'Unable to update show: {error}', error=e)
                    else:
                        logger.debug(
                            u'Not updating episodes for show {show} because the last or next episode is not within the grace period.', show = cur_show.name)
            except (CantUpdateShowException, CantRefreshShowException) as e:
                logger.error(u'Automatic update failed: {0}', error=e)

        ui.ProgressIndicators.setIndicator('dailyUpdate', ui.QueueProgressIndicator("Daily Update", pi_list))

        cache_db_con.action("UPDATE lastUpdate SET `time` = ? WHERE provider=?", [update_timestamp, 'theTVDB'])

        logger.info(u'Completed full update on all shows')

        self.amActive = False
Example #35
0
    def start(self):
        # Rename the main thread
        threading.currentThread().name = "MAIN"

        # initalize encoding defaults
        encodingInit()

        # 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.DATA_DIR = sickbeard.PROG_DIR
        sickbeard.MY_ARGS = sys.argv[1:]

        # Need console logging for SickBeard.py and SickBeard-console.exe
        self.consoleLogging = (not hasattr(
            sys, "frozen")) or (sickbeard.MY_NAME.lower().find('-console') > 0)

        # Do this before importing sickbeard, to prevent locked files and incorrect import
        oldtornado = ek(
            os.path.abspath,
            ek(os.path.join, ek(os.path.dirname, __file__), 'tornado'))
        if ek(os.path.isdir, oldtornado):
            ek(shutil.move, oldtornado, oldtornado + '_kill')
            ek(removetree, oldtornado + '_kill')

        try:
            opts, _ = getopt.getopt(sys.argv[1:], "hqdp::", [
                'help', 'quiet', 'nolaunch', 'daemon', 'pidfile=', 'port=',
                'datadir=', 'config=', 'noresize'
            ])
        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.consoleLogging = False

            # 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.noLaunch = True

            # Override default/configured port
            if o in ('-p', '--port'):
                try:
                    self.forcedPort = int(a)
                except ValueError:
                    sys.exit("Port: " + str(a) + " is not a number. Exiting.")

            # Run as a double forked daemon
            if o in ('-d', '--daemon'):
                self.runAsDaemon = True
                # When running as daemon disable consoleLogging and don't start browser
                self.consoleLogging = False
                self.noLaunch = True

                if sys.platform == 'win32' or sys.platform == 'darwin':
                    self.runAsDaemon = False

            # Write a pidfile if requested
            if o in ('--pidfile', ):
                self.CREATEPID = True
                self.PIDFILE = str(a)

                # If the pidfile already exists, sickbeard may still be running, so exit
                if ek(os.path.exists, self.PIDFILE):
                    sys.exit("PID file: " + self.PIDFILE +
                             " already exists. Exiting.")

            # Specify folder to load the config file from
            if o in ('--config', ):
                sickbeard.CONFIG_FILE = ek(os.path.abspath, a)

            # Specify folder to use as the data dir
            if o in ('--datadir', ):
                sickbeard.DATA_DIR = ek(os.path.abspath, a)

            # Prevent resizing of the banner/posters even if PIL is installed
            if o in ('--noresize', ):
                sickbeard.NO_RESIZE = True

        # The pidfile is only useful in daemon mode, make sure we can write the file properly
        if self.CREATEPID:
            if self.runAsDaemon:
                pid_dir = ek(os.path.dirname, self.PIDFILE)
                if not ek(os.access, pid_dir, os.F_OK):
                    sys.exit("PID dir: " + pid_dir +
                             " doesn't exist. Exiting.")
                if not ek(os.access, pid_dir, os.W_OK):
                    sys.exit("PID dir: " + pid_dir +
                             " must be writable (write permissions). Exiting.")

            else:
                if self.consoleLogging:
                    sys.stdout.write(
                        "Not running in daemon mode. PID file creation disabled.\n"
                    )

                self.CREATEPID = False

        # If they don't specify a config file then put it in the data dir
        if not sickbeard.CONFIG_FILE:
            sickbeard.CONFIG_FILE = ek(os.path.join, sickbeard.DATA_DIR,
                                       "config.ini")

        # 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 datadir '" +
                                 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("Datadir must be writeable '" +
                             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 '" + sickbeard.CONFIG_FILE +
                                 "' must be writeable.")
            elif not ek(os.access, ek(os.path.dirname, sickbeard.CONFIG_FILE),
                        os.W_OK):
                raise SystemExit("Config file root dir '" +
                                 ek(os.path.dirname, sickbeard.CONFIG_FILE) +
                                 "' must be writeable.")

        ek(os.chdir, sickbeard.DATA_DIR)

        # Check if we need to perform a restore first
        restoreDir = ek(os.path.join, sickbeard.DATA_DIR, 'restore')
        if ek(os.path.exists, restoreDir):
            success = self.restoreDB(restoreDir, sickbeard.DATA_DIR)
            if self.consoleLogging:
                sys.stdout.write("Restore: restoring DB and config.ini %s!\n" %
                                 ("FAILED", "SUCCESSFUL")[success])

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

        sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE)

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

        if self.runAsDaemon:
            self.daemonize()

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

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

        if self.forcedPort:
            logging.info("Forcing web server to port " + str(self.forcedPort))
            self.startPort = self.forcedPort
        else:
            self.startPort = 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.webhost = sickbeard.WEB_HOST
        else:
            if sickbeard.WEB_IPV6:
                self.webhost = '::'
            else:
                self.webhost = '0.0.0.0'

        # start tornado web server
        sickbeard.WEB_SERVER = SRWebServer({
            'port':
            int(self.startPort),
            'host':
            self.webhost,
            '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()

        # Clean up after update
        if sickbeard.GIT_NEWVER:
            toclean = ek(os.path.join, sickbeard.CACHE_DIR, 'mako')
            for root, dirs, files in ek(os.walk, toclean, topdown=False):
                for name in files:
                    ek(os.remove, ek(os.path.join, root, name))
                for name in dirs:
                    ek(os.rmdir, ek(os.path.join, root, name))
            sickbeard.GIT_NEWVER = False

        # Fire up all our threads
        logging.info("Starting SiCKRAGE:[{}] CONFIG:[{}]".format(
            sickbeard.BRANCH, sickbeard.CONFIG_FILE))
        sickbeard.start()

        # Build internal name cache
        name_cache.buildNameCache()

        # Prepopulate 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 (Disabled until we use api)
        # sickbeard.showUpdateScheduler.forceRun()

        # Launch browser
        if sickbeard.LAUNCH_BROWSER and not (self.noLaunch
                                             or self.runAsDaemon):
            sickbeard.launchBrowser(
                'https' if sickbeard.ENABLE_HTTPS else 'http', self.startPort,
                sickbeard.WEB_ROOT)

        # main loop
        while True:
            time.sleep(1)
Example #36
0
    def start(self):  # pylint: disable=too-many-branches,too-many-statements
        """
        Start SickRage
        """
        # 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.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'

        # pylint: disable=no-member
        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(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)  # pylint: disable=no-member
        except (AttributeError, LookupError):
            sys.exit(
                'Sorry, you MUST add the SickRage folder to the PYTHONPATH environment variable\n'
                'or find another way to force Python to use %s for string encoding.'
                % sickbeard.SYS_ENCODING)

        # Need console logging for SickBeard.py and SickBeard-console.exe
        self.console_logging = (not hasattr(
            sys, 'frozen')) or (sickbeard.MY_NAME.lower().find('-console') > 0)

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

        try:
            opts, _ = getopt.getopt(sys.argv[1:], 'hqdp::', [
                'help', 'quiet', 'nolaunch', 'daemon', 'pidfile=', 'port=',
                'datadir=', 'config=', 'noresize'
            ])
        except getopt.GetoptError:
            sys.exit(self.help_message())

        for option, value in opts:
            # Prints help message
            if option in ('-h', '--help'):
                sys.exit(self.help_message())

            # For now we'll just silence the logging
            if option in ('-q', '--quiet'):
                self.console_logging = False

            # Suppress launching web browser
            # Needed for OSes without default browser assigned
            # Prevent duplicate browser window when restarting in the app
            if option in ('--nolaunch', ):
                self.no_launch = True

            # Override default/configured port
            if option in ('-p', '--port'):
                try:
                    self.forced_port = int(value)
                except ValueError:
                    sys.exit(
                        'Port: {0} is not a number. Exiting.'.format(value))

            # Run as a double forked daemon
            if option 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 sys.platform == 'win32' or sys.platform == 'darwin':
                    self.run_as_daemon = False

            # Write a pid file if requested
            if option in ('--pidfile', ):
                self.create_pid = True
                self.pid_file = str(value)

                # If the pid file already exists, SickRage may still be running, so exit
                if ek(os.path.exists, self.pid_file):
                    sys.exit('PID file: {0} already exists. Exiting.'.format(
                        self.pid_file))

            # Specify folder to load the config file from
            if option in ('--config', ):
                sickbeard.CONFIG_FILE = ek(os.path.abspath, value)

            # Specify folder to use as the data directory
            if option in ('--datadir', ):
                sickbeard.DATA_DIR = ek(os.path.abspath, value)

            # Prevent resizing of the banner/posters even if PIL is installed
            if option in ('--noresize', ):
                sickbeard.NO_RESIZE = True

        # 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):
                    sys.exit(
                        '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

        # If they don't specify a config file then put it in the data dir
        if not sickbeard.CONFIG_FILE:
            sickbeard.CONFIG_FILE = ek(os.path.join, sickbeard.DATA_DIR,
                                       'config.ini')

        # 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)

        # 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 SickRage [{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)
Example #37
0
    def start(self):
        # do some preliminary stuff
        sickbeard.MY_FULLNAME = os.path.normpath(os.path.abspath(__file__))
        sickbeard.MY_NAME = os.path.basename(sickbeard.MY_FULLNAME)
        sickbeard.PROG_DIR = os.path.dirname(sickbeard.MY_FULLNAME)
        sickbeard.DATA_DIR = sickbeard.PROG_DIR
        sickbeard.MY_ARGS = sys.argv[1:]
        sickbeard.SYS_ENCODING = None

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

        # For OSes that are poorly configured I'll just randomly force UTF-8
        if not sickbeard.SYS_ENCODING or sickbeard.SYS_ENCODING in (
                'ANSI_X3.4-1968', 'US-ASCII', 'ASCII'):
            sickbeard.SYS_ENCODING = 'UTF-8'

        if not hasattr(sys, 'setdefaultencoding'):
            moves.reload_module(sys)

        if PY2:
            try:
                # On non-unicode builds this raises an AttributeError,
                # if encoding type is not valid it throws a LookupError
                # noinspection PyUnresolvedReferences
                sys.setdefaultencoding(sickbeard.SYS_ENCODING)
            except (BaseException, Exception):
                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.'
                    % sickbeard.SYS_ENCODING)
                sys.exit(1)

        # Need console logging for sickgear.py and SickBeard-console.exe
        self.console_logging = (not hasattr(
            sys, 'frozen')) or (0 < sickbeard.MY_NAME.lower().find('-console'))

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

        try:
            opts, args = getopt.getopt(sys.argv[1:], 'hfqdsp::', [
                'help', 'forceupdate', 'quiet', 'nolaunch', 'daemon',
                'systemd', 'pidfile=', 'port=', 'datadir=', 'config=',
                'noresize'
            ])
        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 'win32' == sys.platform:
                    self.run_as_daemon = False

            # Run as a systemd service
            if o in ('-s', '--systemd') and 'win32' != sys.platform:
                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, sickbeard 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', ):
                sickbeard.CONFIG_FILE = os.path.abspath(a)

            # Specify folder to use as the data dir
            if o in ('--datadir', ):
                sickbeard.DATA_DIR = os.path.abspath(a)

            # Prevent resizing of the banner/posters even if PIL is installed
            if o in ('--noresize', ):
                sickbeard.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 sickbeard.CONFIG_FILE:
            sickbeard.CONFIG_FILE = os.path.join(sickbeard.DATA_DIR,
                                                 'config.ini')

        # Make sure that we can create the data dir
        if not os.access(sickbeard.DATA_DIR, os.F_OK):
            try:
                os.makedirs(sickbeard.DATA_DIR, 0o744)
            except os.error:
                sys.exit(u'Unable to create data directory: %s Exiting.' %
                         sickbeard.DATA_DIR)

        # Make sure we can write to the data dir
        if not os.access(sickbeard.DATA_DIR, os.W_OK):
            sys.exit(
                u'Data directory: %s must be writable (write permissions). Exiting.'
                % sickbeard.DATA_DIR)

        # Make sure we can write to the config file
        if not os.access(sickbeard.CONFIG_FILE, os.W_OK):
            if os.path.isfile(sickbeard.CONFIG_FILE):
                sys.exit(
                    u'Config file: %s must be writeable (write permissions). Exiting.'
                    % sickbeard.CONFIG_FILE)
            elif not os.access(os.path.dirname(sickbeard.CONFIG_FILE),
                               os.W_OK):
                sys.exit(
                    u'Config file directory: %s must be writeable (write permissions). Exiting'
                    % os.path.dirname(sickbeard.CONFIG_FILE))
        os.chdir(sickbeard.DATA_DIR)

        if self.console_logging:
            print(u'Starting up SickGear from %s' % sickbeard.CONFIG_FILE)

        # Load the config and publish it to the sickbeard package
        if not os.path.isfile(sickbeard.CONFIG_FILE):
            print(u'Unable to find "%s", all settings will be default!' %
                  sickbeard.CONFIG_FILE)

        sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE)
        try:
            stack_size = int(sickbeard.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
        sickbeard.PID = os.getpid()

        # Initialize the config
        sickbeard.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 = 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 '0.0.0.0' != sickbeard.WEB_HOST:
            self.webhost = sickbeard.WEB_HOST
        else:
            self.webhost = (('0.0.0.0', '::')[sickbeard.WEB_IPV6],
                            '')[sickbeard.WEB_IPV64]

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

        # start web server
        try:
            # used to check if existing SG instances have been started
            sickbeard.helpers.wait_for_free_port(
                sickbeard.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()
            sickbeard.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 sickbeard.LAUNCH_BROWSER and not self.no_launch:
                logger.log(u'Launching browser and exiting', logger.ERROR)
                sickbeard.launch_browser(self.start_port)
            self.exit(1)

        # Launch browser
        if sickbeard.LAUNCH_BROWSER and not self.no_launch:
            sickbeard.launch_browser(self.start_port)

        # check all db versions
        for d, min_v, max_v, base_v, mo in [
            ('failed.db', sickbeard.failed_db.MIN_DB_VERSION,
             sickbeard.failed_db.MAX_DB_VERSION,
             sickbeard.failed_db.TEST_BASE_VERSION, 'FailedDb'),
            ('cache.db', sickbeard.cache_db.MIN_DB_VERSION,
             sickbeard.cache_db.MAX_DB_VERSION,
             sickbeard.cache_db.TEST_BASE_VERSION, 'CacheDb'),
            ('sickbeard.db', sickbeard.mainDB.MIN_DB_VERSION,
             sickbeard.mainDB.MAX_DB_VERSION,
             sickbeard.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:
                sickbeard.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)
                sickbeard.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:
                sickbeard.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)
                sickbeard.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:
                    sickbeard.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)
                    sickbeard.classes.loading_msg.set_msg_progress(
                        load_msg, 'Finished')

        # migrate the config if it needs it
        from sickbeard.config import ConfigMigrator
        migrator = ConfigMigrator(sickbeard.CFG)
        if migrator.config_version > migrator.expected_config_version:
            self.execute_rollback('ConfigFile',
                                  migrator.expected_config_version,
                                  'Downgrading config.ini')
            migrator = ConfigMigrator(sickbeard.CFG)
        migrator.migrate_config()

        # free memory
        global rollback_loaded
        rollback_loaded = None
        sickbeard.classes.loading_msg.message = 'Init SickGear'

        # Initialize the threads and other stuff
        sickbeard.initialize(console_logging=self.console_logging)

        # Check if we need to perform a restore first
        restore_dir = os.path.join(sickbeard.DATA_DIR, 'restore')
        if os.path.exists(restore_dir):
            sickbeard.classes.loading_msg.message = 'Restoring files'
            if self.restore(restore_dir, sickbeard.DATA_DIR):
                logger.log(u'Restore successful...')
            else:
                logger.log_error_and_exit(u'Restore FAILED!')

        # Build from the DB to start with
        sickbeard.classes.loading_msg.message = 'Loading shows from db'
        self.load_shows_from_db()

        # Fire up all our threads
        sickbeard.classes.loading_msg.message = 'Starting threads'
        sickbeard.start()

        # Build internal name cache
        sickbeard.classes.loading_msg.message = 'Build name cache'
        name_cache.buildNameCache()

        # refresh network timezones
        sickbeard.classes.loading_msg.message = 'Checking network timezones'
        network_timezones.update_network_dict()

        # load all ids from xem
        sickbeard.classes.loading_msg.message = 'Loading xem data'
        startup_background_tasks = threading.Thread(
            name='FETCH-XEMDATA',
            target=sickbeard.scene_exceptions.get_xem_ids)
        startup_background_tasks.start()

        sickbeard.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=sickbeard.history.history_snatched_proper_fix)
            history_snatched_proper_task.start()

        if not db.DBConnection().has_flag('kodi_nfo_default_removed'):
            sickbeard.metadata.kodi.remove_default_attr()

        if sickbeard.USE_FAILED_DOWNLOADS:
            failed_history.remove_old_history()

        # Start an update if we're supposed to
        if self.force_update or sickbeard.UPDATE_SHOWS_ON_START:
            sickbeard.classes.loading_msg.message = 'Starting a forced show update'
            sickbeard.showUpdateScheduler.action.run()

        sickbeard.classes.loading_msg.message = 'Switching to default web server'
        time.sleep(2)
        self.webserver.switch_handlers()

        # # Launch browser
        # if sickbeard.LAUNCH_BROWSER and not self.no_launch:
        #     sickbeard.launch_browser(self.start_port)

        # main loop
        while True:
            time.sleep(1)
Example #38
0
    def run(self, force=False):

        self.amActive = True

        try:
            update_datetime = datetime.datetime.now()
            update_date = update_datetime.date()

            # refresh network timezones
            network_timezones.update_network_dict()

            # update xem id lists
            sickbeard.scene_exceptions.get_xem_ids()

            # update scene exceptions
            sickbeard.scene_exceptions.retrieve_exceptions()

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

            # clear the data of unused providers
            sickbeard.helpers.clear_unused_providers()

            logger.log(u'Doing full update on all shows')

            # clean out cache directory, remove everything > 12 hours old
            sickbeard.helpers.clearCache()

            # select 10 'Ended' tv_shows updated more than 90 days ago and all shows not updated more then 180 days ago to include in this update
            stale_should_update = []
            stale_update_date = (update_date - datetime.timedelta(days=90)).toordinal()
            stale_update_date_max = (update_date - datetime.timedelta(days=180)).toordinal()

            # last_update_date <= 90 days, sorted ASC because dates are ordinal
            myDB = db.DBConnection()
            sql_results = myDB.mass_action([[
                'SELECT indexer_id FROM tv_shows WHERE last_update_indexer <= ? AND last_update_indexer >= ? ORDER BY last_update_indexer ASC LIMIT 10;',
                [stale_update_date, stale_update_date_max]], ['SELECT indexer_id FROM tv_shows WHERE last_update_indexer < ?;', [stale_update_date_max]]])

            for sql_result in sql_results:
                for cur_result in sql_result:
                    stale_should_update.append(int(cur_result['indexer_id']))

            # start update process
            piList = []
            for curShow in sickbeard.showList:

                try:
                    # get next episode airdate
                    curShow.nextEpisode()

                    # if should_update returns True (not 'Ended') or show is selected stale 'Ended' then update, otherwise just refresh
                    if curShow.should_update(update_date=update_date) or curShow.indexerid in stale_should_update:
                        curQueueItem = sickbeard.showQueueScheduler.action.updateShow(curShow, scheduled_update=True)  # @UndefinedVariable
                    else:
                        logger.log(
                            u'Not updating episodes for show ' + curShow.name + ' because it\'s marked as ended and last/next episode is not within the grace period.',
                            logger.DEBUG)
                        curQueueItem = sickbeard.showQueueScheduler.action.refreshShow(curShow, True, True)  # @UndefinedVariable

                    piList.append(curQueueItem)

                except (exceptions.CantUpdateException, exceptions.CantRefreshException) as e:
                    logger.log(u'Automatic update failed: ' + ex(e), logger.ERROR)

            ui.ProgressIndicators.setIndicator('dailyUpdate', ui.QueueProgressIndicator('Daily Update', piList))

            logger.log(u'Added all shows to show queue for full update')

        finally:
            self.amActive = False
Example #39
0
    def run(self, force=False):

        self.amActive = True

        try:
            update_datetime = datetime.datetime.now()
            update_date = update_datetime.date()

            # refresh network timezones
            try:
                network_timezones.update_network_dict()
            except Exception:
                logger.log('network timezone update error', logger.ERROR)
                logger.log(traceback.format_exc(), logger.ERROR)

            # refresh webdl types
            try:
                properFinder.load_webdl_types()
            except (StandardError, Exception):
                logger.log('error loading webdl_types', logger.DEBUG)

            # update xem id lists
            try:
                sickbeard.scene_exceptions.get_xem_ids()
            except Exception:
                logger.log('xem id list update error', logger.ERROR)
                logger.log(traceback.format_exc(), logger.ERROR)

            # update scene exceptions
            try:
                sickbeard.scene_exceptions.retrieve_exceptions()
            except Exception:
                logger.log('scene exceptions update error', logger.ERROR)
                logger.log(traceback.format_exc(), logger.ERROR)

            # sure, why not?
            if sickbeard.USE_FAILED_DOWNLOADS:
                try:
                    failed_history.remove_old_history()
                except Exception:
                    logger.log('Failed History cleanup error', logger.ERROR)
                    logger.log(traceback.format_exc(), logger.ERROR)

            # clear the data of unused providers
            try:
                sickbeard.helpers.clear_unused_providers()
            except Exception:
                logger.log('unused provider cleanup error', logger.ERROR)
                logger.log(traceback.format_exc(), logger.ERROR)

            # cleanup image cache
            try:
                sickbeard.helpers.cleanup_cache()
            except Exception:
                logger.log('image cache cleanup error', logger.ERROR)
                logger.log(traceback.format_exc(), logger.ERROR)

            # cleanup manual search history
            sickbeard.search_queue.remove_old_fifo(sickbeard.search_queue.MANUAL_SEARCH_HISTORY)

            # add missing mapped ids
            if not sickbeard.background_mapping_task.is_alive():
                logger.log(u'Updating the Indexer mappings')
                import threading
                try:
                    sickbeard.background_mapping_task = threading.Thread(
                        name='LOAD-MAPPINGS', target=sickbeard.indexermapper.load_mapped_ids, kwargs={'update': True})
                    sickbeard.background_mapping_task.start()
                except Exception:
                    logger.log('missing mapped ids update error', logger.ERROR)
                    logger.log(traceback.format_exc(), logger.ERROR)

            logger.log(u'Doing full update on all shows')

            # clean out cache directory, remove everything > 12 hours old
            try:
                sickbeard.helpers.clearCache()
            except Exception:
                logger.log('cache dir cleanup error', logger.ERROR)
                logger.log(traceback.format_exc(), logger.ERROR)

            # select 10 'Ended' tv_shows updated more than 90 days ago
            # and all shows not updated more then 180 days ago to include in this update
            stale_should_update = []
            stale_update_date = (update_date - datetime.timedelta(days=90)).toordinal()
            stale_update_date_max = (update_date - datetime.timedelta(days=180)).toordinal()

            # last_update_date <= 90 days, sorted ASC because dates are ordinal
            my_db = db.DBConnection()
            sql_results = my_db.mass_action([
                ['SELECT indexer_id FROM tv_shows WHERE last_update_indexer <= ? AND ' +
                 'last_update_indexer >= ? ORDER BY last_update_indexer ASC LIMIT 10;',
                 [stale_update_date, stale_update_date_max]],
                ['SELECT indexer_id FROM tv_shows WHERE last_update_indexer < ?;', [stale_update_date_max]]])

            for sql_result in sql_results:
                for cur_result in sql_result:
                    stale_should_update.append(int(cur_result['indexer_id']))

            # start update process
            pi_list = []
            for curShow in sickbeard.showList:

                try:
                    # get next episode airdate
                    curShow.nextEpisode()

                    # if should_update returns True (not 'Ended') or show is selected stale 'Ended' then update,
                    # otherwise just refresh
                    if curShow.should_update(update_date=update_date) or curShow.indexerid in stale_should_update:
                        cur_queue_item = sickbeard.showQueueScheduler.action.updateShow(curShow, scheduled_update=True)
                    else:
                        logger.log(
                            u'Not updating episodes for show ' + curShow.name + ' because it\'s marked as ended and ' +
                            'last/next episode is not within the grace period.', logger.DEBUG)
                        cur_queue_item = sickbeard.showQueueScheduler.action.refreshShow(curShow, True, True, force_image_cache=True)

                    pi_list.append(cur_queue_item)

                except (exceptions.CantUpdateException, exceptions.CantRefreshException) as e:
                    logger.log(u'Automatic update failed: ' + ex(e), logger.ERROR)

            ui.ProgressIndicators.setIndicator('dailyUpdate', ui.QueueProgressIndicator('Daily Update', pi_list))

            logger.log(u'Added all shows to show queue for full update')

        finally:
            self.amActive = False
Example #40
0
    def start(self):
        # do some preliminary stuff
        sickbeard.MY_FULLNAME = os.path.normpath(os.path.abspath(__file__))
        sickbeard.MY_NAME = os.path.basename(sickbeard.MY_FULLNAME)
        sickbeard.PROG_DIR = os.path.dirname(sickbeard.MY_FULLNAME)
        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'

        # pylint: disable=E1101
        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(sys)

        try:
            # pylint: disable=E1101
            # 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 Exception:
            sys.exit(
                "Sorry, you MUST add the SickRage folder to the PYTHONPATH environment variable\n"
                + "or find another way to force Python to use " +
                sickbeard.SYS_ENCODING + " for string encoding.")

        # Need console logging for SickBeard.py and SickBeard-console.exe
        self.consoleLogging = (not hasattr(
            sys, "frozen")) or (sickbeard.MY_NAME.lower().find('-console') > 0)

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

        try:
            opts, _ = getopt.getopt(sys.argv[1:], "hqdp::", [
                'help', 'quiet', 'nolaunch', 'daemon', 'pidfile=', 'port=',
                'datadir=', 'config=', 'noresize'
            ])
        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.consoleLogging = False

            # 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.noLaunch = True

            # Override default/configured port
            if o in ('-p', '--port'):
                try:
                    self.forcedPort = int(a)
                except ValueError:
                    sys.exit("Port: " + str(a) + " is not a number. Exiting.")

            # Run as a double forked daemon
            if o in ('-d', '--daemon'):
                self.runAsDaemon = True
                # When running as daemon disable consoleLogging and don't start browser
                self.consoleLogging = False
                self.noLaunch = True

                if sys.platform == 'win32' or sys.platform == 'darwin':
                    self.runAsDaemon = False

            # Write a pidfile if requested
            if o in ('--pidfile', ):
                self.CREATEPID = True
                self.PIDFILE = str(a)

                # If the pidfile already exists, sickbeard may still be running, so exit
                if os.path.exists(self.PIDFILE):
                    sys.exit("PID file: " + self.PIDFILE +
                             " already exists. Exiting.")

            # Specify folder to load the config file from
            if o in ('--config', ):
                sickbeard.CONFIG_FILE = os.path.abspath(a)

            # Specify folder to use as the data dir
            if o in ('--datadir', ):
                sickbeard.DATA_DIR = os.path.abspath(a)

            # Prevent resizing of the banner/posters even if PIL is installed
            if o in ('--noresize', ):
                sickbeard.NO_RESIZE = True

        # The pidfile is only useful in daemon mode, make sure we can write the file properly
        if self.CREATEPID:
            if self.runAsDaemon:
                pid_dir = os.path.dirname(self.PIDFILE)
                if not os.access(pid_dir, os.F_OK):
                    sys.exit("PID dir: " + pid_dir +
                             " doesn't exist. Exiting.")
                if not os.access(pid_dir, os.W_OK):
                    sys.exit("PID dir: " + pid_dir +
                             " must be writable (write permissions). Exiting.")

            else:
                if self.consoleLogging:
                    sys.stdout.write(
                        u"Not running in daemon mode. PID file creation disabled.\n"
                    )

                self.CREATEPID = False

        # If they don't specify a config file then put it in the data dir
        if not sickbeard.CONFIG_FILE:
            sickbeard.CONFIG_FILE = os.path.join(sickbeard.DATA_DIR,
                                                 "config.ini")

        # Make sure that we can create the data dir
        if not os.access(sickbeard.DATA_DIR, os.F_OK):
            try:
                os.makedirs(sickbeard.DATA_DIR, 0744)
            except os.error:
                raise SystemExit("Unable to create datadir '" +
                                 sickbeard.DATA_DIR + "'")

        # Make sure we can write to the data dir
        if not os.access(sickbeard.DATA_DIR, os.W_OK):
            raise SystemExit("Datadir must be writeable '" +
                             sickbeard.DATA_DIR + "'")

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

        os.chdir(sickbeard.DATA_DIR)

        # Check if we need to perform a restore first
        restoreDir = os.path.join(sickbeard.DATA_DIR, 'restore')
        if os.path.exists(restoreDir):
            success = self.restoreDB(restoreDir, sickbeard.DATA_DIR)
            if self.consoleLogging:
                sys.stdout.write(
                    u"Restore: restoring DB and config.ini %s!\n" %
                    ("FAILED", "SUCCESSFUL")[success])

        # Load the config and publish it to the sickbeard package
        if self.consoleLogging and not os.path.isfile(sickbeard.CONFIG_FILE):
            sys.stdout.write(u"Unable to find '" + sickbeard.CONFIG_FILE +
                             "' , all settings will be default!" + "\n")

        sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE)

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

        if self.runAsDaemon:
            self.daemonize()

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

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

        if self.forcedPort:
            logger.log(u"Forcing web server to port " + str(self.forcedPort))
            self.startPort = self.forcedPort
        else:
            self.startPort = 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.webhost = sickbeard.WEB_HOST
        else:
            if sickbeard.WEB_IPV6:
                self.webhost = '::'
            else:
                self.webhost = '0.0.0.0'

        # web server options
        self.web_options = {
            'port':
            int(self.startPort),
            'host':
            self.webhost,
            'data_root':
            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':
            os.path.join(sickbeard.PROG_DIR, sickbeard.HTTPS_CERT),
            'https_key':
            os.path.join(sickbeard.PROG_DIR, sickbeard.HTTPS_KEY),
        }

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

        if self.consoleLogging:
            print "Starting up SickRage " + sickbeard.BRANCH + " from " + sickbeard.CONFIG_FILE

        # Clean up after update
        if sickbeard.GIT_NEWVER:
            toclean = os.path.join(sickbeard.CACHE_DIR, 'mako')
            for root, dirs, files in os.walk(toclean, topdown=False):
                for name in files:
                    os.remove(os.path.join(root, name))
                for name in dirs:
                    os.rmdir(os.path.join(root, name))
            sickbeard.GIT_NEWVER = False

        # Fire up all our threads
        sickbeard.start()

        # Build internal name cache
        name_cache.buildNameCache()

        # Prepopulate 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 (Disabled until we use api)
        # sickbeard.showUpdateScheduler.forceRun()

        # Launch browser
        if sickbeard.LAUNCH_BROWSER and not (self.noLaunch
                                             or self.runAsDaemon):
            sickbeard.launchBrowser(
                'https' if sickbeard.ENABLE_HTTPS else 'http', self.startPort,
                sickbeard.WEB_ROOT)

        # main loop
        while True:
            time.sleep(1)
Example #41
0
    def run(self, force=False):

        self.amActive = True

        logger.log(u"Searching for new released episodes ...")

        if not network_timezones.network_dict:
            network_timezones.update_network_dict()

        if network_timezones.network_dict:
            curDate = (datetime.date.today() + datetime.timedelta(days=1)).toordinal()
        else:
            curDate = (datetime.date.today() + datetime.timedelta(days=2)).toordinal()

        curTime = datetime.datetime.now(network_timezones.sb_timezone)

        myDB = db.DBConnection()
        sqlResults = myDB.select("SELECT * FROM tv_episodes WHERE status = ? AND season > 0 AND airdate <= ?",
                                 [common.UNAIRED, curDate])

        sql_l = []
        show = None

        for sqlEp in sqlResults:
            try:
                if not show or int(sqlEp["showid"]) != show.indexerid:
                    show = helpers.findCertainShow(sickbeard.showList, int(sqlEp["showid"]))

                # for when there is orphaned series in the database but not loaded into our showlist
                if not show:
                    continue

            except exceptions.MultipleShowObjectsException:
                logger.log(u"ERROR: expected to find a single show matching " + str(sqlEp['showid']))
                continue

            try:
                end_time = network_timezones.parse_date_time(sqlEp['airdate'], show.airs,
                                                             show.network) + datetime.timedelta(
                    minutes=helpers.tryInt(show.runtime, 60))
                # filter out any episodes that haven't aried yet
                if end_time > curTime:
                    continue
            except:
                # if an error occured assume the episode hasn't aired yet
                continue

            UpdateWantedList = 0
            ep = show.getEpisode(int(sqlEp["season"]), int(sqlEp["episode"]))
            with ep.lock:
                if ep.show.paused:
                    ep.status = common.SKIPPED
                elif ep.season == 0:
                    logger.log(u"New episode " + ep.prettyName() + " airs today, setting status to SKIPPED because is a special season")
                    ep.status = common.SKIPPED
                elif sickbeard.TRAKT_USE_ROLLING_DOWNLOAD and sickbeard.USE_TRAKT:
                    ep.status = common.SKIPPED
                    UpdateWantedList = 1
                else:
                    logger.log(u"New episode %s airs today, setting to default episode status for this show: %s" % (ep.prettyName(), common.statusStrings[ep.show.default_ep_status]))
                    ep.status = ep.show.default_ep_status

                sql_l.append(ep.get_sql())
        else:
            logger.log(u"No new released episodes found ...")

        if len(sql_l) > 0:
            myDB = db.DBConnection()
            myDB.mass_action(sql_l)

        sickbeard.traktRollingScheduler.action.updateWantedList()

        # queue episode for daily search
        dailysearch_queue_item = sickbeard.search_queue.DailySearchQueueItem()
        sickbeard.searchQueueScheduler.action.add_item(dailysearch_queue_item)

        self.amActive = False
Example #42
0
    def start(self):
        # do some preliminary stuff
        sickbeard.MY_FULLNAME = os.path.normpath(os.path.abspath(__file__))
        sickbeard.MY_NAME = os.path.basename(sickbeard.MY_FULLNAME)
        sickbeard.PROG_DIR = os.path.dirname(sickbeard.MY_FULLNAME)
        sickbeard.DATA_DIR = sickbeard.PROG_DIR
        sickbeard.MY_ARGS = sys.argv[1:]
        sickbeard.SYS_ENCODING = None

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

        # For OSes that are poorly configured I'll just randomly force UTF-8
        if not sickbeard.SYS_ENCODING or sickbeard.SYS_ENCODING in ('ANSI_X3.4-1968', 'US-ASCII', 'ASCII'):
            sickbeard.SYS_ENCODING = 'UTF-8'

        if not hasattr(sys, 'setdefaultencoding'):
            reload(sys)

        try:
            # pylint: disable=E1101
            # 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:
            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.' % sickbeard.SYS_ENCODING
            sys.exit(1)

        # Need console logging for SickBeard.py and SickBeard-console.exe
        self.consoleLogging = (not hasattr(sys, 'frozen')) or (sickbeard.MY_NAME.lower().find('-console') > 0)

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

        try:
            opts, args = getopt.getopt(sys.argv[1:], 'hfqdp::',
                                       ['help', 'forceupdate', 'quiet', 'nolaunch', 'daemon', 'pidfile=', 'port=',
                                        'datadir=', 'config=', 'noresize'])  # @UnusedVariable
        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.consoleLogging = False

            # Should we update (from indexer) all shows in the DB right away?
            if o in ('-f', '--forceupdate'):
                self.forceUpdate = 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.noLaunch = True

            # Override default/configured port
            if o in ('-p', '--port'):
                try:
                    self.forcedPort = 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.runAsDaemon = True
                # When running as daemon disable consoleLogging and don't start browser
                self.consoleLogging = False
                self.noLaunch = True

                if sys.platform == 'win32':
                    self.runAsDaemon = False

            # Write a pidfile if requested
            if o in ('--pidfile',):
                self.CREATEPID = True
                self.PIDFILE = str(a)

                # If the pidfile already exists, sickbeard may still be running, so exit
                if os.path.exists(self.PIDFILE):
                    sys.exit('PID file: %s already exists. Exiting.' % self.PIDFILE)

            # Specify folder to load the config file from
            if o in ('--config',):
                sickbeard.CONFIG_FILE = os.path.abspath(a)

            # Specify folder to use as the data dir
            if o in ('--datadir',):
                sickbeard.DATA_DIR = os.path.abspath(a)

            # Prevent resizing of the banner/posters even if PIL is installed
            if o in ('--noresize',):
                sickbeard.NO_RESIZE = True

        # The pidfile is only useful in daemon mode, make sure we can write the file properly
        if self.CREATEPID:
            if self.runAsDaemon:
                pid_dir = os.path.dirname(self.PIDFILE)
                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.consoleLogging:
                    print u'Not running in daemon mode. PID file creation disabled'

                self.CREATEPID = False

        # If they don't specify a config file then put it in the data dir
        if not sickbeard.CONFIG_FILE:
            sickbeard.CONFIG_FILE = os.path.join(sickbeard.DATA_DIR, 'config.ini')

        # Make sure that we can create the data dir
        if not os.access(sickbeard.DATA_DIR, os.F_OK):
            try:
                os.makedirs(sickbeard.DATA_DIR, 0744)
            except os.error:
                sys.exit(u'Unable to create data directory: %s Exiting.' % sickbeard.DATA_DIR)

        # Make sure we can write to the data dir
        if not os.access(sickbeard.DATA_DIR, os.W_OK):
            sys.exit(u'Data directory: %s must be writable (write permissions). Exiting.' % sickbeard.DATA_DIR)

        # Make sure we can write to the config file
        if not os.access(sickbeard.CONFIG_FILE, os.W_OK):
            if os.path.isfile(sickbeard.CONFIG_FILE):
                sys.exit(u'Config file: %s must be writeable (write permissions). Exiting.' % sickbeard.CONFIG_FILE)
            elif not os.access(os.path.dirname(sickbeard.CONFIG_FILE), os.W_OK):
                sys.exit(u'Config file directory: %s must be writeable (write permissions). Exiting'
                         % os.path.dirname(sickbeard.CONFIG_FILE))
        os.chdir(sickbeard.DATA_DIR)

        if self.consoleLogging:
            print u'Starting up SickGear from %s' % sickbeard.CONFIG_FILE

        # Load the config and publish it to the sickbeard package
        if not os.path.isfile(sickbeard.CONFIG_FILE):
            print u'Unable to find "%s", all settings will be default!' % sickbeard.CONFIG_FILE

        sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE)

        CUR_DB_VERSION = db.DBConnection().checkDBVersion()

        if CUR_DB_VERSION > 0:
            if CUR_DB_VERSION < MIN_DB_VERSION:
                print u'Your database version (%s) is too old to migrate from with this version of SickGear' \
                      % CUR_DB_VERSION
                sys.exit(u'Upgrade using a previous version of SG first, or start with no database file to begin fresh')
            if CUR_DB_VERSION > MAX_DB_VERSION:
                print u'Your database version (%s) has been incremented past what this version of SickGear supports' \
                      % CUR_DB_VERSION
                sys.exit(
                    u'If you have used other forks of SG, your database may be unusable due to their modifications')

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

        if self.runAsDaemon:
            self.daemonize()

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

        if self.forcedPort:
            logger.log(u'Forcing web server to port %s' % self.forcedPort)
            self.startPort = self.forcedPort
        else:
            self.startPort = 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.webhost = sickbeard.WEB_HOST
        else:
            if sickbeard.WEB_IPV6:
                self.webhost = '::'
            else:
                self.webhost = '0.0.0.0'

        # web server options
        self.web_options = {
            'port': int(self.startPort),
            'host': self.webhost,
            'data_root': 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': os.path.join(sickbeard.PROG_DIR, sickbeard.HTTPS_CERT),
            'https_key': os.path.join(sickbeard.PROG_DIR, sickbeard.HTTPS_KEY),
        }

        # start web server
        try:
            # used to check if existing SG instances have been started
            sickbeard.helpers.wait_for_free_port(self.web_options['host'], self.web_options['port'])

            self.webserver = WebServer(self.web_options)
            self.webserver.start()
        except Exception:
            logger.log(u'Unable to start web server, is something else running on port %d?' % self.startPort,
                       logger.ERROR)
            if sickbeard.LAUNCH_BROWSER and not self.runAsDaemon:
                logger.log(u'Launching browser and exiting', logger.ERROR)
                sickbeard.launchBrowser(self.startPort)
            os._exit(1)

        # Check if we need to perform a restore first
        restoreDir = os.path.join(sickbeard.DATA_DIR, 'restore')
        if os.path.exists(restoreDir):
            if self.restore(restoreDir, sickbeard.DATA_DIR):
                logger.log(u'Restore successful...')
            else:
                logger.log_error_and_exit(u'Restore FAILED!')

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

        # Fire up all our threads
        sickbeard.start()

        # Build internal name cache
        name_cache.buildNameCache()

        # refresh network timezones
        network_timezones.update_network_dict()

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

        # Start an update if we're supposed to
        if self.forceUpdate or sickbeard.UPDATE_SHOWS_ON_START:
            sickbeard.showUpdateScheduler.action.run(force=True)  # @UndefinedVariable

        # Launch browser
        if sickbeard.LAUNCH_BROWSER and not (self.noLaunch or self.runAsDaemon):
            sickbeard.launchBrowser(self.startPort)

        # main loop
        while True:
            time.sleep(1)
Example #43
0
    def run(self, force=False):
        """
        Runs the daily searcher, queuing selected episodes for search

        :param force: Force search
        """
        if self.amActive:
            return

        self.amActive = True

        logger.log(u"Searching for new released episodes ...")

        if not network_timezones.network_dict:
            network_timezones.update_network_dict()

        if network_timezones.network_dict:
            curDate = (datetime.date.today() + datetime.timedelta(days=1)).toordinal()
        else:
            curDate = (datetime.date.today() + datetime.timedelta(days=2)).toordinal()

        curTime = datetime.datetime.now(network_timezones.sb_timezone)

        myDB = db.DBConnection()
        sqlResults = myDB.select("SELECT * FROM tv_episodes WHERE status = ? AND season > 0 AND (airdate <= ? and airdate > 1)",
                                 [common.UNAIRED, curDate])

        sql_l = []
        show = None

        for sqlEp in sqlResults:
            try:
                if not show or int(sqlEp["showid"]) != show.indexerid:
                    show = helpers.findCertainShow(sickbeard.showList, int(sqlEp["showid"]))

                # for when there is orphaned series in the database but not loaded into our showlist
                if not show or show.paused:
                    continue

            except MultipleShowObjectsException:
                logger.log(u"ERROR: expected to find a single show matching " + str(sqlEp['showid']))
                continue

            if show.airs and show.network:
                # This is how you assure it is always converted to local time
                air_time = network_timezones.parse_date_time(sqlEp['airdate'], show.airs, show.network).astimezone(network_timezones.sb_timezone)

                # filter out any episodes that haven't started airing yet,
                # but set them to the default status while they are airing
                # so they are snatched faster
                if air_time > curTime:
                    continue

            ep = show.getEpisode(int(sqlEp["season"]), int(sqlEp["episode"]))
            with ep.lock:
                if ep.season == 0:
                    logger.log(u"New episode " + ep.prettyName() + " airs today, setting status to SKIPPED because is a special season")
                    ep.status = common.SKIPPED
                else:
                    logger.log(u"New episode %s airs today, setting to default episode status for this show: %s" % (ep.prettyName(), common.statusStrings[ep.show.default_ep_status]))
                    ep.status = ep.show.default_ep_status

                sql_l.append(ep.get_sql())

        if len(sql_l) > 0:
            myDB = db.DBConnection()
            myDB.mass_action(sql_l)
        else:
            logger.log(u"No new released episodes found ...")

        # queue episode for daily search
        dailysearch_queue_item = sickbeard.search_queue.DailySearchQueueItem()
        sickbeard.searchQueueScheduler.action.add_item(dailysearch_queue_item)

        self.amActive = False
Example #44
0
    def run(self, force=False):

        self.amActive = True

        try:
            update_datetime = datetime.datetime.now()
            update_date = update_datetime.date()

            # refresh network timezones
            network_timezones.update_network_dict()

            # update xem id lists
            sickbeard.scene_exceptions.get_xem_ids()

            # update scene exceptions
            sickbeard.scene_exceptions.retrieve_exceptions()

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

            # clear the data of unused providers
            sickbeard.helpers.clear_unused_providers()

            logger.log(u'Doing full update on all shows')

            # clean out cache directory, remove everything > 12 hours old
            sickbeard.helpers.clearCache()

            # select 10 'Ended' tv_shows updated more than 90 days ago and all shows not updated more then 180 days ago to include in this update
            stale_should_update = []
            stale_update_date = (update_date -
                                 datetime.timedelta(days=90)).toordinal()
            stale_update_date_max = (update_date -
                                     datetime.timedelta(days=180)).toordinal()

            # last_update_date <= 90 days, sorted ASC because dates are ordinal
            myDB = db.DBConnection()
            sql_results = myDB.mass_action([
                [
                    'SELECT indexer_id FROM tv_shows WHERE last_update_indexer <= ? AND last_update_indexer >= ? ORDER BY last_update_indexer ASC LIMIT 10;',
                    [stale_update_date, stale_update_date_max]
                ],
                [
                    'SELECT indexer_id FROM tv_shows WHERE last_update_indexer < ?;',
                    [stale_update_date_max]
                ]
            ])

            for sql_result in sql_results:
                for cur_result in sql_result:
                    stale_should_update.append(int(cur_result['indexer_id']))

            # start update process
            piList = []
            for curShow in sickbeard.showList:

                try:
                    # get next episode airdate
                    curShow.nextEpisode()

                    # if should_update returns True (not 'Ended') or show is selected stale 'Ended' then update, otherwise just refresh
                    if curShow.should_update(
                            update_date=update_date
                    ) or curShow.indexerid in stale_should_update:
                        curQueueItem = sickbeard.showQueueScheduler.action.updateShow(
                            curShow,
                            scheduled_update=True)  # @UndefinedVariable
                    else:
                        logger.log(
                            u'Not updating episodes for show ' + curShow.name +
                            ' because it\'s marked as ended and last/next episode is not within the grace period.',
                            logger.DEBUG)
                        curQueueItem = sickbeard.showQueueScheduler.action.refreshShow(
                            curShow, True, True)  # @UndefinedVariable

                    piList.append(curQueueItem)

                except (exceptions.CantUpdateException,
                        exceptions.CantRefreshException) as e:
                    logger.log(u'Automatic update failed: ' + ex(e),
                               logger.ERROR)

            ui.ProgressIndicators.setIndicator(
                'dailyUpdate',
                ui.QueueProgressIndicator('Daily Update', piList))

            logger.log(u'Added all shows to show queue for full update')

        finally:
            self.amActive = False
Example #45
0
    def run(self, force=False):

        self.amActive = True

        logger.log(u"Searching for new released episodes ...")

        if not network_timezones.network_dict:
            network_timezones.update_network_dict()

        if network_timezones.network_dict:
            curDate = (datetime.date.today() +
                       datetime.timedelta(days=1)).toordinal()
        else:
            curDate = (datetime.date.today() +
                       datetime.timedelta(days=2)).toordinal()

        curTime = datetime.datetime.now(network_timezones.sb_timezone)

        myDB = db.DBConnection()
        sqlResults = myDB.select(
            "SELECT * FROM tv_episodes WHERE status = ? AND season > 0 AND airdate <= ?",
            [common.UNAIRED, curDate])

        sql_l = []
        show = None

        for sqlEp in sqlResults:
            try:
                if not show or int(sqlEp["showid"]) != show.indexerid:
                    show = helpers.findCertainShow(sickbeard.showList,
                                                   int(sqlEp["showid"]))

                # for when there is orphaned series in the database but not loaded into our showlist
                if not show:
                    continue

            except exceptions.MultipleShowObjectsException:
                logger.log(u"ERROR: expected to find a single show matching " +
                           str(sqlEp['showid']))
                continue

            try:
                end_time = network_timezones.parse_date_time(
                    sqlEp['airdate'], show.airs,
                    show.network) + datetime.timedelta(
                        minutes=helpers.tryInt(show.runtime, 60))
                # filter out any episodes that haven't aried yet
                if end_time > curTime:
                    continue
            except:
                # if an error occured assume the episode hasn't aired yet
                continue

            ep = show.getEpisode(int(sqlEp["season"]), int(sqlEp["episode"]))
            with ep.lock:
                if ep.show.paused:
                    ep.status = common.SKIPPED
                else:
                    myDB = db.DBConnection()
                    sql_selection = "SELECT show_name, indexer_id, season, episode, paused FROM (SELECT * FROM tv_shows s,tv_episodes e WHERE s.indexer_id = e.showid) T1 WHERE T1.paused = 0 and T1.episode_id IN (SELECT T2.episode_id FROM tv_episodes T2 WHERE T2.showid = T1.indexer_id and T2.status in (?) ORDER BY T2.season,T2.episode LIMIT 1) and airdate is not null and indexer_id = ? ORDER BY T1.show_name,season,episode"
                    results = myDB.select(sql_selection,
                                          [common.SKIPPED, sqlEp["showid"]])
                    if not sickbeard.TRAKT_USE_ROLLING_DOWNLOAD:
                        if ep.season == 0:
                            logger.log(
                                u"New episode " + ep.prettyName() +
                                " airs today, setting status to SKIPPED, due to trakt integration"
                            )
                            ep.status = common.SKIPPED
                        else:
                            logger.log(u"New episode " + ep.prettyName() +
                                       " airs today, setting status to WANTED")
                            ep.status = common.WANTED
                    else:
                        sn_sk = results[0]["season"]
                        ep_sk = results[0]["episode"]
                        if (int(sn_sk) * 100 +
                                int(ep_sk)) < (int(sqlEp["season"]) * 100 +
                                               int(sqlEp["episode"])):
                            logger.log(
                                u"New episode " + ep.prettyName() +
                                " airs today, setting status to SKIPPED, due to trakt integration"
                            )
                            ep.status = common.SKIPPED
                        else:
                            if ep.season == 0:
                                logger.log(
                                    u"New episode " + ep.prettyName() +
                                    " airs today, setting status to SKIPPED, due to trakt integration"
                                )
                                ep.status = common.SKIPPED
                            else:
                                logger.log(
                                    u"New episode " + ep.prettyName() +
                                    " airs today, setting status to WANTED")
                                ep.status = common.WANTED

                sql_l.append(ep.get_sql())
        else:
            logger.log(u"No new released episodes found ...")

        if len(sql_l) > 0:
            myDB = db.DBConnection()
            myDB.mass_action(sql_l)

        # queue episode for daily search
        dailysearch_queue_item = sickbeard.search_queue.DailySearchQueueItem()
        sickbeard.searchQueueScheduler.action.add_item(dailysearch_queue_item)

        self.amActive = False
Example #46
0
    def run(self, force=False):  # pylint: disable=unused-argument, too-many-locals, too-many-branches, too-many-statements

        self.amActive = True

        bad_indexer = [INDEXER_TVRAGE]
        update_datetime = datetime.datetime.now()
        update_date = update_datetime.date()

        # update_timestamp = calendar.timegm(update_datetime.timetuple())
        update_timestamp = time.mktime(update_datetime.timetuple())
        my_db = db.DBConnection('cache.db')
        result = my_db.select("SELECT `time` FROM lastUpdate WHERE provider = 'theTVDB'")
        if result:
            last_update = int(result[0]['time'])
        else:
            last_update = update_timestamp - 86400
            my_db.action("INSERT INTO lastUpdate (provider,`time`) VALUES (?, ?)", ['theTVDB', last_update])

        # refresh network timezones
        network_timezones.update_network_dict()

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

        update_delta = update_timestamp - last_update

        if update_delta >= 691200:      # 8 days ( 7 days + 1 day of buffer time)
            update_file = 'updates_month.xml'
        elif update_delta >= 90000:     # 25 hours ( 1 day + 1 hour of buffer time)
            update_file = 'updates_week.xml'
        else:
            update_file = 'updates_day.xml'

        # url = 'http://thetvdb.com/api/Updates.php?type=series&time=%s' % last_update
        url = 'http://thetvdb.com/api/%s/updates/%s' % (sickbeard.indexerApi(INDEXER_TVDB).api_params['apikey'], update_file)
        data = helpers.getURL(url, session=self.session)
        if not data:
            logger.log(u"Could not get the recently updated show data from %s. Retrying later. Url was: %s" % (sickbeard.indexerApi(INDEXER_TVDB).name, url))
            self.amActive = False
            return

        updated_shows = []
        try:
            tree = ET.fromstring(data)
            for show in tree.findall("Series"):
                updated_shows.append(int(show.find('id').text))
        except SyntaxError:
            pass

        logger.log(u"Doing full update on all shows")

        pi_list = []
        for cur_show in sickbeard.showList:

            if cur_show.indexer in bad_indexer:
                logger.log(u"Indexer is no longer available for show [ %s ] " % cur_show.name, logger.WARNING)
            else:
                indexer_name = sickbeard.indexerApi(cur_show.indexer).name

            try:
                if indexer_name == 'theTVDB':
                    if cur_show.indexerid in updated_shows:
                        pi_list.append(sickbeard.showQueueScheduler.action.updateShow(cur_show, True))
                    # else:
                    #     pi_list.append(sickbeard.showQueueScheduler.action.refreshShow(cur_show, True))
                else:
                    cur_show.nextEpisode()

                    if cur_show.should_update(update_date=update_date):
                        try:
                            pi_list.append(sickbeard.showQueueScheduler.action.updateShow(cur_show, True))
                        except CantUpdateShowException as e:
                            logger.log(u"Unable to update show: {0}".format(str(e)), logger.DEBUG)
                    else:
                        logger.log(
                            u"Not updating episodes for show " + cur_show.name + " because it's last/next episode is not within the grace period.",
                            logger.DEBUG)
                        # pi_list.append(sickbeard.showQueueScheduler.action.refreshShow(cur_show, True))
            except (CantUpdateShowException, CantRefreshShowException) as e:
                logger.log(u"Automatic update failed: " + ex(e), logger.ERROR)

        ui.ProgressIndicators.setIndicator('dailyUpdate', ui.QueueProgressIndicator("Daily Update", pi_list))

        my_db.action("UPDATE lastUpdate SET `time` = ? WHERE provider=?", [update_timestamp, 'theTVDB'])

        logger.log(u"Completed full update on all shows")

        self.amActive = False
Example #47
0
    def run(self, force=False):  # pylint: disable=unused-argument, too-many-locals, too-many-branches, too-many-statements

        self.amActive = True

        bad_indexer = [INDEXER_TVRAGE]
        update_datetime = datetime.datetime.now()
        update_date = update_datetime.date()

        # update_timestamp = calendar.timegm(update_datetime.timetuple())
        update_timestamp = time.mktime(update_datetime.timetuple())
        cache_db_con = db.DBConnection('cache.db')
        result = cache_db_con.select(
            "SELECT `time` FROM lastUpdate WHERE provider = 'theTVDB'")
        if result:
            last_update = int(result[0]['time'])
        else:
            last_update = update_timestamp - 86400
            cache_db_con.action(
                "INSERT INTO lastUpdate (provider,`time`) VALUES (?, ?)",
                ['theTVDB', last_update])

        # refresh network timezones
        network_timezones.update_network_dict()

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

        update_delta = update_timestamp - last_update

        if update_delta >= 691200:  # 8 days ( 7 days + 1 day of buffer time)
            update_file = 'updates_month.xml'
        elif update_delta >= 90000:  # 25 hours ( 1 day + 1 hour of buffer time)
            update_file = 'updates_week.xml'
        else:
            update_file = 'updates_day.xml'

        # url = 'http://thetvdb.com/api/Updates.php?type=series&time=%s' % last_update
        url = 'http://thetvdb.com/api/%s/updates/%s' % (sickbeard.indexerApi(
            INDEXER_TVDB).api_params['apikey'], update_file)
        data = helpers.getURL(url, session=self.session)
        if not data:
            logger.log(
                u"Could not get the recently updated show data from %s. Retrying later. Url was: %s"
                % (sickbeard.indexerApi(INDEXER_TVDB).name, url))
            self.amActive = False
            return

        updated_shows = []
        try:
            tree = ET.fromstring(data)
            for show in tree.findall("Series"):
                updated_shows.append(int(show.find('id').text))
        except SyntaxError:
            pass

        logger.log(u"Doing full update on all shows")

        pi_list = []
        for cur_show in sickbeard.showList:

            if cur_show.indexer in bad_indexer:
                logger.log(
                    u"Indexer is no longer available for show [ %s ] " %
                    cur_show.name, logger.WARNING)
            else:
                indexer_name = sickbeard.indexerApi(cur_show.indexer).name

            try:
                if indexer_name == 'theTVDB':
                    if cur_show.indexerid in updated_shows:
                        pi_list.append(
                            sickbeard.showQueueScheduler.action.updateShow(
                                cur_show, True))
                    # else:
                    #     pi_list.append(sickbeard.showQueueScheduler.action.refreshShow(cur_show, True))
                else:
                    cur_show.nextEpisode()

                    if cur_show.should_update(update_date=update_date):
                        try:
                            pi_list.append(
                                sickbeard.showQueueScheduler.action.updateShow(
                                    cur_show, True))
                        except CantUpdateShowException as e:
                            logger.log(
                                u"Unable to update show: {0}".format(str(e)),
                                logger.DEBUG)
                    else:
                        logger.log(
                            u"Not updating episodes for show " +
                            cur_show.name +
                            " because it's last/next episode is not within the grace period.",
                            logger.DEBUG)
                        # pi_list.append(sickbeard.showQueueScheduler.action.refreshShow(cur_show, True))
            except (CantUpdateShowException, CantRefreshShowException) as e:
                logger.log(u"Automatic update failed: " + ex(e), logger.ERROR)

        ui.ProgressIndicators.setIndicator(
            'dailyUpdate', ui.QueueProgressIndicator("Daily Update", pi_list))

        cache_db_con.action(
            "UPDATE lastUpdate SET `time` = ? WHERE provider=?",
            [update_timestamp, 'theTVDB'])

        logger.log(u"Completed full update on all shows")

        self.amActive = False
Example #48
0
    def start(self):  # pylint: disable=too-many-branches,too-many-statements
        """
        Start SickRage
        """
        # 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.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"

        # pylint: disable=no-member
        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(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)  # pylint: disable=no-member
        except (AttributeError, LookupError):
            sys.exit(
                "Sorry, you MUST add the SickRage folder to the PYTHONPATH environment variable\n"
                "or find another way to force Python to use %s for string encoding." % sickbeard.SYS_ENCODING
            )

        # Need console logging for SickBeard.py and SickBeard-console.exe
        self.console_logging = (not hasattr(sys, "frozen")) or (sickbeard.MY_NAME.lower().find("-console") > 0)

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

        try:
            opts, _ = getopt.getopt(
                sys.argv[1:],
                "hqdp::",
                ["help", "quiet", "nolaunch", "daemon", "pidfile=", "port=", "datadir=", "config=", "noresize"],
            )
        except getopt.GetoptError:
            sys.exit(self.help_message())

        for option, value in opts:
            # Prints help message
            if option in ("-h", "--help"):
                sys.exit(self.help_message())

            # For now we'll just silence the logging
            if option in ("-q", "--quiet"):
                self.console_logging = False

            # Suppress launching web browser
            # Needed for OSes without default browser assigned
            # Prevent duplicate browser window when restarting in the app
            if option in ("--nolaunch",):
                self.no_launch = True

            # Override default/configured port
            if option in ("-p", "--port"):
                try:
                    self.forced_port = int(value)
                except ValueError:
                    sys.exit("Port: %s is not a number. Exiting." % value)

            # Run as a double forked daemon
            if option 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 sys.platform == "win32" or sys.platform == "darwin":
                    self.run_as_daemon = False

            # Write a pid file if requested
            if option in ("--pidfile",):
                self.create_pid = True
                self.pid_file = str(value)

                # If the pid file already exists, SickRage may still be running, so exit
                if ek(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 option in ("--config",):
                sickbeard.CONFIG_FILE = ek(os.path.abspath, value)

            # Specify folder to use as the data directory
            if option in ("--datadir",):
                sickbeard.DATA_DIR = ek(os.path.abspath, value)

            # Prevent resizing of the banner/posters even if PIL is installed
            if option in ("--noresize",):
                sickbeard.NO_RESIZE = True

        # 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: %s doesn't exist. Exiting." % pid_dir)
                if not ek(os.access, pid_dir, os.W_OK):
                    sys.exit("PID dir: %s must be writable (write permissions). Exiting." % pid_dir)

            else:
                if self.console_logging:
                    sys.stdout.write("Not running in daemon mode. PID file creation disabled.\n")

                self.create_pid = False

        # If they don't specify a config file then put it in the data dir
        if not sickbeard.CONFIG_FILE:
            sickbeard.CONFIG_FILE = ek(os.path.join, sickbeard.DATA_DIR, "config.ini")

        # 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: %s" % 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: %s" % 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: %s" % 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: %s" % 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 %s!\n" % ("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 %s, all settings will be default!\n" % sickbeard.CONFIG_FILE)

        sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE)

        # 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 SickRage [%s] from '%s'" % (sickbeard.BRANCH, sickbeard.CONFIG_FILE))

        self.clear_cache()

        if self.forced_port:
            logger.log("Forcing web server to port %s" % 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 (Disabled until we use api)
        # 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)
Example #49
0
    def start(self):  # pylint: disable=too-many-branches,too-many-statements
        """
        Start SickRage
        """
        # 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.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'

        # pylint: disable=no-member
        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(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)  # pylint: disable=no-member
        except (AttributeError, LookupError):
            sys.exit('Sorry, you MUST add the SickRage folder to the PYTHONPATH environment variable\n'
                     'or find another way to force Python to use %s for string encoding.' % sickbeard.SYS_ENCODING)

        # Need console logging for SickBeard.py and SickBeard-console.exe
        self.console_logging = (not hasattr(sys, 'frozen')) or (sickbeard.MY_NAME.lower().find('-console') > 0)

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

        try:
            opts, _ = getopt.getopt(
                sys.argv[1:], 'hqdp::',
                ['help', 'quiet', 'nolaunch', 'daemon', 'pidfile=', 'port=', 'datadir=', 'config=', 'noresize']
            )
        except getopt.GetoptError:
            sys.exit(self.help_message())

        for option, value in opts:
            # Prints help message
            if option in ('-h', '--help'):
                sys.exit(self.help_message())

            # For now we'll just silence the logging
            if option in ('-q', '--quiet'):
                self.console_logging = False

            # Suppress launching web browser
            # Needed for OSes without default browser assigned
            # Prevent duplicate browser window when restarting in the app
            if option in ('--nolaunch',):
                self.no_launch = True

            # Override default/configured port
            if option in ('-p', '--port'):
                try:
                    self.forced_port = int(value)
                except ValueError:
                    sys.exit('Port: {0} is not a number. Exiting.'.format(value))

            # Run as a double forked daemon
            if option 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 sys.platform == 'win32' or sys.platform == 'darwin':
                    self.run_as_daemon = False

            # Write a pid file if requested
            if option in ('--pidfile',):
                self.create_pid = True
                self.pid_file = str(value)

                # If the pid file already exists, SickRage may still be running, so exit
                if ek(os.path.exists, self.pid_file):
                    sys.exit('PID file: {0} already exists. Exiting.'.format(self.pid_file))

            # Specify folder to load the config file from
            if option in ('--config',):
                sickbeard.CONFIG_FILE = ek(os.path.abspath, value)

            # Specify folder to use as the data directory
            if option in ('--datadir',):
                sickbeard.DATA_DIR = ek(os.path.abspath, value)

            # Prevent resizing of the banner/posters even if PIL is installed
            if option in ('--noresize',):
                sickbeard.NO_RESIZE = True

        # 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):
                    sys.exit('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

        # If they don't specify a config file then put it in the data dir
        if not sickbeard.CONFIG_FILE:
            sickbeard.CONFIG_FILE = ek(os.path.join, sickbeard.DATA_DIR, 'config.ini')

        # 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)

        # 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 SickRage [{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)
Example #50
0
    def run(self, force=False):

        self.amActive = True

        try:
            update_datetime = datetime.datetime.now()
            update_date = update_datetime.date()

            # refresh network timezones
            try:
                network_timezones.update_network_dict()
            except Exception:
                logger.log('network timezone update error', logger.ERROR)
                logger.log(traceback.format_exc(), logger.ERROR)

            # refresh webdl types
            try:
                properFinder.load_webdl_types()
            except (StandardError, Exception):
                logger.log('error loading webdl_types', logger.DEBUG)

            # update xem id lists
            try:
                sickbeard.scene_exceptions.get_xem_ids()
            except Exception:
                logger.log('xem id list update error', logger.ERROR)
                logger.log(traceback.format_exc(), logger.ERROR)

            # update scene exceptions
            try:
                sickbeard.scene_exceptions.retrieve_exceptions()
            except Exception:
                logger.log('scene exceptions update error', logger.ERROR)
                logger.log(traceback.format_exc(), logger.ERROR)

            # sure, why not?
            if sickbeard.USE_FAILED_DOWNLOADS:
                try:
                    failed_history.remove_old_history()
                except Exception:
                    logger.log('Failed History cleanup error', logger.ERROR)
                    logger.log(traceback.format_exc(), logger.ERROR)

            # clear the data of unused providers
            try:
                sickbeard.helpers.clear_unused_providers()
            except Exception:
                logger.log('unused provider cleanup error', logger.ERROR)
                logger.log(traceback.format_exc(), logger.ERROR)

            # cleanup image cache
            try:
                sickbeard.helpers.cleanup_cache()
            except Exception:
                logger.log('image cache cleanup error', logger.ERROR)
                logger.log(traceback.format_exc(), logger.ERROR)

            # cleanup manual search history
            sickbeard.search_queue.remove_old_fifo(sickbeard.search_queue.MANUAL_SEARCH_HISTORY)

            # add missing mapped ids
            if not sickbeard.background_mapping_task.is_alive():
                logger.log(u'Updating the Indexer mappings')
                import threading
                try:
                    sickbeard.background_mapping_task = threading.Thread(
                        name='LOAD-MAPPINGS', target=sickbeard.indexermapper.load_mapped_ids, kwargs={'update': True})
                    sickbeard.background_mapping_task.start()
                except Exception:
                    logger.log('missing mapped ids update error', logger.ERROR)
                    logger.log(traceback.format_exc(), logger.ERROR)

            logger.log(u'Doing full update on all shows')

            # clean out cache directory, remove everything > 12 hours old
            try:
                sickbeard.helpers.clearCache()
            except Exception:
                logger.log('cache dir cleanup error', logger.ERROR)
                logger.log(traceback.format_exc(), logger.ERROR)

            # select 10 'Ended' tv_shows updated more than 90 days ago
            # and all shows not updated more then 180 days ago to include in this update
            stale_should_update = []
            stale_update_date = (update_date - datetime.timedelta(days=90)).toordinal()
            stale_update_date_max = (update_date - datetime.timedelta(days=180)).toordinal()

            # last_update_date <= 90 days, sorted ASC because dates are ordinal
            my_db = db.DBConnection()
            sql_results = my_db.mass_action([
                ['SELECT indexer_id FROM tv_shows WHERE last_update_indexer <= ? AND ' +
                 'last_update_indexer >= ? ORDER BY last_update_indexer ASC LIMIT 10;',
                 [stale_update_date, stale_update_date_max]],
                ['SELECT indexer_id FROM tv_shows WHERE last_update_indexer < ?;', [stale_update_date_max]]])

            for sql_result in sql_results:
                for cur_result in sql_result:
                    stale_should_update.append(int(cur_result['indexer_id']))

            # start update process
            pi_list = []
            for curShow in sickbeard.showList:

                try:
                    # get next episode airdate
                    curShow.nextEpisode()

                    # if should_update returns True (not 'Ended') or show is selected stale 'Ended' then update,
                    # otherwise just refresh
                    if curShow.should_update(update_date=update_date) or curShow.indexerid in stale_should_update:
                        cur_queue_item = sickbeard.showQueueScheduler.action.updateShow(curShow, scheduled_update=True)
                    else:
                        logger.log(
                            u'Not updating episodes for show ' + curShow.name + ' because it\'s marked as ended and ' +
                            'last/next episode is not within the grace period.', logger.DEBUG)
                        cur_queue_item = sickbeard.showQueueScheduler.action.refreshShow(curShow, True, True)

                    pi_list.append(cur_queue_item)

                except (exceptions.CantUpdateException, exceptions.CantRefreshException) as e:
                    logger.log(u'Automatic update failed: ' + ex(e), logger.ERROR)

            ui.ProgressIndicators.setIndicator('dailyUpdate', ui.QueueProgressIndicator('Daily Update', pi_list))

            logger.log(u'Added all shows to show queue for full update')

        finally:
            self.amActive = False