Skip to content

Commit 2d2fded

Browse files
Improve logging, file operations and Widevine CDM detection (#310)
1 parent b375bc2 commit 2d2fded

File tree

6 files changed

+169
-132
lines changed

6 files changed

+169
-132
lines changed

lib/inputstreamhelper/__init__.py

Lines changed: 39 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
import os
77

88
from . import config
9-
from .kodiutils import (addon_version, get_proxies, get_setting, get_setting_bool, get_setting_float, get_setting_int, jsonrpc,
9+
from .kodiutils import (addon_version, delete, exists, get_proxies, get_setting, get_setting_bool, get_setting_float, get_setting_int, jsonrpc,
1010
kodi_to_ascii, kodi_version, localize, log, notification, ok_dialog, progress_dialog, select_dialog,
1111
set_setting, set_setting_bool, textviewer, translate_path, yesno_dialog)
1212
from .utils import arch, http_download, run_cmd, store, system_os, temp_path, unzip
1313
from .widevine.arm import install_widevine_arm, select_best_chromeos_image, unmount
14-
from .widevine.widevine import (backup_path, has_widevine, ia_cdm_path, install_cdm_from_backup, latest_widevine_version,
15-
load_widevine_config, missing_widevine_libs, widevine_config_path, widevine_eula, widevine_path)
14+
from .widevine.widevine import (backup_path, has_widevinecdm, ia_cdm_path, install_cdm_from_backup, latest_widevine_version,
15+
load_widevine_config, missing_widevine_libs, widevine_config_path, widevine_eula, widevinecdm_path)
1616

1717
# NOTE: Work around issue caused by platform still using os.popen()
1818
# This helps to survive 'IOError: [Errno 10] No child processes'
@@ -46,7 +46,7 @@ def __init__(self, protocol, drm=None):
4646
self.drm = drm
4747

4848
from platform import uname
49-
log('Platform information: {uname}', uname=uname())
49+
log(0, 'Platform information: {uname}', uname=uname())
5050

5151
if self.protocol not in config.INPUTSTREAM_PROTOCOLS:
5252
raise InputStreamException('UnsupportedProtocol')
@@ -97,7 +97,7 @@ def _inputstream_version(self):
9797

9898
@staticmethod
9999
def _get_lib_version(path):
100-
if not path:
100+
if not path or not exists(path):
101101
return '(Not found)'
102102
import re
103103
with open(path, 'rb') as library:
@@ -111,20 +111,20 @@ def _has_inputstream(self):
111111
"""Checks if selected InputStream add-on is installed."""
112112
data = jsonrpc(method='Addons.GetAddonDetails', params=dict(addonid=self.inputstream_addon))
113113
if 'error' in data:
114-
log('{addon} is not installed.', addon=self.inputstream_addon)
114+
log(3, '{addon} is not installed.', addon=self.inputstream_addon)
115115
return False
116116

117-
log('{addon} is installed.', addon=self.inputstream_addon)
117+
log(0, '{addon} is installed.', addon=self.inputstream_addon)
118118
return True
119119

120120
def _inputstream_enabled(self):
121121
"""Returns whether selected InputStream add-on is enabled.."""
122122
data = jsonrpc(method='Addons.GetAddonDetails', params=dict(addonid=self.inputstream_addon, properties=['enabled']))
123123
if data.get('result', {}).get('addon', {}).get('enabled'):
124-
log('{addon} {version} is enabled.', addon=self.inputstream_addon, version=self._inputstream_version())
124+
log(0, '{addon} {version} is enabled.', addon=self.inputstream_addon, version=self._inputstream_version())
125125
return True
126126

127-
log('{addon} is disabled.', addon=self.inputstream_addon)
127+
log(3, '{addon} is disabled.', addon=self.inputstream_addon)
128128
return False
129129

130130
def _enable_inputstream(self):
@@ -138,23 +138,23 @@ def _enable_inputstream(self):
138138
def _supports_widevine():
139139
"""Checks if Widevine is supported on the architecture/operating system/Kodi version."""
140140
if arch() not in config.WIDEVINE_SUPPORTED_ARCHS:
141-
log('Unsupported Widevine architecture found: {arch}', arch=arch())
141+
log(4, 'Unsupported Widevine architecture found: {arch}', arch=arch())
142142
ok_dialog(localize(30004), localize(30007, arch=arch())) # Widevine not available on this architecture
143143
return False
144144

145145
if system_os() not in config.WIDEVINE_SUPPORTED_OS:
146-
log('Unsupported Widevine OS found: {os}', os=system_os())
146+
log(4, 'Unsupported Widevine OS found: {os}', os=system_os())
147147
ok_dialog(localize(30004), localize(30011, os=system_os())) # Operating system not supported by Widevine
148148
return False
149149

150150
from distutils.version import LooseVersion # pylint: disable=import-error,no-name-in-module,useless-suppression
151151
if LooseVersion(config.WIDEVINE_MINIMUM_KODI_VERSION[system_os()]) > LooseVersion(kodi_version()):
152-
log('Unsupported Kodi version for Widevine: {version}', version=kodi_version())
152+
log(4, 'Unsupported Kodi version for Widevine: {version}', version=kodi_version())
153153
ok_dialog(localize(30004), localize(30010, version=config.WIDEVINE_MINIMUM_KODI_VERSION[system_os()])) # Kodi too old
154154
return False
155155

156156
if 'WindowsApps' in translate_path('special://xbmcbin/'): # uwp is not supported
157-
log('Unsupported UWP Kodi version detected.')
157+
log(4, 'Unsupported UWP Kodi version detected.')
158158
ok_dialog(localize(30004), localize(30012)) # Windows Store Kodi falls short
159159
return False
160160

@@ -190,7 +190,7 @@ def install_and_finish(self, progress, version):
190190
install_cdm_from_backup(version)
191191

192192
progress.update(98, message=localize(30050)) # Finishing
193-
if has_widevine():
193+
if has_widevinecdm():
194194
wv_check = self._check_widevine()
195195
if wv_check:
196196
progress.update(100, message=localize(30051)) # Widevine CDM successfully installed.
@@ -229,10 +229,9 @@ def install_widevine(self):
229229
@staticmethod
230230
def remove_widevine():
231231
"""Removes Widevine CDM"""
232-
from xbmcvfs import delete, exists
233-
widevinecdm = widevine_path()
234-
if widevinecdm and exists(widevinecdm):
235-
log('Remove Widevine CDM at {path}', path=widevinecdm)
232+
if has_widevinecdm():
233+
widevinecdm = widevinecdm_path()
234+
log(0, 'Removed Widevine CDM at {path}', path=widevinecdm)
236235
delete(widevinecdm)
237236
notification(localize(30037), localize(30052)) # Success! Widevine successfully removed.
238237
return True
@@ -251,7 +250,7 @@ def _first_run():
251250
if LooseVersion(addon_version()) > LooseVersion(settings_version):
252251
# New version found, save addon_version to settings
253252
set_setting('version', addon_version())
254-
log('inputstreamhelper version {version} is running for the first time', version=addon_version())
253+
log(2, 'InputStreamHelper version {version} is running for the first time', version=addon_version())
255254
return True
256255
return False
257256

@@ -263,7 +262,7 @@ def _update_widevine(self):
263262
if last_update and not self._first_run():
264263
last_update_dt = datetime.fromtimestamp(get_setting_float('last_update', 0.0))
265264
if last_update_dt + timedelta(days=get_setting_int('update_frequency', 14)) >= datetime.utcnow():
266-
log('Widevine update check was made on {date}', date=last_update_dt.isoformat())
265+
log(2, 'Widevine update check was made on {date}', date=last_update_dt.isoformat())
267266
return
268267

269268
wv_config = load_widevine_config()
@@ -274,35 +273,35 @@ def _update_widevine(self):
274273
else:
275274
component = 'Chrome OS'
276275
current_version = select_best_chromeos_image(wv_config)['version']
277-
log('Latest {component} version is {version}', component=component, version=latest_version)
278-
log('Current {component} version installed is {version}', component=component, version=current_version)
276+
log(0, 'Latest {component} version is {version}', component=component, version=latest_version)
277+
log(0, 'Current {component} version installed is {version}', component=component, version=current_version)
279278

280279
from distutils.version import LooseVersion # pylint: disable=import-error,no-name-in-module,useless-suppression
281280
if LooseVersion(latest_version) > LooseVersion(current_version):
282-
log('There is an update available for {component}', component=component)
281+
log(2, 'There is an update available for {component}', component=component)
283282
if yesno_dialog(localize(30040), localize(30033), nolabel=localize(30028), yeslabel=localize(30034)):
284283
self.install_widevine()
285284
else:
286-
log('User declined to update {component}.', component=component)
285+
log(3, 'User declined to update {component}.', component=component)
287286
else:
288287
from time import mktime
289288
set_setting('last_update', mktime(datetime.utcnow().timetuple()))
290-
log('User is on the latest available {component} version.', component=component)
289+
log(0, 'User is on the latest available {component} version.', component=component)
291290

292291
def _check_widevine(self):
293292
"""Checks that all Widevine components are installed and available."""
294293
if system_os() == 'Android': # no checks needed for Android
295294
return True
296295

297-
if not os.path.exists(widevine_config_path()):
298-
log('Widevine or Chrome OS recovery.conf is missing. Reinstall is required.')
296+
if not exists(widevine_config_path()):
297+
log(4, 'Widevine or Chrome OS recovery.conf is missing. Reinstall is required.')
299298
ok_dialog(localize(30001), localize(30031)) # An update of Widevine is required
300299
return self.install_widevine()
301300

302301
if 'x86' in arch(): # check that widevine arch matches system arch
303302
wv_config = load_widevine_config()
304303
if config.WIDEVINE_ARCH_MAP_X86[arch()] != wv_config['arch']:
305-
log('Widevine/system arch mismatch. Reinstall is required.')
304+
log(4, 'Widevine/system arch mismatch. Reinstall is required.')
306305
ok_dialog(localize(30001), localize(30031)) # An update of Widevine is required
307306
return self.install_widevine()
308307

@@ -326,7 +325,7 @@ def cleanup():
326325
store('attached_loop_dev', False)
327326
if store('modprobe_loop'):
328327
notification(localize(30035), localize(30036)) # Unload by hand in CLI
329-
if not has_widevine():
328+
if not has_widevinecdm():
330329
rmtree(ia_cdm_path())
331330

332331
rmtree(temp_path())
@@ -338,7 +337,7 @@ def _supports_hls(self):
338337
if LooseVersion(self._inputstream_version()) >= LooseVersion(config.HLS_MINIMUM_IA_VERSION):
339338
return True
340339

341-
log('HLS is unsupported on {addon} version {version}', addon=self.inputstream_addon, version=self._inputstream_version())
340+
log(3, 'HLS is unsupported on {addon} version {version}', addon=self.inputstream_addon, version=self._inputstream_version())
342341
return False
343342

344343
def _check_drm(self):
@@ -349,7 +348,7 @@ def _check_drm(self):
349348
if self.drm != 'widevine':
350349
return True
351350

352-
if has_widevine():
351+
if has_widevinecdm():
353352
return self._check_widevine()
354353

355354
if yesno_dialog(localize(30041), localize(30002), nolabel=localize(30028), yeslabel=localize(30038)): # Widevine required
@@ -368,16 +367,16 @@ def _install_inputstream(self):
368367
# Check if InputStream add-on exists!
369368
Addon('{}'.format(self.inputstream_addon))
370369

371-
log('inputstream addon installed from repo.')
370+
log(0, 'InputStream add-on installed from repo.')
372371
return True
373372
except RuntimeError:
374-
log('inputstream addon not installed.')
373+
log(3, 'InputStream add-on not installed.')
375374
return False
376375

377376
def check_inputstream(self):
378377
"""Main function. Ensures that all components are available for InputStream add-on playback."""
379378
if get_setting_bool('disabled', False): # blindly return True if helper has been disabled
380-
log('inputstreamhelper is disabled in its settings.xml.')
379+
log(3, 'InputStreamHelper is disabled in its settings.xml.')
381380
return True
382381
if self.drm == 'widevine' and not self._supports_widevine():
383382
return False
@@ -391,7 +390,7 @@ def check_inputstream(self):
391390
if not ret:
392391
return False
393392
self._enable_inputstream()
394-
log('{addon} {version} is installed and enabled.', addon=self.inputstream_addon, version=self._inputstream_version())
393+
log(0, '{addon} {version} is installed and enabled.', addon=self.inputstream_addon, version=self._inputstream_version())
395394

396395
if self.protocol == 'hls' and not self._supports_hls():
397396
ok_dialog(localize(30004), # HLS Minimum version is needed
@@ -415,13 +414,13 @@ def info_dialog(self):
415414
text += ' - ' + localize(30812, version=self._inputstream_version(), state=istream_state) + '\n'
416415
text += '\n'
417416

418-
text += ' - ' + localize(30820) + '\n' # Widevine information
417+
text += localize(30820) + '\n' # Widevine information
419418
if system_os() == 'Android':
420419
text += ' - ' + localize(30821) + '\n'
421420
else:
422421
from datetime import datetime
423422
wv_updated = datetime.fromtimestamp(get_setting_float('last_update', 0.0)).strftime("%Y-%m-%d %H:%M") if get_setting_float('last_update', 0.0) else 'Never'
424-
text += ' - ' + localize(30822, version=self._get_lib_version(widevine_path()), date=wv_updated) + '\n'
423+
text += ' - ' + localize(30822, version=self._get_lib_version(widevinecdm_path()), date=wv_updated) + '\n'
425424
text += ' - ' + localize(30823, path=ia_cdm_path()) + '\n'
426425

427426
if arch() in ('arm', 'arm64'): # Chrome OS version
@@ -431,7 +430,7 @@ def info_dialog(self):
431430

432431
text += localize(30830, url=config.ISSUE_URL) # Report issues
433432

434-
log('\n{info}'.format(info=kodi_to_ascii(text)), level=2)
433+
log(2, '\n{info}'.format(info=kodi_to_ascii(text)))
435434
textviewer(localize(30901), text)
436435

437436
def rollback_libwv(self):
@@ -440,7 +439,7 @@ def rollback_libwv(self):
440439
versions = os.listdir(bpath)
441440

442441
# Return if Widevine is not installed
443-
if not os.path.exists(widevine_config_path()):
442+
if not exists(widevine_config_path()):
444443
notification(localize(30004), localize(30041))
445444
return
446445

@@ -465,7 +464,7 @@ def rollback_libwv(self):
465464

466465
version = select_dialog(localize(30057), show_versions)
467466
if version != -1:
468-
log('Rollback to version {version}', version=versions[version])
467+
log(0, 'Rollback to version {version}', version=versions[version])
469468
install_cdm_from_backup(versions[version])
470469
notification(localize(30037), localize(30051)) # Success! Widevine successfully installed.
471470

lib/inputstreamhelper/api.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ def run(params):
2424
elif params[1] == 'info':
2525
info_dialog()
2626
else:
27-
log("invalid API call method '{method}'", method=params[1])
27+
log(4, "Invalid API call method '{method}'", method=params[1])
2828

2929
elif len(params) > 4:
30-
log('invalid API call, too many parameters')
30+
log(4, 'Invalid API call, too many parameters.')
3131
else:
3232
ADDON.openSettings()
3333

lib/inputstreamhelper/kodiutils.py

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from xbmcgui import DialogProgress
99
from .unicodes import from_unicode, to_unicode
1010

11-
# NOTE: We need to add the add-on id in here explicitly !
11+
# NOTE: We need to explicitly add the add-on id here!
1212
ADDON = xbmcaddon.Addon('script.module.inputstreamhelper')
1313

1414

@@ -262,9 +262,13 @@ def get_proxies():
262262
return dict(http=proxy_address, https=proxy_address)
263263

264264

265-
def log(msg, level=xbmc.LOGDEBUG, **kwargs):
266-
"""InputStream Helper log method"""
267-
xbmc.log(msg=from_unicode('[{addon}] {msg}'.format(addon=addon_id(), msg=msg.format(**kwargs))), level=level)
265+
def log(level=0, message='', **kwargs):
266+
"""Log info messages to Kodi"""
267+
if kwargs:
268+
from string import Formatter
269+
message = Formatter().vformat(message, (), SafeDict(**kwargs))
270+
message = '[{addon}] {message}'.format(addon=addon_id(), message=message)
271+
xbmc.log(from_unicode(message), level)
268272

269273

270274
def jsonrpc(*args, **kwargs):
@@ -273,7 +277,7 @@ def jsonrpc(*args, **kwargs):
273277

274278
# We do not accept both args and kwargs
275279
if args and kwargs:
276-
log('ERROR: Wrong use of jsonrpc()')
280+
log(4, 'ERROR: Wrong use of jsonrpc()')
277281
return None
278282

279283
# Process a list of actions
@@ -305,3 +309,43 @@ def kodi_to_ascii(string):
305309
string = string.replace('[COLOR yellow]', '')
306310
string = string.replace('[/COLOR]', '')
307311
return string
312+
313+
314+
def copy(src, dest):
315+
"""Copy a file (using xbmcvfs)"""
316+
from xbmcvfs import copy as vfscopy
317+
log(2, "Copy file '{src}' to '{dest}'.", src=src, dest=dest)
318+
return vfscopy(src, dest)
319+
320+
321+
def delete(path):
322+
"""Remove a file (using xbmcvfs)"""
323+
from xbmcvfs import delete as vfsdelete
324+
log(2, "Delete file '{path}'.", path=path)
325+
return vfsdelete(path)
326+
327+
328+
def exists(path):
329+
"""Whether the path exists (using xbmcvfs)"""
330+
from xbmcvfs import exists as vfsexists
331+
return vfsexists(path)
332+
333+
334+
def mkdir(path):
335+
"""Create a directory (using xbmcvfs)"""
336+
from xbmcvfs import mkdir as vfsmkdir
337+
log(2, "Create directory '{path}'.", path=path)
338+
return vfsmkdir(path)
339+
340+
341+
def mkdirs(path):
342+
"""Create directory including parents (using xbmcvfs)"""
343+
from xbmcvfs import mkdirs as vfsmkdirs
344+
log(2, "Recursively create directory '{path}'.", path=path)
345+
return vfsmkdirs(path)
346+
347+
348+
def stat_file(path):
349+
"""Return information about a file (using xbmcvfs)"""
350+
from xbmcvfs import Stat
351+
return Stat(path)

0 commit comments

Comments
 (0)