<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Module: KodiHelper
# Created on: 13.01.2017

import xbmcplugin
import xbmcgui
import xbmc
import json
import base64
from MSL import MSL
from os import remove
from os.path import join, isfile
from urllib import urlencode
from xbmcaddon import Addon
from uuid import uuid4
from utils import get_user_agent_for_current_platform
from UniversalAnalytics import Tracker
try:
   import cPickle as pickle
except:
   import pickle

class KodiHelper:
    """Consumes all the configuration data from Kodi as well as turns data into lists of folders and videos"""

    def __init__ (self, plugin_handle=None, base_url=None):
        """Fetches all needed info from Kodi & configures the baseline of the plugin

        Parameters
        ----------
        plugin_handle : :obj:`int`
            Plugin handle

        base_url : :obj:`str`
            Plugin base url
        """
        addon = self.get_addon()
        self.plugin_handle = plugin_handle
        self.base_url = base_url
        self.plugin = addon.getAddonInfo('name')
        self.version = addon.getAddonInfo('version')
        self.base_data_path = xbmc.translatePath(addon.getAddonInfo('profile'))
        self.home_path = xbmc.translatePath('special://home')
        self.plugin_path = addon.getAddonInfo('path')
        self.cookie_path = self.base_data_path + 'COOKIE'
        self.data_path = self.base_data_path + 'DATA'
        self.config_path = join(self.base_data_path, 'config')
        self.msl_data_path = xbmc.translatePath('special://profile/addon_data/service.msl').decode('utf-8') + '/'
        self.verb_log = addon.getSetting('logging') == 'true'
        self.custom_export_name = addon.getSetting('customexportname')
        self.default_fanart = addon.getAddonInfo('fanart')
        self.library = None
        self.setup_memcache()

    def get_addon (self):
        """Returns a fresh addon instance"""
        return Addon()

    def refresh (self):
        """Refresh the current list"""
        return xbmc.executebuiltin('Container.Refresh')

    def show_rating_dialog (self):
        """Asks the user for a movie rating

        Returns
        -------
        :obj:`int`
            Movie rating between 0 & 10
        """
        dlg = xbmcgui.Dialog()
        return dlg.numeric(heading=self.get_local_string(string_id=30019) + ' ' + self.get_local_string(string_id=30022), type=0)

    def show_search_term_dialog (self):
        """Asks the user for a term to query the netflix search for

        Returns
        -------
        :obj:`str`
            Term to search for
        """
        dlg = xbmcgui.Dialog()
        term = dlg.input(self.get_local_string(string_id=30003), type=xbmcgui.INPUT_ALPHANUM)
        if len(term) == 0:
            term = ' '
        return term

    def show_add_to_library_title_dialog (self, original_title):
        """Asks the user for an alternative title for the show/movie that gets exported to the local library

        Parameters
        ----------
        original_title : :obj:`str`
            Original title of the show (as suggested by the addon)

        Returns
        -------
        :obj:`str`
            Title to persist
        """
        if self.custom_export_name == 'true':
            return original_title
        dlg = xbmcgui.Dialog()
        custom_title = dlg.input(heading=self.get_local_string(string_id=30031), defaultt=original_title, type=xbmcgui.INPUT_ALPHANUM) or original_title
        return original_title or custom_title

    def show_password_dialog (self):
        """Asks the user for its Netflix password

        Returns
        -------
        :obj:`str`
            Netflix password
        """
        dlg = xbmcgui.Dialog()
        return dlg.input(self.get_local_string(string_id=30004), type=xbmcgui.INPUT_ALPHANUM, option=xbmcgui.ALPHANUM_HIDE_INPUT)

    def show_email_dialog (self):
        """Asks the user for its Netflix account email

        Returns
        -------
        term : :obj:`str`
            Netflix account email
        """
        dlg = xbmcgui.Dialog()
        return dlg.input(self.get_local_string(string_id=30005), type=xbmcgui.INPUT_ALPHANUM)

    def show_login_failed_notification (self):
        """Shows notification that the login failed

        Returns
        -------
        bool
            Dialog shown
        """
        dialog = xbmcgui.Dialog()
        dialog.notification(self.get_local_string(string_id=30008), self.get_local_string(string_id=30009), xbmcgui.NOTIFICATION_ERROR, 5000)
        return True

    def show_missing_inputstream_addon_notification (self):
        """Shows notification that the inputstream addon couldn't be found

        Returns
        -------
        bool
            Dialog shown
        """
        dialog = xbmcgui.Dialog()
        dialog.notification(self.get_local_string(string_id=30028), self.get_local_string(string_id=30029), xbmcgui.NOTIFICATION_ERROR, 5000)
        return True

    def show_no_search_results_notification (self):
        """Shows notification that no search results could be found

        Returns
        -------
        bool
            Dialog shown
        """
        dialog = xbmcgui.Dialog()
        dialog.notification(self.get_local_string(string_id=30011), self.get_local_string(string_id=30013))
        return True

    def show_no_seasons_notification (self):
        """Shows notification that no seasons be found

        Returns
        -------
        bool
            Dialog shown
        """
        dialog = xbmcgui.Dialog()
        dialog.notification(self.get_local_string(string_id=30010), self.get_local_string(string_id=30012))
        return True

    def set_setting (self, key, value):
        """Public interface for the addons setSetting method

        Returns
        -------
        bool
            Setting could be set or not
        """
        return self.get_addon().setSetting(key, value)

    def get_credentials (self):
        """Returns the users stored credentials

        Returns
        -------
        :obj:`dict` of :obj:`str`
            The users stored account data
        """
        return {
            'email': self.get_addon().getSetting('email'),
            'password': self.get_addon().getSetting('password')
        }

    def get_esn(self):
        """
        Returns the esn from settings
        """
        self.log(msg='Is FILE: ' + str(isfile(self.msl_data_path + 'msl_data.json')))
        self.log(msg=self.get_addon().getSetting('esn'))
        return self.get_addon().getSetting('esn')

    def set_esn(self, esn):
        """
        Returns the esn from settings
        """
        stored_esn = self.get_esn()        
        if not stored_esn and esn:
            self.set_setting('esn', esn)
            self.delete_manifest_data()            
            return esn
        return stored_esn
    
    def delete_manifest_data(self):
        if isfile(self.msl_data_path + 'msl_data.json'):
            remove(self.msl_data_path + 'msl_data.json')
        if isfile(self.msl_data_path + 'manifest.json'):
            remove(self.msl_data_path + 'manifest.json')
        msl = MSL(kodi_helper=self)
        msl.perform_key_handshake()
        msl.save_msl_data()

    def get_dolby_setting(self):
        """
        Returns if the dolby sound is enabled
        :return: True|False
        """
        return self.get_addon().getSetting('enable_dolby_sound') == 'true'

    def get_custom_library_settings (self):
        """Returns the settings in regards to the custom library folder(s)

        Returns
        -------
        :obj:`dict` of :obj:`str`
            The users library settings
        """
        return {
            'enablelibraryfolder': self.get_addon().getSetting('enablelibraryfolder'),
            'customlibraryfolder': self.get_addon().getSetting('customlibraryfolder')
        }

    def get_ssl_verification_setting (self):
        """Returns the setting that describes if we should verify the ssl transport when loading data

        Returns
        -------
        bool
            Verify or not
        """
        return self.get_addon().getSetting('ssl_verification') == 'true'

    def set_main_menu_selection (self, type):
        """Persist the chosen main menu entry in memory

        Parameters
        ----------
        type : :obj:`str`
            Selected menu item
        """
        xbmcgui.Window(xbmcgui.getCurrentWindowId()).setProperty('main_menu_selection', type)

    def get_main_menu_selection (self):
        """Gets the persisted chosen main menu entry from memory

        Returns
        -------
        :obj:`str`
            The last chosen main menu entry
        """
        return xbmcgui.Window(xbmcgui.getCurrentWindowId()).getProperty('main_menu_selection')

    def setup_memcache (self):
        """Sets up the memory cache if not existant"""
        cached_items = xbmcgui.Window(xbmcgui.getCurrentWindowId()).getProperty('memcache')
        # no cache setup yet, create one
        if len(cached_items) < 1:
            xbmcgui.Window(xbmcgui.getCurrentWindowId()).setProperty('memcache', pickle.dumps({}))

    def invalidate_memcache (self):
        """Invalidates the memory cache"""
        xbmcgui.Window(xbmcgui.getCurrentWindowId()).setProperty('memcache', pickle.dumps({}))

    def get_cached_item (self, cache_id):
        """Returns an item from the in memory cache

        Parameters
        ----------
        cache_id : :obj:`str`
            ID of the cache entry

        Returns
        -------
        mixed
            Contents of the requested cache item or none
        """
        cached_items = pickle.loads(xbmcgui.Window(xbmcgui.getCurrentWindowId()).getProperty('memcache'))

        return cached_items.get(cache_id)

    def add_cached_item (self, cache_id, contents):
        """Adds an item to the in memory cache

        Parameters
        ----------
        cache_id : :obj:`str`
            ID of the cache entry

        contents : mixed
            Cache entry contents
        """
        cached_items = pickle.loads(xbmcgui.Window(xbmcgui.getCurrentWindowId()).getProperty('memcache'))
        cached_items.update({cache_id: contents})
        xbmcgui.Window(xbmcgui.getCurrentWindowId()).setProperty('memcache', pickle.dumps(cached_items))

    def SetView(self, content):
        """Set the view mode

        Parameters
        ----------
        content : :obj:`str`

            Type of content in container (folder, movie, show, season, episode, login)

        """
        custom_view = self.get_addon().getSetting('customview')
        if custom_view == 'true':
            view = int(self.get_addon().getSetting('viewmode'+content))
            if view != -1:
                xbmc.executebuiltin('Container.SetViewMode(%s)' % view)

    def build_profiles_listing (self, profiles, action, build_url):
        """Builds the profiles list Kodi screen

        Parameters
        ----------
        profiles : :obj:`list` of :obj:`dict` of :obj:`str`
            List of user profiles

        action : :obj:`str`
            Action paramter to build the subsequent routes

        build_url : :obj:`fn`
            Function to build the subsequent routes

        Returns
        -------
        bool
            List could be build
        """
        for profile in profiles:
            url = build_url({'action': action, 'profile_id': profile['id']})
            li = xbmcgui.ListItem(label=profile['profileName'], iconImage=profile['avatar'])
            li.setProperty('fanart_image', self.default_fanart)
            xbmcplugin.addDirectoryItem(handle=self.plugin_handle, url=url, listitem=li, isFolder=True)
            xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_LABEL)
        xbmcplugin.endOfDirectory(self.plugin_handle)
        self.SetView('login')
        return True

    def build_main_menu_listing (self, video_list_ids, user_list_order, actions, build_url):
        """Builds the video lists (my list, continue watching, etc.) Kodi screen

        Parameters
        ----------
        video_list_ids : :obj:`dict` of :obj:`str`
            List of video lists

        user_list_order : :obj:`list` of :obj:`str`
            Ordered user lists, to determine what should be displayed in the main menue

        actions : :obj:`dict` of :obj:`str`
            Dictionary of actions to build subsequent routes

        build_url : :obj:`fn`
            Function to build the subsequent routes

        Returns
        -------
        bool
            List could be build
        """
        preselect_items = []
        for category in user_list_order:
            for video_list_id in video_list_ids['user']:
                if video_list_ids['user'][video_list_id]['name'] == category:
                    label = video_list_ids['user'][video_list_id]['displayName']
                    if category == 'netflixOriginals':
                        label = label.capitalize()
                    li = xbmcgui.ListItem(label=label)
                    li.setProperty('fanart_image', self.default_fanart)
                    # determine action route
                    action = actions['default']
                    if category in actions.keys():
                        action = actions[category]
                    # determine if the item should be selected
                    preselect_items.append((False, True)[category == self.get_main_menu_selection()])
                    url = build_url({'action': action, 'video_list_id': video_list_id, 'type': category})
                    xbmcplugin.addDirectoryItem(handle=self.plugin_handle, url=url, listitem=li, isFolder=True)

        # add recommendations/genres as subfolders (save us some space on the home page)
        i18n_ids = {
            'recommendations': self.get_local_string(30001),
            'genres': self.get_local_string(30010)
        }
        for type in i18n_ids.keys():
            # determine if the lists have contents
            if len(video_list_ids[type]) > 0:
                # determine action route
                action = actions['default']
                if type in actions.keys():
                    action = actions[type]
                # determine if the item should be selected
                preselect_items.append((False, True)[type == self.get_main_menu_selection()])
                li_rec = xbmcgui.ListItem(label=i18n_ids[type])
                li_rec.setProperty('fanart_image', self.default_fanart)
                url_rec = build_url({'action': action, 'type': type})
                xbmcplugin.addDirectoryItem(handle=self.plugin_handle, url=url_rec, listitem=li_rec, isFolder=True)

        # add search as subfolder
        action = actions['default']
        if 'search' in actions.keys():
            action = actions[type]
        li_rec = xbmcgui.ListItem(label=self.get_local_string(30011))
        li_rec.setProperty('fanart_image', self.default_fanart)
        url_rec = build_url({'action': action, 'type': 'search'})
        xbmcplugin.addDirectoryItem(handle=self.plugin_handle, url=url_rec, listitem=li_rec, isFolder=True)

        # no srting & close
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_UNSORTED)
        xbmcplugin.endOfDirectory(self.plugin_handle)
        self.SetView('folder')

        # (re)select the previously selected main menu entry
        idx = 1
        for item in preselect_items:
            idx += 1
            preselected_list_item = idx if item else None
        preselected_list_item = idx + 1 if self.get_main_menu_selection() == 'search' else preselected_list_item
        if preselected_list_item != None:
            xbmc.executebuiltin('ActivateWindowAndFocus(%s, %s)' % (str(xbmcgui.Window(xbmcgui.getCurrentWindowId()).getFocusId()), str(preselected_list_item)))
        return True

    def build_video_listing (self, video_list, actions, type, build_url):
        """Builds the video lists (my list, continue watching, etc.) contents Kodi screen

        Parameters
        ----------
        video_list_ids : :obj:`dict` of :obj:`str`
            List of video lists

        actions : :obj:`dict` of :obj:`str`
            Dictionary of actions to build subsequent routes

        type : :obj:`str`
            None or 'queue' f.e. when itÂ´s a special video lists

        build_url : :obj:`fn`
            Function to build the subsequent routes

        Returns
        -------
        bool
            List could be build
        """
        for video_list_id in video_list:
            video = video_list[video_list_id]
            li = xbmcgui.ListItem(label=video['title'])
            # add some art to the item
            li = self._generate_art_info(entry=video, li=li)
            # add list item info
            li, infos = self._generate_entry_info(entry=video, li=li)
            li = self._generate_context_menu_items(entry=video, li=li)
            # lists can be mixed with shows & movies, therefor we need to check if its a movie, so play it right away
            if video_list[video_list_id]['type'] == 'movie':
                # itÂ´s a movie, so we need no subfolder & a route to play it
                isFolder = False
                url = build_url({'action': 'play_video', 'video_id': video_list_id, 'infoLabels': infos})
                self.SetView('movie')
            else:
                # it´s a show, so we need a subfolder & route (for seasons)
                isFolder = True
                params = {'action': actions[video['type']], 'show_id': video_list_id}
                if 'tvshowtitle' in infos:
                    params['tvshowtitle'] = base64.urlsafe_b64encode(infos.get('tvshowtitle', '').encode('utf-8'))
                url = build_url(params)
                self.SetView('show')
            xbmcplugin.addDirectoryItem(handle=self.plugin_handle, url=url, listitem=li, isFolder=isFolder)

        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_LABEL)
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_TITLE)
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_VIDEO_YEAR)
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_GENRE)
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_LASTPLAYED)
        xbmcplugin.endOfDirectory(self.plugin_handle)
        return True

    def build_search_result_listing (self, video_list, actions, build_url):
        """Builds the search results list Kodi screen

        Parameters
        ----------
        video_list : :obj:`dict` of :obj:`str`
            List of videos or shows

        actions : :obj:`dict` of :obj:`str`
            Dictionary of actions to build subsequent routes

        build_url : :obj:`fn`
            Function to build the subsequent routes

        Returns
        -------
        bool
            List could be build
        """
        return self.build_video_listing(video_list=video_list, actions=actions, type='search', build_url=build_url)

    def build_no_seasons_available (self):
        """Builds the season list screen if no seasons could be found

        Returns
        -------
        bool
            List could be build
        """
        self.show_no_seasons_notification()
        xbmcplugin.endOfDirectory(self.plugin_handle)
        return True

    def build_no_search_results_available (self, build_url, action):
        """Builds the search results screen if no matches could be found

        Parameters
        ----------
        action : :obj:`str`
            Action paramter to build the subsequent routes

        build_url : :obj:`fn`
            Function to build the subsequent routes

        Returns
        -------
        bool
            List could be build
        """
        self.show_no_search_results_notification()
        return xbmcplugin.endOfDirectory(self.plugin_handle)

    def build_user_sub_listing (self, video_list_ids, type, action, build_url):
        """Builds the video lists screen for user subfolders (genres & recommendations)

        Parameters
        ----------
        video_list_ids : :obj:`dict` of :obj:`str`
            List of video lists

        type : :obj:`str`
            List type (genre or recommendation)

        action : :obj:`str`
            Action paramter to build the subsequent routes

        build_url : :obj:`fn`
            Function to build the subsequent routes

        Returns
        -------
        bool
            List could be build
        """
        for video_list_id in video_list_ids:
            li = xbmcgui.ListItem(video_list_ids[video_list_id]['displayName'])
            li.setProperty('fanart_image', self.default_fanart)
            url = build_url({'action': action, 'video_list_id': video_list_id})
            xbmcplugin.addDirectoryItem(handle=self.plugin_handle, url=url, listitem=li, isFolder=True)

        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_LABEL)
        xbmcplugin.endOfDirectory(self.plugin_handle)
        self.SetView('folder')
        return True

    def build_season_listing (self, seasons_sorted, build_url):
        """Builds the season list screen for a show

        Parameters
        ----------
        seasons_sorted : :obj:`list` of :obj:`dict` of :obj:`str`
            Sorted list of season entries

        build_url : :obj:`fn`
            Function to build the subsequent routes

        Returns
        -------
        bool
            List could be build
        """
        for season in seasons_sorted:
            li = xbmcgui.ListItem(label=season['text'])
            # add some art to the item
            li = self._generate_art_info(entry=season, li=li)
            # add list item info
            li, infos = self._generate_entry_info(entry=season, li=li, base_info={'mediatype': 'season'})
            li = self._generate_context_menu_items(entry=season, li=li)
            params = {'action': 'episode_list', 'season_id': season['id']}
            if 'tvshowtitle' in infos:
                params['tvshowtitle'] = base64.urlsafe_b64encode(infos.get('tvshowtitle', '').encode('utf-8'))
            url = build_url(params)
            xbmcplugin.addDirectoryItem(handle=self.plugin_handle, url=url, listitem=li, isFolder=True)

        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_NONE)
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_VIDEO_YEAR)
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_LABEL)
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_LASTPLAYED)
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_TITLE)
        xbmcplugin.endOfDirectory(self.plugin_handle)
        self.SetView('season')
        return True

    def build_episode_listing (self, episodes_sorted, build_url):
        """Builds the episode list screen for a season of a show

        Parameters
        ----------
        episodes_sorted : :obj:`list` of :obj:`dict` of :obj:`str`
            Sorted list of episode entries

        build_url : :obj:`fn`
            Function to build the subsequent routes

        Returns
        -------
        bool
            List could be build
        """
        for episode in episodes_sorted:
            li = xbmcgui.ListItem(label=episode['title'])
            # add some art to the item
            li = self._generate_art_info(entry=episode, li=li)
            # add list item info
            li, infos = self._generate_entry_info(entry=episode, li=li, base_info={'mediatype': 'episode'})
            li = self._generate_context_menu_items(entry=episode, li=li)
            url = build_url({'action': 'play_video', 'video_id': episode['id'], 'start_offset': episode['bookmark'], 'infoLabels': infos})
            xbmcplugin.addDirectoryItem(handle=self.plugin_handle, url=url, listitem=li, isFolder=False)

        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_EPISODE)
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_NONE)
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_VIDEO_YEAR)
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_LABEL)
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_LASTPLAYED)
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_TITLE)
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_DURATION)
        xbmcplugin.endOfDirectory(self.plugin_handle)
        self.SetView('episode')
        return True

    def play_item (self, esn, video_id, start_offset=-1, infoLabels={}):
        """Plays a video

        Parameters
        ----------
        esn : :obj:`str`
            ESN needed for Widevine/Inputstream

        video_id : :obj:`str`
            ID of the video that should be played

        start_offset : :obj:`str`
            Offset to resume playback from (in seconds)
        
        infoLabels : :obj:`str`
            the listitem's infoLabels

        Returns
        -------<commit_msg>change SetView to set_custom_view<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Module: KodiHelper
# Created on: 13.01.2017

import xbmcplugin
import xbmcgui
import xbmc
import json
import base64
from MSL import MSL
from os import remove
from os.path import join, isfile
from urllib import urlencode
from xbmcaddon import Addon
from uuid import uuid4
from utils import get_user_agent_for_current_platform
from UniversalAnalytics import Tracker
try:
   import cPickle as pickle
except:
   import pickle

class KodiHelper:
    """Consumes all the configuration data from Kodi as well as turns data into lists of folders and videos"""

    def __init__ (self, plugin_handle=None, base_url=None):
        """Fetches all needed info from Kodi & configures the baseline of the plugin

        Parameters
        ----------
        plugin_handle : :obj:`int`
            Plugin handle

        base_url : :obj:`str`
            Plugin base url
        """
        addon = self.get_addon()
        self.plugin_handle = plugin_handle
        self.base_url = base_url
        self.plugin = addon.getAddonInfo('name')
        self.version = addon.getAddonInfo('version')
        self.base_data_path = xbmc.translatePath(addon.getAddonInfo('profile'))
        self.home_path = xbmc.translatePath('special://home')
        self.plugin_path = addon.getAddonInfo('path')
        self.cookie_path = self.base_data_path + 'COOKIE'
        self.data_path = self.base_data_path + 'DATA'
        self.config_path = join(self.base_data_path, 'config')
        self.msl_data_path = xbmc.translatePath('special://profile/addon_data/service.msl').decode('utf-8') + '/'
        self.verb_log = addon.getSetting('logging') == 'true'
        self.custom_export_name = addon.getSetting('customexportname')
        self.default_fanart = addon.getAddonInfo('fanart')
        self.library = None
        self.setup_memcache()

    def get_addon (self):
        """Returns a fresh addon instance"""
        return Addon()

    def refresh (self):
        """Refresh the current list"""
        return xbmc.executebuiltin('Container.Refresh')

    def show_rating_dialog (self):
        """Asks the user for a movie rating

        Returns
        -------
        :obj:`int`
            Movie rating between 0 & 10
        """
        dlg = xbmcgui.Dialog()
        return dlg.numeric(heading=self.get_local_string(string_id=30019) + ' ' + self.get_local_string(string_id=30022), type=0)

    def show_search_term_dialog (self):
        """Asks the user for a term to query the netflix search for

        Returns
        -------
        :obj:`str`
            Term to search for
        """
        dlg = xbmcgui.Dialog()
        term = dlg.input(self.get_local_string(string_id=30003), type=xbmcgui.INPUT_ALPHANUM)
        if len(term) == 0:
            term = ' '
        return term

    def show_add_to_library_title_dialog (self, original_title):
        """Asks the user for an alternative title for the show/movie that gets exported to the local library

        Parameters
        ----------
        original_title : :obj:`str`
            Original title of the show (as suggested by the addon)

        Returns
        -------
        :obj:`str`
            Title to persist
        """
        if self.custom_export_name == 'true':
            return original_title
        dlg = xbmcgui.Dialog()
        custom_title = dlg.input(heading=self.get_local_string(string_id=30031), defaultt=original_title, type=xbmcgui.INPUT_ALPHANUM) or original_title
        return original_title or custom_title

    def show_password_dialog (self):
        """Asks the user for its Netflix password

        Returns
        -------
        :obj:`str`
            Netflix password
        """
        dlg = xbmcgui.Dialog()
        return dlg.input(self.get_local_string(string_id=30004), type=xbmcgui.INPUT_ALPHANUM, option=xbmcgui.ALPHANUM_HIDE_INPUT)

    def show_email_dialog (self):
        """Asks the user for its Netflix account email

        Returns
        -------
        term : :obj:`str`
            Netflix account email
        """
        dlg = xbmcgui.Dialog()
        return dlg.input(self.get_local_string(string_id=30005), type=xbmcgui.INPUT_ALPHANUM)

    def show_login_failed_notification (self):
        """Shows notification that the login failed

        Returns
        -------
        bool
            Dialog shown
        """
        dialog = xbmcgui.Dialog()
        dialog.notification(self.get_local_string(string_id=30008), self.get_local_string(string_id=30009), xbmcgui.NOTIFICATION_ERROR, 5000)
        return True

    def show_missing_inputstream_addon_notification (self):
        """Shows notification that the inputstream addon couldn't be found

        Returns
        -------
        bool
            Dialog shown
        """
        dialog = xbmcgui.Dialog()
        dialog.notification(self.get_local_string(string_id=30028), self.get_local_string(string_id=30029), xbmcgui.NOTIFICATION_ERROR, 5000)
        return True

    def show_no_search_results_notification (self):
        """Shows notification that no search results could be found

        Returns
        -------
        bool
            Dialog shown
        """
        dialog = xbmcgui.Dialog()
        dialog.notification(self.get_local_string(string_id=30011), self.get_local_string(string_id=30013))
        return True

    def show_no_seasons_notification (self):
        """Shows notification that no seasons be found

        Returns
        -------
        bool
            Dialog shown
        """
        dialog = xbmcgui.Dialog()
        dialog.notification(self.get_local_string(string_id=30010), self.get_local_string(string_id=30012))
        return True

    def set_setting (self, key, value):
        """Public interface for the addons setSetting method

        Returns
        -------
        bool
            Setting could be set or not
        """
        return self.get_addon().setSetting(key, value)

    def get_credentials (self):
        """Returns the users stored credentials

        Returns
        -------
        :obj:`dict` of :obj:`str`
            The users stored account data
        """
        return {
            'email': self.get_addon().getSetting('email'),
            'password': self.get_addon().getSetting('password')
        }

    def get_esn(self):
        """
        Returns the esn from settings
        """
        self.log(msg='Is FILE: ' + str(isfile(self.msl_data_path + 'msl_data.json')))
        self.log(msg=self.get_addon().getSetting('esn'))
        return self.get_addon().getSetting('esn')

    def set_esn(self, esn):
        """
        Returns the esn from settings
        """
        stored_esn = self.get_esn()        
        if not stored_esn and esn:
            self.set_setting('esn', esn)
            self.delete_manifest_data()            
            return esn
        return stored_esn
    
    def delete_manifest_data(self):
        if isfile(self.msl_data_path + 'msl_data.json'):
            remove(self.msl_data_path + 'msl_data.json')
        if isfile(self.msl_data_path + 'manifest.json'):
            remove(self.msl_data_path + 'manifest.json')
        msl = MSL(kodi_helper=self)
        msl.perform_key_handshake()
        msl.save_msl_data()

    def get_dolby_setting(self):
        """
        Returns if the dolby sound is enabled
        :return: True|False
        """
        return self.get_addon().getSetting('enable_dolby_sound') == 'true'

    def get_custom_library_settings (self):
        """Returns the settings in regards to the custom library folder(s)

        Returns
        -------
        :obj:`dict` of :obj:`str`
            The users library settings
        """
        return {
            'enablelibraryfolder': self.get_addon().getSetting('enablelibraryfolder'),
            'customlibraryfolder': self.get_addon().getSetting('customlibraryfolder')
        }

    def get_ssl_verification_setting (self):
        """Returns the setting that describes if we should verify the ssl transport when loading data

        Returns
        -------
        bool
            Verify or not
        """
        return self.get_addon().getSetting('ssl_verification') == 'true'

    def set_main_menu_selection (self, type):
        """Persist the chosen main menu entry in memory

        Parameters
        ----------
        type : :obj:`str`
            Selected menu item
        """
        xbmcgui.Window(xbmcgui.getCurrentWindowId()).setProperty('main_menu_selection', type)

    def get_main_menu_selection (self):
        """Gets the persisted chosen main menu entry from memory

        Returns
        -------
        :obj:`str`
            The last chosen main menu entry
        """
        return xbmcgui.Window(xbmcgui.getCurrentWindowId()).getProperty('main_menu_selection')

    def setup_memcache (self):
        """Sets up the memory cache if not existant"""
        cached_items = xbmcgui.Window(xbmcgui.getCurrentWindowId()).getProperty('memcache')
        # no cache setup yet, create one
        if len(cached_items) < 1:
            xbmcgui.Window(xbmcgui.getCurrentWindowId()).setProperty('memcache', pickle.dumps({}))

    def invalidate_memcache (self):
        """Invalidates the memory cache"""
        xbmcgui.Window(xbmcgui.getCurrentWindowId()).setProperty('memcache', pickle.dumps({}))

    def get_cached_item (self, cache_id):
        """Returns an item from the in memory cache

        Parameters
        ----------
        cache_id : :obj:`str`
            ID of the cache entry

        Returns
        -------
        mixed
            Contents of the requested cache item or none
        """
        cached_items = pickle.loads(xbmcgui.Window(xbmcgui.getCurrentWindowId()).getProperty('memcache'))

        return cached_items.get(cache_id)

    def add_cached_item (self, cache_id, contents):
        """Adds an item to the in memory cache

        Parameters
        ----------
        cache_id : :obj:`str`
            ID of the cache entry

        contents : mixed
            Cache entry contents
        """
        cached_items = pickle.loads(xbmcgui.Window(xbmcgui.getCurrentWindowId()).getProperty('memcache'))
        cached_items.update({cache_id: contents})
        xbmcgui.Window(xbmcgui.getCurrentWindowId()).setProperty('memcache', pickle.dumps(cached_items))

    def set_custom_view(self, content):
        """Set the view mode

        Parameters
        ----------
        content : :obj:`str`

            Type of content in container (folder, movie, show, season, episode, login)

        """
        custom_view = self.get_addon().getSetting('customview')
        if custom_view == 'true':
            view = int(self.get_addon().getSetting('viewmode'+content))
            if view != -1:
                xbmc.executebuiltin('Container.SetViewMode(%s)' % view)

    def build_profiles_listing (self, profiles, action, build_url):
        """Builds the profiles list Kodi screen

        Parameters
        ----------
        profiles : :obj:`list` of :obj:`dict` of :obj:`str`
            List of user profiles

        action : :obj:`str`
            Action paramter to build the subsequent routes

        build_url : :obj:`fn`
            Function to build the subsequent routes

        Returns
        -------
        bool
            List could be build
        """
        for profile in profiles:
            url = build_url({'action': action, 'profile_id': profile['id']})
            li = xbmcgui.ListItem(label=profile['profileName'], iconImage=profile['avatar'])
            li.setProperty('fanart_image', self.default_fanart)
            xbmcplugin.addDirectoryItem(handle=self.plugin_handle, url=url, listitem=li, isFolder=True)
            xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_LABEL)
        xbmcplugin.endOfDirectory(self.plugin_handle)
        self.set_custom_view('login')
        return True

    def build_main_menu_listing (self, video_list_ids, user_list_order, actions, build_url):
        """Builds the video lists (my list, continue watching, etc.) Kodi screen

        Parameters
        ----------
        video_list_ids : :obj:`dict` of :obj:`str`
            List of video lists

        user_list_order : :obj:`list` of :obj:`str`
            Ordered user lists, to determine what should be displayed in the main menue

        actions : :obj:`dict` of :obj:`str`
            Dictionary of actions to build subsequent routes

        build_url : :obj:`fn`
            Function to build the subsequent routes

        Returns
        -------
        bool
            List could be build
        """
        preselect_items = []
        for category in user_list_order:
            for video_list_id in video_list_ids['user']:
                if video_list_ids['user'][video_list_id]['name'] == category:
                    label = video_list_ids['user'][video_list_id]['displayName']
                    if category == 'netflixOriginals':
                        label = label.capitalize()
                    li = xbmcgui.ListItem(label=label)
                    li.setProperty('fanart_image', self.default_fanart)
                    # determine action route
                    action = actions['default']
                    if category in actions.keys():
                        action = actions[category]
                    # determine if the item should be selected
                    preselect_items.append((False, True)[category == self.get_main_menu_selection()])
                    url = build_url({'action': action, 'video_list_id': video_list_id, 'type': category})
                    xbmcplugin.addDirectoryItem(handle=self.plugin_handle, url=url, listitem=li, isFolder=True)

        # add recommendations/genres as subfolders (save us some space on the home page)
        i18n_ids = {
            'recommendations': self.get_local_string(30001),
            'genres': self.get_local_string(30010)
        }
        for type in i18n_ids.keys():
            # determine if the lists have contents
            if len(video_list_ids[type]) > 0:
                # determine action route
                action = actions['default']
                if type in actions.keys():
                    action = actions[type]
                # determine if the item should be selected
                preselect_items.append((False, True)[type == self.get_main_menu_selection()])
                li_rec = xbmcgui.ListItem(label=i18n_ids[type])
                li_rec.setProperty('fanart_image', self.default_fanart)
                url_rec = build_url({'action': action, 'type': type})
                xbmcplugin.addDirectoryItem(handle=self.plugin_handle, url=url_rec, listitem=li_rec, isFolder=True)

        # add search as subfolder
        action = actions['default']
        if 'search' in actions.keys():
            action = actions[type]
        li_rec = xbmcgui.ListItem(label=self.get_local_string(30011))
        li_rec.setProperty('fanart_image', self.default_fanart)
        url_rec = build_url({'action': action, 'type': 'search'})
        xbmcplugin.addDirectoryItem(handle=self.plugin_handle, url=url_rec, listitem=li_rec, isFolder=True)

        # no srting & close
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_UNSORTED)
        xbmcplugin.endOfDirectory(self.plugin_handle)
        self.set_custom_view('folder')

        # (re)select the previously selected main menu entry
        idx = 1
        for item in preselect_items:
            idx += 1
            preselected_list_item = idx if item else None
        preselected_list_item = idx + 1 if self.get_main_menu_selection() == 'search' else preselected_list_item
        if preselected_list_item != None:
            xbmc.executebuiltin('ActivateWindowAndFocus(%s, %s)' % (str(xbmcgui.Window(xbmcgui.getCurrentWindowId()).getFocusId()), str(preselected_list_item)))
        return True

    def build_video_listing (self, video_list, actions, type, build_url):
        """Builds the video lists (my list, continue watching, etc.) contents Kodi screen

        Parameters
        ----------
        video_list_ids : :obj:`dict` of :obj:`str`
            List of video lists

        actions : :obj:`dict` of :obj:`str`
            Dictionary of actions to build subsequent routes

        type : :obj:`str`
            None or 'queue' f.e. when itÂ´s a special video lists

        build_url : :obj:`fn`
            Function to build the subsequent routes

        Returns
        -------
        bool
            List could be build
        """
        for video_list_id in video_list:
            video = video_list[video_list_id]
            li = xbmcgui.ListItem(label=video['title'])
            # add some art to the item
            li = self._generate_art_info(entry=video, li=li)
            # add list item info
            li, infos = self._generate_entry_info(entry=video, li=li)
            li = self._generate_context_menu_items(entry=video, li=li)
            # lists can be mixed with shows & movies, therefor we need to check if its a movie, so play it right away
            if video_list[video_list_id]['type'] == 'movie':
                # itÂ´s a movie, so we need no subfolder & a route to play it
                isFolder = False
                url = build_url({'action': 'play_video', 'video_id': video_list_id, 'infoLabels': infos})
                self.set_custom_view('movie')
            else:
                # it´s a show, so we need a subfolder & route (for seasons)
                isFolder = True
                params = {'action': actions[video['type']], 'show_id': video_list_id}
                if 'tvshowtitle' in infos:
                    params['tvshowtitle'] = base64.urlsafe_b64encode(infos.get('tvshowtitle', '').encode('utf-8'))
                url = build_url(params)
                self.set_custom_view('show')
            xbmcplugin.addDirectoryItem(handle=self.plugin_handle, url=url, listitem=li, isFolder=isFolder)

        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_LABEL)
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_TITLE)
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_VIDEO_YEAR)
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_GENRE)
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_LASTPLAYED)
        xbmcplugin.endOfDirectory(self.plugin_handle)
        return True

    def build_search_result_listing (self, video_list, actions, build_url):
        """Builds the search results list Kodi screen

        Parameters
        ----------
        video_list : :obj:`dict` of :obj:`str`
            List of videos or shows

        actions : :obj:`dict` of :obj:`str`
            Dictionary of actions to build subsequent routes

        build_url : :obj:`fn`
            Function to build the subsequent routes

        Returns
        -------
        bool
            List could be build
        """
        return self.build_video_listing(video_list=video_list, actions=actions, type='search', build_url=build_url)

    def build_no_seasons_available (self):
        """Builds the season list screen if no seasons could be found

        Returns
        -------
        bool
            List could be build
        """
        self.show_no_seasons_notification()
        xbmcplugin.endOfDirectory(self.plugin_handle)
        return True

    def build_no_search_results_available (self, build_url, action):
        """Builds the search results screen if no matches could be found

        Parameters
        ----------
        action : :obj:`str`
            Action paramter to build the subsequent routes

        build_url : :obj:`fn`
            Function to build the subsequent routes

        Returns
        -------
        bool
            List could be build
        """
        self.show_no_search_results_notification()
        return xbmcplugin.endOfDirectory(self.plugin_handle)

    def build_user_sub_listing (self, video_list_ids, type, action, build_url):
        """Builds the video lists screen for user subfolders (genres & recommendations)

        Parameters
        ----------
        video_list_ids : :obj:`dict` of :obj:`str`
            List of video lists

        type : :obj:`str`
            List type (genre or recommendation)

        action : :obj:`str`
            Action paramter to build the subsequent routes

        build_url : :obj:`fn`
            Function to build the subsequent routes

        Returns
        -------
        bool
            List could be build
        """
        for video_list_id in video_list_ids:
            li = xbmcgui.ListItem(video_list_ids[video_list_id]['displayName'])
            li.setProperty('fanart_image', self.default_fanart)
            url = build_url({'action': action, 'video_list_id': video_list_id})
            xbmcplugin.addDirectoryItem(handle=self.plugin_handle, url=url, listitem=li, isFolder=True)

        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_LABEL)
        xbmcplugin.endOfDirectory(self.plugin_handle)
        self.set_custom_view('folder')
        return True

    def build_season_listing (self, seasons_sorted, build_url):
        """Builds the season list screen for a show

        Parameters
        ----------
        seasons_sorted : :obj:`list` of :obj:`dict` of :obj:`str`
            Sorted list of season entries

        build_url : :obj:`fn`
            Function to build the subsequent routes

        Returns
        -------
        bool
            List could be build
        """
        for season in seasons_sorted:
            li = xbmcgui.ListItem(label=season['text'])
            # add some art to the item
            li = self._generate_art_info(entry=season, li=li)
            # add list item info
            li, infos = self._generate_entry_info(entry=season, li=li, base_info={'mediatype': 'season'})
            li = self._generate_context_menu_items(entry=season, li=li)
            params = {'action': 'episode_list', 'season_id': season['id']}
            if 'tvshowtitle' in infos:
                params['tvshowtitle'] = base64.urlsafe_b64encode(infos.get('tvshowtitle', '').encode('utf-8'))
            url = build_url(params)
            xbmcplugin.addDirectoryItem(handle=self.plugin_handle, url=url, listitem=li, isFolder=True)

        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_NONE)
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_VIDEO_YEAR)
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_LABEL)
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_LASTPLAYED)
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_TITLE)
        xbmcplugin.endOfDirectory(self.plugin_handle)
        self.set_custom_view('season')
        return True

    def build_episode_listing (self, episodes_sorted, build_url):
        """Builds the episode list screen for a season of a show

        Parameters
        ----------
        episodes_sorted : :obj:`list` of :obj:`dict` of :obj:`str`
            Sorted list of episode entries

        build_url : :obj:`fn`
            Function to build the subsequent routes

        Returns
        -------
        bool
            List could be build
        """
        for episode in episodes_sorted:
            li = xbmcgui.ListItem(label=episode['title'])
            # add some art to the item
            li = self._generate_art_info(entry=episode, li=li)
            # add list item info
            li, infos = self._generate_entry_info(entry=episode, li=li, base_info={'mediatype': 'episode'})
            li = self._generate_context_menu_items(entry=episode, li=li)
            url = build_url({'action': 'play_video', 'video_id': episode['id'], 'start_offset': episode['bookmark'], 'infoLabels': infos})
            xbmcplugin.addDirectoryItem(handle=self.plugin_handle, url=url, listitem=li, isFolder=False)

        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_EPISODE)
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_NONE)
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_VIDEO_YEAR)
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_LABEL)
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_LASTPLAYED)
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_TITLE)
        xbmcplugin.addSortMethod(handle=self.plugin_handle, sortMethod=xbmcplugin.SORT_METHOD_DURATION)
        xbmcplugin.endOfDirectory(self.plugin_handle)
        self.set_custom_view('episode')
        return True

    def play_item (self, esn, video_id, start_offset=-1, infoLabels={}):
        """Plays a video

        Parameters
        ----------
        esn : :obj:`str`
            ESN needed for Widevine/Inputstream

        video_id : :obj:`str`
            ID of the video that should be played

        start_offset : :obj:`str`
            Offset to resume playback from (in seconds)
        
        infoLabels : :obj:`str`
            the listitem's infoLabels

        Returns
        -------
{'fraction_non_alphanumeric': 0.10306189938917636, 'fraction_numerical': 0.005213399214099522, 'mean_word_length': 2.2730803514580415, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5871585', 'n_tokens_mistral': 14995, 'n_tokens_neox': 14510, 'n_words': 3393}