Skip to content

Commit 45e48f2

Browse files
authored
Port new kodiutils changes and stubs (#236)
This PR includes: - Make use of the jsonrpc() function - Introduce addon_profile() (replacing get_userdata_path) - Update xbmc stubs
1 parent 88eb9f5 commit 45e48f2

File tree

14 files changed

+407
-124
lines changed

14 files changed

+407
-124
lines changed

.github/codecov.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
coverage:
2-
range: 50..100
32
round: nearest
43
status:
54
project:
65
default:
76
target: 75%
87
threshold: 6%
9-
# base: auto
10-
# branches: null
118
patch: false
129
comment: false
1310
ignore:

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@
33
.tox/
44
__pycache__/
55
*.py[cod]
6+
7+
# Userdata
68
test/cdm/
79
test/userdata/addon_settings.json
810
test/userdata/addon_data/
911
test/userdata/cdm/
12+
test/userdata/backup/
1013
test/userdata/tmp/
1114

15+
# Temporary
1216
experiment/
1317
*.zip
1418

.pylintrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ disable=
1010
too-many-lines,
1111
too-many-locals,
1212
too-many-return-statements,
13+
unexpected-keyword-arg,

.travis.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ sudo: false
1111

1212
env:
1313
PYTHONPATH: lib:test
14+
PYTHONIOENCODING: utf-8
1415

1516
install:
1617
- pip install -r requirements.txt
@@ -21,9 +22,9 @@ script:
2122
- pylint lib/ test/
2223
#- kodi-addon-checker . --branch=krypton
2324
#- kodi-addon-checker . --branch=leia
24-
- coverage run -a default.py
2525
- proxy.py &
2626
- coverage run -m unittest discover
27+
- coverage run -a default.py
2728

2829
after_success:
2930
- codecov

Makefile

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
export PYTHONPATH := $(CURDIR)/lib:$(CURDIR)/test
2-
addon_xml := addon.xml
2+
PYTHON := python
33

4-
# Collect information to build as sensible package name
5-
name = $(shell xmllint --xpath 'string(/addon/@id)' $(addon_xml))
6-
version = $(shell xmllint --xpath 'string(/addon/@version)' $(addon_xml))
4+
name = $(shell xmllint --xpath 'string(/addon/@id)' addon.xml)
5+
version = $(shell xmllint --xpath 'string(/addon/@version)' addon.xml)
76
git_branch = $(shell git rev-parse --abbrev-ref HEAD)
87
git_hash = $(shell git rev-parse --short HEAD)
98

@@ -13,7 +12,7 @@ include_paths = $(patsubst %,$(name)/%,$(include_files))
1312
exclude_files = \*.new \*.orig \*.pyc \*.pyo
1413
zip_dir = $(name)/
1514

16-
languages := de_de el_gr fr_fr it_it nl_nl ru_ru sv_se
15+
languages = $(filter-out en_gb, $(patsubst resources/language/resource.language.%, %, $(wildcard resources/language/*)))
1716

1817
blue = \e[1;34m
1918
white = \e[1;37m
@@ -31,11 +30,11 @@ sanity: tox pylint language
3130

3231
tox:
3332
@echo -e "$(white)=$(blue) Starting sanity tox test$(reset)"
34-
tox -q
33+
$(PYTHON) -m tox -q
3534

3635
pylint:
3736
@echo -e "$(white)=$(blue) Starting sanity pylint test$(reset)"
38-
pylint lib/ test/
37+
$(PYTHON) -m pylint lib/ test/
3938

4039
language:
4140
@echo -e "$(white)=$(blue) Starting language test$(reset)"
@@ -50,25 +49,28 @@ addon: clean
5049

5150
unit: clean
5251
@echo -e "$(white)=$(blue) Starting unit tests$(reset)"
53-
-pkill -ef proxy.py
54-
proxy.py &
55-
python -m unittest discover
56-
pkill -ef proxy.py
52+
-pkill -ef '$(PYTHON) -m proxy'
53+
$(PYTHON) -m proxy &
54+
$(PYTHON) -m unittest discover
55+
pkill -ef '$(PYTHON) -m proxy'
5756

5857
run:
5958
@echo -e "$(white)=$(blue) Run CLI$(reset)"
60-
python default.py
59+
$(PYTHON) default.py
6160

6261
zip: clean
6362
@echo -e "$(white)=$(blue) Building new package$(reset)"
6463
@rm -f ../$(zip_name)
6564
cd ..; zip -r $(zip_name) $(include_paths) -x $(exclude_files)
6665
@echo -e "$(white)=$(blue) Successfully wrote package as: $(white)../$(zip_name)$(reset)"
6766

67+
codecov:
68+
@echo -e "$(white)=$(blue) Test codecov.yml syntax$(reset)"
69+
curl --data-binary @.github/codecov.yml https://codecov.io/validate
70+
6871
clean:
6972
@echo -e "$(white)=$(blue) Cleaning up$(reset)"
70-
find . -name '*.pyc' -type f -delete
71-
find . -name '*.pyo' -type f -delete
73+
find . -name '*.py[cod]' -type f -delete
7274
find . -name '__pycache__' -type d -delete
7375
find test/userdata/ -mindepth 1 -not -name '*settings.json' -delete
7476
rm -rf .pytest_cache/ .tox/ *.log lib/inputstreamhelper.egg-info/

default.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@
22
''' This is the actual InputStream Helper API script entry point '''
33

44
from __future__ import absolute_import, division, unicode_literals
5-
import os
65
import sys
7-
import xbmc
8-
import xbmcaddon
6+
import os.path
7+
from xbmc import translatePath
8+
from xbmcaddon import Addon
99

1010

1111
def to_unicode(text, encoding='utf-8'):
1212
''' Force text to unicode '''
1313
return text.decode(encoding) if isinstance(text, bytes) else text
1414

1515

16-
sys.path.append(os.path.join(to_unicode(xbmc.translatePath(xbmcaddon.Addon().getAddonInfo('path'))), 'lib'))
16+
sys.path.append(os.path.join(to_unicode(translatePath(Addon().getAddonInfo('path'))), 'lib'))
1717
from inputstreamhelper.api import run # noqa: E402
1818

1919
run(sys.argv)

lib/inputstreamhelper/__init__.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from __future__ import absolute_import, division, unicode_literals
44
import os
55
from inputstreamhelper import config
6-
from .kodiutils import (browsesingle, execute_jsonrpc, get_addon_info, get_proxies, get_setting, get_userdata_path, kodi_to_ascii, localize,
6+
from .kodiutils import (addon_profile, browsesingle, get_addon_info, get_proxies, get_setting, jsonrpc, kodi_to_ascii, localize,
77
log, notification, ok_dialog, progress_dialog, select_dialog, set_setting, textviewer, translate_path, yesno_dialog)
88

99
# NOTE: Work around issue caused by platform still using os.popen()
@@ -118,7 +118,7 @@ def _ia_cdm_path(cls):
118118
def _backup_path(cls):
119119
''' Return the path to the cdm backups '''
120120
from xbmcvfs import exists, mkdir
121-
path = os.path.join(get_userdata_path(), 'backup')
121+
path = os.path.join(addon_profile(), 'backup')
122122
if not exists(path):
123123
mkdir(path)
124124
return path
@@ -433,7 +433,7 @@ def _http_download(self, url, message=None):
433433

434434
def _has_inputstream(self):
435435
"""Checks if selected InputStream add-on is installed."""
436-
data = execute_jsonrpc(dict(jsonrpc='2.0', id=1, method='Addons.GetAddonDetails', params=dict(addonid=self.inputstream_addon)))
436+
data = jsonrpc(method='Addons.GetAddonDetails', params=dict(addonid=self.inputstream_addon))
437437
if 'error' in data:
438438
log('{addon} is not installed.', addon=self.inputstream_addon)
439439
return False
@@ -443,7 +443,7 @@ def _has_inputstream(self):
443443

444444
def _inputstream_enabled(self):
445445
"""Returns whether selected InputStream add-on is enabled.."""
446-
data = execute_jsonrpc(dict(jsonrpc='2.0', id=1, method='Addons.GetAddonDetails', params=dict(addonid=self.inputstream_addon, properties=['enabled'])))
446+
data = jsonrpc(method='Addons.GetAddonDetails', params=dict(addonid=self.inputstream_addon, properties=['enabled']))
447447
if data.get('result', {}).get('addon', {}).get('enabled'):
448448
log('{addon} {version} is enabled.', addon=self.inputstream_addon, version=self._inputstream_version())
449449
return True
@@ -453,7 +453,7 @@ def _inputstream_enabled(self):
453453

454454
def _enable_inputstream(self):
455455
"""Enables selected InputStream add-on."""
456-
data = execute_jsonrpc(dict(jsonrpc='2.0', id=1, method='Addons.SetAddonEnabled', params=dict(addonid=self.inputstream_addon, enabled=True)))
456+
data = jsonrpc(method='Addons.SetAddonEnabled', params=dict(addonid=self.inputstream_addon, enabled=True))
457457
if 'error' in data:
458458
return False
459459
return True

lib/inputstreamhelper/kodiutils.py

Lines changed: 52 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from xbmcaddon import Addon
88
from .unicodehelper import from_unicode, to_unicode
99

10-
ADDON = Addon('script.module.inputstreamhelper')
10+
ADDON = Addon()
1111

1212

1313
class SafeDict(dict):
@@ -17,20 +17,22 @@ def __missing__(self, key):
1717
return '{' + key + '}'
1818

1919

20-
def has_socks():
21-
''' Test if socks is installed, and remember this information '''
20+
def addon_profile():
21+
''' Cache and return add-on profile '''
22+
return to_unicode(xbmc.translatePath(ADDON.getAddonInfo('profile')))
2223

23-
# If it wasn't stored before, check if socks is installed
24-
if not hasattr(has_socks, 'installed'):
25-
try:
26-
import socks # noqa: F401; pylint: disable=unused-variable,unused-import
27-
has_socks.installed = True
28-
except ImportError:
29-
has_socks.installed = False
30-
return None # Detect if this is the first run
3124

32-
# Return the stored value
33-
return has_socks.installed
25+
def has_socks():
26+
''' Test if socks is installed, and use a static variable to remember '''
27+
if hasattr(has_socks, 'cached'):
28+
return getattr(has_socks, 'cached')
29+
try:
30+
import socks # noqa: F401; pylint: disable=unused-variable,unused-import
31+
except ImportError:
32+
has_socks.cached = False
33+
return None # Detect if this is the first run
34+
has_socks.cached = True
35+
return True
3436

3537

3638
def browsesingle(type, heading, shares='', mask='', useThumbs=False, treatAsFolder=False, defaultt=None): # pylint: disable=invalid-name,redefined-builtin
@@ -90,15 +92,17 @@ def yesno_dialog(heading='', message='', nolabel=None, yeslabel=None, autoclose=
9092
def localize(string_id, **kwargs):
9193
''' Return the translated string from the .po language files, optionally translating variables '''
9294
if kwargs:
93-
import string
94-
return string.Formatter().vformat(ADDON.getLocalizedString(string_id), (), SafeDict(**kwargs))
95-
95+
from string import Formatter
96+
return Formatter().vformat(ADDON.getLocalizedString(string_id), (), SafeDict(**kwargs))
9697
return ADDON.getLocalizedString(string_id)
9798

9899

99-
def get_setting(setting_id, default=None):
100+
def get_setting(key, default=None):
100101
''' Get an add-on setting '''
101-
value = to_unicode(ADDON.getSetting(setting_id))
102+
try:
103+
value = to_unicode(ADDON.getSetting(key))
104+
except RuntimeError: # Occurs when the add-on is disabled
105+
return default
102106
if value == '' and default is not None:
103107
return default
104108
return value
@@ -109,15 +113,15 @@ def translate_path(path):
109113
return to_unicode(xbmc.translatePath(path))
110114

111115

112-
def set_setting(setting_id, setting_value):
116+
def set_setting(key, value):
113117
''' Set an add-on setting '''
114-
return ADDON.setSetting(setting_id, setting_value)
118+
return ADDON.setSetting(key, from_unicode(str(value)))
115119

116120

117-
def get_global_setting(setting):
121+
def get_global_setting(key):
118122
''' Get a Kodi setting '''
119-
result = execute_jsonrpc(dict(jsonrpc='2.0', id=1, method='Settings.GetSettingValue', params=dict(setting='%s' % setting)))
120-
return result.get('result', dict()).get('value')
123+
result = jsonrpc(method='Settings.GetSettingValue', params=dict(setting=key))
124+
return result.get('result', {}).get('value')
121125

122126

123127
def get_proxies():
@@ -163,28 +167,40 @@ def get_proxies():
163167
return dict(http=proxy_address, https=proxy_address)
164168

165169

166-
def get_userdata_path():
167-
''' Return the addon's addon_data path '''
168-
return translate_path(ADDON.getAddonInfo('profile'))
169-
170-
171170
def get_addon_info(key):
172171
''' Return addon information '''
173172
return to_unicode(ADDON.getAddonInfo(key))
174173

175174

176-
def execute_jsonrpc(payload):
177-
''' Kodi JSON-RPC request. Return the response in a dictionary. '''
178-
import json
179-
log('jsonrpc payload: {payload}', payload=payload)
180-
response = xbmc.executeJSONRPC(json.dumps(payload))
181-
log('jsonrpc response: {response}', response=response)
182-
return json.loads(response)
175+
def jsonrpc(*args, **kwargs):
176+
''' Perform JSONRPC calls '''
177+
from json import dumps, loads
178+
179+
# We do not accept both args and kwargs
180+
if args and kwargs:
181+
log('ERROR: Wrong use of jsonrpc()')
182+
return None
183+
184+
# Process a list of actions
185+
if args:
186+
for (idx, cmd) in enumerate(args):
187+
if cmd.get('id') is None:
188+
cmd.update(id=idx)
189+
if cmd.get('jsonrpc') is None:
190+
cmd.update(jsonrpc='2.0')
191+
return loads(xbmc.executeJSONRPC(dumps(args)))
192+
193+
# Process a single action
194+
if kwargs.get('id') is None:
195+
kwargs.update(id=0)
196+
if kwargs.get('jsonrpc') is None:
197+
kwargs.update(jsonrpc='2.0')
198+
return loads(xbmc.executeJSONRPC(dumps(kwargs)))
183199

184200

185201
def log(msg, level=xbmc.LOGDEBUG, **kwargs):
186202
''' InputStream Helper log method '''
187-
xbmc.log(msg=from_unicode('[{addon}]: {msg}'.format(addon=get_addon_info('id'), msg=msg.format(**kwargs))), level=level)
203+
xbmc.log(msg=from_unicode('[{addon}] {msg}'.format(addon=get_addon_info('id'), msg=msg.format(**kwargs))), level=level)
188204

189205

190206
def kodi_to_ascii(string):

test/test_proxy.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@
2020
class LinuxProxyTests(unittest.TestCase):
2121

2222
def setUp(self):
23-
xbmc.GLOBAL_SETTINGS['network.usehttpproxy'] = True
24-
xbmc.GLOBAL_SETTINGS['network.httpproxytype'] = 0
25-
xbmc.GLOBAL_SETTINGS['network.httpproxyserver'] = '127.0.0.1'
26-
xbmc.GLOBAL_SETTINGS['network.httpproxyport'] = '8899'
23+
xbmc.settings['network.usehttpproxy'] = True
24+
xbmc.settings['network.httpproxytype'] = 0
25+
xbmc.settings['network.httpproxyserver'] = '127.0.0.1'
26+
xbmc.settings['network.httpproxyport'] = '8899'
2727

2828
def tearDown(self):
29-
xbmc.GLOBAL_SETTINGS['network.usehttpproxy'] = False
29+
xbmc.settings['network.usehttpproxy'] = False
3030

3131
def test_check_inputstream_mpd(self):
3232
inputstreamhelper.system_os = lambda: 'Linux'

0 commit comments

Comments
 (0)