flexget.plugins.cli.series
Covered: 76 lines
Missed: 112 lines
Skipped 45 lines
Percent: 40 %
  1
from datetime import datetime, timedelta
  2
from flexget.manager import Session
  3
from string import capwords
  4
from sqlalchemy.orm import join
  5
from sqlalchemy import desc
  6
from flexget.plugin import register_plugin, register_parser_option, DependencyError
  8
try:
  9
    from flexget.plugins.filter.series import SeriesDatabase, Series, Episode, Release, forget_series, forget_series_episode
 10
except ImportError:
 11
    raise DependencyError(issued_by='cli_series', missing='series', message='Series commandline interface not loaded')
 14
class SeriesReport(SeriesDatabase):
 16
    """Produces --series report"""
 18
    options = {}
 20
    @staticmethod
 21
    def optik_series(option, opt, value, parser):
 22
        """--series [NAME]"""
 23
        SeriesReport.options['got'] = True
 24
        if parser.rargs:
 25
            SeriesReport.options['name'] = parser.rargs[0]
 27
    def on_process_start(self, feed):
 28
        if self.options:
 29
            feed.manager.disable_feeds()
 31
            if not 'name' in self.options:
 32
                self.display_summary()
 33
            else:
 34
                self.display_details()
 36
    def display_details(self):
 37
        """Display detailed series information, ie. --series NAME"""
 39
        from flexget.manager import Session
 40
        session = Session()
 42
        name = unicode(self.options['name'].lower())
 43
        series = session.query(Series).filter(Series.name == name).first()
 44
        if not series:
 45
            print 'Unknown series `%s`' % name
 46
            return
 48
        print ' %-63s%-15s' % ('Identifier, Title', 'Quality')
 49
        print '-' * 79
 52
        episodes = session.query(Episode).filter(Episode.series_id == series.id).\
 53
            order_by(Episode.identifier).all()
 55
        for episode in episodes:
 57
            if episode.identifier is None:
 58
                print ' None <--- Broken!'
 59
            else:
 60
                print ' %s - %s' % (episode.identifier, episode.age)
 62
            for release in episode.releases:
 63
                status = release.quality.name
 64
                title = release.title
 65
                if len(title) > 55:
 66
                    title = title[:55] + '...'
 67
                if release.proper_count > 0:
 68
                    status += '-proper'
 69
                    if release.proper_count > 1:
 70
                        status += str(release.proper_count)
 71
                if release.downloaded:
 72
                    print '  * %-60s%-15s' % (title, status)
 73
                else:
 74
                    print '    %-60s%-15s' % (title, status)
 76
        print '-' * 79
 77
        print ' * = downloaded'
 78
        session.close()
 80
    def get_series_summary(self):
 81
        result = {}
 82
        session = Session()
 83
        try:
 84
            for series in session.query(Series).all():
 85
                name = unicode(series.name)
 87
                if name.islower():
 88
                    name = capwords(name)
 89
                result[name] = {'identified_by': series.identified_by}
 90
                episode = self.latest_seen_episode(session, series)
 91
                if episode:
 92
                    latest = {'first_seen': episode.first_seen,
 93
                              'episode_instance': episode,
 94
                              'episode_id': episode.identifier,
 95
                              'age': episode.age,
 96
                              'status': self.get_latest_status(episode)}
 97
                    result[name]['latest'] = latest
 98
        finally:
 99
            session.close()
100
        return result
102
    def get_latest_status(self, episode):
103
        """
104
        :param episode: Instance of Episode
105
        :return: Status string for given episode
106
        """
107
        status = ''
108
        for release in sorted(episode.releases, key=lambda r: r.quality):
109
            if release.downloaded:
110
                status += '['
111
            status += release.quality.name
112
            if release.proper_count > 0:
113
                status += '-proper'
114
                if release.proper_count > 1:
115
                    status += str(release.proper_count)
116
            if release.downloaded:
117
                status += ']'
118
            status += ' '
119
        return status if status else None
121
    def latest_seen_episode(self, session, series):
122
        """
123
        :param session: SQLAlchemy session
124
        :param series: Instance of Series
125
        :return: Instance of latest Episode or None
126
        """
127
        if unicode(series.name).lower() == 'penn and teller':
128
            pass
129
        episode = None
131
        if series.identified_by in ('ep', 'auto', None):
132
            episode = session.query(Episode).select_from(join(Episode, Series)).\
133
                filter(Series.id == series.id).\
134
                filter(Episode.season != None).\
135
                order_by(desc(Episode.season)).\
136
                order_by(desc(Episode.number)).first()
138
        if series.identified_by in ('id', 'auto', None):
139
            if not episode:
140
                episode = session.query(Episode).join(Series, Release).\
141
                    filter(Series.id == series.id).\
142
                    filter(Episode.season == None).\
143
                order_by(desc(Release.first_seen)).first()
144
        return episode
146
    def display_summary(self, discontinued=False):
147
        """
148
        Display series summary. ie --series
149
        :param discontinued: Whether to display active or discontinued series
150
        """
152
        formatting = ' %-30s %-10s %-10s %-20s'
153
        print formatting % ('Name', 'Latest', 'Age', 'Status')
154
        print '-' * 79
156
        hidden = 0
157
        series = self.get_series_summary()
158
        for series_name, data in sorted(series.iteritems()):
159
            new_ep = ' '
160
            if len(series_name) > 30:
161
                series_name = series_name[:27] + '...'
163
            if 'latest' in data:
164
                if data['latest']['first_seen'] > datetime.now() - timedelta(days=2):
165
                    new_ep = '>'
166
                if data['latest']['first_seen'] < datetime.now() - timedelta(days=30 * 7):
167
                    hidden += 1
168
                    continue
170
            latest = data.get('latest', {})
171
            status = latest.get('status', 'N/A')
172
            age = latest.get('age', 'N/A')
173
            episode_id = latest.get('episode_id', 'N/A')
175
            print new_ep + formatting[1:] % (series_name, episode_id, age if age else '', status)
177
        print '-' * 79
178
        print ' [] = downloaded | > = new episode %s' % \
179
              '| %i series unseen past 6 months hidden' % hidden if hidden else ''
180
        print ' Use --series NAME to get detailed information'
183
class SeriesForget(object):
185
    """Provides --series-forget"""
187
    options = {}
189
    @staticmethod
190
    def optik_series_forget(option, opt, value, parser):
191
        """
192
        Callback for Optik
193
        --series-forget NAME [ID]
194
        """
195
        if not parser.rargs:
196
            return # how to handle invalid?
197
        if len(parser.rargs) > 0:
198
            SeriesForget.options['name'] = parser.rargs[0]
199
        if len(parser.rargs) > 1:
200
            SeriesForget.options['episode'] = parser.rargs[1]
202
    def on_process_start(self, feed):
203
        if self.options:
204
            feed.manager.disable_feeds()
206
            name = unicode(self.options.get('name'))
208
            if self.options.get('episode'):
210
                identifier = self.options.get('episode').upper()
211
                if identifier and name:
212
                    try:
213
                        forget_series_episode(name, identifier)
214
                        print 'Removed episode `%s` from series `%s`.' % (identifier, name.capitalize())
215
                    except ValueError, e:
216
                        print e.message
217
            else:
219
                try:
220
                    forget_series(name)
221
                    print 'Removed series `%s` from database.' % name.capitalize()
222
                except ValueError, e:
223
                    print e.message
226
register_plugin(SeriesReport, '--series', builtin=True)
227
register_plugin(SeriesForget, '--series-forget', builtin=True)
229
register_parser_option('--series', action='callback', callback=SeriesReport.optik_series,
230
                       help='Display series summary.')
231
register_parser_option('--series-forget', action='callback', callback=SeriesForget.optik_series_forget,
232
                       help='Remove complete series or single episode from database: <NAME> [EPISODE]')