class SimpleCache(object):
    """simple stateless caching system for Kodi"""
    enable_mem_cache = True
    global_checksum = None
    _exit = False
    _auto_clean_interval = datetime.timedelta(hours=4)
    _win = None
    _busy_tasks = []
    _database = None
    _enabled = True

    def __init__(self, enabled=True):
        """Initialize our caching class"""
        self._enabled = enabled
        self._win = xbmcgui.Window(10000)
        self._monitor = xbmc.Monitor()
        self.check_cleanup()
        self._log_msg("Initialized")

    def close(self):
        """tell any tasks to stop immediately (as we can be called multithreaded) and cleanup objects"""
        self._exit = True
        # wait for all tasks to complete
        while self._busy_tasks and not self._monitor.abortRequested():
            xbmc.sleep(25)
        del self._win
        del self._monitor
        self._log_msg("Closed")

    def __del__(self):
        """make sure close is called"""
        if not self._exit:
            self.close()

    def get(self, endpoint, checksum=""):
        """
            get object from cache and return the results
            endpoint: the (unique) name of the cache object as reference
            checkum: optional argument to check if the checksum in the cacheobject matches the checkum provided
        """
        if self._enabled is not True:
            return None
        checksum = self._get_checksum(checksum)
        cur_time = self._get_timestamp(datetime.datetime.now())
        result = None
        # 1: try memory cache first
        if self.enable_mem_cache:
            result = self._get_mem_cache(endpoint, checksum, cur_time)

        # 2: fallback to _database cache
        if result is None:
            result = self._get_db_cache(endpoint, checksum, cur_time)

        return result

    def set(self, endpoint, data, checksum="", expiration=datetime.timedelta(seconds=3600)):
        """
            set data in cache
        """
        task_name = "set.%s" % endpoint
        self._busy_tasks.append(task_name)
        checksum = self._get_checksum(checksum)
        expires = self._get_timestamp(datetime.datetime.now() + expiration)

        # memory cache: write to window property
        if self.enable_mem_cache and not self._exit:
            self._set_mem_cache(endpoint, checksum, expires, data)

        # db cache
        if not self._exit:
            self._set_db_cache(endpoint, checksum, expires, data)

        # remove this task from list
        self._busy_tasks.remove(task_name)

    def check_cleanup(self):
        """check if cleanup is needed - public method, may be called by calling addon"""
        cur_time = datetime.datetime.now()
        lastexecuted = self._win.getProperty("simplecache.clean.lastexecuted")
        if not lastexecuted:
            self._win.setProperty("simplecache.clean.lastexecuted", repr(cur_time))
        elif (eval(lastexecuted) + self._auto_clean_interval) < cur_time:
            # cleanup needed...
            self._do_cleanup()

    def _get_mem_cache(self, endpoint, checksum, cur_time):
        """
            get cache data from memory cache
            we use window properties because we need to be stateless
        """
        result = None
        cachedata = self._win.getProperty(endpoint)

        if cachedata:
            cachedata = eval(cachedata)
            if cachedata[0] > cur_time:
                if not checksum or checksum == cachedata[2]:
                    result = cachedata[1]
        return result

    def _set_mem_cache(self, endpoint, checksum, expires, data):
        """
            window property cache as alternative for memory cache
            usefull for (stateless) plugins
        """
        cachedata = (expires, data, checksum)
        cachedata_str = repr(cachedata)
        self._win.setProperty(endpoint, cachedata_str)

    def _get_db_cache(self, endpoint, checksum, cur_time):
        """get cache data from sqllite _database"""
        result = None
        query = "SELECT expires, data, checksum FROM simplecache WHERE id = ?"
        cache_data = self._execute_sql(query, (endpoint,))
        if cache_data:
            cache_data = cache_data.fetchone()
            if cache_data and cache_data[0] > cur_time:
                if not checksum or cache_data[2] == checksum:
                    result = eval(cache_data[1])
                    # also set result in memory cache for further access
                    if self.enable_mem_cache:
                        self._set_mem_cache(endpoint, checksum, cache_data[0], result)
        return result

    def _set_db_cache(self, endpoint, checksum, expires, data):
        """ store cache data in _database """
        query = "INSERT OR REPLACE INTO simplecache( id, expires, data, checksum) VALUES (?, ?, ?, ?)"
        data = repr(data)
        self._execute_sql(query, (endpoint, expires, data, checksum))

    def _do_cleanup(self):
        """perform cleanup task"""
        if self._exit or self._monitor.abortRequested():
            return
        self._busy_tasks.append(__name__)
        cur_time = datetime.datetime.now()
        cur_timestamp = self._get_timestamp(cur_time)
        self._log_msg("Running cleanup...")
        if self._win.getProperty("simplecachecleanbusy"):
            return
        self._win.setProperty("simplecachecleanbusy", "busy")

        query = "SELECT id, expires FROM simplecache"
        for cache_data in self._execute_sql(query).fetchall():
            cache_id = cache_data[0]
            cache_expires = cache_data[1]

            if self._exit or self._monitor.abortRequested():
                return

            # always cleanup all memory objects on each interval
            self._win.clearProperty(cache_id)

            # clean up db cache object only if expired
            if cache_expires < cur_timestamp:
                query = 'DELETE FROM simplecache WHERE id = ?'
                self._execute_sql(query, (cache_id,))
                self._log_msg("delete from db %s" % cache_id)

        # compact db
        self._execute_sql("VACUUM")

        # remove task from list
        self._busy_tasks.remove(__name__)
        self._win.setProperty("simplecache.clean.lastexecuted", repr(cur_time))
        self._win.clearProperty("simplecachecleanbusy")
        self._log_msg("Auto cleanup done")

    def _get_database(self):
        """get reference to our sqllite _database - performs basic integrity check"""
        dbpath = ADDON.getAddonInfo('profile')
        dbfile = xbmcvfs.translatePath("%s/simplecache.db" % dbpath)

        if not xbmcvfs.exists(dbpath):
            xbmcvfs.mkdirs(dbpath)
        try:
            connection = sqlite3.connect(dbfile, timeout=30, isolation_level=None)
            connection.execute('SELECT * FROM simplecache LIMIT 1')
            return connection
        except Exception as error:
            # our _database is corrupt or doesn't exist yet, we simply try to recreate it
            if xbmcvfs.exists(dbfile):
                xbmcvfs.delete(dbfile)
            try:
                connection = sqlite3.connect(dbfile, timeout=30, isolation_level=None)
                connection.execute(
                    """CREATE TABLE IF NOT EXISTS simplecache(
                    id TEXT UNIQUE, expires INTEGER, data TEXT, checksum INTEGER)""")
                return connection
            except Exception as error:
                self._log_msg("Exception while initializing _database: %s" % str(error), xbmc.LOGWARNING)
                self.close()
                return None

    def _execute_sql(self, query, data=None):
        """little wrapper around execute and executemany to just retry a db command if db is locked"""
        retries = 0
        result = None
        error = None
        # always use new db object because we need to be sure that data is available for other simplecache instances
        with self._get_database() as _database:
            while not retries == 10 and not self._monitor.abortRequested():
                if self._exit:
                    return None
                try:
                    if isinstance(data, list):
                        result = _database.executemany(query, data)
                    elif data:
                        result = _database.execute(query, data)
                    else:
                        result = _database.execute(query)
                    return result
                except sqlite3.OperationalError as error:
                    if "_database is locked" in error:
                        self._log_msg("retrying DB commit...")
                        retries += 1
                        self._monitor.waitForAbort(0.5)
                    else:
                        break
                except Exception as error:
                    break
            self._log_msg("_database ERROR ! -- %s" % str(error), xbmc.LOGWARNING)
        return None

    @staticmethod
    def _log_msg(msg, loglevel=xbmc.LOGDEBUG):
        """helper to send a message to the kodi log"""
        debug("CACHE {}".format(msg))

    @staticmethod
    def _get_timestamp(date_time):
        """Converts a datetime object to unix timestamp"""
        return int(time.mktime(date_time.timetuple()))

    def _get_checksum(self, stringinput):
        """get int checksum from string"""
        if not stringinput and not self.global_checksum:
            return 0
        if self.global_checksum:
            stringinput = "%s-%s" % (self.global_checksum, stringinput)
        else:
            stringinput = str(stringinput)
        return reduce(lambda x, y: x + y, map(ord, stringinput)) def use_cache(cache_days=14):
    """
        wrapper around our simple cache to use as decorator
        Usage: define an instance of SimpleCache with name "cache" (self.cache) in your class
        Any method that needs caching just add @use_cache as decorator
        NOTE: use unnamed arguments for calling the method and named arguments for optional settings
    """

    def decorator(func):
        """our decorator"""

        def decorated(*args, **kwargs):
            """process the original method and apply caching of the results"""
            method_class = args[0]
            method_class_name = method_class.__class__.__name__
            cache_str = "%s.%s" % (method_class_name, func.__name__)
            # cache identifier is based on positional args only
            # named args are considered optional and ignored
            for item in args[1:]:
                cache_str += u".%s" % item
            cache_str = cache_str.lower()
            cachedata = method_class.cache.get(cache_str)
            global_cache_ignore = False
            try:
                global_cache_ignore = method_class.ignore_cache
            except Exception:
                pass
            if cachedata is not None and not kwargs.get("ignore_cache", False) and not global_cache_ignore:
                return cachedata
            else:
                result = func(*args, **kwargs)
                method_class.cache.set(cache_str, result, expiration=datetime.timedelta(days=cache_days))
                return result

        return decorated

    return decorator def debug(text):
    xbmc.log('[{}] {}'.format(APP, text), xbmc.LOGDEBUG) def info(text):
    xbmc.log('[{}] {}'.format(APP, text), xbmc.LOGINFO) BASE_URL = 'https://stream-cinema.online/kodi' API_VERSION = '2.0' class SC:
    ACTION = 'action'
    ACTION_ADD2HP = 'add2hp'
    ACTION_ADD_CUSTOM_FILTER = 'add_custom_filter'
    ACTION_ADD_TO_LIBRARY = 'add_to_library'
    ACTION_ADD_TO_LIBRARY_WITH_SUBSCRIPTION = 'add_to_library_sub'
    ACTION_ANDROID = 'android'
    ACTION_BUFFER = 'buffer'
    ACTION_CMD = 'cmd'
    ACTION_CSEARCH = 'csearch'
    ACTION_DEBUG = 'debug'
    ACTION_DEL2HP = 'del2hp'
    ACTION_DEL_CUSTOM_FILTER = 'del_custom_filter'
    ACTION_DEL_PREFERRED_LANGUAGE = 'sel_preferred_language'
    ACTION_DOWNLOAD = 'down'
    ACTION_LAST = 'last'
    ACTION_PIN = 'pin'
    ACTION_PLAY_URL = 'playUrl'
    ACTION_REMOVE_FROM_LIST = 'remove_from_list'
    ACTION_REMOVE_FROM_SUBSCRIPTION = 'remove_from_sub'
    ACTION_SELECT_STREAM = 'selectStream'
    ACTION_SET_PREFERRED_LANGUAGE = 'set_preferred_language'
    ACTION_UPDATE_ADDON = 'update_addon'
    CMD = 'cmd'
    DEFAULT_LANG = 'cs'
    ITEM_ACTION = 'action'
    ITEM_ART = 'art'
    ITEM_AUDIO_INFO = 'ainfo'
    ITEM_BITRATE = 'bitrate'
    ITEM_CMD = 'cmd'
    ITEM_CUSTOM_FILTER = 'custom'
    ITEM_DIR = 'dir'
    ITEM_DOWNLOAD = 'down'
    ITEM_FOCUS = 'focus'
    ITEM_HPDIR = 'hpdir'
    ITEM_I18N_ART = 'i18n_art'
    ITEM_I18N_INFO = 'i18n_info'
    ITEM_ID = 'id'
    ITEM_IDENT = 'ident'
    ITEM_INFO = 'info'
    ITEM_LIBRARY = 'library'
    ITEM_LIBRARY_SUB = 'library_sub'
    ITEM_LANG = 'lang'
    ITEM_MENU = 'menu'
    ITEM_NEXT = 'next'
    ITEM_PAGE = 'page'
    ITEM_PREFERRED_LANG = 'pref_lang.{}'
    ITEM_PROVIDER = 'provider'
    ITEM_QUALITY = 'quality'
    ITEM_SIZE = 'size'
    ITEM_STRMS = 'strms'
    ITEM_SUBS = 'subs'
    ITEM_SYSTEM = 'system'
    ITEM_TITLE = 'title'
    ITEM_TYPE = 'type'
    ITEM_URL = 'url'
    ITEM_UIDS = 'unique_ids'
    ITEM_VIDEO = 'video'
    ITEM_VIDEO_INFO = 'vinfo'
    ITEM_VISIBLE = 'visible'
    ITEM_WIDGET = 'widget'
    MEDIA_TYPE = 'mediatype'
    MEDIA_TYPE_AUDIO = 'audio'
    MEDIA_TYPE_EPISODE = 'episode'
    MEDIA_TYPE_FILE = 'file'
    MEDIA_TYPE_SEASON = 'season'
    MEDIA_TYPE_TV_SHOW = 'tvshow'
    MEDIA_TYPE_VIDEO = 'video'
    NEXT = 'next'
    PROVIDER = 'kraska'
    RUN_PLUGIN = 'RunPlugin({})'
    TXT_PINNED = 'p-{}'
    TXT_CUSTOM_FORMAT = 'custom-{}'
    TRAKT_CACHE_WATCHET_ACTIVITY = 'SC:watched:activity' ADDON = xbmcaddon.Addon(id=ADDON_ID) def get_uuid():
    # pylint: disable=global-statement
    global __CRYPT_KEY__
    if not __CRYPT_KEY__:
        __CRYPT_KEY__ = _get_system_uuid()
    return __CRYPT_KEY__ def get_skin_name():
    return xbmc.getSkinDir() def get_setting_as_bool(key):
    try:
        return addon.getSettingBool(key)
    except:
        return get_setting(key) == 'True' or get_setting(key) == 'true' def get_setting_as_int(key):
    try:
        return addon.getSettingInt(key)
    except:
        return int(addon.getSetting(key)) if addon.getSetting(key) else None def get_setting(key):
    return addon.getSetting(key) def user_agent(system=True):
    if system is False or not hasattr(xbmc, 'getUserAgent'):

        version = SYSTEM_BUILD_VERSION
        sp = version.find(' ')
        if sp > 0:
            version = version[:sp]

        platform = get_system_platform()
        app = get_app_name()

        if platform == 'linux':
            useragent = '{}/{} (X11; U; Linux i686)'
        elif platform == 'android':
            useragent = '{}/{} (Linux; Android)'
        elif platform == 'windows':
            useragent = '{}/{} (Windows; U; Windows NT)'
        elif platform == 'ios':
            useragent = '{}/{} (iPhone; CPU iPhone OS like Mac OS X)'
        elif platform == 'osx':
            useragent = '{}/{} (Macintosh; Intel Mac OS X)'
        else:
            useragent = '{}/{} (X11; U; Unknown i686)'
        useragent = useragent.format(app, version)
    else:
        useragent = xbmc.getUserAgent()

    return '{0} ({1}; ver{2})'.format(
        useragent,
        SYSTEM_LANG_CODE,
        addon.getAddonInfo('version')
    ) class Http:
    @staticmethod
    def request(method, url, timeout=HTTP.TIMEOUT, **kwargs):
        # info('URL {}'.format(url))
        ret = Http.req().request(
            method=method,
            url=url,
            timeout=timeout,
            **kwargs
        )
        # info('url req head: {}'.format(ret.request.headers))
        # info('url res head: {}'.format(ret.headers))
        debug('Http url: {}'.format(ret.url))
        try:
            ret.raise_for_status()
        except requests.exceptions.HTTPError as e:
            ret = e.response

        return ret

    @staticmethod
    def req():
        return requests

    @staticmethod
    def get(url, **kwargs):
        return Http.request(HTTP.GET, url, **kwargs)

    @staticmethod
    def post(url, **kwargs):
        return Http.request(HTTP.POST, url, **kwargs)

    @staticmethod
    def head(url, **kwargs):
        return Http.request(HTTP.HEAD, url, **kwargs)

    @staticmethod
    def delete(url, **kwargs):
        return Http.request(HTTP.DELETE, url, **kwargs)

    @staticmethod
    def put(url, **kwargs):
        return Http.request(HTTP.PUT, url, **kwargs)

    @staticmethod
    def patch(url, **kwargs):
        return Http.request(HTTP.PATCH, url, **kwargs) SYSTEM_LANG_CODE = get_language_code()
import datetime
import time
from resources.lib.common.cache import SimpleCache, use_cache
from resources.lib.common.logger import debug, info
from resources.lib.constants import BASE_URL, API_VERSION, SC, ADDON
from resources.lib.kodiutils import get_uuid, get_skin_name, get_setting_as_bool, get_setting_as_int, get_setting
from resources.lib.system import user_agent, Http, SYSTEM_LANG_CODE
    from urllib.parse import urlparse, parse_qs
    from urlparse import urlparse, parse_qs
# -*- coding: utf-8 -*-

from __future__ import print_function, unicode_literals



try:
    # Python 3
except ImportError:
    # Python 2


class Sc:
    RATING_MAP = {
        "0": 0,
        "1": 6,
        "2": 12,
        "3": 15,
        "4": 18,
    }

    cache = SimpleCache()

    @staticmethod
    def get(path, params=None, ttl=None):
        sorted_values, url = Sc.prepare(params, path)
        key = '{}{}{}'.format(ADDON.getAddonInfo('version'), url, sorted_values)
        debug('CALL {} PARAMS {} KEY {}'.format(url, sorted_values, key))
        start = time.time()
        ret = Sc.cache.get(key)
        if ret is None:
            start = time.time()
            res = Http.get(url, headers=Sc.headers(), params=sorted_values)
            end = time.time()
            res.raise_for_status()
            ret = res.json()
            Sc.save_cache(ret, key, ttl)
        else:
            debug('GET from cache'.format())
            end = time.time()

        debug('GET took {0:.2f}ms'.format((end - start) * 1000))
        return ret

    @staticmethod
    def prepare(params, path):
        url = BASE_URL + path
        o = urlparse(url)
        query = parse_qs(o.query)
        url = o._replace(query=None).geturl()
        p = Sc.default_params(query)
        # debug('p: {}'.format(p))
        query.update(p)
        if params is not None:
            query.update(params)
        sorted_values = sorted(query.items(), key=lambda val: val[0])
        # debug('sorted: {}'.format(sorted_values))
        return sorted_values, url

    @staticmethod
    def post(path, **kwargs):
        sorted_values, url = Sc.prepare(path=path, params={})
        start = time.time()
        res = Http.post(url, params=sorted_values, headers=Sc.headers(), **kwargs)
        end = time.time()
        debug('POST took {0:.2f}ms'.format((end - start) * 1000))
        return res.json()

    @staticmethod
    def default_params(query):
        params = {
            'ver': API_VERSION,
            'uid': get_uuid(),
            'skin': get_skin_name(),
            'lang': SYSTEM_LANG_CODE,
        }
        # plugin_url = 'plugin://{}/{}'.format(ADDON_ID, query.params.orig_args if query.params.orig_args else '')
        # try:
        #     kv = KodiViewModeDb()
        #     sort = kv.get_sort(plugin_url)
        # except:
        #     sort = (0, 1)
        # try:
        #     if sort is not None:
        #         params.update({'sm': '{},{}'.format(sort[0], sort[1])})
        # except:
        #     debug('ERR API SORT: {}'.format(traceback.format_exc()))
        #     pass
        parental_control = Sc.parental_control_is_active()
        if get_setting_as_bool('stream.dubed') or (parental_control and get_setting_as_bool('parental.control.dubed')):
            params.update({'dub': 1})

        if not parental_control and get_setting_as_bool('stream.dubed.titles'):
            params.update({'dub': 1, "tit": 1})

        if parental_control:
            params.update({"m": Sc.RATING_MAP.get(get_setting('parental.control.rating'))})

        if get_setting_as_bool('plugin.show.genre'):
            params.update({'gen': 1})

        if 'HDR' not in query:
            params.update({'HDR':  0 if get_setting_as_bool('stream.exclude.hdr') else 1})

        if 'DV' not in query:
            params.update({'DV':  0 if get_setting_as_bool('stream.exclude.dolbyvision') else 1})

        if get_setting_as_bool('plugin.show.old.menu'):
            params.update({'old': 1})

        return params

    @staticmethod
    def parental_control_is_active():
        now = datetime.datetime.now()

        hour_start = get_setting_as_int('parental.control.start')