Пример #1
0
 def update_progress_control(self, timeout, wait):
     if self.progress_control is None:
         return
     self.current_progress_percent -= 100 * wait / timeout
     self.progress_control.setPercent(self.current_progress_percent)  # pylint: disable=no-member,useless-suppression
     self.setProperty('remaining', from_unicode('%02d' % ceil((timeout / 1000) * (self.current_progress_percent / 100))))
     self.setProperty('endtime', from_unicode(localize_time(datetime.now() + timedelta(seconds=50 * 60))))
Пример #2
0
    def update_progress(self, remaining):
        # Run time and end time for next episode
        runtime = utils.get_int(self.item, 'runtime', 0)
        if runtime:
            runtime = datetime.timedelta(seconds=runtime)
            endtime = datetime.datetime.now() + runtime
            endtime = statichelper.from_unicode(utils.localize_time(endtime))
            self.setProperty('endtime', endtime)

        # Remaining time countdown for current episode
        remaining_str = '{0:02.0f}'.format(remaining)
        self.log(remaining_str)
        self.setProperty(
            'remaining', statichelper.from_unicode(remaining_str)
        )

        if not self.progress_control:
            return

        # Set total countdown time on initial progress update
        if remaining and self.countdown_total_time is None:
            self.countdown_total_time = remaining
        # Calculate countdown progress on subsequent updates
        elif remaining:
            percent = 100 * remaining / self.countdown_total_time
            self.current_progress_percent = min(100, max(0, percent))

        self.update_progress_control()
Пример #3
0
 def _get_login_json(self):
     ''' Get login json '''
     from json import load
     payload = dict(
         loginID=from_unicode(get_setting('username')),
         password=from_unicode(get_setting('password')),
         sessionExpiration='-1',
         APIKey=self._API_KEY,
         targetEnv='jssdk',
     )
     data = urlencode(payload).encode()
     log(2, 'URL post: {url}', url=unquote(self._LOGIN_URL))
     req = Request(self._LOGIN_URL, data=data)
     login_json = load(urlopen(req))
     return login_json
Пример #4
0
def log_error(message, **kwargs):
    ''' Log error messages to Kodi '''
    if kwargs:
        from string import Formatter
        message = Formatter().vformat(message, (), SafeDict(**kwargs))
    message = '[{addon}] {message}'.format(addon=addon_id(), message=message)
    xbmc.log(from_unicode(message), 4)
Пример #5
0
def unfollow(program, title):
    ''' The API interface to unfollow a program used by the context menu '''
    move_down = bool(plugin.args.get('move_down'))
    from favorites import Favorites
    Favorites().unfollow(program=program,
                         title=to_unicode(unquote_plus(from_unicode(title))),
                         move_down=move_down)
Пример #6
0
def log(msg, name=__name__, level=LOGINFO):
    """Log information to the Kodi log"""

    # Log everything
    if LOG_ENABLE_SETTING == constants.LOG_ENABLE_DEBUG:
        log_enable = level != LOGNONE
    # Only log important messages
    elif LOG_ENABLE_SETTING == constants.LOG_ENABLE_INFO:
        log_enable = LOGDEBUG < level < LOGNONE
    # Log nothing
    else:
        log_enable = False

    if not log_enable:
        return

    # Force minimum required log level to display in Kodi event log
    if level < MIN_LOG_LEVEL:  # pylint: disable=consider-using-max-builtin
        level = MIN_LOG_LEVEL

    # Convert to unicode for string formatting with Unicode literal
    msg = statichelper.to_unicode(msg)
    msg = '[{0}] {1} -> {2}'.format(get_addon_id(), name, msg)
    # Convert back for older Kodi versions
    xbmc.log(statichelper.from_unicode(msg), level=level)
Пример #7
0
def unwatchlater(uuid, title, url):
    ''' The API interface to unwatch an episode used by the context menu '''
    from resumepoints import ResumePoints
    ResumePoints().unwatchlater(uuid=uuid,
                                title=to_unicode(
                                    unquote_plus(from_unicode(title))),
                                url=url)
Пример #8
0
 def _get_new_xvrttoken(self, login_json, token_variant=None):
     ''' Get new X-VRT-Token from VRT NU website '''
     token = None
     login_token = login_json.get('sessionInfo', dict()).get('login_token')
     if login_token:
         from json import dumps
         login_cookie = 'glt_%s=%s' % (self._API_KEY, login_token)
         payload = dict(
             uid=login_json.get('UID'),
             uidsig=login_json.get('UIDSignature'),
             ts=login_json.get('signatureTimestamp'),
             email=from_unicode(get_setting('username')),
         )
         data = dumps(payload).encode()
         headers = {
             'Content-Type': 'application/json',
             'Cookie': login_cookie
         }
         log(2, 'URL post: {url}', url=unquote(self._TOKEN_GATEWAY_URL))
         req = Request(self._TOKEN_GATEWAY_URL, data=data, headers=headers)
         try:  # Python 3
             setcookie_header = urlopen(req).info().get('Set-Cookie')
         except AttributeError:  # Python 2
             setcookie_header = urlopen(req).info().getheader('Set-Cookie')
         xvrttoken = TokenResolver._create_token_dictionary(
             setcookie_header)
         if token_variant == 'roaming':
             xvrttoken = self._get_roaming_xvrttoken(xvrttoken)
         if xvrttoken is not None:
             token = xvrttoken.get('X-VRT-Token')
             self._set_cached_token(xvrttoken, token_variant)
             notification(message=localize(30952))  # Login succeeded.
     return token
Пример #9
0
    def update_progress_control(self, remaining=None, runtime=None):
        self.current_progress_percent = self.current_progress_percent - self.progress_step_size
        try:
            self.progress_control = self.getControl(3014)
        except RuntimeError:  # Occurs when skin does not include progress control
            pass
        else:
            self.progress_control.setPercent(self.current_progress_percent)  # pylint: disable=no-member,useless-suppression

        if remaining:
            self.setProperty('remaining', from_unicode('%02d' % remaining))
        if runtime:
            self.setProperty(
                'endtime',
                from_unicode(
                    localize_time(datetime.now() +
                                  timedelta(seconds=runtime))))
Пример #10
0
def log(msg, name=None, level=1):
    """Log information to the Kodi log"""
    log_level = get_setting_int('logLevel', level)
    debug_logging = get_global_setting('debug.showloginfo')
    set_property('logLevel', log_level)
    if not debug_logging and log_level < level:
        return
    level = LOGDEBUG if debug_logging else LOGNOTICE
    xlog('[%s] %s -> %s' % (addon_id(), name, from_unicode(msg)), level=level)
Пример #11
0
def log(level=1, message='', **kwargs):
    ''' Log info messages to Kodi '''
    debug_logging = get_global_setting(
        'debug.showloginfo')  # Returns a boolean
    max_log_level = int(get_setting('max_log_level', 0))
    if not debug_logging and not (level <= max_log_level
                                  and max_log_level != 0):
        return
    if kwargs:
        from string import Formatter
        message = Formatter().vformat(message, (), SafeDict(**kwargs))
    message = '[{addon}] {message}'.format(addon=addon_id(), message=message)
    xbmc.log(from_unicode(message), level % 3 if debug_logging else 2)
Пример #12
0
def log(msg, name=None, level=1):
    """Log information to the Kodi log"""
    log_level = get_setting_int('logLevel', level)
    debug_logging = get_global_setting('debug.showloginfo')
    set_property('logLevel', log_level)
    if not debug_logging and log_level < level:
        return
    if debug_logging:
        level = LOGDEBUG
    elif get_kodi_version() >= 19:
        level = LOGINFO
    else:
        level = LOGINFO + 1
    xlog('[%s] %s -> %s' % (addon_id(), name, from_unicode(msg)), level=level)
Пример #13
0
    def get_properties(self, api_data):
        ''' Get properties from single item json api data '''
        properties = dict()

        # Only fill in properties when using VRT NU resumepoints because setting resumetime/totaltime breaks standard Kodi watched status
        if self._resumepoints.is_activated():
            assetpath = self.get_assetpath(api_data)
            if assetpath:
                # We need to ensure forward slashes are quoted
                program_title = statichelper.to_unicode(
                    quote_plus(
                        statichelper.from_unicode(api_data.get('program'))))

                assetuuid = self._resumepoints.assetpath_to_uuid(assetpath)
                url = statichelper.reformat_url(api_data.get('url', ''),
                                                'medium')
                properties.update(assetuuid=assetuuid,
                                  url=url,
                                  title=program_title)

                position = self._resumepoints.get_position(assetuuid)
                total = self._resumepoints.get_total(assetuuid)
                if position and total and SECONDS_MARGIN < position < total - SECONDS_MARGIN:
                    properties['resumetime'] = position
                    log(2, '[Metadata] manual resumetime set to %d' % position)

            duration = self.get_duration(api_data)
            if duration:
                properties['totaltime'] = duration

            episode = self.get_episode(api_data)
            season = self.get_season(api_data)
            if episode and season:
                properties['episodeno'] = 's%se%s' % (season, episode)

            year = self.get_year(api_data)
            if year:
                properties['year'] = year

        return properties
Пример #14
0
def set_property(key, value, window_id=10000):
    """Set a Window property"""
    return Window(window_id).setProperty(key, from_unicode(str(value)))
Пример #15
0
    def get_episodes(self, program=None, season=None, episodes=None, category=None, feature=None, programtype=None, keywords=None,
                     whatson_id=None, video_id=None, video_url=None, page=None, use_favorites=False, variety=None, cache_file=None):
        ''' Get episodes or season data from VRT NU Search API '''

        # Contruct params
        if page:
            page = statichelper.realpage(page)
            all_items = False
            params = {
                'from': ((page - 1) * 50) + 1,
                'i': 'video',
                'size': 50,
            }
        elif variety == 'single':
            all_items = False
            params = {
                'i': 'video',
                'size': '1',
            }
        else:
            all_items = True
            params = {
                'i': 'video',
                'size': '300',
            }

        if variety:
            season = 'allseasons'

            if variety == 'offline':
                from datetime import datetime
                import dateutil.tz
                params['facets[assetOffTime]'] = datetime.now(dateutil.tz.gettz('Europe/Brussels')).strftime('%Y-%m-%d')

            if variety == 'oneoff':
                params['facets[programType]'] = 'oneoff'

            if variety == 'watchlater':
                self._resumepoints.refresh(ttl=5 * 60)
                episode_urls = self._resumepoints.watchlater_urls()
                params['facets[url]'] = '[%s]' % (','.join(episode_urls))

            if variety == 'continue':
                self._resumepoints.refresh(ttl=5 * 60)
                episode_urls = self._resumepoints.resumepoints_urls()
                params['facets[url]'] = '[%s]' % (','.join(episode_urls))

            if use_favorites:
                program_urls = [statichelper.program_to_url(p, 'medium') for p in self._favorites.programs()]
                params['facets[programUrl]'] = '[%s]' % (','.join(program_urls))
            elif variety in ('offline', 'recent'):
                channel_filter = [channel.get('name') for channel in CHANNELS if get_setting(channel.get('name'), 'true') == 'true']
                params['facets[programBrands]'] = '[%s]' % (','.join(channel_filter))

        if program:
            params['facets[programUrl]'] = statichelper.program_to_url(program, 'medium')

        if season and season != 'allseasons':
            params['facets[seasonTitle]'] = season

        if episodes:
            params['facets[episodeNumber]'] = '[%s]' % (','.join(str(episode) for episode in episodes))

        if category:
            params['facets[categories]'] = category

        if feature:
            params['facets[programTags.title]'] = feature

        if programtype:
            params['facets[programType]'] = programtype

        if keywords:
            if not season:
                season = 'allseasons'
            params['q'] = quote_plus(statichelper.from_unicode(keywords))
            params['highlight'] = 'true'

        if whatson_id:
            params['facets[whatsonId]'] = whatson_id

        if video_id:
            params['facets[videoId]'] = video_id

        if video_url:
            params['facets[url]'] = video_url

        # Construct VRT NU Search API Url and get api data
        querystring = '&'.join('{}={}'.format(key, value) for key, value in list(params.items()))
        search_url = self._VRTNU_SEARCH_URL + '?' + querystring.replace(' ', '%20')  # Only encode spaces to minimize url length

        from json import load
        if cache_file:
            # Get api data from cache if it is fresh
            search_json = get_cache(cache_file, ttl=60 * 60)
            if not search_json:
                log(2, 'URL get: {url}', url=unquote(search_url))
                req = Request(search_url)
                try:
                    search_json = load(urlopen(req))
                except (TypeError, ValueError):  # No JSON object could be decoded
                    return []
                except HTTPError as exc:
                    url_length = len(req.get_selector())
                    if exc.code == 413 and url_length > 8192:
                        ok_dialog(heading='HTTP Error 413', message=localize(30967))
                        log_error('HTTP Error 413: Exceeded maximum url length: '
                                  'VRT Search API url has a length of {length} characters.', length=url_length)
                        return []
                    if exc.code == 400 and 7600 <= url_length <= 8192:
                        ok_dialog(heading='HTTP Error 400', message=localize(30967))
                        log_error('HTTP Error 400: Probably exceeded maximum url length: '
                                  'VRT Search API url has a length of {length} characters.', length=url_length)
                        return []
                    raise
                update_cache(cache_file, search_json)
        else:
            log(2, 'URL get: {url}', url=unquote(search_url))
            search_json = load(urlopen(search_url))

        # Check for multiple seasons
        seasons = None
        if 'facets[seasonTitle]' not in unquote(search_url):
            facets = search_json.get('facets', dict()).get('facets')
            seasons = next((f.get('buckets', []) for f in facets if f.get('name') == 'seasons' and len(f.get('buckets', [])) > 1), None)

        episodes = search_json.get('results', [{}])
        show_seasons = bool(season != 'allseasons')

        # Return seasons
        if show_seasons and seasons:
            return (seasons, episodes)

        api_pages = search_json.get('meta').get('pages').get('total')
        api_page_size = search_json.get('meta').get('pages').get('size')
        total_results = search_json.get('meta').get('total_results')

        if all_items and total_results > api_page_size:
            for api_page in range(1, api_pages):
                api_page_url = search_url + '&from=' + str(api_page * api_page_size + 1)
                api_page_json = load(urlopen(api_page_url))
                episodes += api_page_json.get('results', [{}])

        # Return episodes
        return episodes
Пример #16
0
    def run(self):
        """Main service loop"""
        self.log('Service started', 0)

        while not self.abortRequested():
            # check every 1 sec
            if self.waitForAbort(1):
                # Abort was requested while waiting. We should exit
                break

            if not self.player.is_tracking():
                continue

            if bool(get_property('PseudoTVRunning') == 'True'):
                self.player.disable_tracking()
                self.playback_manager.demo.hide()
                continue

            if get_setting_bool('disableNextUp'):
                # Next Up is disabled
                self.player.disable_tracking()
                self.playback_manager.demo.hide()
                continue

            # Method isExternalPlayer() was added in Kodi v18 onward
            if kodi_version_major() >= 18 and self.player.isExternalPlayer():
                self.log('Up Next tracking stopped, external player detected',
                         2)
                self.player.disable_tracking()
                self.playback_manager.demo.hide()
                continue

            last_file = self.player.get_last_file()
            try:
                current_file = self.player.getPlayingFile()
            except RuntimeError:
                self.log(
                    'Up Next tracking stopped, failed player.getPlayingFile()',
                    2)
                self.player.disable_tracking()
                self.playback_manager.demo.hide()
                continue

            if last_file and last_file == from_unicode(current_file):
                # Already processed this playback before
                continue

            try:
                total_time = self.player.getTotalTime()
            except RuntimeError:
                self.log(
                    'Up Next tracking stopped, failed player.getTotalTime()',
                    2)
                self.player.disable_tracking()
                self.playback_manager.demo.hide()
                continue

            if total_time == 0:
                self.log('Up Next tracking stopped, no file is playing', 2)
                self.player.disable_tracking()
                self.playback_manager.demo.hide()
                continue

            try:
                play_time = self.player.getTime()
            except RuntimeError:
                self.log('Up Next tracking stopped, failed player.getTime()',
                         2)
                self.player.disable_tracking()
                self.playback_manager.demo.hide()
                continue

            notification_time = self.api.notification_time(
                total_time=total_time)
            if total_time - play_time > notification_time:
                # Media hasn't reach notification time yet, waiting a bit longer...
                continue

            self.player.set_last_file(from_unicode(current_file))
            self.log(
                'Show notification as episode (of length %d secs) ends in %d secs'
                % (total_time, notification_time), 2)
            self.playback_manager.launch_up_next()
            self.log('Up Next style autoplay succeeded', 2)
            self.player.disable_tracking()

        self.log('Service stopped', 0)
Пример #17
0
    def get_context_menu(self, api_data, program, cache_file):
        ''' Get context menu '''
        from addon import plugin
        favorite_marker = ''
        watchlater_marker = ''
        context_menu = []

        # WATCH LATER
        if self._resumepoints.is_activated():
            assetpath = None

            # VRT NU Search API
            if api_data.get('type') == 'episode':
                program_title = api_data.get('program')
                assetpath = api_data.get('assetPath')

            # VRT NU Schedule API (some are missing vrt.whatson-id)
            elif api_data.get('vrt.whatson-id') or api_data.get('startTime'):
                program_title = api_data.get('title')
                assetpath = api_data.get('assetPath')

            if assetpath is not None:
                # We need to ensure forward slashes are quoted
                program_title = statichelper.to_unicode(
                    quote_plus(statichelper.from_unicode(program_title)))
                url = statichelper.url_to_episode(api_data.get('url', ''))
                assetuuid = self._resumepoints.assetpath_to_uuid(assetpath)
                if self._resumepoints.is_watchlater(assetuuid):
                    extras = dict()
                    # If we are in a watchlater menu, move cursor down before removing a favorite
                    if plugin.path.startswith('/resumepoints/watchlater'):
                        extras = dict(move_down=True)
                    # Unwatch context menu
                    context_menu.append(
                        (statichelper.capitalize(localize(30402)),
                         'RunPlugin(%s)' % url_for('unwatchlater',
                                                   uuid=assetuuid,
                                                   title=program_title,
                                                   url=url,
                                                   **extras)))
                    watchlater_marker = '[COLOR yellow]ᶫ[/COLOR]'
                else:
                    # Watch context menu
                    context_menu.append(
                        (statichelper.capitalize(localize(30401)),
                         'RunPlugin(%s)' % url_for('watchlater',
                                                   uuid=assetuuid,
                                                   title=program_title,
                                                   url=url)))

        # FOLLOW PROGRAM
        if self._favorites.is_activated():

            # VRT NU Search API
            if api_data.get('type') == 'episode':
                program_title = api_data.get('program')
                program_type = api_data.get('programType')
                follow_suffix = localize(
                    30410) if program_type != 'oneoff' else ''  # program
                follow_enabled = True

            # VRT NU Suggest API
            elif api_data.get('type') == 'program':
                program_title = api_data.get('title')
                follow_suffix = ''
                follow_enabled = True

            # VRT NU Schedule API (some are missing vrt.whatson-id)
            elif api_data.get('vrt.whatson-id') or api_data.get('startTime'):
                program_title = api_data.get('title')
                follow_suffix = localize(30410)  # program
                follow_enabled = bool(api_data.get('url'))

            if follow_enabled:
                program_title = statichelper.to_unicode(
                    quote_plus(statichelper.from_unicode(program_title))
                )  # We need to ensure forward slashes are quoted
                if self._favorites.is_favorite(program):
                    extras = dict()
                    # If we are in a favorites menu, move cursor down before removing a favorite
                    if plugin.path.startswith('/favorites'):
                        extras = dict(move_down=True)
                    context_menu.append((
                        localize(30412, title=follow_suffix),  # Unfollow
                        'RunPlugin(%s)' % url_for('unfollow',
                                                  program=program,
                                                  title=program_title,
                                                  **extras)))
                    favorite_marker = '[COLOR yellow]ᵛ[/COLOR]'
                else:
                    context_menu.append((
                        localize(30411, title=follow_suffix),  # Follow
                        'RunPlugin(%s)' % url_for(
                            'follow', program=program, title=program_title)))

        # GO TO PROGRAM
        if api_data.get('programType') != 'oneoff':
            if plugin.path.startswith(
                ('/favorites/offline', '/favorites/recent', '/offline',
                 '/recent', '/resumepoints/continue',
                 '/resumepoints/watchlater', '/tvguide')):
                context_menu.append((
                    localize(30417),  # Go to program
                    'Container.Update(%s)' %
                    url_for('programs', program=program, season='allseasons')))

        # REFRESH MENU
        context_menu.append((
            localize(30413),  # Refresh menu
            'RunPlugin(%s)' % url_for('delete_cache', cache_file=cache_file)))

        return context_menu, favorite_marker, watchlater_marker
Пример #18
0
def set_property(key, value, window_id=constants.WINDOW_HOME):
    """Set a Window property"""

    value = statichelper.from_unicode(str(value))
    return xbmcgui.Window(window_id).setProperty(key, value)
Пример #19
0
def follow(program, title):
    ''' The API interface to follow a program used by the context menu '''
    from favorites import Favorites
    Favorites().follow(program=program,
                       title=to_unicode(unquote_plus(from_unicode(title))))
Пример #20
0
    def run(self):
        ''' Main service loop '''
        self.log('Service started', 0)

        while not self.abortRequested():
            # check every 1 sec
            if self.waitForAbort(1):
                # Abort was requested while waiting. We should exit
                break

            if not self.player.is_tracking():
                continue

            up_next_disabled = bool(get_setting('disableNextUp') == 'true')
            if bool(get_property('PseudoTVRunning') ==
                    'True') or up_next_disabled:
                continue

            last_file = self.player.get_last_file()
            try:
                current_file = self.player.getPlayingFile()
            except RuntimeError:
                self.log(
                    'Failed getPlayingFile: No file is playing - stop up next tracking',
                    2)
                self.player.disable_tracking()
                continue

            if last_file and last_file == current_file:
                continue

            try:
                total_time = self.player.getTotalTime()
            except RuntimeError:
                self.log(
                    'Failed getTotalTime: No file is playing - stop up next tracking',
                    2)
                self.player.disable_tracking()
                continue

            if total_time == 0:
                continue

            try:
                play_time = self.player.getTime()
            except RuntimeError:
                self.log(
                    'Failed getTime: No file is playing - stop up next tracking',
                    2)
                self.player.disable_tracking()
                continue

            notification_time = self.api.notification_time(
                total_time=total_time)
            if total_time - play_time > notification_time:
                continue

            self.player.set_last_file(from_unicode(current_file))
            self.log(
                'Show notification as episode (of length %d secs) ends in %d secs'
                % (total_time, notification_time), 2)
            self.playback_manager.launch_up_next()
            self.log('Up Next style autoplay succeeded', 2)
            self.player.disable_tracking()

        self.log('Service stopped', 0)
Пример #21
0
def show_listing(list_items,
                 category=None,
                 sort='unsorted',
                 ascending=True,
                 content=None,
                 cache=None,
                 selected=None):
    ''' Show a virtual directory in Kodi '''
    from addon import plugin
    from xbmcgui import ListItem

    xbmcplugin.setPluginFanart(handle=plugin.handle,
                               image=from_unicode(addon_fanart()))

    usemenucaching = get_setting('usemenucaching', 'true') == 'true'
    if cache is None:
        cache = usemenucaching
    elif usemenucaching is False:
        cache = False

    if content:
        # content is one of: files, songs, artists, albums, movies, tvshows, episodes, musicvideos
        xbmcplugin.setContent(plugin.handle, content=content)

    # Jump through hoops to get a stable breadcrumbs implementation
    category_label = ''
    if category:
        if not content:
            category_label = 'VRT NU / '
        if plugin.path.startswith(('/favorites/', '/resumepoints/')):
            category_label += localize(30428) + ' / '  # My
        if isinstance(category, int):
            category_label += localize(category)
        else:
            category_label += category
    elif not content:
        category_label = 'VRT NU'
    xbmcplugin.setPluginCategory(handle=plugin.handle, category=category_label)

    # FIXME: Since there is no way to influence descending order, we force it here
    if not ascending:
        sort = 'unsorted'

    # NOTE: When showing tvshow listings and 'showoneoff' was set, force 'unsorted'
    if get_setting(
            'showoneoff',
            'true') == 'true' and sort == 'label' and content == 'tvshows':
        sort = 'unsorted'

    # Add all sort methods to GUI (start with preferred)
    xbmcplugin.addSortMethod(handle=plugin.handle,
                             sortMethod=SORT_METHODS[sort])
    for key in sorted(SORT_METHODS):
        if key != sort:
            xbmcplugin.addSortMethod(handle=plugin.handle,
                                     sortMethod=SORT_METHODS[key])

    # FIXME: This does not appear to be working, we have to order it ourselves
#    xbmcplugin.setProperty(handle=plugin.handle, key='sort.ascending', value='true' if ascending else 'false')
#    if ascending:
#        xbmcplugin.setProperty(handle=plugin.handle, key='sort.order', value=str(SORT_METHODS[sort]))
#    else:
#        # NOTE: When descending, use unsorted
#        xbmcplugin.setProperty(handle=plugin.handle, key='sort.order', value=str(SORT_METHODS['unsorted']))

    listing = []
    for title_item in list_items:
        # Three options:
        #  - item is a virtual directory/folder (not playable, path)
        #  - item is a playable file (playable, path)
        #  - item is non-actionable item (not playable, no path)
        is_folder = bool(not title_item.is_playable and title_item.path)
        is_playable = bool(title_item.is_playable and title_item.path)

        list_item = ListItem(label=title_item.title)

        if title_item.prop_dict:
            # FIXME: The setProperties method is new in Kodi18, so we cannot use it just yet.
            # list_item.setProperties(values=title_item.prop_dict)
            for key, value in list(title_item.prop_dict.items()):
                list_item.setProperty(key=key, value=str(value))
        list_item.setProperty(key='IsInternetStream',
                              value='true' if is_playable else 'false')
        list_item.setProperty(key='IsPlayable',
                              value='true' if is_playable else 'false')

        # FIXME: The setIsFolder method is new in Kodi18, so we cannot use it just yet.
        # list_item.setIsFolder(is_folder)

        if title_item.art_dict:
            list_item.setArt(dict(fanart=addon_fanart()))
            list_item.setArt(title_item.art_dict)

        if title_item.info_dict:
            # type is one of: video, music, pictures, game
            list_item.setInfo(type='video', infoLabels=title_item.info_dict)

        if title_item.stream_dict:
            # type is one of: video, audio, subtitle
            list_item.addStreamInfo('video', title_item.stream_dict)

        if title_item.context_menu:
            list_item.addContextMenuItems(title_item.context_menu)

        url = None
        if title_item.path:
            url = title_item.path

        listing.append((url, list_item, is_folder))

    # Jump to specific item
    if selected is not None:
        pass


#        from xbmcgui import getCurrentWindowId, Window
#        wnd = Window(getCurrentWindowId())
#        wnd.getControl(wnd.getFocusId()).selectItem(selected)

    succeeded = xbmcplugin.addDirectoryItems(plugin.handle, listing,
                                             len(listing))
    xbmcplugin.endOfDirectory(plugin.handle,
                              succeeded,
                              updateListing=False,
                              cacheToDisc=cache)