Example #1
0
    def download(self):
        title = 'Re-download'
        selected = self.display_list(title, download=True)

        url = selected[-1]
        search = Search()
        search.download(chosen_show=url, destination=Config.staging)
Example #2
0
    def download(self):
        title = 'Re-download'
        choice, data = self.display_list(title, table_type='redownload')

        selected = [i for i in data[1] if choice in i][0]

        url = selected[-1]
        search = Search()
        search.download(chosen_show=url, destination=Config.staging)
Example #3
0
    def __init__(self, dbdata=[], show_type='current'):
        typelist = ('new', 'nondb', 'current')
        if show_type not in typelist:
            raise Exception('incorrect show type')

        if show_type == 'current':
            self._set_db_data(dbdata)
            self._get_thetvdb_series_data()
            self.search_provider = Search()
        elif show_type == 'nondb':
            self.search_provider = Search()

        self.console_columns = Config.console_columns

        self.db = DB
Example #4
0
    def __init__(self, dbdata=[], show_type='current'):
        typelist = ('new', 'nondb', 'current')
        if show_type not in typelist:
            raise Exception('incorrect show type')

        self.tvapi = tvdb_api.Tvdb(apikey=Config.thetvdb_apikey,
                                   cache=Config.use_cache)

        if show_type == 'current':
            self._set_db_data(dbdata)
            self._get_thetvdb_series_data()
            self.search_provider = Search()
        elif show_type == 'nondb':
            self.search_provider = Search()

        self.show_type = show_type
        self.console_columns = Config.console_columns

        self.db = DB
Example #5
0
    def __init__(self, dbdata=[], show_type='current'):
        typelist = ('new', 'nondb', 'current')
        if show_type not in typelist:
            raise Exception('incorrect show type')

        cache = Config.use_cache
        if Config.use_cache:
            # set to a dir since the default location does not work.
            cache = Config.user_dir
        self.tvapi = tvdb_api.Tvdb(apikey=Config.thetvdb_apikey, cache=cache)

        if show_type == 'current':
            self._set_db_data(dbdata)
            self._get_thetvdb_series_data()
            self.search_provider = Search()
        elif show_type == 'nondb':
            self.search_provider = Search()

        self.show_type = show_type
        self.console_columns = Config.console_columns

        self.db = DB
Example #6
0
def config(edit, test_se, show, create_config_name):
    """tvol's config information.

    Show information of where various files are, (config.ini,
    database) and a list of the search engines and the url's they use.
    """
    send(te, v)

    if create_config_name:
        Config.create_config(create_config_name, create=True)
        return

    if edit:
        click.edit(filename=Config.user_config)
        return

    if test_se:
        search = Search()
        search.test_each(test_se, show)
        return

    import shutil

    title = 'green'
    bold = True
    ul = True

    # file locations
    click.echo()
    click.secho('File locations:', fg=title, bold=bold, underline=ul)
    click.echo()

    click.echo('config file:     %s' % Config.user_config)
    click.echo('Database file:   %s' % Config.db_file)
    click.echo('NZB staging dir: %s' % Config.staging)
    click.echo('TV dir:          %s' % Config.tv_dir)
    click.echo('Alt client:      %s' % Config.client)
    click.echo('Magnet dir:      %s' % Config.magnet_dir)
    click.echo('Template:        %s' % Config.template)

    click.echo()
    for script in ['tvol', 'transmission_done', 'deluge_done']:
        loc = shutil.which(script)
        script = script + ':'
        click.echo('%s %s' % (script.ljust(18), loc))

    # config sets
    files = [f for f in os.listdir(Config.user_dir)
             if re.match('^.*\..*\.(sqlite3|ini)', f)]
    files.sort(key=lambda f: f.split('.')[1])
    if files:
        click.echo()
        click.secho('Config sets', fg=title, bold=bold, underline=ul)
        click.echo()
        count = 1
        for gr, items in groupby(files, key=lambda f: f.split('.')[1]):
            config_set = ', '.join(list(items))
            click.echo('%s. %s' % (count, config_set))
            count += 1

    # search engines
    click.echo()
    click.secho('Search engines:', fg=title, bold=bold, underline=ul)
    search = Search()
    engines_types = [search.torrent_engines, search.newsgroup_engines]
    for engines in engines_types:
        for engine in engines:
            click.echo()
            click.secho(engine.name, bold=True, nl=False)
            click.echo(' (%s)' % engine.shortname)
            try:
                for url in engine.provider_urls:
                    click.echo('  %s' % url)
            except AttributeError:
                # there is no provider_urls.  Most likely an nzb
                # search engine which only uses one url: engine.url.
                click.echo('  %s' % engine.url)

    # blacklisted search engines
    if Config.blacklist:
        click.echo()
        click.secho('Search engine blacklist:',
                    fg=title, bold=bold, underline=ul)
        click.echo()
        for bl in Config.blacklist:
            click.echo(bl)

    # ip addresses
    click.echo()
    click.secho('Ip address information:',
                fg=title, bold=bold, underline=ul)
    click.echo()

    l = Location()
    click.echo('Your public ip address:')
    click.secho('  %s' % l.ip, bold=True)
    if Config.warnvpn:
        click.echo()
        click.echo('Your whitelisted ip addresses:')
        whitelist = DB.get_config('ip_whitelist')
        short = '.'.join(l.ip.split('.')[:Config.parts_to_match])
        for ip in whitelist:
            color = None
            if ip.startswith(short):
                color = 'green'
            click.secho('  %s' % ip, fg=color)
Example #7
0
def config(edit, test_se):
    """tvol's config information.

    Show information of where various files are, (config.ini,
    database) and a list of the search engines and the url's they use.
    """

    if edit:
        click.edit(filename=Config.user_config)
        return

    if test_se:
        search = Search()
        search.test_each(test_se)
        return

    import shutil

    title = 'green'
    bold = True
    ul = True

    # file locations
    click.echo()
    click.secho('File locations:', fg=title, bold=bold, underline=ul)
    click.echo()

    click.echo('config file:     %s' % Config.user_config)
    click.echo('Database file:   %s' % Config.db_file)
    click.echo('NZB staging dir: %s' % Config.staging)
    click.echo('TV dir:          %s' % Config.tv_dir)
    click.echo('Alt client:      %s' % Config.client)
    click.echo('Magnet dir:      %s' % Config.magnet_dir)
    click.echo('Template:        %s' % Config.template)

    click.echo()
    for script in ['tvol', 'transmission_done', 'deluge_done']:
        loc = shutil.which(script)
        script = script + ':'
        click.echo('%s %s' % (script.ljust(18), loc))

    # search engines
    click.echo()
    click.secho('Search engines:', fg=title, bold=bold, underline=ul)
    search = Search()
    engines_types = [search.torrent_engines, search.newsgroup_engines]
    for engines in engines_types:
        for engine in engines:
            click.echo()
            click.secho(engine.Provider.name, bold=True, nl=False)
            click.echo(' (%s)' % engine.Provider.shortname)
            for url in engine.Provider.provider_urls:
                click.echo('  %s' % url)

    # blacklisted search engines
    if Config.blacklist:
        click.echo()
        click.secho('Search engine blacklist:',
                    fg=title,
                    bold=bold,
                    underline=ul)
        click.echo()
        for bl in Config.blacklist:
            click.echo(bl)

    # ip addresses
    click.echo()
    click.secho('Ip address information:', fg=title, bold=bold, underline=ul)
    click.echo()

    l = Location()
    click.echo('Your public ip address:')
    click.secho('  %s' % l.ip, bold=True)
    if Config.ip:
        click.echo()
        click.echo('Your whitelisted ip addresses:')
        short = '.'.join(l.ip.split('.')[:Config.parts_to_match])
        for ip in Config.ip:
            color = None
            if ip.startswith(short):
                color = 'green'
            click.secho('  %s' % ip, fg=color)
Example #8
0
class Show:
    """
    Local db fields added:
    ----------------------
    db_current_season, db_last_episode, db_name, db_thetvdb_series_id

    Thetvdb season fields added:
    ----------------------------
    actors, added, addedby, airs_dayofweek, airs_time, banner,
    contentrating, fanart, firstaired, genre, get_thetvdb_data, id,
    imdb_id, language, lastupdated, network, networkid, overview,
    poster, rating, ratingcount, runtime, seriesid, seriesName,
    set_db_data, status, zap2it_id

    Thetvdb episode fieldnames:
    ---------------------------
    episodenumber, rating, overview, dvd_episodenumber, dvd_discid,
    combined_episodenumber, epimgflag, id, seasonid, seasonnumber,
    writer, lastupdated, filename, absolute_number, ratingcount,
    combined_season, imdb_id, director, dvd_chapter, dvd_season,
    gueststars, seriesid, language, productioncode, firstaired,
    episodename
    """
    logging.getLogger('tvdb_api').setLevel(logging.WARNING)

    def se_ep(self, season, episode):
        season_just = str(season).rjust(2, '0')
        episode = str(episode).rjust(2, '0')
        fixed = 'S%sE%s' % (season_just, episode)

        return fixed

    def __init__(self, dbdata=[], show_type='current'):
        typelist = ('new', 'nondb', 'current')
        if show_type not in typelist:
            raise Exception('incorrect show type')

        cache = Config.use_cache
        if Config.use_cache:
            # set to a dir since the default location does not work.
            cache = Config.user_dir
        self.tvapi = tvdb_api.Tvdb(apikey=Config.thetvdb_apikey, cache=cache)

        if show_type == 'current':
            self._set_db_data(dbdata)
            self._get_thetvdb_series_data()
            self.search_provider = Search()
        elif show_type == 'nondb':
            self.search_provider = Search()

        self.show_type = show_type
        self.console_columns = Config.console_columns

        self.db = DB

    def _set_db_data(self, dbdata):
        """Add the data from the local db"""
        self.db_name = dbdata['name']
        self.db_thetvdb_series_id = dbdata['thetvdb_series_id']
        self.db_ragetv_series_id = dbdata['ragetv_series_id']
        self.db_current_season = dbdata['season']
        self.db_last_episode = dbdata['episode']
        # Sometimes the thetvdb name is slighly different than the
        # name to use for searching, thats why we use search_engine_name
        self.db_search_engine_name = dbdata['search_engine_name']
        self.db_status = dbdata['status']
        self.search_by_date = dbdata['search_by_date']
        self.date_format = dbdata['date_format']

    def _get_thetvdb_series_data(self):
        """Dynamicaly add all the data from Thetvdb.com

        networkid      - None
        rating         - 8.6
        airs_dayofweek - Thursday
        contentrating  - TV-14
        seriesName     - 30 Rock
        id             - 79488
        airs_time      - 8:30 PM
        network        - NBC
        fanart         - http://www.thetvdb.com/banners/fanart/original/79488-11.jpg
        lastupdated    - 1358045748
        actors         - |Tina Fey|Alec Baldwin|Tracy Morgan|Jane Krakowski|Kevin Brown|Maulik Pancholy|Keith Powell|Katrina Bowden|Lonny Ross|Scott Adsit|Judah Friedlander|Jack McBrayer|John Lutz|Grizz Chapman|
        ratingcount    - 196
        status         - Continuing
        added          - None
        poster         - http://www.thetvdb.com/banners/posters/79488-7.jpg
        imdb_id        - tt0496424
        genre          - |Comedy|
        banner         - http://www.thetvdb.com/banners/graphical/79488-g11.jpg
        seriesid       - 58326
        language       - en
        zap2it_id      - SH00848357
        addedby        - None
        firstaired     - 2006-10-11
        runtime        - 30
        overview       - Emmy Award Winner Tina Fey writ...

        """

        # tv = tvdb_api.Tvdb(apikey=Config.thetvdb_apikey,
        #                    cache=Config.use_cache)
        # self.tvapi = tv
        # tv = self.tvapi

        tvdb_msg = format_paragraphs('''

            An error occurred while retrieving data from thetvdb.com.

            This may mean that thetvdb is having issues.  This error
            is usually caused by corrupted or incomplete data being
            returned from thetvdb.

            Keep retrying using the --no-cache flag or wait a while.

            Checking thetvdb's twitter feed sometimes gives current
            status: https://twitter.com/thetvdb

            Or isitdownrightnow.com:
            http://www.isitdownrightnow.com/thetvdb.com.html

            If the error continues, open an issue at GitHub and paste
            this error. https://github.com/8cylinder/tv-overlord/issues

            Error #: {error_no}

            Error message from stack trace:

            {stack_msg}
        ''')

        try:
            series = self.tvapi[self.db_name]
            self.show_exists = True
        except xml.etree.ElementTree.ParseError as e:
            print('-' * 20)
            print(e)
            print('-' * 20)
        except KeyError as e:
            # print('>>>', e)
            msg = tvdb_msg.format(error_no=101, stack_msg=e)
            click.echo(msg, err=True)
            sys.exit(1)
        except tvdb_api.tvdb_shownotfound:
            self.show_exists = False
            click.echo('\nShow not found: %s' % self.db_name, err=True)
            return
        except tvdb_api.tvdb_error as e:
            # msg = tvdb_msg.format(error_no=102, stack_msg=e)
            # click.echo(msg, err=True)
            # sys.exit(1)
            print('>>> ERROR', self.db_name)
            return
        except UnboundLocalError as e:
            msg = tvdb_msg.format(error_no=103, stack_msg=e)
            click.echo(msg, err=True)
            sys.exit(1)
        except requests.exceptions.ChunkedEncodingError as e:
            msg = tvdb_msg.format(error_no=104, stack_msg=e)
            click.echo(msg, err=True)
            sys.exit(1)
        except requests.exceptions.ConnectionError as e:
            msg = tvdb_msg.format(error_no=105, stack_msg=e)
            click.echo(msg, err=True)
            sys.exit(1)

        for i in series.data:
            setattr(self, i, series.data[i])
        self.series = series

    def download_missing(self, episode_display_count, download_today=False):
        missing = self._get_missing(download_today)

        if self.db_search_engine_name:
            search_title = self.db_search_engine_name
        else:
            # cleanup name because e.g. "'" in the name is preventing results
            search_title = RE_CLEANUP_FOR_SEARCH.sub(' ', self.db_name)
        # if self does not have the attribute series
        # its because of an error in the xml downloaded
        # from thetvdb site
        if not hasattr(self, 'series'):
            return

        for episode in missing:
            if self.search_by_date:
                if self.date_format:
                    date_search = episode['date'].strftime(self.date_format)
                else:
                    date_search = episode['date'].strftime('%Y %m %d')
                # season_number = episode_number = None
                season_number = episode['season']
                episode_number = episode['episode']
            else:
                date_search = None
                season_number = episode['season']
                episode_number = episode['episode']

            showid = None
            results = self.search_provider.search(
                search_title,
                season=season_number,
                episode=episode_number,
                date_search=date_search,
                search_type=Config.search_type,
            )

            if results:
                showid = self._ask(
                    results,
                    display_count=episode_display_count,
                    season=season_number,
                    episode=episode_number,
                    date_search=date_search,
                )
            else:
                click.echo(
                    '"%s" is listed in TheTVDB, but not found in any search engines'
                    % (search_title))

            if showid == 'skip_rest':
                return
            elif showid == 'skip':
                continue
            elif showid == 'mark':
                # mark the episode as watched, but don't download it
                self._update_db(season=episode['season'],
                                episode=episode['episode'])
                continue

            self._download(showid)
            self._update_db(season=episode['season'],
                            episode=episode['episode'])

    def re_search(self, show_name, season, episode):
        results = self.search_provider.search(
            show_name,
            season=season,
            episode=episode,
            search_type=Config.search_type,
        )
        if results:
            showid = self._ask(
                results,
                display_count=10,
                season=season,
                episode=episode,
            )
        else:
            click.echo(
                '"%s" is listed in TheTVDB, but not found in any search engines'
                % (show_name))

        if showid == 'skip_rest':
            return
        elif showid == 'skip':
            return
        elif showid == 'mark':
            # mark the episode as watched, but don't download it
            self._update_db(season=episode['season'],
                            episode=episode['episode'])
            return

        self._download(showid)
        # self._update_db(season=season, episode=episode)

    def is_missing(self, download_today=False):
        missing = self._get_missing(download_today)
        self.missing = missing

        ret = True
        try:
            if len(missing) == 0:
                ret = False
        except:
            ret = False

        return ret

    def show_missing(self):
        missing = self.missing
        if len(missing) == 0:
            return False
        ret = style(self.db_name, fg='green', bold=True)
        ret += ' - %s, %s' % (self.airsDayOfWeek, self.airsTime)
        ret += '\n'
        indent = '    '
        missing_list = []
        for s in missing:
            se = 'S%sE%s' % (s['season'].rjust(2, '0'), s['episode'].rjust(
                2, '0'))
            missing_list.append(se)
        ret += textwrap.fill(', '.join(missing_list),
                             width=int(self.console_columns),
                             initial_indent=indent,
                             subsequent_indent=indent)
        return ret

    def add_new(self, name):
        try:
            result = self.tvapi.search(name)
        except KeyError as e:
            click.echo(
                'Show data returned from TheTVDB.com has an error in it.',
                err=True)
            sys.exit(1)

        if not result:
            click.echo('No show found.')
            return
        else:
            click.echo('Multiple shows found, type a number to select.')
            click.echo('Type "<ctrl> c" to cancel.')
            click.echo()
            indent = '     '
            for index, show in enumerate(result):
                title = show['seriesName']
                click.echo(' %2s. ' % (index + 1), nl=False)
                click.secho(title, bold=True)
                if 'overview' in show:
                    click.echo(
                        format_paragraphs(show['overview'], indent=indent))
                if 'firstaired' in show:
                    click.secho('%sFirst aired: %s' %
                                (indent, show['firstaired']),
                                fg='green')
                click.echo()

            choice = click.prompt('Choose number (or [enter] for #1)',
                                  default=1,
                                  type=click.IntRange(min=1, max=len(result)))
            idchoice = choice - 1
            show = result[idchoice]

        self.db_name = show['seriesName']  # name
        self._get_thetvdb_series_data()

        last_season = 1
        last_episode = 0
        for season in self.series:
            last_season = season
            for episode in self.series[season]:
                b_date = self.series[season][episode]['firstaired']
                if not b_date:
                    continue  # some episodes have no broadcast date
                split_date = b_date.split('-')
                broadcast_date = datetime.datetime(int(split_date[0]),
                                                   int(split_date[1]),
                                                   int(split_date[2]))
                today = datetime.datetime.today()
                if broadcast_date > today:
                    break
                last_episode = episode

        last_sxxexx = style(sxxexx(last_season, last_episode), bold=True)

        if int(last_season) == 1 and int(last_episode) == 0:
            se = 0
            ep = 0
        else:
            click.echo()
            click.echo('The last episode broadcast was %s.' % last_sxxexx)
            msg = 'Start downloading the [f]irst (or [enter]), [l]atest or season and episode?'
            start = click.prompt(msg, default='f')

            if start == 'f':
                se = ep = 0
            elif start == 'l':
                se = last_season
                ep = last_episode
            else:
                try:
                    se, ep = [int(i) for i in start.split()]
                except ValueError:
                    sys.exit(
                        'Season and episode must be two numbers seperated by a space.'
                    )

            if ep > 0:
                ep -= 1  # episode in db is the NEXT episode

        msg = self._add_new_db(season=se, episode=ep)
        click.echo()
        click.echo(msg)

    def add_bulk(self, seriesname, season=0, episode=0):

        self.db_name = seriesname
        self._get_thetvdb_series_data()
        msg = self._add_new_db(season=season, episode=episode)
        click.echo(msg)

    def non_db(self, search_str, display_count):
        self.db_name = search_str
        show_data = self._ask(self.search_provider.search(
            search_str, search_type=Config.search_type),
                              None,
                              None,
                              display_count,
                              nondb=True)

        if not show_data:
            return
        elif show_data == 'skip':
            return

        self._download(show_data)

    def _get_missing(self, download_today=False):
        """Returns a list of missing episodes"""
        missing = []
        today = datetime.date.today()
        last_watched = [int(self.db_current_season), int(self.db_last_episode)]

        # if SELF does not have the attribute: 'series'
        # it's because of an error in the xml downloaded
        # from thetvdb site
        if not hasattr(self, 'series'):
            return

        # Check the db_next_episode date and see if
        # we should check if a show is ready
        #   IF: a date is in the db and that date is less that today,
        # THEN: 'return' since its not time yet to check for new episodes
        if hasattr(self, 'db_next_episode'):
            next_date = self.db_next_episode.split('-')
            next_date = datetime.date(int(next_date[0]), int(next_date[1]),
                                      int(next_date[2]))
            if today <= next_date:
                return missing

        for i in self.series:  # for each season
            for j in self.series[i]:  # for each episode
                b_date = self.series[i][j]['firstaired']
                if not b_date:
                    continue  # some episode have no broacast date?
                split_date = b_date.split('-')
                broadcast_date = datetime.date(int(split_date[0]),
                                               int(split_date[1]),
                                               int(split_date[2]))

                if not download_today:
                    # download yesterday's and older shows
                    if broadcast_date >= today:  # unaired future date
                        # since this date is the next future date, put
                        # this in the db so we can check for the next
                        # episode at that future date
                        self.set_next_episode(broadcast_date)
                        break
                else:
                    # download today's and older shows
                    if broadcast_date > today:  # unaired future date
                        self.set_next_episode(broadcast_date)
                        break

                last_season = self.series[i][j]['seasonnumber']
                last_episode = self.series[i][j]['episodenumber']
                last_broadcast = [int(last_season), int(last_episode)]
                if last_season == '0' or last_episode == '0':
                    # don't display the S00E01 or S05E00 type special episodes
                    break
                if last_watched < last_broadcast:
                    missing.append({
                        'season': last_season,
                        'episode': last_episode,
                        'date': broadcast_date
                    })
        return missing

    def set_next_episode(self, next_date):
        sql = 'UPDATE shows SET next_episode=:next_date WHERE name=:show_name'
        values = {
            'next_date': next_date.isoformat(),
            'show_name': self.db_name
        }
        DB.run_sql(sql, values)

    def edit(self, action):
        if action == 'delete':
            if click.confirm('Are you sure you want to delete %s?' %
                             self.seriesName):
                self.delete()
                msg = '%s deleted' % self.seriesName
            else:
                msg = '%s not deleted' % self.seriesName
        elif action == 'deactivate':
            self.set_inactive()
            msg = '%s deactivated' % self.seriesName
        elif action == 'activate':
            self.set_active()
            msg = '%s activated' % self.seriesName
        else:
            sys.exit('Incorrect action for show.edit()')

        return msg

    def _ask(self,
             shows,
             season,
             episode,
             display_count,
             nondb=False,
             date_search=None):
        click.echo()
        if not shows[1]:
            # use ljust to cover over the progressbar
            click.secho('No results found.'.ljust(Config.console_columns))
            click.echo(' ' * Config.console_columns)
            return 'skip'
        if date_search:
            show_title = '%s %s' % (self.db_name, date_search)
        elif season and episode:
            show_title = '%s %s' % (self.db_name, self.se_ep(season, episode))
        else:
            show_title = '%s' % shows[0][0][0]

        table_type = 'nondb' if nondb else 'download'
        tbl = ConsoleTable(shows, table_type=table_type)
        tbl.set_title(show_title)
        tbl.set_count(display_count)
        show_to_dl = tbl.generate()

        # save data to Tracking
        tracking = Tracking()
        if show_to_dl not in ['skip', 'skip_rest', 'mark']:
            nondbshow = True if self.show_type == 'nondb' else False
            tracking.save(self.db_name,
                          season,
                          episode,
                          shows,
                          show_to_dl,
                          nondbshow=nondbshow)

        return show_to_dl

    def _download(self, show_data):
        self.search_provider.download(chosen_show=show_data,
                                      destination=Config.staging,
                                      search_type=Config.search_type)

    def set_active(self):
        sql = '''UPDATE shows
                 SET status="active"
                 WHERE thetvdb_series_id=:tvdb_id'''
        values = {'tvdb_id': self.db_thetvdb_series_id}
        DB.run_sql(sql, values)

    def set_inactive(self):
        sql = '''UPDATE shows
                 SET status="inactive"
                 WHERE thetvdb_series_id=:tvdb_id'''
        values = {'tvdb_id': self.db_thetvdb_series_id}
        DB.run_sql(sql, values)

    def delete(self):
        sql = '''DELETE from shows
                 WHERE thetvdb_series_id=:tvdb_id'''
        values = {'tvdb_id': self.db_thetvdb_series_id}
        DB.run_sql(sql, values)

    def _update_db(self, season, episode):
        sql = """UPDATE shows
                 SET season=:season, episode=:episode
                 WHERE thetvdb_series_id=:tvdb_id"""
        values = {
            'season': season,
            'episode': episode,
            'tvdb_id': self.db_thetvdb_series_id
        }
        DB.run_sql(sql, values)

    def _add_new_db(self, season=0, episode=0):
        if self.db.show_exists(self.id):
            sql = '''UPDATE shows SET
                        status="active",
                        episode=:episode,
                        season=:season
                     WHERE thetvdb_series_id=:thetvdb_id;'''
            values = {
                'thetvdb_id': self.id,
                'episode': episode,
                'season': season
            }
            msg = '%s is already in the db. Its status is now set to "active"' % self.seriesName
        else:
            sql = '''
                INSERT INTO shows (
                  network_status, status, thetvdb_series_id,
                  name, season, episode)
                VALUES (:network_status, :status, :thetvdb_id,
                        :name, :season, :episode)'''
            values = {
                'network_status': self.status,
                'status': 'active',
                'thetvdb_id': self.id,
                'name': self.seriesName,
                'season': season,
                'episode': episode
            }
            episode += 1
            episode = str(episode)
            if season == 0:
                season += 1
            season = str(season)
            msg = '%s %s added.' % (self.seriesName, sxxexx(season, episode))

        DB.run_sql(sql, values)
        return msg
Example #9
0
class Show:
    """
    Local db fields added:
    ----------------------
    db_current_season, db_last_episode, db_name, db_thetvdb_series_id

    Thetvdb season fields added:
    ----------------------------
    actors, added, addedby, airs_dayofweek, airs_time, banner,
    contentrating, fanart, firstaired, genre, get_thetvdb_data, id,
    imdb_id, language, lastupdated, network, networkid, overview,
    poster, rating, ratingcount, runtime, seriesid, seriesname,
    set_db_data, status, zap2it_id

    Thetvdb episode fieldnames:
    ---------------------------
    episodenumber, rating, overview, dvd_episodenumber, dvd_discid,
    combined_episodenumber, epimgflag, id, seasonid, seasonnumber,
    writer, lastupdated, filename, absolute_number, ratingcount,
    combined_season, imdb_id, director, dvd_chapter, dvd_season,
    gueststars, seriesid, language, productioncode, firstaired,
    episodename
    """
    logging.getLogger('tvdb_api').setLevel(logging.WARNING)

    def se_ep(self, season, episode):
        season_just = str(season).rjust(2, '0')
        episode = str(episode).rjust(2, '0')
        fixed = 'S%sE%s' % (season_just, episode)

        return fixed

    def __init__(self, dbdata=[], show_type='current'):
        typelist = ('new', 'nondb', 'current')
        if show_type not in typelist:
            raise Exception('incorrect show type')

        self.tvapi = tvdb_api.Tvdb(apikey=Config.thetvdb_apikey,
                                   cache=Config.use_cache)

        if show_type == 'current':
            self._set_db_data(dbdata)
            self._get_thetvdb_series_data()
            self.search_provider = Search()
        elif show_type == 'nondb':
            self.search_provider = Search()

        self.show_type = show_type
        self.console_columns = Config.console_columns

        self.db = DB


    def _set_db_data(self, dbdata):
        """Add the data from the local db"""
        self.db_name = dbdata['name']
        self.db_thetvdb_series_id = dbdata['thetvdb_series_id']
        self.db_ragetv_series_id = dbdata['ragetv_series_id']
        self.db_current_season = dbdata['season']
        self.db_last_episode = dbdata['episode']
        # Sometimes the thetvdb name is slighly different than the
        # name to use for searching, thats why we use search_engine_name
        self.db_search_engine_name = dbdata['search_engine_name']
        self.db_status = dbdata['status']
        self.search_by_date = dbdata['search_by_date']
        self.date_format = dbdata['date_format']


    def _get_thetvdb_series_data(self):
        """Dynamicaly add all the data from Thetvdb.com

        networkid      - None
        rating         - 8.6
        airs_dayofweek - Thursday
        contentrating  - TV-14
        seriesname     - 30 Rock
        id             - 79488
        airs_time      - 8:30 PM
        network        - NBC
        fanart         - http://www.thetvdb.com/banners/fanart/original/79488-11.jpg
        lastupdated    - 1358045748
        actors         - |Tina Fey|Alec Baldwin|Tracy Morgan|Jane Krakowski|Kevin Brown|Maulik Pancholy|Keith Powell|Katrina Bowden|Lonny Ross|Scott Adsit|Judah Friedlander|Jack McBrayer|John Lutz|Grizz Chapman|
        ratingcount    - 196
        status         - Continuing
        added          - None
        poster         - http://www.thetvdb.com/banners/posters/79488-7.jpg
        imdb_id        - tt0496424
        genre          - |Comedy|
        banner         - http://www.thetvdb.com/banners/graphical/79488-g11.jpg
        seriesid       - 58326
        language       - en
        zap2it_id      - SH00848357
        addedby        - None
        firstaired     - 2006-10-11
        runtime        - 30
        overview       - Emmy Award Winner Tina Fey writ...

        """

        # tv = tvdb_api.Tvdb(apikey=Config.thetvdb_apikey,
        #                    cache=Config.use_cache)
        # self.tvapi = tv
        # tv = self.tvapi

        tvdb_msg = format_paragraphs('''

            An error occurred while retrieving data from thetvdb.com.

            This may mean that thetvdb is having issues.  This error
            is usually caused by corrupted or incomplete data being
            returned from thetvdb.

            Keep retrying using the --no-cache flag or wait a while.

            Checking thetvdb's twitter feed sometimes gives current
            status: https://twitter.com/thetvdb

            Or isitdownrightnow.com:
            http://www.isitdownrightnow.com/thetvdb.com.html

            If the error continues, open an issue at GitHub and paste
            this error. https://github.com/8cylinder/tv-overlord/issues

            Error #: {error_no}

            Error message from stack trace:

            {stack_msg}
        ''')

        try:
            series = self.tvapi[self.db_name]
            self.show_exists = True
        except xml.etree.ElementTree.ParseError as e:
            print('-' * 20)
            print(e)
            print('-' * 20)
        except KeyError as e:
            # print('>>>', e)
            msg = tvdb_msg.format(error_no=101, stack_msg=e)
            click.echo(msg, err=True)
            sys.exit(1)
        except tvdb_api.tvdb_shownotfound:
            self.show_exists = False
            click.echo('\nShow not found: %s' % self.db_name, err=True)
            return
        except tvdb_api.tvdb_error as e:
            msg = tvdb_msg.format(error_no=102, stack_msg=e)
            click.echo(msg, err=True)
            sys.exit(1)
        except UnboundLocalError as e:
            msg = tvdb_msg.format(error_no=103, stack_msg=e)
            click.echo(msg, err=True)
            sys.exit(1)
        except requests.exceptions.ChunkedEncodingError as e:
            msg = tvdb_msg.format(error_no=104, stack_msg=e)
            click.echo(msg, err=True)
            sys.exit(1)
        except requests.exceptions.ConnectionError as e:
            msg = tvdb_msg.format(error_no=105, stack_msg=e)
            click.echo(msg, err=True)
            sys.exit(1)

        for i in series.data:
            setattr(self, i, series.data[i])
        self.series = series

    def download_missing(self, episode_display_count, download_today=False):
        missing = self._get_missing(download_today)

        if self.db_search_engine_name:
            search_title = self.db_search_engine_name
        else:
            # cleanup name because e.g. "'" in the name is preventing results
            search_title = RE_CLEANUP_FOR_SEARCH.sub(' ', self.db_name)
        # if self does not have the attribute series
        # its because of an error in the xml downloaded
        # from thetvdb site
        if not hasattr(self, 'series'):
            return

        for episode in missing:
            if self.search_by_date:
                if self.date_format:
                    date_search = episode['date'].strftime(self.date_format)
                else:
                    date_search = episode['date'].strftime('%Y %m %d')
                # season_number = episode_number = None
                season_number = episode['season']
                episode_number = episode['episode']
            else:
                date_search = None
                season_number = episode['season']
                episode_number = episode['episode']

            showid = None
            results = self.search_provider.search(
                search_title,
                season=season_number,
                episode=episode_number,
                date_search=date_search,
                search_type=Config.search_type,
            )

            if results:
                showid = self._ask(
                    results,
                    display_count=episode_display_count,
                    season=season_number,
                    episode=episode_number,
                    date_search=date_search,
                )
            else:
                click.echo('"%s" is listed in TheTVDB, but not found in any search engines' % (
                    search_title))

            if showid == 'skip_rest':
                return
            elif showid == 'skip':
                continue
            elif showid == 'mark':
                # mark the episode as watched, but don't download it
                self._update_db(season=episode['season'],
                                episode=episode['episode'])
                continue

            self._download(showid)
            self._update_db(season=episode['season'],
                            episode=episode['episode'])

    def re_search(self, show_name, season, episode):
        results = self.search_provider.search(
            show_name,
            season=season,
            episode=episode,
            search_type=Config.search_type,
        )
        if results:
            showid = self._ask(
                results,
                display_count=10,
                season=season,
                episode=episode,
            )
        else:
            click.echo('"%s" is listed in TheTVDB, but not found in any search engines' % (
                show_name))

        if showid == 'skip_rest':
            return
        elif showid == 'skip':
            return
        elif showid == 'mark':
            # mark the episode as watched, but don't download it
            self._update_db(season=episode['season'],
                            episode=episode['episode'])
            return

        self._download(showid)
        # self._update_db(season=season, episode=episode)

    def is_missing(self, download_today=False):
        missing = self._get_missing(download_today)
        self.missing = missing

        ret = True
        try:
            if len(missing) == 0:
                ret = False
        except:
            ret = False

        return ret

    def show_missing(self):
        missing = self.missing
        if len(missing) == 0:
            return False
        ret = style(self.db_name, fg='green', bold=True)
        ret += ' - %s, %s' % (self.airs_dayofweek, self.airs_time)
        ret += '\n'
        indent = '    '
        missing_list = []
        for s in missing:
            se = 'S%sE%s' % (s['season'].rjust(2, '0'),
                             s['episode'].rjust(2, '0'))
            missing_list.append(se)
        ret += textwrap.fill(', '.join(missing_list),
                             width=int(self.console_columns),
                             initial_indent=indent,
                             subsequent_indent=indent)
        return ret

    def add_new(self, name):
        try:
            result = self.tvapi.search(name)
        except KeyError as e:
            click.echo(
                'Show data returned from TheTVDB.com has an error in it.',
                err=True)
            sys.exit(1)

        if not result:
            click.echo('No show found.')
            return
        else:
            click.echo('Multiple shows found, type a number to select.')
            click.echo('Type "<ctrl> c" to cancel.')
            click.echo()
            indent = '     '
            for index, show in enumerate(result):
                title = show['seriesname']
                click.echo(' %2s. ' % (index + 1), nl=False)
                click.secho(title, bold=True)
                if 'overview' in show:
                    click.echo(format_paragraphs(
                        show['overview'], indent=indent))
                if 'firstaired' in show:
                    click.secho(
                        '%sFirst aired: %s' % (indent, show['firstaired']),
                        fg='green')
                click.echo()

            choice = click.prompt(
                'Choose number (or [enter] for #1)', default=1,
                type=click.IntRange(min=1, max=len(result)))
            idchoice = choice - 1
            show = result[idchoice]

        self.db_name = show['seriesname']  # name
        self._get_thetvdb_series_data()

        last_season = 1
        last_episode = 0
        for season in self.series:
            last_season = season
            for episode in self.series[season]:
                b_date = self.series[season][episode]['firstaired']
                if not b_date:
                    continue  # some episodes have no broadcast date
                split_date = b_date.split('-')
                broadcast_date = datetime.datetime(
                    int(split_date[0]), int(split_date[1]), int(split_date[2]))
                today = datetime.datetime.today()
                if broadcast_date > today:
                    break
                last_episode = episode

        last_sxxexx = style(sxxexx(last_season, last_episode), bold=True)

        if int(last_season) == 1 and int(last_episode) == 0:
            se = 0
            ep = 0
        else:
            click.echo()
            click.echo('The last episode broadcast was %s.' % last_sxxexx)
            msg = 'Start downloading the [f]irst (or [enter]), [l]atest or season and episode?'
            start = click.prompt(msg, default='f')

            if start == 'f':
                se = ep = 0
            elif start == 'l':
                se = last_season
                ep = last_episode
            else:
                try:
                    se, ep = [int(i) for i in start.split()]
                except ValueError:
                    sys.exit('Season and episode must be two numbers seperated by a space.')

            if ep > 0:
                ep -= 1  # episode in db is the NEXT episode

        msg = self._add_new_db(season=se, episode=ep)
        click.echo()
        click.echo(msg)

    def add_bulk(self, seriesname, season=0, episode=0):

        self.db_name = seriesname
        self._get_thetvdb_series_data()
        msg = self._add_new_db(season=season, episode=episode)
        click.echo(msg)

    def non_db(self, search_str, display_count):
        self.db_name = search_str
        show_data = self._ask(
            self.search_provider.search(
                search_str, search_type=Config.search_type),
            None, None, display_count, nondb=True)

        if not show_data:
            return
        elif show_data == 'skip':
            return

        self._download(show_data)

    def _get_missing(self, download_today=False):
        """Returns a list of missing episodes"""
        missing = []
        today = datetime.date.today()
        last_watched = [
            int(self.db_current_season),
            int(self.db_last_episode)
        ]

        # if SELF does not have the attribute: 'series'
        # it's because of an error in the xml downloaded
        # from thetvdb site
        if not hasattr(self, 'series'):
            return

        # Check the db_next_episode date and see if
        # we should check if a show is ready
        #   IF: a date is in the db and that date is less that today,
        # THEN: 'return' since its not time yet to check for new episodes
        if hasattr(self, 'db_next_episode'):
            next_date = self.db_next_episode.split('-')
            next_date = datetime.date(
                int(next_date[0]), int(next_date[1]), int(next_date[2])
            )
            if today <= next_date:
                return missing

        for i in self.series:  # for each season
            for j in self.series[i]:  # for each episode
                b_date = self.series[i][j]['firstaired']
                if not b_date:
                    continue  # some episode have no broacast date?
                split_date = b_date.split('-')
                broadcast_date = datetime.date(
                    int(split_date[0]), int(split_date[1]), int(split_date[2]))

                if not download_today:
                    # download yesterday's and older shows
                    if broadcast_date >= today:  # unaired future date
                        # since this date is the next future date, put
                        # this in the db so we can check for the next
                        # episode at that future date
                        self.set_next_episode(broadcast_date)
                        break
                else:
                    # download today's and older shows
                    if broadcast_date > today:  # unaired future date
                        self.set_next_episode(broadcast_date)
                        break

                last_season = self.series[i][j]['seasonnumber']
                last_episode = self.series[i][j]['episodenumber']
                last_broadcast = [
                    int(last_season),
                    int(last_episode)
                ]
                if last_season == '0' or last_episode == '0':
                    # don't display the S00E01 or S05E00 type special episodes
                    break
                if last_watched < last_broadcast:
                    missing.append({'season': last_season,
                                    'episode': last_episode,
                                    'date': broadcast_date})
        return missing

    def set_next_episode(self, next_date):
        sql = 'UPDATE shows SET next_episode=:next_date WHERE name=:show_name'
        values = {'next_date': next_date.isoformat(),
                  'show_name': self.db_name}
        DB.run_sql(sql, values)

    def edit(self, action):
        if action == 'delete':
            if click.confirm('Are you sure you want to delete %s?' % self.seriesname):
                self.delete()
                msg = '%s deleted' % self.seriesname
            else:
                msg = '%s not deleted' % self.seriesname
        elif action == 'deactivate':
            self.set_inactive()
            msg = '%s deactivated' % self.seriesname
        elif action == 'activate':
            self.set_active()
            msg = '%s activated' % self.seriesname
        else:
            sys.exit('Incorrect action for show.edit()')

        return msg

    def _ask(self, shows, season, episode, display_count,
             nondb=False, date_search=None):
        click.echo()
        if not shows[1]:
            # use ljust to cover over the progressbar
            click.secho('No results found.'.ljust(Config.console_columns))
            click.echo(' ' * Config.console_columns)
            return 'skip'
        if date_search:
            show_title = '%s %s' % (self.db_name, date_search)
        elif season and episode:
            show_title = '%s %s' % (self.db_name, self.se_ep(season, episode))
        else:
            show_title = '%s' % shows[0][0][0]

        table_type = 'nondb' if nondb else 'download'
        tbl = ConsoleTable(shows, table_type=table_type)
        tbl.set_title(show_title)
        tbl.set_count(display_count)
        show_to_dl = tbl.generate()

        # save data to Tracking
        tracking = Tracking()
        if show_to_dl not in ['skip', 'skip_rest', 'mark']:
            nondbshow = True if self.show_type == 'nondb' else False
            tracking.save(self.db_name, season, episode, shows,
                          show_to_dl, nondbshow=nondbshow)

        return show_to_dl

    def _download(self, show_data):
        self.search_provider.download(
            chosen_show=show_data,
            destination=Config.staging,
            search_type=Config.search_type)

    def set_active(self):
        sql = '''UPDATE shows
                 SET status="active"
                 WHERE thetvdb_series_id=:tvdb_id'''
        values = {'tvdb_id': self.db_thetvdb_series_id}
        DB.run_sql(sql, values)

    def set_inactive(self):
        sql = '''UPDATE shows
                 SET status="inactive"
                 WHERE thetvdb_series_id=:tvdb_id'''
        values = {'tvdb_id': self.db_thetvdb_series_id}
        DB.run_sql(sql, values)

    def delete(self):
        sql = '''DELETE from shows
                 WHERE thetvdb_series_id=:tvdb_id'''
        values = {'tvdb_id': self.db_thetvdb_series_id}
        DB.run_sql(sql, values)

    def _update_db(self, season, episode):
        sql = """UPDATE shows
                 SET season=:season, episode=:episode
                 WHERE thetvdb_series_id=:tvdb_id"""
        values = {'season': season, 'episode': episode,
                  'tvdb_id': self.db_thetvdb_series_id}
        DB.run_sql(sql, values)

    def _add_new_db(self, season=0, episode=0):
        if self.db.show_exists(self.id):
            sql = '''UPDATE shows SET
                        status="active",
                        episode=:episode,
                        season=:season
                     WHERE thetvdb_series_id=:thetvdb_id;'''
            values = {
                'thetvdb_id': self.id,
                'episode': episode,
                'season': season
            }
            msg = '%s is already in the db. Its status is now set to "active"' % self.seriesname
        else:
            sql = '''
                INSERT INTO shows (
                  network_status, status, thetvdb_series_id,
                  name, season, episode)
                VALUES (:network_status, :status, :thetvdb_id,
                        :name, :season, :episode)'''
            values = {'network_status': self.status,
                      'status': 'active',
                      'thetvdb_id': self.id,
                      'name': self.seriesname,
                      'season': season,
                      'episode': episode}
            episode += 1
            episode = str(episode)
            if season == 0:
                season += 1
            season = str(season)
            msg = '%s %s added.' % (self.seriesname, sxxexx(season, episode))

        DB.run_sql(sql, values)
        return msg
Example #10
0
class Show:
    """
    Local db fields added:
    ----------------------
    db_current_season, db_last_episode, db_name, db_thetvdb_series_id

    Thetvdb season fields added:
    ----------------------------
    actors, added, addedby, airs_dayofweek, airs_time, banner,
    contentrating, fanart, firstaired, genre, get_thetvdb_data, id,
    imdb_id, language, lastupdated, network, networkid, overview,
    poster, rating, ratingcount, runtime, seriesid, seriesname,
    set_db_data, status, zap2it_id

    Thetvdb episode fieldnames:
    ---------------------------
    episodenumber, rating, overview, dvd_episodenumber, dvd_discid,
    combined_episodenumber, epimgflag, id, seasonid, seasonnumber,
    writer, lastupdated, filename, absolute_number, ratingcount,
    combined_season, imdb_id, director, dvd_chapter, dvd_season,
    gueststars, seriesid, language, productioncode, firstaired,
    episodename
    """
    tvapi = tvdb_api.Tvdb(apikey=Config.thetvdb_apikey,
                               cache=Config.use_cache)

    def se_ep(self, season, episode):
        season_just = str(season).rjust(2, '0')
        episode = str(episode).rjust(2, '0')
        fixed = 'S%sE%s' % (season_just, episode)

        return fixed

    def __init__(self, dbdata=[], show_type='current'):
        typelist = ('new', 'nondb', 'current')
        if show_type not in typelist:
            raise Exception('incorrect show type')

        if show_type == 'current':
            self._set_db_data(dbdata)
            self._get_thetvdb_series_data()
            self.search_provider = Search()
        elif show_type == 'nondb':
            self.search_provider = Search()

        self.console_columns = Config.console_columns

        self.db = DB()


    def _set_db_data(self, dbdata):
        """Add the data from the local db"""
        self.db_name = dbdata['name']
        self.db_thetvdb_series_id = dbdata['thetvdb_series_id']
        self.db_ragetv_series_id = dbdata['ragetv_series_id']
        self.db_current_season = dbdata['season']
        self.db_last_episode = dbdata['episode']
        # Sometimes the thetvdb name is slighly different than the
        # name to use for searching, thats why we use search_engine_name
        self.db_search_engine_name = dbdata['search_engine_name']
        self.db_status = dbdata['status']

    def _get_thetvdb_series_data(self):
        """Dynamicaly add all the data from Thetvdb.com

        networkid      - None
        rating         - 8.6
        airs_dayofweek - Thursday
        contentrating  - TV-14
        seriesname     - 30 Rock
        id             - 79488
        airs_time      - 8:30 PM
        network        - NBC
        fanart         - http://www.thetvdb.com/banners/fanart/original/79488-11.jpg
        lastupdated    - 1358045748
        actors         - |Tina Fey|Alec Baldwin|Tracy Morgan|Jane Krakowski|Kevin Brown|Maulik Pancholy|Keith Powell|Katrina Bowden|Lonny Ross|Scott Adsit|Judah Friedlander|Jack McBrayer|John Lutz|Grizz Chapman|
        ratingcount    - 196
        status         - Continuing
        added          - None
        poster         - http://www.thetvdb.com/banners/posters/79488-7.jpg
        imdb_id        - tt0496424
        genre          - |Comedy|
        banner         - http://www.thetvdb.com/banners/graphical/79488-g11.jpg
        seriesid       - 58326
        language       - en
        zap2it_id      - SH00848357
        addedby        - None
        firstaired     - 2006-10-11
        runtime        - 30
        overview       - Emmy Award Winner Tina Fey writ...

        """

        # tv = tvdb_api.Tvdb(apikey=Config.thetvdb_apikey,
        #                    cache=Config.use_cache)
        # self.tvapi = tv
        tv = self.tvapi

        try:
            series = tv[self.db_name]
            self.show_exists = True
        except KeyError:
            click.echo('TheTVDB is down or very slow, try again.')
            sys.exit(1)
        except tvdb_api.tvdb_shownotfound:
            click.echo('Show not found: %s' % self.db_name)
            self.show_exists = False
            sys.exit(1)
        except tvdb_api.tvdb_error as e_msg:
            click.echo()
            click.echo('Error: %s' % self.db_name)
            click.echo('-----------------------------')
            click.echo(e_msg)
            sys.exit(1)
        except UnboundLocalError as e:
            click.echo('+++++++++++++++++++++++++')
            click.echo(e)
            click.echo('+++++++++++++++++++++++++')
            sys.exit(1)

        for i in series.data:
            setattr(self, i, series.data[i])
        self.series = series

    def download_missing(self, episode_display_count, download_today=False):
        missing = self._get_missing(download_today)
        if self.db_search_engine_name:
            search_title = self.db_search_engine_name
        else:
            search_title = self.db_name

        # if self does not have the attirbute series
        # its because of an error in the xml downloaded
        # from thetvdb site
        if not hasattr(self, 'series'):
            return

        for episode in missing:
            showid = None
            results = self.search_provider.search(
                search_title,
                season=episode['season'],
                episode=episode['episode'],
                search_type=Config.search_type,
            )

            if results:
                showid = self._ask(
                    results,
                    display_count=episode_display_count,
                    season=episode['season'],
                    episode=episode['episode']
                )
            else:
                click.echo('"%s" is listed in TheTVDB, but not found in any search engines' % (
                    search_title))

            if showid == 'skip_rest':
                return
            elif showid == 'skip':
                continue
            elif showid == 'mark':
                # mark the episode as watched, but don't download it
                self._update_db(season=episode['season'],
                                episode=episode['episode'])
                continue

            self._download(showid)
            self._update_db(season=episode['season'],
                            episode=episode['episode'])

    def is_missing(self, download_today=False):
        missing = self._get_missing(download_today)
        self.missing = missing

        ret = True
        try:
            if len(missing) == 0:
                ret = False
        except:
            ret = False

        return ret

    def show_missing(self):
        missing = self.missing
        if len(missing) == 0:
            return False
        ret = style(self.db_name, fg='green', bold=True)
        ret += ' - %s, %s' % (self.airs_dayofweek, self.airs_time)
        ret += '\n'
        indent = '    '
        missing_list = []
        for s in missing:
            se = 'S%sE%s' % (s['season'].rjust(2, '0'),
                             s['episode'].rjust(2, '0'))
            missing_list.append(se)
        ret += textwrap.fill(', '.join(missing_list),
                             width=int(self.console_columns),
                             initial_indent=indent,
                             subsequent_indent=indent)
        return ret

    def add_new(self, name):
        result = self.tvapi.search(name)

        if len(result) > 1:
            click.echo('Multiple shows found, type a number to select.')
            click.echo('Type "<ctrl> c" to cancel.')
            click.echo()
            for index, show in enumerate(result):
                click.echo('  %s. %s' % (index + 1, show['seriesname']))
            click.echo()

            choice = click.prompt(
                'Choose number', default=1,
                type=click.IntRange(min=1, max=len(result)))
            idchoice = choice - 1
            if idchoice not in range(len(result)):
                sys.exit('Invalid choice: %s' % choice)
            show = result[idchoice]
        elif not result:
            click.echo('No show found.')
            return
        else:
            show = result[0]

        self.db_name = show['seriesname']  # name
        self._get_thetvdb_series_data()
        indent = '  '

        if not self.show_exists:
            sys.exit()

        click.echo()
        click.echo(self.seriesname)
        click.echo('-' * len(self.seriesname))
        if self.overview:
            click.echo(textwrap.fill(self.overview, initial_indent=indent,
                                subsequent_indent=indent))
        else:
            click.echo('No description provided.')
        click.echo()
        click.echo('%sFirst aired: %s' % (indent, self.firstaired))
        click.echo('%sStatus: %s' % (indent, self.status))
        click.echo()
        click.echo('Is this the correct show? [y/n]', nl=False)
        correct = click.getchar(echo=False)
        try:
            # this is nessesary for windows
            correct = correct.decode('utf-8')
        except AttributeError:
            pass
        click.echo(' %s' % correct)

        if str(correct) != 'y':
            click.echo('Not added')
            return

        last_season = 1
        last_episode = 0
        for season in self.series:
            last_season = season
            for episode in self.series[season]:
                b_date = self.series[season][episode]['firstaired']
                if not b_date:
                    continue  # some episodes have no broadcast date
                split_date = b_date.split('-')
                broadcast_date = datetime.datetime(
                    int(split_date[0]), int(split_date[1]), int(split_date[2]))
                today = datetime.datetime.today()
                if broadcast_date > today:
                    break
                last_episode = episode

        last_sxxexx = sxxexx(last_season, last_episode)
        click.echo()
        click.echo('The last episode broadcast was %s.' % last_sxxexx)
        click.echo('Start downloading the [f]irst, [l]atest or season and episode?')
        msg = 'Type "f", "l" or a season and episode number'
        start = click.prompt(msg)

        if start == 'f':
            se = ep = 0
        elif start == 'l':
            se = last_season
            ep = last_episode
        else:
            try:
                se, ep = [int(i) for i in start.split()]
            except ValueError:
                sys.exit('Season and episode must be two numbers seperated by a space')

        if ep > 0: ep -= 1  # episode in db is the NEXT episode

        msg = self._add_new_db(season=se, episode=ep)
        click.echo()
        click.echo(msg)

    def add_bulk(self, seriesname, season=0, episode=0):

        self.db_name = seriesname
        self._get_thetvdb_series_data()
        msg = self._add_new_db(season=season, episode=episode)
        click.echo(msg)

    def non_db(self, search_str, display_count):
        self.db_name = search_str
        show_data = self._ask(
            self.search_provider.search(
                search_str, search_type=Config.search_type),
            None, None, display_count, nondb=True)

        if not show_data:
            return
        elif show_data == 'skip':
            return

        self._download(show_data)

    def _get_missing(self, download_today=False):
        """Returns a list of missing episodes"""
        missing = []
        today = datetime.date.today()
        last_watched = [
            int(self.db_current_season),
            int(self.db_last_episode)
        ]

        # if SELF does not have the attribute: 'series'
        # it's because of an error in the xml downloaded
        # from thetvdb site
        if not hasattr(self, 'series'):
            return

        # Check the db_next_episode date and see if
        # we should check if a show is ready
        #   IF: a date is in the db and that date is less that today,
        # THEN: 'return' since its not time yet to check for new episodes
        if hasattr(self, 'db_next_episode'):
            next_date = self.db_next_episode.split('-')
            next_date = datetime.date(
                int(next_date[0]), int(next_date[1]), int(next_date[2])
            )
            if today <= next_date:
                return missing

        for i in self.series:  # for each season
            for j in self.series[i]:  # for each episode
                b_date = self.series[i][j]['firstaired']
                if not b_date:
                    continue  # some episode have no broacast date?
                split_date = b_date.split('-')
                broadcast_date = datetime.date(
                    int(split_date[0]), int(split_date[1]), int(split_date[2]))

                if not download_today:
                    # download yesterday's and older shows
                    if broadcast_date >= today:  # unaired future date
                        # since this date is the next future date, put
                        # this in the db so we can check for the next
                        # episode at that future date
                        self.set_next_episode(broadcast_date)
                        break
                else:
                    # download today's and older shows
                    if broadcast_date > today:  # unaired future date
                        self.set_next_episode(broadcast_date)
                        break

                last_season = self.series[i][j]['seasonnumber']
                last_episode = self.series[i][j]['episodenumber']
                last_broadcast = [
                    int(last_season),
                    int(last_episode)
                ]
                if last_season == '0' or last_episode == '0':
                    # don't display the S00E01 or S05E00 type special episodes
                    break
                if last_watched < last_broadcast:
                    missing.append({'season': last_season,
                                    'episode': last_episode})
        return missing

    def set_next_episode(self, next_date):
        sql = 'UPDATE shows SET next_episode=:next_date WHERE name=:show_name'
        conn = sqlite3.connect(Config.db_file)
        curs = conn.cursor()
        values = {'next_date': next_date.isoformat(),
                  'show_name': self.db_name}
        curs.execute(sql, values)
        conn.commit()
        conn.close()

    def _ask(self, shows, season, episode, display_count, nondb=False):
        click.echo()
        if not shows[1]:
            # use ljust to cover over the progressbar
            click.secho('No results found.'.ljust(Config.console_columns))
            return 'skip'
        if season and episode:
            show_title = '%s %s' % (self.db_name, self.se_ep(season, episode))
        else:
            show_title = '%s' % shows[0][0][0]

        tbl = ConsoleTable(shows, nondb=nondb)
        tbl.set_title(show_title)
        tbl.set_count(display_count)
        show_to_dl = tbl.generate()

        # save data to Tracking
        tracking = Tracking()
        if show_to_dl not in ['skip', 'skip_rest', 'mark']:
            tracking.save(self.db_name, season, episode, shows, show_to_dl)

        return show_to_dl

    def _download(self, show_data):
        self.search_provider.download(
            chosen_show=show_data,
            destination=Config.staging,
            search_type=Config.search_type)

    def set_inactive(self):
        sql = '''UPDATE shows
                 SET status="inactive"
                 WHERE thetvdb_series_id=:tvdb_id'''
        conn = sqlite3.connect(Config.db_file)
        curs = conn.cursor()
        values = {'tvdb_id': self.db_thetvdb_series_id}
        curs.execute(sql, values)

        conn.commit()
        conn.close()

    def _update_db(self, season, episode):
        sql = """UPDATE shows
                 SET season=:season, episode=:episode
                 WHERE thetvdb_series_id=:tvdb_id"""
        conn = sqlite3.connect(Config.db_file)
        curs = conn.cursor()
        values = {'season': season, 'episode': episode,
                  'tvdb_id': self.db_thetvdb_series_id}
        curs.execute(sql, values)

        conn.commit()
        conn.close()

    def _add_new_db(self, season=0, episode=0):
        if self.db.show_exists(self.id):
            sql = '''UPDATE shows SET
                        status="active",
                        episode=:episode,
                        season=:season
                     WHERE thetvdb_series_id=:thetvdb_id;'''
            values = {
                'thetvdb_id': self.id,
                'episode': episode,
                'season': season
            }
            msg = '%s is already in the db. Its status is now set to "active"' % self.seriesname
        else:
            sql = '''
                INSERT INTO shows (
                  network_status, status, thetvdb_series_id,
                  name, season, episode)
                VALUES (:network_status, :status, :thetvdb_id,
                        :name, :season, :episode)'''
            values = {'network_status': self.status,
                      'status': 'active',
                      'thetvdb_id': self.id,
                      'name': self.seriesname,
                      'season': season,
                      'episode': episode}
            msg = '%s %s, added' % (self.seriesname, sxxexx(season, episode))

        conn = sqlite3.connect(Config.db_file)
        curs = conn.cursor()
        curs.execute(sql, values)
        conn.commit()
        conn.close()

        return msg
Example #11
0
def config(edit, test_se, show, create_config_name):
    """tvol's config information.

    Show information of where various files are, (config.ini,
    database) and a list of the search engines and the url's they use.
    """
    send(te, v)

    if create_config_name:
        Config.create_config(create_config_name, create=True)
        return

    if edit:
        click.edit(filename=Config.user_config)
        return

    if test_se:
        search = Search()
        search.test_each(test_se, show)
        return

    import shutil

    title = 'green'
    bold = True
    ul = True

    # file locations
    click.echo()
    click.secho('File locations:', fg=title, bold=bold, underline=ul)
    click.echo()

    click.echo('config file:     %s' % Config.user_config)
    click.echo('Database file:   %s' % Config.db_file)
    click.echo('NZB staging dir: %s' % Config.staging)
    click.echo('TV dir:          %s' % Config.tv_dir)
    click.echo('Alt client:      %s' % Config.client)
    click.echo('Magnet dir:      %s' % Config.magnet_dir)
    click.echo('Template:        %s' % Config.template)

    click.echo()
    for script in ['tvol', 'transmission_done', 'deluge_done']:
        loc = shutil.which(script)
        script = script + ':'
        click.echo('%s %s' % (script.ljust(18), loc))

    # config sets
    files = [f for f in os.listdir(Config.user_dir)
             if re.match('^.*\..*\.(sqlite3|ini)', f)]
    files.sort(key=lambda f: f.split('.')[1])
    if files:
        click.echo()
        click.secho('Config sets', fg=title, bold=bold, underline=ul)
        click.echo()
        count = 1
        for gr, items in groupby(files, key=lambda f: f.split('.')[1]):
            config_set = ', '.join(list(items))
            click.echo('%s. %s' % (count, config_set))
            count += 1

    # search engines
    click.echo()
    click.secho('Search engines:', fg=title, bold=bold, underline=ul)
    search = Search()
    engines_types = [search.torrent_engines, search.newsgroup_engines]
    for engines in engines_types:
        for engine in engines:
            click.echo()
            click.secho(engine.Provider.name, bold=True, nl=False)
            click.echo(' (%s)' % engine.Provider.shortname)
            for url in engine.Provider.provider_urls:
                click.echo('  %s' % url)

    # blacklisted search engines
    if Config.blacklist:
        click.echo()
        click.secho('Search engine blacklist:',
                    fg=title, bold=bold, underline=ul)
        click.echo()
        for bl in Config.blacklist:
            click.echo(bl)

    # ip addresses
    click.echo()
    click.secho('Ip address information:',
                fg=title, bold=bold, underline=ul)
    click.echo()

    l = Location()
    click.echo('Your public ip address:')
    click.secho('  %s' % l.ip, bold=True)
    if Config.warnvpn:
        click.echo()
        click.echo('Your whitelisted ip addresses:')
        whitelist = DB.get_config('ip_whitelist')
        short = '.'.join(l.ip.split('.')[:Config.parts_to_match])
        for ip in whitelist:
            color = None
            if ip.startswith(short):
                color = 'green'
            click.secho('  %s' % ip, fg=color)