class Source(database.Model):
    ERROR = 0
    OK = 1

    TYPE_URL = 0
    TYPE_FILE = 1
    TYPE_ADDON = 2
    TYPE_CUSTOM = 3

    ARCHIVE_AUTO = 0
    ARCHIVE_GZIP = 1
    ARCHIVE_XZ = 2
    ARCHIVE_NONE = 3

    source_type = peewee.IntegerField()
    archive_type = peewee.IntegerField(default=ARCHIVE_AUTO)

    path = peewee.CharField()
    enabled = peewee.BooleanField(default=True)

    results = database.JSONField(default=list)

    TYPES = [TYPE_URL, TYPE_FILE, TYPE_ADDON, TYPE_CUSTOM]
    TYPE_LABELS = {
        TYPE_URL: _.URL,
        TYPE_FILE: _.FILE,
        TYPE_ADDON: _.ADDON,
        TYPE_CUSTOM: 'Custom',
    }

    def save(self, *args, **kwargs):
        try:
            super(Source, self).save(*args, **kwargs)
        except peewee.IntegrityError as e:
            raise Error(_.SOURCE_EXISTS)

    @property
    def plot(self):
        plot = u''

        if not self.enabled:
            plot = _.DISABLED_MERGE
        elif not self.results:
            plot = _.PENDING_MERGE
        else:
            for result in self.results:
                _time = arrow.get(
                    result[0]).to('local').format('DD/MM/YY h:mm:ss a')
                _result = u'{}'.format(
                    _(result[2],
                      _color='lightgreen' if result[1] == self.OK else 'red'))
                plot += _(u'{}\n{}\n\n'.format(_time, _result))

        return plot

    @property
    def thumb(self):
        if self.source_type == self.TYPE_ADDON:
            try:
                return xbmcaddon.Addon(self.path).getAddonInfo('icon')
            except:
                return None
        else:
            return None

    @property
    def label(self):
        if self.source_type == self.TYPE_ADDON:
            try:
                label = xbmcaddon.Addon(self.path).getAddonInfo('name')
            except:
                label = '{} (Unknown)'.format(self.path)
        else:
            label = self.path

        return label

    @property
    def name(self):
        if not self.enabled:
            name = _(_.DISABLED, label=self.label, _color='gray')
        elif not self.results:
            name = _(self.label, _color='orange')
        elif self.results[0][1] == self.OK:
            name = _(self.label, _color='lightgreen')
        else:
            name = _(self.label, _color='red', _bold=True)

        return name

    @classmethod
    def user_create(cls):
        obj = cls()

        if obj.select_path(creating=True):
            return obj

        return None

    def select_path(self, creating=False):
        try:
            default = self.TYPES.index(self.source_type)
        except:
            default = -1

        index = gui.select(_.SELECT_SOURCE_TYPE,
                           [self.TYPE_LABELS[x] for x in self.TYPES],
                           preselect=default)
        if index < 0:
            return False

        orig_source_type = self.source_type
        self.source_type = self.TYPES[index]

        if self.source_type == self.TYPE_ADDON:
            addons = self.get_addon_sources()
            if not addons:
                raise Error(_.NO_SOURCE_ADDONS)

            options = []
            default = -1
            addons.sort(key=lambda x: x[0].getAddonInfo('name').lower())

            for idx, row in enumerate(addons):
                options.append(
                    plugin.Item(label=row[0].getAddonInfo('name'),
                                art={'thumb': row[0].getAddonInfo('icon')}))
                if orig_source_type == self.TYPE_ADDON and row[0].getAddonInfo(
                        'id') == self.path:
                    default = idx

            index = gui.select(_.SELECT_SOURCE_ADDON,
                               options,
                               preselect=default,
                               useDetails=True)
            if index < 0:
                return False

            addon, data = addons[index]
            self.path = addon.getAddonInfo('id')
        elif self.source_type == self.TYPE_URL:
            self.path = gui.input(_.ENTER_SOURCE_URL,
                                  default=self.path if orig_source_type
                                  == self.TYPE_URL else '').strip()
        elif self.source_type == self.TYPE_FILE:
            self.path = xbmcgui.Dialog().browseSingle(
                1,
                _.SELECT_SOURCE_FILE,
                '',
                '',
                defaultt=self.path
                if orig_source_type == self.TYPE_FILE else '')
        elif self.source_type == self.TYPE_CUSTOM:
            self.path = gui.input('Custom Name',
                                  default=self.path if orig_source_type
                                  == self.TYPE_CUSTOM else '').strip()

        if not self.path:
            return False

        self.save()

        if self.source_type == self.TYPE_ADDON:
            if creating:
                if self.__class__ == Playlist and METHOD_EPG in data:
                    epg = EPG(source_type=EPG.TYPE_ADDON, path=self.path)
                    try:
                        epg.save()
                    except:
                        pass

                elif self.__class__ == EPG and METHOD_PLAYLIST in data:
                    playlist = Playlist(source_type=Playlist.TYPE_ADDON,
                                        path=self.path)
                    try:
                        playlist.save()
                    except:
                        pass

            for key in data.get('settings', {}):
                value = data['settings'][key].replace('$ID', self.path)
                log.debug('Set setting {}={} for addon {}'.format(
                    key, value, self.path))
                addon.setSetting(key, value)

            if 'configure' in data:
                path = data['configure'].replace('$ID', self.path)
                run_plugin(path, wait=True)

        return True

    @staticmethod
    def auto_archive_type(path):
        archive_extensions = {
            '.gz': Source.ARCHIVE_GZIP,
            '.xz': Source.ARCHIVE_XZ,
        }

        name, ext = os.path.splitext(path.lower())
        return archive_extensions.get(ext, Source.ARCHIVE_NONE)

    def select_archive_type(self):
        values = [
            self.ARCHIVE_AUTO, self.ARCHIVE_GZIP, self.ARCHIVE_XZ,
            self.ARCHIVE_NONE
        ]
        labels = [_.ARCHIVE_AUTO, _.GZIP, _.XZ, _.ARCHIVE_NONE]

        try:
            default = values.index(self.archive_type)
        except:
            default = 0

        index = gui.select(_.SELECT_ARCHIVE_TYPE, labels, preselect=default)
        if index < 0:
            return False

        self.archive_type = values[index]
        return True

    @property
    def archive_type_name(self):
        if self.archive_type == self.ARCHIVE_AUTO:
            return _.ARCHIVE_AUTO
        elif self.archive_type == self.ARCHIVE_GZIP:
            return _.GZIP
        elif self.archive_type == self.ARCHIVE_XZ:
            return _.XZ
        else:
            return _.ARCHIVE_NONE

    def toggle_enabled(self):
        self.enabled = not self.enabled
        return True

    @classmethod
    def has_sources(cls):
        return cls.select().where(cls.enabled == True).exists()

    @classmethod
    def wizard(cls):
        source = cls()
        if not source.select_path():
            return

        return source

    @classmethod
    def get_addon_sources(cls):
        data = kodi_rpc('Addons.GetAddons', {
            'installed': True,
            'enabled': True,
            'type': 'xbmc.python.pluginsource'
        },
                        raise_on_error=True)
        installed = [
            x.path for x in cls.select(cls.path).where(
                cls.source_type == cls.TYPE_ADDON)
        ]

        addons = []
        for row in data['addons']:
            if row['addonid'] in installed:
                continue

            addon, data = merge_info(row['addonid'])
            if not addon or not data:
                continue

            if cls == Playlist and METHOD_PLAYLIST not in data:
                continue
            elif cls == EPG and METHOD_EPG not in data:
                continue

            addons.append([addon, data])

        return addons

    class Meta:
        indexes = ((('path', ), True), )
class Channel(database.Model):
    slug = peewee.CharField(primary_key=True)
    playlist = peewee.ForeignKeyField(Playlist,
                                      backref="channels",
                                      on_delete='cascade')
    url = peewee.CharField()
    order = peewee.IntegerField()
    chno = peewee.IntegerField(null=True)
    name = peewee.CharField(null=True)
    custom = peewee.BooleanField(default=False)

    groups = database.JSONField(default=list)
    radio = peewee.BooleanField(default=False)
    epg_id = peewee.CharField(null=True)
    logo = peewee.CharField(null=True)
    attribs = database.JSONField(default=dict)
    properties = database.JSONField(default=dict)
    visible = peewee.IntegerField(default=True)
    is_live = peewee.BooleanField(default=True)

    modified = peewee.BooleanField(default=False)

    @property
    def label(self):
        label = ''
        if self.chno is not None:
            label = '{} - '.format(self.chno)

        label += self.name or _.NO_NAME

        return label

    @property
    def plot(self):
        plot = u'{}\n{}'.format(_.URL, self.url)

        # if self.groups:
        #     plot += u'\n\n{}\n{}'.format('Groups', '\n'.join(self.groups))

        if self.playlist_id:
            plot += u'\n\n{}\n{}'.format(_.PLAYLIST, self.playlist.path)

        if self.epg_id:
            plot += u'\n\n{}\n{}'.format('EPG ID', self.epg_id)

        return plot

    def get_play_path(self):
        if not self.radio and self.url.lower().startswith(
                'http') and settings.getBool('iptv_merge_proxy', True):
            return plugin.url_for(play_channel, slug=self.slug)
        else:
            return self.url

    def get_lines(self):
        lines = u'#EXTINF:-1'

        attribs = self.attribs.copy()
        attribs.update({
            'tvg-id':
            self.epg_id,
            'group-title':
            ';'.join([x for x in self.groups
                      if x.strip()]) if self.groups else None,
            'tvg-chno':
            self.chno,
            'tvg-logo':
            self.logo,
            'radio':
            'true' if self.radio else None,
        })

        for key in sorted(attribs.keys()):
            value = attribs[key]
            if value is not None:
                lines += u' {}="{}"'.format(key, value)

        lines += u',{}\n'.format(self.name if self.name else '')

        if not self.is_live:
            lines += u'#EXT-X-PLAYLIST-TYPE:VOD\n'

        if self.radio or not self.url.lower().startswith(
                'http') or not settings.getBool('iptv_merge_proxy', True):
            for key in self.properties:
                lines += u'#KODIPROP:{}={}\n'.format(key, self.properties[key])

        lines += u'{}'.format(self.get_play_path())

        return lines

    @classmethod
    def epg_ids(cls):
        query = cls.select(cls.epg_id).where(cls.visible == True).distinct()
        with cls.merged():
            return [x[0] for x in query.tuples()]

    @classmethod
    def playlist_list(cls, radio=None):
        query = cls.select(cls).join(Playlist).where(
            cls.visible == True).order_by(cls.chno.asc(nulls='LAST'),
                                          cls.playlist.order, cls.order)

        if radio is not None:
            query = query.where(cls.radio == radio)

        with cls.merged():
            for channel in query:
                yield (channel)

    @classmethod
    def channel_list(cls,
                     radio=None,
                     playlist_id=0,
                     page=1,
                     page_size=0,
                     search=None):
        query = cls.select(cls).join(Playlist).order_by(
            cls.chno.asc(nulls='LAST'), cls.playlist.order, cls.order)

        if radio is not None:
            query = query.where(cls.radio == radio)

        if playlist_id is None:
            query = query.where(cls.playlist_id.is_null())
        elif playlist_id:
            query = query.where(cls.playlist_id == playlist_id)

        if search:
            query = query.where(
                cls.name.concat(' ').concat(cls.url)**'%{}%'.format(search))

        if page_size > 0:
            query = query.paginate(page, page_size)

        with cls.merged():
            for channel in query.prefetch(Playlist):
                yield (channel)

    @classmethod
    @contextmanager
    def merged(cls):
        channel_updates = set()

        for override in Override.select(Override, Channel).join(
                Channel, on=(Channel.slug == Override.slug), attr='channel'):
            channel = override.channel

            for key in override.fields:
                if hasattr(channel, key):
                    setattr(channel, key, override.fields[key])
                else:
                    log.debug('Skipping unknown override key: {}'.format(key))

            channel.modified = True if not channel.custom else False
            channel.attribs.update(override.attribs)
            channel.properties.update(override.properties)
            channel_updates.add(channel)

        if not channel_updates:
            yield
            return

        with database.db.atomic() as transaction:
            try:
                Channel.bulk_update(channel_updates,
                                    fields=Channel._meta.fields)
                yield
                transaction.rollback()
            except Exception as e:
                transaction.rollback()
                raise

    @classmethod
    def from_url(cls, playlist, url):
        order = Channel.select(peewee.fn.MAX(Channel.order) + 1).where(
            Channel.playlist == playlist).scalar() or 1

        return Channel(
            playlist=playlist,
            slug='{}.{}'.format(playlist.id,
                                hash_6(time.time(),
                                       url.lower().strip())),
            url=url,
            name=url,
            order=order,
            custom=True,
        )

    @classmethod
    def from_playlist(cls, extinf):
        colon = extinf.find(':', 0)
        comma = extinf.rfind(',', 0)

        name = None
        if colon >= 0 and comma >= 0 and comma > colon:
            name = extinf[comma + 1:].strip()

        attribs = {}
        for key, value in re.findall('([\w-]+)="([^"]*)"', extinf):
            attribs[key.lower()] = value.strip()

        is_radio = attribs.pop('radio', 'false').lower() == 'true'

        try:
            chno = int(attribs.pop('tvg-chno'))
        except:
            chno = None

        groups = attribs.pop('group-title', '').strip()

        if groups:
            groups = groups.split(';')
        else:
            groups = []

        channel = Channel(
            chno=chno,
            name=name,
            groups=groups,
            radio=is_radio,
            epg_id=attribs.pop('tvg-id', None) or attribs.get('tvg-name')
            or name,
            logo=attribs.pop('tvg-logo', None),
        )

        channel.attribs = attribs

        return channel
class Override(database.Model):
    playlist = peewee.ForeignKeyField(Playlist,
                                      backref="overrides",
                                      on_delete='cascade')
    slug = peewee.CharField(primary_key=True)
    fields = database.JSONField(default=dict)
    attribs = database.JSONField(default=dict)
    properties = database.JSONField(default=dict)
    headers = database.JSONField(default=dict)

    def edit_logo(self, channel):
        self.fields['logo'] = self.fields.get('logo', channel.logo)
        new_value = gui.input('Channel Logo', default=self.fields['logo'])

        if new_value == channel.logo:
            self.fields.pop('logo')
        elif new_value:
            self.fields['logo'] = new_value
        else:
            return False

        return True

    def edit_name(self, channel):
        self.fields['name'] = self.fields.get('name', channel.name)
        new_value = gui.input('Channel Name', default=self.fields['name'])

        if new_value == channel.name:
            self.fields.pop('name')
        elif new_value:
            self.fields['name'] = new_value
        else:
            return False

        return True

    def edit_chno(self, channel):
        self.fields['chno'] = self.fields.get('chno', channel.chno)
        new_chno = gui.numeric(
            'Channel Number',
            default=self.fields['chno'] if self.fields['chno'] != None else '')

        try:
            new_chno = int(new_chno)
        except:
            new_chno = None

        if new_chno == channel.chno:
            self.fields.pop('chno')
        elif new_chno:
            self.fields['chno'] = new_chno
        else:
            return False

        return True

    def edit_groups(self, channel):
        self.fields['groups'] = self.fields.get('groups', channel.groups)
        new_groups = gui.input('Channel Groups',
                               default=';'.join(self.fields['groups'])
                               if self.fields['groups'] else '').split(';')

        if new_groups == channel.groups:
            self.fields.pop('groups')
        elif new_groups:
            self.fields['groups'] = new_groups
        else:
            return False

        return True

    def edit_epg_id(self, channel):
        self.fields['epg_id'] = self.fields.get('epg_id', channel.epg_id)
        new_id = gui.input('EPG ID', default=self.fields['epg_id'])

        if new_id == channel.epg_id:
            self.fields.pop('epg_id')
        elif new_id:
            self.fields['epg_id'] = new_id
        else:
            return False

        return True

    def toggle_visible(self, channel):
        self.fields['visible'] = not self.fields.get('visible',
                                                     channel.visible)

        if self.fields['visible'] == channel.visible:
            self.fields.pop('visible', None)

        return True

    def save(self, *args, **kwargs):
        if not self.fields and not self.attribs and not self.properties:
            self.delete_instance()
        else:
            super(Override, self).save(*args, **kwargs)

    @classmethod
    def clean(cls):
        cls.delete().where((cls.fields == {}) & (cls.attribs == {})
                           & (cls.properties == {})
                           & (cls.headers == {})).execute()
Esempio n. 4
0
class Game(database.Model):
    FULL = 1
    CONDENSED = 8

    UPCOMING = 0  #Not yet played
    LIVE = 1  #Live
    PROCESSING = 2  #Can re-watch entire live stream
    PLAYED = 3  #Can watch full and condensend game

    id = peewee.IntegerField(primary_key=True)
    slug = peewee.TextField(unique=True, index=True)
    state = peewee.IntegerField(index=True)
    start = peewee.IntegerField()
    end = peewee.IntegerField()
    info = database.JSONField()

    @property
    def result(self):
        home = self.info['home']
        away = self.info['away']

        if home['score'] == '' or away['score'] == '':
            return None
        if int(home['score']) == int(away['score']):
            return _(_.A_DRAW,
                     win_team=home['name'],
                     win_score=home['score'],
                     lose_team=away['name'],
                     lose_score=away['score'])
        elif int(home['score']) > int(away['score']):
            return _(_.X_WINS,
                     win_team=home['name'],
                     win_score=home['score'],
                     lose_team=away['name'],
                     lose_score=away['score'])
        else:
            return _(_.X_WINS,
                     win_team=away['name'],
                     win_score=away['score'],
                     lose_team=home['name'],
                     lose_score=home['score'])

    @property
    def aired(self):
        return arrow.get(self.start).to('local').isoformat()

    @property
    def description(self):
        home = self.info['home']
        away = self.info['away']
        show_hours = settings.getInt('show_hours') if settings.getBool(
            'show_score') else -1

        result = ''
        if home['score'] and away['score'] and show_hours != -1 and arrow.now(
        ) > arrow.get(self.start).shift(hours=show_hours):
            result = self.result

        return _(_.GAME_DESC,
                 home_team=home['name'],
                 away_team=away['name'],
                 kick_off=self.kickoff,
                 result=result)

    @property
    def kickoff(self):
        return _(_.KICK_OFF,
                 date_time=arrow.get(
                     self.start).to('local').format('h:mmA D/M/YY'))

    @property
    def duration(self):
        if self.end == 0:
            return None
        return self.end - self.start

    @property
    def playable(self):
        return self.state in (Game.LIVE, Game.PROCESSING, Game.PLAYED)

    @property
    def title(self):
        return _(_.GAME_TITLE,
                 home_team=self.info['home']['name'],
                 away_team=self.info['away']['name'])

    @property
    def image(self):
        return IMG_URL.format('/teams/{}_ph_eb.png'.format(
            self.info['home']['code']))