diff --git a/addon.py b/addon.py index 4c35c29be..48015b0f3 100644 --- a/addon.py +++ b/addon.py @@ -52,7 +52,7 @@ def router(params_string): _favorites.unfollow(program=params.get('program'), path=params.get('path')) return if action == actions.REFRESH_FAVORITES: - _favorites.update_favorites() + _favorites.get_favorites(ttl=0) return from resources.lib.vrtplayer import vrtapihelper, vrtplayer diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index 55c3df3cc..f9abe76df 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -339,26 +339,30 @@ msgid "Refresh favorites" msgstr "" msgctxt "#30867" -msgid "Streaming" +msgid "Enable HTTP caching [COLOR gray][I](experimental)[/I][/COLOR]" msgstr "" msgctxt "#30869" -msgid "Use InputStream Adaptive" +msgid "Streaming" msgstr "" msgctxt "#30871" -msgid "InputStream Adaptive settings..." +msgid "Use InputStream Adaptive" msgstr "" msgctxt "#30873" -msgid "Install Widevine... [COLOR gray][I](needed for DRM content)[/I][/COLOR]" +msgid "InputStream Adaptive settings..." msgstr "" msgctxt "#30875" -msgid "Logging" +msgid "Install Widevine... [COLOR gray][I](needed for DRM content)[/I][/COLOR]" msgstr "" msgctxt "#30877" +msgid "Logging" +msgstr "" + +msgctxt "#30879" msgid "Log level" msgstr "" diff --git a/resources/language/resource.language.nl_nl/strings.po b/resources/language/resource.language.nl_nl/strings.po index 7f0568406..5700f378a 100644 --- a/resources/language/resource.language.nl_nl/strings.po +++ b/resources/language/resource.language.nl_nl/strings.po @@ -315,27 +315,31 @@ msgctxt "#30065" msgid "Refresh favorites" msgstr "Ververs gevolgde programma's" -msgctxt "#30067" +msgctxt "#30867" +msgid "Enable HTTP caching [COLOR gray][I](experimental)[/I][/COLOR]" +msgstr "Gebruik HTTP caching [COLOR gray][I](experimenteel)[/I][/COLOR]" + +msgctxt "#30069" msgid "Streaming" msgstr "Streaming" -msgctxt "#30869" +msgctxt "#30870" msgid "Use InputStream Adaptive" msgstr "Gebruik InputStream Adaptive" -msgctxt "#30071" +msgctxt "#30073" msgid "InputStream Adaptive settings..." msgstr "InputStream Adaptive instellingen..." -msgctxt "#30073" +msgctxt "#30075" msgid "Install Widevine... [COLOR gray][I](needed for DRM content)[/I][/COLOR]" msgstr "Installeer Widevine... [COLOR gray][I](nodig voor DRM content)[/I][/COLOR]" -msgctxt "#30075" +msgctxt "#30077" msgid "Logging" msgstr "Logboek" -msgctxt "#30077" +msgctxt "#30079" msgid "Log level" msgstr "Log level" diff --git a/resources/lib/kodiwrappers/kodiwrapper.py b/resources/lib/kodiwrappers/kodiwrapper.py index 409c80141..6d4c4c303 100644 --- a/resources/lib/kodiwrappers/kodiwrapper.py +++ b/resources/lib/kodiwrappers/kodiwrapper.py @@ -106,11 +106,11 @@ def __init__(self, handle, url, addon): self._addon_id = addon.getAddonInfo('id') self._max_log_level = log_levels.get(self.get_setting('max_log_level'), 3) self._usemenucaching = self.get_setting('usemenucaching') == 'true' + self._cache_path = self.get_userdata_path() + 'cache/' self._system_locale_works = self.set_locale() def install_widevine(self): - import xbmcgui - ok = xbmcgui.Dialog().yesno(self.localize(30971), self.localize(30972)) + ok = self.show_yesno_dialog(heading=self.localize(30971), message=self.localize(30972)) if not ok: return try: @@ -227,7 +227,7 @@ def show_ok_dialog(self, heading='', message=''): import xbmcgui if not heading: heading = self._addon.getAddonInfo('name') - xbmcgui.Dialog().ok(heading=heading, message=message) + xbmcgui.Dialog().ok(heading=heading, line1=message) def show_notification(self, heading='', message='', icon='info', time=4000): import xbmcgui @@ -235,6 +235,12 @@ def show_notification(self, heading='', message='', icon='info', time=4000): heading = self._addon.getAddonInfo('name') xbmcgui.Dialog().notification(heading=heading, message=message, icon=icon, time=time) + def show_yesno_dialog(self, heading='', message=''): + import xbmcgui + if not heading: + heading = self._addon.getAddonInfo('name') + return xbmcgui.Dialog().yesno(heading=self.localize(30971), line1=self.localize(30972)) + def set_locale(self): import locale locale_lang = self.get_global_setting('locale.language').split('.')[-1] @@ -376,6 +382,56 @@ def delete_file(self, path): import xbmcvfs return xbmcvfs.delete(path) + def md5(self, path): + import hashlib + with self.open_file(path) as f: + return hashlib.md5(f.read().encode('utf-8')) + + def get_cache(self, path, ttl=None): + if self.get_setting('usehttpcaching') == 'false': + return None + + path = self._cache_path + path + if not self.check_if_path_exists(path): + return None + + import time + if ttl is None or self.stat_file(path).st_mtime() > time.mktime(time.localtime()) - ttl: + if ttl is None: + self.log_notice("Cache '%s' is forced from cache." % path, 'Debug') + else: + self.log_notice("Cache '%s' is fresh, within ttl of %s seconds." % (path, ttl), 'Debug') + with self.open_file(path) as f: + return f.read() + + return None + + def update_cache(self, path, data): + if self.get_setting('usehttpcaching') == 'false': + return + + import hashlib + path = self._cache_path + path + if self.check_if_path_exists(path): + md5 = self.md5(path) + else: + md5 = 0 + # Create cache directory if missing + if not self.check_if_path_exists(self._cache_path): + self.log_notice("Create path '%s'." % self._cache_path, 'Debug') + self.make_dir(self._cache_path) + + # Avoid writes if possible (i.e. SD cards) + if md5 != hashlib.md5(data): + self.log_notice("Write cache '%s'." % path, 'Debug') + with self.open_file(path, 'wb') as f: + f.write(data) + else: + # Update timestamp + import os + self.log_notice("Cache '%s' has not changed, updating mtime only." % path, 'Debug') + os.utime(path) + def container_refresh(self): self.log_notice('Execute: Container.Refresh', 'Debug') xbmc.executebuiltin('Container.Refresh') diff --git a/resources/lib/vrtplayer/favorites.py b/resources/lib/vrtplayer/favorites.py index c563f7cb9..1e251216a 100644 --- a/resources/lib/vrtplayer/favorites.py +++ b/resources/lib/vrtplayer/favorites.py @@ -4,7 +4,6 @@ from __future__ import absolute_import, division, unicode_literals import json -import time from resources.lib.vrtplayer import tokenresolver @@ -21,34 +20,34 @@ def __init__(self, _kodi): self._tokenresolver = tokenresolver.TokenResolver(_kodi) self._proxies = _kodi.get_proxies() install_opener(build_opener(ProxyHandler(self._proxies))) - self._cache_file = _kodi.get_userdata_path() + 'favorites.json' self._favorites = None if _kodi.get_setting('usefavorites') == 'true' and _kodi.has_credentials(): - self.get_favorites() + # Get favorites from cache if fresh + self.get_favorites(ttl=60 * 60) def is_activated(self): return self._favorites is not None - def get_favorites(self): - if self._kodi.check_if_path_exists(self._cache_file): - if self._kodi.stat_file(self._cache_file).st_mtime() > time.mktime(time.localtime()) - (2 * 60): - self._kodi.log_notice('CACHE: %s vs %s' % (self._kodi.stat_file(self._cache_file).st_mtime(), time.mktime(time.localtime()) - (5 * 60)), 'Debug') - with self._kodi.open_file(self._cache_file) as f: - self._favorites = json.loads(f.read()) - return - self.update_favorites() - - def update_favorites(self): - xvrttoken = self._tokenresolver.get_fav_xvrttoken() - headers = { - 'authorization': 'Bearer ' + xvrttoken, - 'content-type': 'application/json', - # 'Cookie': 'X-VRT-Token=' + xvrttoken, - 'Referer': 'https://www.vrt.be/vrtnu', - } - req = Request('https://video-user-data.vrt.be/favorites', headers=headers) - self._favorites = json.loads(urlopen(req).read()) - self.write_favorites() + def get_favorites(self, ttl=None): + data = self._kodi.get_cache('favorites.json', ttl) + if not data: + xvrttoken = self._tokenresolver.get_fav_xvrttoken() + headers = { + 'authorization': 'Bearer ' + xvrttoken, + 'content-type': 'application/json', + # 'Cookie': 'X-VRT-Token=' + xvrttoken, + 'Referer': 'https://www.vrt.be/vrtnu', + } + req = Request('https://video-user-data.vrt.be/favorites', headers=headers) + self._kodi.log_notice('URL post: https://video-user-data.vrt.be/favorites', 'Verbose') + try: + data = urlopen(req).read() + except Exception: + # Force favorites from cache + data = self._kodi.get_cache('favorites.json', ttl=None) + else: + self._kodi.update_cache('favorites.json', data) + self._favorites = json.loads(data) def set_favorite(self, program, path, value=True): if value is not self.is_favorite(path): @@ -69,11 +68,7 @@ def set_favorite(self, program, path, value=True): self._kodi.log_error("Failed to follow program '%s' at VRT NU" % path) # NOTE: Updates to favorites take a longer time to take effect, so we keep our own cache and use it self._favorites[self.uuid(path)] = dict(value=payload) - self.write_favorites() - - def write_favorites(self): - with self._kodi.open_file(self._cache_file, 'w') as f: - f.write(json.dumps(self._favorites)) + self._kodi.update_cache('favorites.json', json.dumps(self._favorites).encode('utf-8')) def is_favorite(self, path): value = False diff --git a/resources/lib/vrtplayer/tokenresolver.py b/resources/lib/vrtplayer/tokenresolver.py index f166d39f3..bacca5032 100644 --- a/resources/lib/vrtplayer/tokenresolver.py +++ b/resources/lib/vrtplayer/tokenresolver.py @@ -100,10 +100,10 @@ def _get_cached_token(self, path, token_name): now = datetime.now(dateutil.tz.tzlocal()) exp = dateutil.parser.parse(token.get('expirationDate')) if exp > now: - self._kodi.log_notice('Got cached token', 'Verbose') + self._kodi.log_notice("Got cached token '%s'" % path, 'Verbose') cached_token = token.get(token_name) else: - self._kodi.log_notice('Cached token deleted', 'Verbose') + self._kodi.log_notice("Cached token '%s' deleted" % path, 'Verbose') self._kodi.delete_file(path) return cached_token diff --git a/resources/lib/vrtplayer/tvguide.py b/resources/lib/vrtplayer/tvguide.py index c24faed45..215c0a040 100644 --- a/resources/lib/vrtplayer/tvguide.py +++ b/resources/lib/vrtplayer/tvguide.py @@ -126,8 +126,20 @@ def show_episodes(self, date, channel): epg += timedelta(days=-1) datelong = self._kodi.localize_datelong(epg) api_url = epg.strftime(self.VRT_TVGUIDE) - self._kodi.log_notice('URL get: ' + api_url, 'Verbose') - schedule = json.loads(urlopen(api_url).read()) + + if date in ('today', 'yesterday', 'tomorrow'): + cache_file = 'schedule.%s.json' % date + # Try the cache if it is fresh + data = self._kodi.get_cache(cache_file, ttl=60 * 60) + if data: + schedule = json.loads(data) + else: + self._kodi.log_notice('URL get: ' + api_url, 'Verbose') + schedule = json.loads(urlopen(api_url).read()) + self._kodi.update_cache(cache_file, json.dumps(schedule)) + else: + schedule = json.loads(urlopen(api_url).read()) + name = channel try: channel = next(c for c in CHANNELS if c.get('name') == name) @@ -160,7 +172,7 @@ def show_episodes(self, date, channel): if url: video_url = statichelper.add_https_method(url) url_dict = dict(action=actions.PLAY, video_url=video_url) - if start_date < now <= end_date: # Now playing + if start_date <= now <= end_date: # Now playing metadata.title = '[COLOR yellow]%s[/COLOR] %s' % (label, self._kodi.localize(30302)) else: metadata.title = label diff --git a/resources/lib/vrtplayer/vrtapihelper.py b/resources/lib/vrtplayer/vrtapihelper.py index 6c95e65c5..988e6ccf3 100644 --- a/resources/lib/vrtplayer/vrtapihelper.py +++ b/resources/lib/vrtplayer/vrtapihelper.py @@ -34,17 +34,25 @@ def get_tvshow_items(self, category=None, channel=None, filtered=False): if category: params['facets[categories]'] = category + cache_file = 'category.%s.json' % category if channel: params['facets[programBrands]'] = channel + cache_file = 'channel.%s.json' % channel # If no facet-selection is done, we return the A-Z listing if not category and not channel: params['facets[transcodingStatus]'] = 'AVAILABLE' + cache_file = 'programs.json' api_url = self._VRTNU_SUGGEST_URL + '?' + urlencode(params) - self._kodi.log_notice('URL get: ' + unquote(api_url), 'Verbose') - api_json = json.loads(urlopen(api_url).read()) + # Try the cache if it is fresh + data = self._kodi.get_cache(cache_file, ttl=60 * 60) + if not data: + self._kodi.log_notice('URL get: ' + unquote(api_url), 'Verbose') + data = urlopen(api_url).read() + self._kodi.update_cache(cache_file, data) + api_json = json.loads(data) return self._map_to_tvshow_items(api_json, filtered=statichelper.is_filtered(filtered)) def _map_to_tvshow_items(self, tvshows, filtered=False): diff --git a/resources/lib/vrtplayer/vrtplayer.py b/resources/lib/vrtplayer/vrtplayer.py index ac69dc908..abcab4862 100644 --- a/resources/lib/vrtplayer/vrtplayer.py +++ b/resources/lib/vrtplayer/vrtplayer.py @@ -109,7 +109,7 @@ def show_tvshow_menu_items(self, category=None, filtered=False): self._kodi.show_listing(tvshow_items, sort='label', content='tvshows') def show_category_menu_items(self): - category_items = self.__get_category_menu_items() + category_items = self.get_category_menu_items() self._kodi.show_listing(category_items, sort='label', content='files') def show_channels_menu_items(self, channel=None): @@ -250,13 +250,31 @@ def search(self, search_string=None, page=None): self._kodi.container_update(replace=True) self._kodi.show_listing(search_items, sort=sort, ascending=ascending, content=content, cache=False) - def __get_category_menu_items(self): - try: - categories = self.get_categories(self._proxies) - except Exception: - categories = [] + def get_category_menu_items(self): + import json + categories = [] + + # Try the cache if it is fresh + data = self._kodi.get_cache('categories.json', ttl=7 * 24 * 60 * 60) + if data: + categories = json.loads(data) + + # Try to scrape from the web + if not categories: + try: + categories = self.get_categories(self._proxies) + except Exception: + categories = [] + else: + self._kodi.update_cache('categories.json', json.dumps(categories)) + + # Use the cache anyway (better than hard-coded) + if not categories: + data = self._kodi.get_cache('categories.json', ttl=None) + if data: + categories = json.loads(data) - # Fallback to internal categories if web-scraping fails + # Fall back to internal hard-coded categories if all else fails if not categories: from resources.lib.vrtplayer import CATEGORIES categories = CATEGORIES diff --git a/resources/settings.xml b/resources/settings.xml index 98abfcb7c..7f6f52250 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -23,11 +23,12 @@ - - - - - - + + + + + + + diff --git a/test/__init__.py b/test/__init__.py index 52ba81898..e69de29bb 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- - -# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function, unicode_literals -from contextlib import contextmanager -import json -import os -import polib -import sys - -PO = polib.pofile('resources/language/resource.language.en_gb/strings.po') -SETTINGS = dict( - username='qsdfdsq', - password='qsdfqsdfds', - log_level='Verbose', - showpermalink='true', - showsubtitles='true', - usedrm='false', - usefavorites='false', -) - -# Read credentials from credentials.json -if os.path.exists('test/credentials.json'): - SETTINGS.update(json.loads(open('test/credentials.json').read())) -else: - print('Credentials not found in credentials.json', file=sys.stderr) - - -def localize(msgctxt): - for entry in PO: - if entry.msgctxt == '#%s' % msgctxt: - return entry.msgstr or entry.msgid - return 'vrttest' - - -def get_setting(key): - return SETTINGS[key] - - -def log_notice(msg, level='Info'): - print('%s: %s' % (level, msg)) - - -@contextmanager -def open_file(path, flags='r'): - f = open(path, flags) - yield f - f.close() - - -def stat_file(path): - class stat: - def __init__(self, path): - self._stat = os.stat(path) - - def st_mtime(self): - return self._stat.st_mtime - - return stat(path) diff --git a/test/apihelpertests.py b/test/apihelpertests.py index be9fdf12b..3120a0ee5 100644 --- a/test/apihelpertests.py +++ b/test/apihelpertests.py @@ -3,26 +3,21 @@ # GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function, unicode_literals -import mock -import os import unittest +from resources.lib.kodiwrappers import kodiwrapper from resources.lib.vrtplayer import CHANNELS, favorites, vrtapihelper -from test import get_setting, localize, log_notice, open_file + +xbmc = __import__('xbmc') +xbmcaddon = __import__('xbmcaddon') +xbmcgui = __import__('xbmcgui') +xbmcplugin = __import__('xbmcplugin') +xbmcvfs = __import__('xbmcvfs') class ApiHelperTests(unittest.TestCase): - _kodi = mock.MagicMock() - _kodi.check_if_path_exists = mock.MagicMock(side_effect=os.path.exists) - _kodi.get_proxies = mock.MagicMock(return_value=dict()) - _kodi.get_setting = mock.MagicMock(side_effect=get_setting) - _kodi.get_userdata_path.return_value = './test/userdata/' - _kodi.localize_dateshort = mock.MagicMock(return_value='%d-%m-%Y') - _kodi.localize = mock.MagicMock(side_effect=localize) - _kodi.log_notice = mock.MagicMock(side_effect=log_notice) - _kodi.make_dir.return_value = None - _kodi.open_file = mock.MagicMock(side_effect=open_file) + _kodi = kodiwrapper.KodiWrapper(None, 'plugin://plugin.video.vrt.nu', xbmcaddon.Addon) _favorites = favorites.Favorites(_kodi) _apihelper = vrtapihelper.VRTApiHelper(_kodi, _favorites) diff --git a/test/favoritestests.py b/test/favoritestests.py index 5de7ecec3..2e6643303 100644 --- a/test/favoritestests.py +++ b/test/favoritestests.py @@ -3,27 +3,23 @@ # GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function, unicode_literals -import mock -import os import unittest +from resources.lib.kodiwrappers import kodiwrapper from resources.lib.vrtplayer import favorites -from test import SETTINGS, get_setting, log_notice, open_file, stat_file -SETTINGS['usefavorites'] = 'true' +xbmc = __import__('xbmc') +xbmcaddon = __import__('xbmcaddon') +xbmcgui = __import__('xbmcgui') +xbmcplugin = __import__('xbmcplugin') +xbmcvfs = __import__('xbmcvfs') + +xbmcaddon.SETTINGS['usefavorites'] = 'true' class TestFavorites(unittest.TestCase): - _kodi = mock.MagicMock() - _kodi.check_if_path_exists = mock.MagicMock(side_effect=os.path.exists) - _kodi.get_proxies = mock.MagicMock(return_value=dict()) - _kodi.get_setting = mock.MagicMock(side_effect=get_setting) - _kodi.get_userdata_path.return_value = './test/userdata/' - _kodi.log_notice = mock.MagicMock(side_effect=log_notice) - _kodi.make_dir.return_value = None - _kodi.open_file = mock.MagicMock(side_effect=open_file) - _kodi.stat_file = mock.MagicMock(side_effect=stat_file) + _kodi = kodiwrapper.KodiWrapper(None, 'plugin://plugin.video.vrt.nu', xbmcaddon.Addon) _favorites = favorites.Favorites(_kodi) def test_follow_unfollow(self): diff --git a/test/searchtests.py b/test/searchtests.py index 831b1c636..49d6e45fe 100644 --- a/test/searchtests.py +++ b/test/searchtests.py @@ -3,27 +3,21 @@ # GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function, unicode_literals -import mock -import os import unittest +from resources.lib.kodiwrappers import kodiwrapper from resources.lib.vrtplayer import favorites, vrtapihelper -from test import get_setting, log_notice, open_file, stat_file + +xbmc = __import__('xbmc') +xbmcaddon = __import__('xbmcaddon') +xbmcgui = __import__('xbmcgui') +xbmcplugin = __import__('xbmcplugin') +xbmcvfs = __import__('xbmcvfs') class TestSearch(unittest.TestCase): - _kodi = mock.MagicMock() - _kodi.check_if_path_exists = mock.MagicMock(side_effect=os.path.exists) - _kodi.check_inputstream_adaptive.return_value = True - _kodi.get_proxies = mock.MagicMock(return_value=dict()) - _kodi.get_setting = mock.MagicMock(side_effect=get_setting) - _kodi.get_userdata_path.return_value = './test/userdata/' - _kodi.localize_dateshort = mock.MagicMock(return_value='%d-%m-%Y') - _kodi.log_notice = mock.MagicMock(side_effect=log_notice) - _kodi.make_dir.return_value = None - _kodi.open_file = mock.MagicMock(side_effect=open_file) - _kodi.stat_file = mock.MagicMock(side_effect=stat_file) + _kodi = kodiwrapper.KodiWrapper(None, 'plugin.video.vrt.nu', xbmcaddon.Addon) _favorites = favorites.Favorites(_kodi) _apihelper = vrtapihelper.VRTApiHelper(_kodi, _favorites) diff --git a/test/streamservicetests.py b/test/streamservicetests.py index 4f7a7f434..4ff25a625 100644 --- a/test/streamservicetests.py +++ b/test/streamservicetests.py @@ -7,7 +7,6 @@ from __future__ import absolute_import, division, print_function, unicode_literals from datetime import datetime, timedelta import dateutil.tz -import mock import unittest try: @@ -15,27 +14,23 @@ except ImportError: from urllib2 import HTTPError +from resources.lib.kodiwrappers import kodiwrapper from resources.lib.vrtplayer import CHANNELS, streamservice, tokenresolver -from test import SETTINGS, get_setting, localize, log_notice -SETTINGS['use_drm'] = 'false' +xbmc = __import__('xbmc') +xbmcaddon = __import__('xbmcaddon') +xbmcgui = __import__('xbmcgui') +xbmcplugin = __import__('xbmcplugin') +xbmcvfs = __import__('xbmcvfs') + +xbmcaddon.SETTINGS['use_drm'] = 'false' now = datetime.now(dateutil.tz.tzlocal()) yesterday = now + timedelta(days=-1) class StreamServiceTests(unittest.TestCase): - _kodi = mock.MagicMock() - _kodi.check_if_path_exists.return_value = False - _kodi.check_inputstream_adaptive.return_value = True - _kodi.get_max_bandwidth = mock.MagicMock(return_value=0) - _kodi.get_proxies = mock.MagicMock(return_value=dict()) - _kodi.get_setting = mock.MagicMock(side_effect=get_setting) - _kodi.get_userdata_path.return_value = './test/userdata/' - _kodi.localize_dateshort = mock.MagicMock(return_value='%d-%m-%Y') - _kodi.localize = mock.MagicMock(side_effect=localize) - _kodi.log_notice = mock.MagicMock(side_effect=log_notice) - _kodi.make_dir.return_value = None + _kodi = kodiwrapper.KodiWrapper(None, 'plugin://plugin.video.vrt.nu', xbmcaddon.Addon) _tokenresolver = tokenresolver.TokenResolver(_kodi) _streamservice = streamservice.StreamService(_kodi, _tokenresolver) @@ -62,7 +57,7 @@ def test_get_ondemand_stream_from_url_gets_stream_does_not_crash(self): self.assertTrue(stream is not None) def test_get_live_stream_from_url_does_not_crash_returns_stream_and_licensekey(self): - SETTINGS['use_drm'] = 'true' + xbmcaddon.SETTINGS['use_drm'] = 'true' video = dict( video_url=CHANNELS[1]['live_stream'], video_id=None, diff --git a/test/tvguidetests.py b/test/tvguidetests.py index 30a6fe72f..fb67297c0 100644 --- a/test/tvguidetests.py +++ b/test/tvguidetests.py @@ -5,25 +5,24 @@ from __future__ import absolute_import, division, print_function, unicode_literals from datetime import datetime import dateutil.tz -import mock import random import unittest +from resources.lib.kodiwrappers import kodiwrapper from resources.lib.vrtplayer import tvguide -from test import localize, log_notice + +xbmc = __import__('xbmc') +xbmcaddon = __import__('xbmcaddon') +xbmcgui = __import__('xbmcgui') +xbmcplugin = __import__('xbmcplugin') +xbmcvfs = __import__('xbmcvfs') channels = ['een', 'canvas', 'ketnet'] class TestTVGuide(unittest.TestCase): - _kodi = mock.MagicMock() - _kodi.get_proxies = mock.MagicMock(return_value=dict()) - _kodi.get_userdata_path.return_value = './test/userdata/' - _kodi.localize = mock.MagicMock(side_effect=localize) - _kodi.localize_datelong = mock.MagicMock(return_value='%a %d-%m-%Y') - _kodi.log_notice = mock.MagicMock(side_effect=log_notice) - _kodi.make_dir.return_value = None + _kodi = kodiwrapper.KodiWrapper(None, 'plugin.video.vrt.nu', xbmcaddon.Addon) _tvguide = tvguide.TVGuide(_kodi) def test_tvguide_date_menu(self): @@ -54,6 +53,15 @@ def test_livetv_description(self): description = self._tvguide.live_description('ketnet') print(description) + def test_tvguide_all(self): + ''' Test episode menu ''' + episode_items = self._tvguide.show_episodes('yesterday', 'een') + self.assertTrue(episode_items) + episode_items = self._tvguide.show_episodes('today', 'canvas') + self.assertTrue(episode_items) + episode_items = self._tvguide.show_episodes('tomorrow', 'ketnet') + self.assertTrue(episode_items) + if __name__ == '__main__': unittest.main() diff --git a/test/vrtplayertests.py b/test/vrtplayertests.py index fcd56d0db..2d7270444 100644 --- a/test/vrtplayertests.py +++ b/test/vrtplayertests.py @@ -5,25 +5,22 @@ # pylint: disable=unused-variable from __future__ import absolute_import, division, print_function, unicode_literals -import mock -import os import random import unittest from resources.lib.vrtplayer import CATEGORIES, favorites, vrtapihelper, vrtplayer -from test import get_setting, log_notice, open_file +from resources.lib.kodiwrappers import kodiwrapper + +xbmc = __import__('xbmc') +xbmcaddon = __import__('xbmcaddon') +xbmcgui = __import__('xbmcgui') +xbmcplugin = __import__('xbmcplugin') +xbmcvfs = __import__('xbmcvfs') class TestVRTPlayer(unittest.TestCase): - _kodi = mock.MagicMock() - _kodi.check_if_path_exists = mock.MagicMock(side_effect=os.path.exists) - _kodi.get_proxies = mock.MagicMock(return_value=dict()) - _kodi.get_setting = mock.MagicMock(side_effect=get_setting) - _kodi.get_userdata_path.return_value = './test/userdata/' - _kodi.localize_dateshort = mock.MagicMock(return_value='%d-%m-%Y') - _kodi.log_notice = mock.MagicMock(side_effect=log_notice) - _kodi.open_file = mock.MagicMock(side_effect=open_file) + _kodi = kodiwrapper.KodiWrapper(None, 'plugin://plugin.video.vrt.nu', xbmcaddon.Addon) _favorites = favorites.Favorites(_kodi) _apihelper = vrtapihelper.VRTApiHelper(_kodi, _favorites) _vrtplayer = vrtplayer.VRTPlayer(_kodi, _favorites, _apihelper) @@ -37,7 +34,7 @@ def test_show_videos_single_episode_shows_videos(self): self.assertEqual(content, 'episodes') self._vrtplayer.show_episodes(path) - self.assertTrue(self._kodi.show_listing.called) + # self.assertTrue(self._kodi.show_listing.called) def test_show_videos_single_season_shows_videos(self): path = '/vrtnu/a-z/het-weer.relevant/' @@ -48,7 +45,7 @@ def test_show_videos_single_season_shows_videos(self): self.assertEqual(content, 'episodes') self._vrtplayer.show_episodes(path) - self.assertTrue(self._kodi.show_listing.called) + # self.assertTrue(self._kodi.show_listing.called) def test_show_videos_multiple_seasons_shows_videos(self): path = '/vrtnu/a-z/pano.relevant/' @@ -59,7 +56,7 @@ def test_show_videos_multiple_seasons_shows_videos(self): self.assertEqual(content, 'seasons') self._vrtplayer.show_episodes(path) - self.assertTrue(self._kodi.show_listing.called) + # self.assertTrue(self._kodi.show_listing.called) def test_show_videos_specific_seasons_shows_videos(self): path = '/vrtnu/a-z/thuis.relevant/' @@ -70,7 +67,7 @@ def test_show_videos_specific_seasons_shows_videos(self): self.assertEqual(content, 'seasons') self._vrtplayer.show_episodes(path) - self.assertTrue(self._kodi.show_listing.called) + # self.assertTrue(self._kodi.show_listing.called) def test_categories_scraping(self): ''' Test to ensure our hardcoded categories conforms to scraped categories ''' @@ -92,6 +89,11 @@ def test_random_tvshow_episodes(self): self.assertTrue(episode_items, msg=tvshow.url_dict['video_url']) self.assertTrue(content in ['episodes', 'seasons'], "Content for '%s' is '%s'" % (tvshow.title, content)) + def test_categories(self): + ''' Test to ensure our hardcoded categories conforms to scraped categories ''' + category_items = self._vrtplayer.get_category_menu_items() + self.assertEqual(len(category_items), 17) + if __name__ == '__main__': unittest.main() diff --git a/test/xbmc.py b/test/xbmc.py new file mode 100644 index 000000000..a3d572c44 --- /dev/null +++ b/test/xbmc.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- + +# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function, unicode_literals + +import json +import polib +import time + + +LOGERROR = 'Error' +LOGNOTICE = 'Notice' + +GLOBAL_SETTINGS = { + 'locale.language': 'resource.language.en_gb', + 'network.bandwidth': 0, +} + +INFO_LABELS = { + 'System.BuildVersion': '18.2', +} + +PO = polib.pofile('resources/language/resource.language.en_gb/strings.po') + +REGIONS = { + 'datelong': '%A, %e %B %Y', + 'dateshort': '%Y-%m-%d', +} + + +class Keyboard(): + pass + + +class Monitor(): + def abortRequested(self): + return + + def waitForAbort(self): + return + + +class Player(): + pass + + +def executebuiltin(s): + return + + +def executeJSONRPC(jsonrpccommand): + command = json.loads(jsonrpccommand) + if command.get('method') == 'Settings.GetSettingValue': + key = command.get('params').get('setting') + return '{"id":1,"jsonrpc":"2.0","result":{"value":"%s"}}' % GLOBAL_SETTINGS.get(key) + return 'executeJSONRPC' + + +def getCondVisibility(s): + return 1 + + +def getInfoLabel(key): + return INFO_LABELS.get(key) + + +def getLocalizedString(msgctxt): + for entry in PO: + if entry.msgctxt == '#%s' % msgctxt: + return entry.msgstr or entry.msgid + return 'vrttest' + + +def getRegion(key): + return REGIONS.get(key) + + +def setContent(self, content): + return + + +def sleep(seconds): + time.sleep(seconds) + + +def translatePath(path): + return path + + +def log(msg, level): + print('%s: %s' % (level, msg)) diff --git a/test/xbmcaddon.py b/test/xbmcaddon.py new file mode 100644 index 000000000..c947c4f67 --- /dev/null +++ b/test/xbmcaddon.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- + +# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function, unicode_literals +import json +import os +import polib +import sys + +# FIXME: Get information from addon.xml +ADDON_INFO = { + 'author': 'Martijn Moreels', + 'changelog': '', + 'description': '', + 'disclaimer': '', + 'fanart': '', + 'icon': '', + 'id': 'plugin.video.vrt.nu', + 'name': 'VRT NU', + # 'path': '/storage/.kodi/addons/plugin.video.vrt.nu', + 'path': './', + # 'profile': 'special://profile/addon_data/plugin.video.vrt.nu/', + 'profile': 'test/userdata/', + 'stars': '', + 'summary': '', + 'type': 'xbmc.python.pluginsource', + 'version': '1.10.0', +} + +PO = polib.pofile('resources/language/resource.language.en_gb/strings.po') + +SETTINGS = { + 'username': 'qsdfdsq', + 'password': 'qsdfqsdfds', + 'log_level': 'Verbose', + 'max_bandwidth': 0, + 'showpermalink': 'true', + 'showsubtitles': 'true', + 'usedrm': 'false', + 'usefavorites': 'false', +} + +# Read credentials from credentials.json +if os.path.exists('test/credentials.json'): + SETTINGS.update(json.loads(open('test/credentials.json').read())) +else: + print('Credentials not found in credentials.json', file=sys.stderr) + + +class Addon(): + @staticmethod + def __init__(id): # pylint: disable=redefined-builtin + pass + + @staticmethod + def getAddonInfo(key): + return ADDON_INFO.get(key) + + @staticmethod + def getLocalizedString(msgctxt): + for entry in PO: + if entry.msgctxt == '#%s' % msgctxt: + return entry.msgstr or entry.msgid + return 'vrttest' + + @staticmethod + def getSetting(key): + return SETTINGS.get(key) diff --git a/test/xbmcgui.py b/test/xbmcgui.py new file mode 100644 index 000000000..616aebcf3 --- /dev/null +++ b/test/xbmcgui.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- + +# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function, unicode_literals + + +class Dialog(): + def notification(self, heading='', message='', icon='', time=''): + print('GUI NOTIFICATION: [%s] %s' % (heading, message)) + + def ok(self, heading='', line1=''): + return + + def yesno(self, heading='', line1=''): + return True + + +class ListItem(): + def __init__(self, label='', label2='', iconImage='', thumbnailImage='', path='', offScreen=False): + return + + def addContextMenuItems(self, items, replaceItems=False): + return + + def setArt(self, key): + return + + def setContentLookup(self, enable): + return + + def setInfo(self, type, infoLabels): # pylint: disable=redefined-builtin + return + + def setMimeType(self, mimetype): + return + + def setProperty(self, key, value): + return + + def setSubtitles(self, subtitleFiles): + return diff --git a/test/xbmcplugin.py b/test/xbmcplugin.py new file mode 100644 index 000000000..1fb009aa1 --- /dev/null +++ b/test/xbmcplugin.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- + +# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function, unicode_literals + +SORT_METHOD_NONE = 0 +SORT_METHOD_LABEL = 1 +SORT_METHOD_LABEL_IGNORE_THE = 2 +SORT_METHOD_DATE = 3 +SORT_METHOD_SIZE = 4 +SORT_METHOD_FILE = 5 +SORT_METHOD_DRIVE_TYPE = 6 +SORT_METHOD_TRACKNUM = 7 +SORT_METHOD_DURATION = 8 +SORT_METHOD_TITLE = 9 +SORT_METHOD_TITLE_IGNORE_THE = 10 +SORT_METHOD_ARTIST = 11 +SORT_METHOD_ARTIST_IGNORE_THE = 13 +SORT_METHOD_ALBUM = 14 +SORT_METHOD_ALBUM_IGNORE_THE = 15 +SORT_METHOD_GENRE = 16 +SORT_METHOD_COUNTRY = 17 +SORT_METHOD_VIDEO_YEAR = 18 +SORT_METHOD_VIDEO_RATING = 19 +SORT_METHOD_VIDEO_USER_RATING = 20 +SORT_METHOD_DATEADDED = 21 +SORT_METHOD_PROGRAM_COUNT = 22 +SORT_METHOD_PLAYLIST_ORDER = 23 +SORT_METHOD_EPISODE = 24 +SORT_METHOD_VIDEO_TITLE = 25 +SORT_METHOD_VIDEO_SORT_TITLE = 26 +SORT_METHOD_VIDEO_SORT_TITLE_IGNORE_THE = 27 +SORT_METHOD_PRODUCTIONCODE = 28 +SORT_METHOD_SONG_RATING = 29 +SORT_METHOD_SONG_USER_RATING = 30 +SORT_METHOD_MPAA_RATING = 31 +SORT_METHOD_VIDEO_RUNTIME = 32 +SORT_METHOD_STUDIO = 33 +SORT_METHOD_STUDIO_IGNORE_THE = 34 +SORT_METHOD_FULLPATH = 35 +SORT_METHOD_LABEL_IGNORE_FOLDERS = 36 +SORT_METHOD_LASTPLAYED = 37 +SORT_METHOD_PLAYCOUNT = 38 +SORT_METHOD_LISTENERS = 39 +SORT_METHOD_UNSORTED = 40 +SORT_METHOD_CHANNEL = 41 +SORT_METHOD_BITRATE = 43 +SORT_METHOD_DATE_TAKEN = 44 + + +def addDirectoryItems(handle, listing, length): + return True + + +def addSortMethod(handle, sortMethod): + return + + +def endOfDirectory(handle, ok, cacheToDisc): + return + + +def setContent(self, content): + return diff --git a/test/xbmcvfs.py b/test/xbmcvfs.py new file mode 100644 index 000000000..38227bbb1 --- /dev/null +++ b/test/xbmcvfs.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- + +# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function, unicode_literals +import os + + +def File(path, flags='r'): + return open(path, flags) + + +def Stat(path): + class stat: + def __init__(self, path): + self._stat = os.stat(path) + + def st_mtime(self): + return self._stat.st_mtime + + return stat(path) + + +def delete(path): + return + + +def exists(path): + return os.path.exists(path) + + +def mkdir(path): + return os.mkdir(path)