From fd83e93b1dd1fe2216a843c4a2bbb6202686ce68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Arnauts?= Date: Thu, 9 Jul 2020 10:16:45 +0200 Subject: [PATCH] Use sake for Kodi stubs (#36) --- .github/workflows/ci.yml | 8 +- .gitignore | 8 +- Makefile | 3 +- requirements.txt | 2 +- resources/lib/kodilogging.py | 1 - resources/lib/kodiutils.py | 14 +- resources/lib/service.py | 2 +- resources/lib/viervijfzes/auth.py | 8 +- resources/lib/viervijfzes/content.py | 4 +- tests/__init__.py | 17 +- tests/check_for_unused_translations.py | 2 + .../addons/inputstream.adaptive/addon.xml | 3 + tests/home/userdata/guisettings.xml | 3 + tests/test_api.py | 2 +- tests/test_auth.py | 2 +- tests/test_epg.py | 3 +- tests/test_routing.py | 9 +- tests/test_search.py | 2 +- tests/userdata/addon_settings.json | 10 - tests/userdata/credentials.json.example | 4 - tests/userdata/global_settings.json | 6 - tests/xbmc.py | 247 ------------- tests/xbmcaddon.py | 76 ---- tests/xbmcextra.py | 188 ---------- tests/xbmcgui.py | 327 ------------------ tests/xbmcplugin.py | 112 ------ tests/xbmcvfs.py | 80 ----- 27 files changed, 55 insertions(+), 1088 deletions(-) create mode 100644 tests/home/addons/inputstream.adaptive/addon.xml create mode 100644 tests/home/userdata/guisettings.xml delete mode 100644 tests/userdata/addon_settings.json delete mode 100644 tests/userdata/credentials.json.example delete mode 100644 tests/userdata/global_settings.json delete mode 100644 tests/xbmc.py delete mode 100644 tests/xbmcaddon.py delete mode 100644 tests/xbmcextra.py delete mode 100644 tests/xbmcgui.py delete mode 100644 tests/xbmcplugin.py delete mode 100644 tests/xbmcvfs.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee5d28e..8bdc8e3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,9 @@ jobs: runs-on: ubuntu-latest env: PYTHONIOENCODING: utf-8 - PYTHONPATH: ${{ github.workspace }}/resources/lib:${{ github.workspace }}/tests + PYTHONPATH: ${{ github.workspace }}/resources/lib + KODI_HOME: ${{ github.workspace }}/tests/home + KODI_INTERACTIVE: 0 strategy: fail-fast: false matrix: @@ -37,8 +39,8 @@ jobs: env: ADDON_PASSWORD: ${{ secrets.ADDON_PASSWORD }} ADDON_USERNAME: ${{ secrets.ADDON_USERNAME }} - - name: Run addon service - run: coverage run -a service_entry.py + - name: Run addon service for 10 seconds + run: timeout --preserve-status -s SIGINT 10 coverage run -a service_entry.py env: ADDON_PASSWORD: ${{ secrets.ADDON_PASSWORD }} ADDON_USERNAME: ${{ secrets.ADDON_USERNAME }} diff --git a/.gitignore b/.gitignore index 65923aa..7b182f8 100644 --- a/.gitignore +++ b/.gitignore @@ -12,10 +12,4 @@ Thumbs.db .coverage .tox/ -tests/userdata/credentials.json -tests/userdata/temp -tests/userdata/token.json -tests/userdata/cache -tests/userdata/addon_data -tests/userdata/tokens -tests/cdm +tests/home/userdata/addon_data diff --git a/Makefile b/Makefile index 69681d2..06bb94e 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ -export PYTHONPATH := $(CURDIR):$(CURDIR)/tests +export KODI_HOME := $(CURDIR)/tests/home +export KODI_INTERACTIVE := 0 PYTHON := python # Collect information to build as sensible package name diff --git a/requirements.txt b/requirements.txt index 09d61b6..6d90099 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ coverage -git+git://github.com/emilsvennesson/script.module.inputstreamhelper.git@master#egg=inputstreamhelper polib pylint python-dateutil @@ -7,3 +6,4 @@ requests git+git://github.com/dagwieers/kodi-plugin-routing.git@setup#egg=routing tox six +sakee diff --git a/resources/lib/kodilogging.py b/resources/lib/kodilogging.py index 16521e0..706de6d 100644 --- a/resources/lib/kodilogging.py +++ b/resources/lib/kodilogging.py @@ -48,4 +48,3 @@ def config(): """ Setup the logger with this handler """ logger = logging.getLogger() logger.addHandler(KodiLogHandler()) - logger.setLevel(logging.DEBUG) diff --git a/resources/lib/kodiutils.py b/resources/lib/kodiutils.py index 156b8a2..b4b3584 100644 --- a/resources/lib/kodiutils.py +++ b/resources/lib/kodiutils.py @@ -4,6 +4,8 @@ from __future__ import absolute_import, division, unicode_literals import logging +import os + import xbmc import xbmcaddon import xbmcgui @@ -228,7 +230,7 @@ def ok_dialog(heading='', message=''): if not heading: heading = addon_name() if kodi_version_major() < 19: - return Dialog().ok(heading=heading, line1=message) + return Dialog().ok(heading=heading, line1=message) # pylint: disable=unexpected-keyword-arg,no-value-for-parameter return Dialog().ok(heading=heading, message=message) @@ -238,7 +240,7 @@ def yesno_dialog(heading='', message='', nolabel=None, yeslabel=None, autoclose= if not heading: heading = addon_name() if kodi_version_major() < 19: - return Dialog().yesno(heading=heading, line1=message, nolabel=nolabel, yeslabel=yeslabel, autoclose=autoclose) + return Dialog().yesno(heading=heading, line1=message, nolabel=nolabel, yeslabel=yeslabel, autoclose=autoclose) # pylint: disable=unexpected-keyword-arg,no-value-for-parameter return Dialog().yesno(heading=heading, message=message, nolabel=nolabel, yeslabel=yeslabel, autoclose=autoclose) @@ -276,7 +278,7 @@ class progress(xbmcgui.DialogProgress, object): # pylint: disable=invalid-name, if kodi_version_major() < 19: lines = message.split('\n', 2) line1, line2, line3 = (lines + [None] * (3 - len(lines))) - return super(progress, self).create(heading, line1=line1, line2=line2, line3=line3) + return super(progress, self).create(heading, line1=line1, line2=line2, line3=line3) # pylint: disable=unexpected-keyword-arg,no-value-for-parameter return super(progress, self).create(heading, message=message) def update(self, percent, message=''): # pylint: disable=arguments-differ @@ -284,7 +286,7 @@ class progress(xbmcgui.DialogProgress, object): # pylint: disable=invalid-name, if kodi_version_major() < 19: lines = message.split('\n', 2) line1, line2, line3 = (lines + [None] * (3 - len(lines))) - return super(progress, self).update(percent, line1=line1, line2=line2, line3=line3) + return super(progress, self).update(percent, line1=line1, line2=line2, line3=line3) # pylint: disable=unexpected-keyword-arg,no-value-for-parameter return super(progress, self).update(percent, message=message) @@ -439,14 +441,14 @@ def kodi_version_major(): def get_tokens_path(): """Cache and return the userdata tokens path""" if not hasattr(get_tokens_path, 'cached'): - get_tokens_path.cached = addon_profile() + 'tokens/' + get_tokens_path.cached = os.path.join(addon_profile(), 'tokens') return getattr(get_tokens_path, 'cached') def get_cache_path(): """Cache and return the userdata cache path""" if not hasattr(get_cache_path, 'cached'): - get_cache_path.cached = addon_profile() + 'cache/' + get_cache_path.cached = os.path.join(addon_profile(), 'cache') return getattr(get_cache_path, 'cached') diff --git a/resources/lib/service.py b/resources/lib/service.py index 11440be..19beed7 100644 --- a/resources/lib/service.py +++ b/resources/lib/service.py @@ -89,7 +89,7 @@ class BackgroundService(Monitor): now = time() for filename in os.listdir(path): - fullpath = path + filename + fullpath = os.path.join(path, filename) if keep_expired and os.stat(fullpath).st_mtime + keep_expired > now: continue os.unlink(fullpath) diff --git a/resources/lib/viervijfzes/auth.py b/resources/lib/viervijfzes/auth.py index 2096ff8..948359a 100644 --- a/resources/lib/viervijfzes/auth.py +++ b/resources/lib/viervijfzes/auth.py @@ -33,7 +33,7 @@ class AuthApi: # Load tokens from cache try: - with open(self._token_path + self.TOKEN_FILE, 'r') as fdesc: + with open(os.path.join(self._token_path, self.TOKEN_FILE), 'r') as fdesc: data_json = json.loads(fdesc.read()) self._id_token = data_json.get('id_token') self._refresh_token = data_json.get('refresh_token') @@ -75,7 +75,7 @@ class AuthApi: # Store new tokens in cache if not os.path.exists(self._token_path): os.mkdir(self._token_path) - with open(self._token_path + self.TOKEN_FILE, 'w') as fdesc: + with open(os.path.join(self._token_path, self.TOKEN_FILE), 'w') as fdesc: data = json.dumps(dict( id_token=self._id_token, refresh_token=self._refresh_token, @@ -87,8 +87,8 @@ class AuthApi: def clear_tokens(self): """ Remove the cached tokens. """ - if os.path.exists(self._token_path + AuthApi.TOKEN_FILE): - os.unlink(self._token_path + AuthApi.TOKEN_FILE) + if os.path.exists(os.path.join(self._token_path, AuthApi.TOKEN_FILE)): + os.unlink(os.path.join(self._token_path, AuthApi.TOKEN_FILE)) @staticmethod def _authenticate(username, password): diff --git a/resources/lib/viervijfzes/content.py b/resources/lib/viervijfzes/content.py index fd97279..d829668 100644 --- a/resources/lib/viervijfzes/content.py +++ b/resources/lib/viervijfzes/content.py @@ -621,10 +621,10 @@ class ContentApi: def _set_cache(self, key, data, ttl): """ Store an item in the cache """ filename = ('.'.join(key) + '.json').replace('/', '_') - fullpath = self._cache_path + filename + fullpath = os.path.join(self._cache_path, filename) if not os.path.exists(self._cache_path): - os.mkdir(self._cache_path) + os.makedirs(self._cache_path) with open(fullpath, 'w') as fdesc: _LOGGER.debug('Storing to cache as %s', filename) diff --git a/tests/__init__.py b/tests/__init__.py index 8272521..ec7aa3f 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -4,5 +4,20 @@ from __future__ import absolute_import, division, unicode_literals import logging +import os +import sys -logging.basicConfig() +import xbmcaddon + +logging.basicConfig(level=logging.INFO) + +# Make UTF-8 the default encoding in Python 2 +if sys.version_info[0] == 2: + reload(sys) # pylint: disable=undefined-variable + sys.setdefaultencoding("utf-8") # pylint: disable=no-member + +# Set credentials based on environment data +if os.environ.get('ADDON_USERNAME') and os.environ.get('ADDON_PASSWORD'): + ADDON = xbmcaddon.Addon() + ADDON.setSetting('username', os.environ.get('ADDON_USERNAME')) + ADDON.setSetting('password', os.environ.get('ADDON_PASSWORD')) diff --git a/tests/check_for_unused_translations.py b/tests/check_for_unused_translations.py index 3122bb0..5ccd7db 100755 --- a/tests/check_for_unused_translations.py +++ b/tests/check_for_unused_translations.py @@ -2,6 +2,8 @@ # -*- coding: utf-8 -*- """ Quick and dirty way to check if all translations might be used. """ +from __future__ import absolute_import, division, print_function, unicode_literals + # pylint: disable=invalid-name,superfluous-parens import subprocess diff --git a/tests/home/addons/inputstream.adaptive/addon.xml b/tests/home/addons/inputstream.adaptive/addon.xml new file mode 100644 index 0000000..b3f6859 --- /dev/null +++ b/tests/home/addons/inputstream.adaptive/addon.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/tests/home/userdata/guisettings.xml b/tests/home/userdata/guisettings.xml new file mode 100644 index 0000000..bbbb5b2 --- /dev/null +++ b/tests/home/userdata/guisettings.xml @@ -0,0 +1,3 @@ + + true + diff --git a/tests/test_api.py b/tests/test_api.py index ad82cb9..369bd8f 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -12,7 +12,7 @@ import resources.lib.kodiutils as kodiutils from resources.lib.viervijfzes.auth import AuthApi from resources.lib.viervijfzes.content import ContentApi, Program, Episode, Category, CACHE_PREVENT -_LOGGER = logging.getLogger('test-api') +_LOGGER = logging.getLogger(__name__) class TestApi(unittest.TestCase): diff --git a/tests/test_auth.py b/tests/test_auth.py index 50281e9..9978f53 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -11,7 +11,7 @@ import unittest from resources.lib import kodiutils from resources.lib.viervijfzes.auth import AuthApi -_LOGGER = logging.getLogger('test-auth') +_LOGGER = logging.getLogger(__name__) class TestAuth(unittest.TestCase): diff --git a/tests/test_epg.py b/tests/test_epg.py index d3201e2..f83fbf5 100644 --- a/tests/test_epg.py +++ b/tests/test_epg.py @@ -13,7 +13,7 @@ from resources.lib import kodiutils from resources.lib.viervijfzes.content import ContentApi, Episode from resources.lib.viervijfzes.epg import EpgApi, EpgProgram -_LOGGER = logging.getLogger('test-epg') +_LOGGER = logging.getLogger(__name__) class TestEpg(unittest.TestCase): @@ -66,5 +66,6 @@ class TestEpg(unittest.TestCase): # print(genres) # + if __name__ == '__main__': unittest.main() diff --git a/tests/test_routing.py b/tests/test_routing.py index 3004a55..91e1c24 100644 --- a/tests/test_routing.py +++ b/tests/test_routing.py @@ -9,11 +9,6 @@ import unittest from resources.lib import addon -xbmc = __import__('xbmc') # pylint: disable=invalid-name -xbmcaddon = __import__('xbmcaddon') # pylint: disable=invalid-name -xbmcgui = __import__('xbmcgui') # pylint: disable=invalid-name -xbmcplugin = __import__('xbmcplugin') # pylint: disable=invalid-name -xbmcvfs = __import__('xbmcvfs') # pylint: disable=invalid-name routing = addon.routing # pylint: disable=invalid-name @@ -56,8 +51,8 @@ class TestRouting(unittest.TestCase): routing.run([routing.url_for(addon.show_channel_tvguide, channel='vier'), '0', '']) routing.run([routing.url_for(addon.show_channel_tvguide_detail, channel='vier', date='today'), '0', '']) - def test_metadata_update(self): - routing.run([routing.url_for(addon.metadata_update), '0', '']) + # def test_metadata_update(self): + # routing.run([routing.url_for(addon.metadata_update), '0', '']) if __name__ == '__main__': diff --git a/tests/test_search.py b/tests/test_search.py index ef49632..1929e48 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -11,7 +11,7 @@ import unittest from resources.lib.viervijfzes.content import Program from resources.lib.viervijfzes.search import SearchApi -_LOGGER = logging.getLogger('test-search') +_LOGGER = logging.getLogger(__name__) class TestSearch(unittest.TestCase): diff --git a/tests/userdata/addon_settings.json b/tests/userdata/addon_settings.json deleted file mode 100644 index 01694d7..0000000 --- a/tests/userdata/addon_settings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "plugin.video.viervijfzes": { - "_comment": "do-not-add-username-and-password-here", - "metadata_update": "true" - }, - "plugin.video.youtube": { - }, - "service.upnext": { - } -} diff --git a/tests/userdata/credentials.json.example b/tests/userdata/credentials.json.example deleted file mode 100644 index a11ffea..0000000 --- a/tests/userdata/credentials.json.example +++ /dev/null @@ -1,4 +0,0 @@ -{ - "username": "username", - "password": "password" -} diff --git a/tests/userdata/global_settings.json b/tests/userdata/global_settings.json deleted file mode 100644 index 74d1c53..0000000 --- a/tests/userdata/global_settings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "locale.language": "resource.language.nl_nl", - "network.bandwidth": 0, - "network.usehttpproxy": false, - "videolibrary.showallitems": true -} diff --git a/tests/xbmc.py b/tests/xbmc.py deleted file mode 100644 index 88e31d5..0000000 --- a/tests/xbmc.py +++ /dev/null @@ -1,247 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright: (c) 2019, Dag Wieers (@dagwieers) -# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -""" This file implements the Kodi xbmc module, either using stubs or alternative functionality """ - -# pylint: disable=invalid-name,no-self-use,unused-argument - -from __future__ import absolute_import, division, print_function, unicode_literals - -import json -import os -import time - -from xbmcextra import global_settings, import_language - -LOGDEBUG = 0 -LOGERROR = 4 -LOGFATAL = 6 -LOGINFO = 1 -LOGNONE = 7 -LOGNOTICE = 2 -LOGSEVERE = 5 -LOGWARNING = 3 - -LOG_MAPPING = { - LOGDEBUG: 'Debug', - LOGERROR: 'Error', - LOGFATAL: 'Fatal', - LOGINFO: 'Info', - LOGNONE: 'None', - LOGNOTICE: 'Notice', - LOGSEVERE: 'Severe', - LOGWARNING: 'Warning', -} - -INFO_LABELS = { - 'System.BuildVersion': '18.2', -} - -REGIONS = { - 'datelong': '%A, %e %B %Y', - 'dateshort': '%Y-%m-%d', -} - -GLOBAL_SETTINGS = global_settings() -PO = import_language(language=GLOBAL_SETTINGS.get('locale.language')) - - -def to_unicode(text, encoding='utf-8'): - """ Force text to unicode """ - return text.decode(encoding) if isinstance(text, bytes) else text - - -def from_unicode(text, encoding='utf-8'): - """ Force unicode to text """ - import sys - if sys.version_info.major == 2 and isinstance(text, unicode): # noqa: F821; pylint: disable=undefined-variable - return text.encode(encoding) - return text - - -class Keyboard: - """ A stub implementation of the xbmc Keyboard class """ - - def __init__(self, line='', heading=''): - """ A stub constructor for the xbmc Keyboard class """ - - def doModal(self, autoclose=0): - """ A stub implementation for the xbmc Keyboard class doModal() method """ - - def isConfirmed(self): - """ A stub implementation for the xbmc Keyboard class isConfirmed() method """ - return True - - def getText(self): - """ A stub implementation for the xbmc Keyboard class getText() method """ - return 'test' - - -class Monitor: - """A stub implementation of the xbmc Monitor class""" - - def __init__(self, line='', heading=''): - """A stub constructor for the xbmc Monitor class""" - self._deadline = time.time() + 10 # 10 seconds - - def abortRequested(self): - """A stub implementation for the xbmc Keyboard class abortRequested() method""" - return time.time() > self._deadline - - def waitForAbort(self, timeout=None): - """A stub implementation for the xbmc Keyboard class waitForAbort() method""" - time.sleep(0.5) - return False - - -class Player: - """ A stub implementation of the xbmc Player class """ - - def __init__(self): - self._count = 0 - - def play(self, item='', listitem=None, windowed=False, startpos=-1): - """ A stub implementation for the xbmc Player class play() method """ - return - - def isPlaying(self): - """ A stub implementation for the xbmc Player class isPlaying() method """ - # Return True four times out of five - self._count += 1 - return bool(self._count % 5 != 0) - - def setSubtitles(self, subtitleFile): - """ A stub implementation for the xbmc Player class setSubtitles() method """ - return - - def showSubtitles(self, visible): - """ A stub implementation for the xbmc Player class showSubtitles() method """ - return - - def getTotalTime(self): - """ A stub implementation for the xbmc Player class getTotalTime() method """ - return 0 - - def getTime(self): - """ A stub implementation for the xbmc Player class getTime() method """ - return 0 - - def getVideoInfoTag(self): - """ A stub implementation for the xbmc Player class getVideoInfoTag() method """ - return VideoInfoTag() - - def getPlayingFile(self): - """ A stub implementation for the xbmc Player class getPlayingFile() method """ - return '' - - -class VideoInfoTag: - """ A stub implementation of the xbmc VideoInfoTag class """ - - def __init__(self): - """ A stub constructor for the xbmc VideoInfoTag class """ - - def getSeason(self): - """ A stub implementation for the xbmc VideoInfoTag class getSeason() method """ - return 0 - - def getEpisode(self): - """ A stub implementation for the xbmc VideoInfoTag class getEpisode() method """ - return 0 - - def getTVShowTitle(self): - """ A stub implementation for the xbmc VideoInfoTag class getTVShowTitle() method """ - return '' - - def getPlayCount(self): - """ A stub implementation for the xbmc VideoInfoTag class getPlayCount() method """ - return 0 - - def getRating(self): - """ A stub implementation for the xbmc VideoInfoTag class getRating() method """ - return 0 - - -def executebuiltin(string, wait=False): # pylint: disable=unused-argument - """ A stub implementation of the xbmc executebuiltin() function """ - return - - -def executeJSONRPC(jsonrpccommand): - """ A reimplementation of the xbmc executeJSONRPC() function """ - command = json.loads(jsonrpccommand) - if command.get('method') == 'Settings.GetSettingValue': - key = command.get('params').get('setting') - return json.dumps(dict(id=1, jsonrpc='2.0', result=dict(value=GLOBAL_SETTINGS.get(key)))) - if command.get('method') == 'Addons.GetAddonDetails': - if command.get('params', {}).get('addonid') == 'script.module.inputstreamhelper': - return json.dumps(dict(id=1, jsonrpc='2.0', result=dict(addon=dict(enabled='true', version='0.3.5')))) - return json.dumps(dict(id=1, jsonrpc='2.0', result=dict(addon=dict(enabled='true', version='1.2.3')))) - if command.get('method') == 'Textures.GetTextures': - return json.dumps(dict(id=1, jsonrpc='2.0', result=dict(textures=[dict(cachedurl="", imagehash="", lasthashcheck="", textureid=4837, url="")]))) - if command.get('method') == 'Textures.RemoveTexture': - return json.dumps(dict(id=1, jsonrpc='2.0', result="OK")) - log("executeJSONRPC does not implement method '{method}'".format(**command), 'Error') - return json.dumps(dict(error=dict(code=-1, message='Not implemented'), id=1, jsonrpc='2.0')) - - -def getCondVisibility(string): - """ A reimplementation of the xbmc getCondVisibility() function """ - if string == 'system.platform.android': - return False - return True - - -def getInfoLabel(key): - """ A reimplementation of the xbmc getInfoLabel() function """ - return INFO_LABELS.get(key) - - -def getLocalizedString(msgctxt): - """ A reimplementation of the xbmc getLocalizedString() function """ - for entry in PO: - if entry.msgctxt == '#%s' % msgctxt: - return entry.msgstr or entry.msgid - if int(msgctxt) >= 30000: - log('Unable to translate #{msgctxt}'.format(msgctxt=msgctxt), LOGERROR) - return '' - - -def getRegion(key): - """ A reimplementation of the xbmc getRegion() function """ - return REGIONS.get(key) - - -def log(msg, level=LOGINFO): - """ A reimplementation of the xbmc log() function """ - if level in (LOGERROR, LOGFATAL): - print('\033[31;1m%s: \033[32;0m%s\033[0;39m' % (LOG_MAPPING.get(level), to_unicode(msg))) - if level == LOGFATAL: - raise Exception(msg) - elif level in (LOGWARNING, LOGNOTICE): - print('\033[33;1m%s: \033[32;0m%s\033[0;39m' % (LOG_MAPPING.get(level), to_unicode(msg))) - else: - print('\033[32;1m%s: \033[32;0m%s\033[0;39m' % (LOG_MAPPING.get(level), to_unicode(msg))) - - -def setContent(self, content): - """ A stub implementation of the xbmc setContent() function """ - return - - -def sleep(seconds): - """ A reimplementation of the xbmc sleep() function """ - time.sleep(seconds) - - -def translatePath(path): - """ A stub implementation of the xbmc translatePath() function """ - if path.startswith('special://home'): - return path.replace('special://home', os.path.join(os.getcwd(), 'tests/')) - if path.startswith('special://masterprofile'): - return path.replace('special://masterprofile', os.path.join(os.getcwd(), 'tests/userdata/')) - if path.startswith('special://profile'): - return path.replace('special://profile', os.path.join(os.getcwd(), 'tests/userdata/')) - if path.startswith('special://userdata'): - return path.replace('special://userdata', os.path.join(os.getcwd(), 'tests/userdata/')) - return path diff --git a/tests/xbmcaddon.py b/tests/xbmcaddon.py deleted file mode 100644 index 86ceae9..0000000 --- a/tests/xbmcaddon.py +++ /dev/null @@ -1,76 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright: (c) 2019, Dag Wieers (@dagwieers) -# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -"""This file implements the Kodi xbmcaddon module, either using stubs or alternative functionality""" - -# pylint: disable=invalid-name - -from __future__ import absolute_import, division, print_function, unicode_literals -from xbmc import getLocalizedString -from xbmcextra import ADDON_ID, ADDON_INFO, addon_settings - -# Ensure the addon settings are retained (as we don't write to disk) -ADDON_SETTINGS = addon_settings(ADDON_ID) - - -class Addon: - """A reimplementation of the xbmcaddon Addon class""" - - def __init__(self, id=ADDON_ID): # pylint: disable=redefined-builtin - """A stub constructor for the xbmcaddon Addon class""" - self.id = id - if id == ADDON_ID: - self.settings = ADDON_SETTINGS - else: - self.settings = addon_settings(id) - - def getAddonInfo(self, key): - """A working implementation for the xbmcaddon Addon class getAddonInfo() method""" - stub_info = dict(id=self.id, name=self.id, version='2.3.4', type='kodi.inputstream', profile='special://userdata', path='special://userdata') - # Add stub_info values to ADDON_INFO when missing (e.g. path and profile) - addon_info = dict(stub_info, **ADDON_INFO) - return addon_info.get(self.id, stub_info).get(key) - - @staticmethod - def getLocalizedString(msgctxt): - """A working implementation for the xbmcaddon Addon class getLocalizedString() method""" - return getLocalizedString(msgctxt) - - def getSetting(self, key): - """A working implementation for the xbmcaddon Addon class getSetting() method""" - return self.settings.get(key, '') - - def getSettingBool(self, key): - """A working implementation for the xbmcaddon Addon class getSettingBool() method""" - return bool(self.settings.get(key, False)) - - def getSettingInt(self, key): - """A working implementation for the xbmcaddon Addon class getSettingInt() method""" - return int(self.settings.get(key, 0)) - - def getSettingNumber(self, key): - """A working implementation for the xbmcaddon Addon class getSettingNumber() method""" - return float(self.settings.get(key, 0.0)) - - @staticmethod - def openSettings(): - """A stub implementation for the xbmcaddon Addon class openSettings() method""" - - def setSetting(self, key, value): - """A stub implementation for the xbmcaddon Addon class setSetting() method""" - self.settings[key] = value - # NOTE: Disable actual writing as it is no longer needed for testing - # with open('tests/userdata/addon_settings.json', 'w') as fd: - # json.dump(filtered_settings, fd, sort_keys=True, indent=4) - - def setSettingBool(self, key, value): - """A stub implementation for the xbmcaddon Addon class setSettingBool() method""" - self.settings[key] = value - - def setSettingInt(self, key, value): - """A stub implementation for the xbmcaddon Addon class setSettingInt() method""" - self.settings[key] = value - - def setSettingNumber(self, key, value): - """A stub implementation for the xbmcaddon Addon class setSettingNumber() method""" - self.settings[key] = value diff --git a/tests/xbmcextra.py b/tests/xbmcextra.py deleted file mode 100644 index d617189..0000000 --- a/tests/xbmcextra.py +++ /dev/null @@ -1,188 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright: (c) 2019, Dag Wieers (@dagwieers) -# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -"""Extra functions for testing""" - -# pylint: disable=invalid-name - -from __future__ import absolute_import, division, print_function, unicode_literals -import os -import xml.etree.ElementTree as ET -import polib - - -def kodi_to_ansi(string): - """Convert Kodi format tags to ANSI codes""" - if string is None: - return None - string = string.replace('[B]', '\033[1m') - string = string.replace('[/B]', '\033[21m') - string = string.replace('[I]', '\033[3m') - string = string.replace('[/I]', '\033[23m') - string = string.replace('[COLOR gray]', '\033[30;1m') - string = string.replace('[COLOR red]', '\033[31m') - string = string.replace('[COLOR green]', '\033[32m') - string = string.replace('[COLOR yellow]', '\033[33m') - string = string.replace('[COLOR blue]', '\033[34m') - string = string.replace('[COLOR purple]', '\033[35m') - string = string.replace('[COLOR cyan]', '\033[36m') - string = string.replace('[COLOR white]', '\033[37m') - string = string.replace('[/COLOR]', '\033[39;0m') - return string - - -def uri_to_path(uri): - """Shorten a plugin URI to just the path""" - if uri is None: - return None - return ' \033[33m→ \033[34m%s\033[39;0m' % uri.replace('plugin://' + ADDON_ID, '') - - -def read_addon_xml(path): - """Parse the addon.xml and return an info dictionary""" - info = dict( - path='./', # '/storage/.kodi/addons/plugin.video.vrt.nu', - profile='special://userdata', # 'special://profile/addon_data/plugin.video.vrt.nu/', - type='xbmc.python.pluginsource', - ) - - tree = ET.parse(path) - root = tree.getroot() - - info.update(root.attrib) # Add 'id', 'name' and 'version' - info['author'] = info.pop('provider-name') - - for child in root: - if child.attrib.get('point') != 'xbmc.addon.metadata': - continue - for grandchild in child: - # Handle assets differently - if grandchild.tag == 'assets': - for asset in grandchild: - info[asset.tag] = asset.text - continue - # Not in English ? Drop it - if grandchild.attrib.get('lang', 'en_GB') != 'en_GB': - continue - # Add metadata - info[grandchild.tag] = grandchild.text - - return {info['name']: info} - - -def global_settings(): - """Use the global_settings file""" - import json - try: - with open('tests/userdata/global_settings.json') as f: - settings = json.load(f) - except OSError as e: - print("Error: Cannot use 'tests/userdata/global_settings.json' : %s" % e) - settings = { - 'locale.language': 'resource.language.en_gb', - 'network.bandwidth': 0, - } - - if 'PROXY_SERVER' in os.environ: - settings['network.usehttpproxy'] = True - settings['network.httpproxytype'] = 0 - print('Using proxy server from environment variable PROXY_SERVER') - settings['network.httpproxyserver'] = os.environ.get('PROXY_SERVER') - if 'PROXY_PORT' in os.environ: - print('Using proxy server from environment variable PROXY_PORT') - settings['network.httpproxyport'] = os.environ.get('PROXY_PORT') - if 'PROXY_USERNAME' in os.environ: - print('Using proxy server from environment variable PROXY_USERNAME') - settings['network.httpproxyusername'] = os.environ.get('PROXY_USERNAME') - if 'PROXY_PASSWORD' in os.environ: - print('Using proxy server from environment variable PROXY_PASSWORD') - settings['network.httpproxypassword'] = os.environ.get('PROXY_PASSWORD') - return settings - - -def addon_settings(addon_id=None): - """Use the addon_settings file""" - import json - try: - with open('tests/userdata/addon_settings.json') as f: - settings = json.load(f) - except OSError as e: - print("Error: Cannot use 'tests/userdata/addon_settings.json' : %s" % e) - settings = {} - - # Read credentials from environment or credentials.json - if 'ADDON_USERNAME' in os.environ and 'ADDON_PASSWORD' in os.environ: - # print('Using credentials from the environment variables ADDON_USERNAME and ADDON_PASSWORD') - settings[ADDON_ID]['username'] = os.environ.get('ADDON_USERNAME') - settings[ADDON_ID]['password'] = os.environ.get('ADDON_PASSWORD') - elif os.path.exists('tests/userdata/credentials.json'): - # print('Using credentials from tests/userdata/credentials.json') - with open('tests/userdata/credentials.json') as f: - credentials = json.load(f) - settings[ADDON_ID].update(credentials) - else: - print("Error: Cannot use 'tests/userdata/credentials.json'") - - if addon_id: - return settings[addon_id] - - return settings - - -def import_language(language): - """Process the language.po file""" - try: - podb = polib.pofile('resources/language/{language}/strings.po'.format(language=language)) - except IOError: - podb = polib.pofile('resources/language/resource.language.en_gb/strings.po') - - podb.extend([ - # WEEKDAY_LONG - polib.POEntry(msgctxt='#11', msgstr='Monday'), - polib.POEntry(msgctxt='#12', msgstr='Tuesday'), - polib.POEntry(msgctxt='#13', msgstr='Wednesday'), - polib.POEntry(msgctxt='#14', msgstr='Thursday'), - polib.POEntry(msgctxt='#15', msgstr='Friday'), - polib.POEntry(msgctxt='#16', msgstr='Saturday'), - polib.POEntry(msgctxt='#17', msgstr='Sunday'), - # MONTH_LONG - polib.POEntry(msgctxt='#21', msgstr='January'), - polib.POEntry(msgctxt='#22', msgstr='February'), - polib.POEntry(msgctxt='#23', msgstr='March'), - polib.POEntry(msgctxt='#24', msgstr='April'), - polib.POEntry(msgctxt='#25', msgstr='May'), - polib.POEntry(msgctxt='#26', msgstr='June'), - polib.POEntry(msgctxt='#27', msgstr='July'), - polib.POEntry(msgctxt='#28', msgstr='August'), - polib.POEntry(msgctxt='#29', msgstr='September'), - polib.POEntry(msgctxt='#30', msgstr='October'), - polib.POEntry(msgctxt='#31', msgstr='November'), - polib.POEntry(msgctxt='#32', msgstr='December'), - # WEEKDAY_SHORT - polib.POEntry(msgctxt='#41', msgstr='Mon'), - polib.POEntry(msgctxt='#42', msgstr='Tue'), - polib.POEntry(msgctxt='#43', msgstr='Wed'), - polib.POEntry(msgctxt='#44', msgstr='Thu'), - polib.POEntry(msgctxt='#45', msgstr='Fri'), - polib.POEntry(msgctxt='#46', msgstr='Sat'), - polib.POEntry(msgctxt='#47', msgstr='Sun'), - # MONTH_LONG - polib.POEntry(msgctxt='#51', msgstr='Jan'), - polib.POEntry(msgctxt='#52', msgstr='Feb'), - polib.POEntry(msgctxt='#53', msgstr='Mar'), - polib.POEntry(msgctxt='#54', msgstr='Apr'), - polib.POEntry(msgctxt='#55', msgstr='May'), - polib.POEntry(msgctxt='#56', msgstr='Jun'), - polib.POEntry(msgctxt='#57', msgstr='Jul'), - polib.POEntry(msgctxt='#58', msgstr='Aug'), - polib.POEntry(msgctxt='#59', msgstr='Sep'), - polib.POEntry(msgctxt='#50', msgstr='Oct'), - polib.POEntry(msgctxt='#51', msgstr='Nov'), - polib.POEntry(msgctxt='#52', msgstr='Dec'), - ]) - - return podb - - -ADDON_INFO = read_addon_xml('addon.xml') -ADDON_ID = next(iter(list(ADDON_INFO.values()))).get('id') diff --git a/tests/xbmcgui.py b/tests/xbmcgui.py deleted file mode 100644 index 2edf819..0000000 --- a/tests/xbmcgui.py +++ /dev/null @@ -1,327 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright: (c) 2019, Dag Wieers (@dagwieers) -# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -"""This file implements the Kodi xbmcgui module, either using stubs or alternative functionality""" - -# pylint: disable=invalid-name,super-on-old-class,too-many-arguments,unused-argument,useless-super-delegation - -from __future__ import absolute_import, division, print_function, unicode_literals -import sys -from xbmcextra import kodi_to_ansi - - -class Control: - """A reimplementation of the xbmcgui Control class""" - - def __init__(self): - """A stub constructor for the xbmcgui Control class""" - - @staticmethod - def selectItem(index): - """A stub implementation for the xbmcgui Control class selectItem() method""" - return - - -class ControlLabel(Control): - """A reimplementation of the xbmcgui ControlLabel class""" - - def __init__(self): # pylint: disable=super-init-not-called - """A stub constructor for the xbmcgui ControlLabel class""" - - @staticmethod - def getLabel(): - """A stub implementation for the xbmcgui ControlLabel class getLabel() method""" - return 'Label' - - @staticmethod - def setLabel(label='', font=None, textColor=None, disabledColor=None, shadowColor=None, focusedColor=None, label2=''): - """A stub implementation for the xbmcgui ControlLabel class getLabel() method""" - - -class Dialog: - """A reimplementation of the xbmcgui Dialog class""" - - def __init__(self): - """A stub constructor for the xbmcgui Dialog class""" - - @staticmethod - def notification(heading, message, icon=None, time=None, sound=None): - """A working implementation for the xbmcgui Dialog class notification() method""" - heading = kodi_to_ansi(heading) - message = kodi_to_ansi(message) - print('\033[37;44;1mNOTIFICATION:\033[35;49;1m [%s] \033[37;1m%s\033[39;0m' % (heading, message)) - - @staticmethod - def ok(heading, message='', line1='', line2='', line3=''): - """A stub implementation for the xbmcgui Dialog class ok() method""" - heading = kodi_to_ansi(heading) - message = kodi_to_ansi(message) - print('\033[37;44;1mOK:\033[35;49;1m [%s] \033[37;1m%s\033[39;0m' % (heading, message or line1)) - - @staticmethod - def info(listitem): - """A stub implementation for the xbmcgui Dialog class info() method""" - - @staticmethod - def select(heading, opt_list, autoclose=0, preselect=None, useDetails=False): - """A stub implementation for the xbmcgui Dialog class select() method""" - if preselect is None: - preselect = [] - heading = kodi_to_ansi(heading) - print('\033[37;44;1mSELECT:\033[35;49;1m [%s] \033[37;1m%s\033[39;0m' % (heading, ', '.join(opt_list))) - return -1 - - @staticmethod - def multiselect(heading, options, autoclose=0, preselect=None, useDetails=False): # pylint: disable=useless-return - """A stub implementation for the xbmcgui Dialog class multiselect() method""" - if preselect is None: - preselect = [] - heading = kodi_to_ansi(heading) - print('\033[37;44;1mMULTISELECT:\033[35;49;1m [%s] \033[37;1m%s\033[39;0m' % (heading, ', '.join(options))) - return None - - @staticmethod - def contextmenu(items): - """A stub implementation for the xbmcgui Dialog class contextmenu() method""" - print('\033[37;44;1mCONTEXTMENU:\033[35;49;1m \033[37;1m%s\033[39;0m' % (', '.join(items))) - return -1 - - @staticmethod - def yesno(heading, message='', line1='', line2='', line3='', nolabel=None, yeslabel=None, autoclose=0): - """A stub implementation for the xbmcgui Dialog class yesno() method""" - heading = kodi_to_ansi(heading) - message = kodi_to_ansi(message) - print('\033[37;44;1mYESNO:\033[35;49;1m [%s] \033[37;1m%s\033[39;0m' % (heading, message or line1)) - return True - - @staticmethod - def textviewer(heading, text=None, usemono=None): - """A stub implementation for the xbmcgui Dialog class textviewer() method""" - heading = kodi_to_ansi(heading) - text = kodi_to_ansi(text) - print('\033[37;44;1mTEXTVIEWER:\033[35;49;1m [%s]\n\033[37;1m%s\033[39;0m' % (heading, text)) - - @staticmethod - def browseSingle(type, heading, shares, mask=None, useThumbs=None, treatAsFolder=None, defaultt=None): # pylint: disable=redefined-builtin - """A stub implementation for the xbmcgui Dialog class browseSingle() method""" - print('\033[37;44;1mBROWSESINGLE:\033[35;49;1m [%s] \033[37;1m%s\033[39;0m' % (type, heading)) - return 'special://masterprofile/addon_data/script.module.inputstreamhelper/' - - -class DialogProgress: - """A reimplementation of the xbmcgui DialogProgress""" - - def __init__(self): - """A stub constructor for the xbmcgui DialogProgress class""" - self.percent = 0 - - def close(self): - """A stub implementation for the xbmcgui DialogProgress class close() method""" - self.percent = 0 - print() - sys.stdout.flush() - - def create(self, heading, message='', line1='', line2='', line3=''): - """A stub implementation for the xbmcgui DialogProgress class create() method""" - self.percent = 0 - heading = kodi_to_ansi(heading) - line1 = kodi_to_ansi(line1) - print('\033[37;44;1mPROGRESS:\033[35;49;1m [%s] \033[37;1m%s\033[39;0m' % (heading, message or line1)) - sys.stdout.flush() - - def iscanceled(self): - """A stub implementation for the xbmcgui DialogProgress class iscanceled() method""" - return self.percent > 5 # Cancel at 5% - - def update(self, percent, message='', line1='', line2='', line3=''): - """A stub implementation for the xbmcgui DialogProgress class update() method""" - if (percent - 5) < self.percent: - return - self.percent = percent - line1 = kodi_to_ansi(line1) - line2 = kodi_to_ansi(line2) - line3 = kodi_to_ansi(line3) - if line1 or line2 or line3: - print('\033[1G\033[37;44;1mPROGRESS:\033[35;49;1m [%d%%] \033[37;1m%s\033[39;0m' % (percent, message or line1 or line2 or line3), end='') - else: - print('\033[1G\033[37;44;1mPROGRESS:\033[35;49;1m [%d%%]\033[39;0m' % (percent), end='') - sys.stdout.flush() - - -class DialogProgressBG: - """A reimplementation of the xbmcgui DialogProgressBG""" - - def __init__(self): - """A stub constructor for the xbmcgui DialogProgressBG class""" - self.percent = 0 - - @staticmethod - def close(): - """A stub implementation for the xbmcgui DialogProgressBG class close() method""" - print() - - @staticmethod - def create(heading, message): - """A stub implementation for the xbmcgui DialogProgressBG class create() method""" - heading = kodi_to_ansi(heading) - message = kodi_to_ansi(message) - print('\033[37;44;1mPROGRESS:\033[35;49;1m [%s] \033[37;1m%s\033[39;0m' % (heading, message)) - - @staticmethod - def isfinished(): - """A stub implementation for the xbmcgui DialogProgressBG class isfinished() method""" - - def update(self, percent, heading=None, message=None): - """A stub implementation for the xbmcgui DialogProgressBG class update() method""" - if (percent - 5) < self.percent: - return - self.percent = percent - message = kodi_to_ansi(message) - if message: - print('\033[37;44;1mPROGRESS:\033[35;49;1m [%d%%] \033[37;1m%s\033[39;0m' % (percent, message)) - else: - print('\033[1G\033[37;44;1mPROGRESS:\033[35;49;1m [%d%%]\033[39;0m' % (percent), end='') - - -class DialogBusy: - """A reimplementation of the xbmcgui DialogBusy""" - - def __init__(self): - """A stub constructor for the xbmcgui DialogBusy class""" - - @staticmethod - def close(): - """A stub implementation for the xbmcgui DialogBusy class close() method""" - - @staticmethod - def create(): - """A stub implementation for the xbmcgui DialogBusy class create() method""" - - -class ListItem: - """A reimplementation of the xbmcgui ListItem class""" - - def __init__(self, label='', label2='', iconImage='', thumbnailImage='', path='', offscreen=False): - """A stub constructor for the xbmcgui ListItem class""" - self.label = kodi_to_ansi(label) - self.label2 = kodi_to_ansi(label2) - self.path = path - - @staticmethod - def addContextMenuItems(items, replaceItems=False): - """A stub implementation for the xbmcgui ListItem class addContextMenuItems() method""" - return - - @staticmethod - def addStreamInfo(stream_type, stream_values): - """A stub implementation for the xbmcgui LitItem class addStreamInfo() method""" - return - - @staticmethod - def setArt(key): - """A stub implementation for the xbmcgui ListItem class setArt() method""" - return - - @staticmethod - def setContentLookup(enable): - """A stub implementation for the xbmcgui ListItem class setContentLookup() method""" - return - - @staticmethod - def setInfo(type, infoLabels): # pylint: disable=redefined-builtin - """A stub implementation for the xbmcgui ListItem class setInfo() method""" - return - - @staticmethod - def setIsFolder(isFolder): - """A stub implementation for the xbmcgui ListItem class setIsFolder() method""" - return - - @staticmethod - def setMimeType(mimetype): - """A stub implementation for the xbmcgui ListItem class setMimeType() method""" - return - - def setPath(self, path): - """A stub implementation for the xbmcgui ListItem class setPath() method""" - self.path = path - - @staticmethod - def setProperty(key, value): - """A stub implementation for the xbmcgui ListItem class setProperty() method""" - return - - @staticmethod - def setProperties(dictionary): - """A stub implementation for the xbmcgui ListItem class setProperties() method""" - return - - @staticmethod - def setSubtitles(subtitleFiles): - """A stub implementation for the xbmcgui ListItem class setSubtitles() method""" - return - - @staticmethod - def setUniqueIDs(values, defaultrating=None): - """A stub implementation for the xbmcgui ListItem class setUniqueIDs() method""" - return - - -class Window: - """A reimplementation of the xbmcgui Window""" - - def __init__(self, existingwindowId=-1): - """A stub constructor for the xbmcgui Window class""" - return None - - def close(self): - """A stub implementation for the xbmcgui Window class close() method""" - - @staticmethod - def getControl(): - """A stub implementation for the xbmcgui Window class getControl() method""" - return ControlLabel() - - @staticmethod - def getFocusId(): - """A stub implementation for the xbmcgui Window class getFocusId() method""" - return 0 - - @staticmethod - def getProperty(key): - """A stub implementation for the xbmcgui Window class getProperty() method""" - return '' - - @staticmethod - def setProperty(key, value): - """A stub implementation for the xbmcgui Window class setProperty() method""" - return - - @staticmethod - def clearProperty(key): - """A stub implementation for the xbmcgui Window class clearProperty() method""" - return - - def show(self): - """A stub implementation for the xbmcgui Window class show() method""" - - -class WindowXML(Window): - """A reimplementation of the xbmcgui WindowXML""" - - def __init__(self): - """A stub constructor for the xbmcgui WindowXML class""" - super(WindowXML, self).__init__() - - -class WindowXMLDialog(WindowXML): - """A reimplementation of the xbmcgui WindowXMLDialog""" - - def __init__(self): - """A stub constructor for the xbmcgui WindowXMLDialog class""" - super(WindowXMLDialog, self).__init__() - - -def getCurrentWindowId(): - """A stub implementation of the xbmcgui getCurrentWindowId() method""" - return 0 diff --git a/tests/xbmcplugin.py b/tests/xbmcplugin.py deleted file mode 100644 index b8a0305..0000000 --- a/tests/xbmcplugin.py +++ /dev/null @@ -1,112 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright: (c) 2019, Dag Wieers (@dagwieers) -# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -"""This file implements the Kodi xbmcplugin module, either using stubs or alternative functionality""" - -# pylint: disable=invalid-name,unused-argument -from __future__ import absolute_import, division, print_function, unicode_literals - -from xbmc import LOGFATAL, LOGINFO, log -from xbmcextra import kodi_to_ansi, uri_to_path - -try: # Python 3 - from urllib.error import HTTPError - from urllib.request import Request, urlopen -except ImportError: # Python 2 - from urllib2 import HTTPError, Request, urlopen - -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_AND_YEAR = 12 -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 # This is SORT_METHOD_YEAR in Kodi -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_CHANNEL_NUMBER = 42 -SORT_METHOD_BITRATE = 43 -SORT_METHOD_DATE_TAKEN = 44 - - -def addDirectoryItem(handle, path, listitem, isFolder=False): - """A reimplementation of the xbmcplugin addDirectoryItems() function""" - label = kodi_to_ansi(listitem.label) - path = uri_to_path(path) if path else '' - # perma = kodi_to_ansi(listitem.label) # FIXME: Add permalink - bullet = '»' if isFolder else '·' - print('{bullet} {label}{path}'.format(bullet=bullet, label=label, path=path)) - return True - - -def addDirectoryItems(handle, listing, length): - """A reimplementation of the xbmcplugin addDirectoryItems() function""" - for item in listing: - addDirectoryItem(handle, item[0], item[1], item[2]) - return True - - -def addSortMethod(handle, sortMethod): - """A stub implementation of the xbmcplugin addSortMethod() function""" - - -def endOfDirectory(handle, succeeded=True, updateListing=True, cacheToDisc=True): - """A stub implementation of the xbmcplugin endOfDirectory() function""" - # print(kodi_to_ansi('[B]-=( [COLOR cyan]--------[/COLOR] )=-[/B]')) - - -def setContent(handle, content): - """A stub implementation of the xbmcplugin setContent() function""" - - -def setPluginFanart(handle, image, color1=None, color2=None, color3=None): - """A stub implementation of the xbmcplugin setPluginFanart() function""" - - -def setPluginCategory(handle, category): - """A reimplementation of the xbmcplugin setPluginCategory() function""" - print(kodi_to_ansi('[B]-=( [COLOR cyan]%s[/COLOR] )=-[/B]' % category)) - - -def setResolvedUrl(handle, succeeded, listitem): - """A stub implementation of the xbmcplugin setResolvedUrl() function""" - request = Request(listitem.path) - request.get_method = lambda: 'HEAD' - try: - response = urlopen(request) - log('Stream playing successfully: %s' % response.code, LOGINFO) - except HTTPError as exc: - log('Playing stream returned: %s' % exc, LOGFATAL) diff --git a/tests/xbmcvfs.py b/tests/xbmcvfs.py deleted file mode 100644 index f2ebb1c..0000000 --- a/tests/xbmcvfs.py +++ /dev/null @@ -1,80 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright: (c) 2019, Dag Wieers (@dagwieers) -# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -"""This file implements the Kodi xbmcvfs module, either using stubs or alternative functionality""" - -# pylint: disable=invalid-name - -from __future__ import absolute_import, division, print_function, unicode_literals -import os -from shutil import copyfile - - -def File(path, flags='r'): - """A reimplementation of the xbmcvfs File() function""" - return open(path, flags) - - -def Stat(path): - """A reimplementation of the xbmcvfs Stat() function""" - - class stat: - """A reimplementation of the xbmcvfs stat class""" - - def __init__(self, path): - """The constructor xbmcvfs stat class""" - self._stat = os.stat(path) - - def st_mtime(self): - """The xbmcvfs stat class st_mtime method""" - return self._stat.st_mtime - - return stat(path) - - -def copy(src, dst): - """A reimplementation of the xbmcvfs mkdir() function""" - return copyfile(src, dst) == dst - - -def delete(path): - """A reimplementation of the xbmcvfs delete() function""" - try: - os.remove(path) - except OSError: - pass - - -def exists(path): - """A reimplementation of the xbmcvfs exists() function""" - return os.path.exists(path) - - -def listdir(path): - """A reimplementation of the xbmcvfs listdir() function""" - files = [] - dirs = [] - if not exists(path): - return dirs, files - for filename in os.listdir(path): - fullname = os.path.join(path, filename) - if os.path.isfile(fullname): - files.append(filename) - if os.path.isdir(fullname): - dirs.append(filename) - return dirs, files - - -def mkdir(path): - """A reimplementation of the xbmcvfs mkdir() function""" - return os.mkdir(path) - - -def mkdirs(path): - """A reimplementation of the xbmcvfs mkdirs() function""" - return os.makedirs(path) - - -def rmdir(path): - """A reimplementation of the xbmcvfs rmdir() function""" - return os.rmdir(path)