Skip to content

Commit c8e71ed

Browse files
committed
Attempt to use json.load(fdesc) again
This PR includes: - Use json.load(fdesc) - Early exit in tokenresolver
1 parent 2d04db2 commit c8e71ed

File tree

13 files changed

+353
-342
lines changed

13 files changed

+353
-342
lines changed

resources/lib/addon.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
except ImportError: # Python 2
1111
from urllib import unquote_plus
1212

13-
from kodiutils import localize, log_access, notification, refresh_caches
13+
from kodiutils import localize, log_access, notification
1414
from statichelper import from_unicode, to_unicode
15+
from utils import refresh_caches
1516

1617
plugin = Plugin() # pylint: disable=invalid-name
1718

resources/lib/apihelper.py

Lines changed: 16 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,20 @@
55
from __future__ import absolute_import, division, unicode_literals
66

77
try: # Python 3
8-
from urllib.error import HTTPError
98
from urllib.parse import quote_plus, unquote
10-
from urllib.request import build_opener, install_opener, ProxyHandler, Request, urlopen
9+
from urllib.request import build_opener, install_opener, ProxyHandler, urlopen
1110
except ImportError: # Python 2
1211
from urllib import quote_plus
13-
from urllib2 import build_opener, install_opener, ProxyHandler, Request, HTTPError, unquote, urlopen
12+
from urllib2 import build_opener, install_opener, ProxyHandler, unquote, urlopen
1413

1514
from data import CHANNELS
1615
from helperobjects import TitleItem
17-
from kodiutils import (delete_cached_thumbnail, get_cache, get_global_setting, get_proxies, get_setting,
18-
has_addon, localize, localize_from_data, log, log_error, ok_dialog, ttl, update_cache,
19-
url_for)
20-
from statichelper import (add_https_method, convert_html_to_kodilabel, find_entry, from_unicode, play_url_to_id,
21-
program_to_url, realpage, to_unicode, strip_newlines, url_to_program)
16+
from kodiutils import (delete_cached_thumbnail, get_global_setting, get_proxies, get_setting,
17+
has_addon, localize, localize_from_data, log, url_for)
2218
from metadata import Metadata
19+
from statichelper import (add_https_method, convert_html_to_kodilabel, find_entry, from_unicode, play_url_to_id,
20+
program_to_url, realpage, strip_newlines, url_to_program)
21+
from utils import get_cache, get_cached_url_json, get_url_json, ttl, update_cache
2322

2423

2524
class ApiHelper:
@@ -57,16 +56,10 @@ def get_tvshows(self, category=None, channel=None, feature=None):
5756
if not category and not channel and not feature:
5857
params['facets[transcodingStatus]'] = 'AVAILABLE' # Required for getting results in Suggests API
5958
cache_file = 'programs.json'
60-
tvshows = get_cache(cache_file, ttl=ttl('indirect')) # Try the cache if it is fresh
61-
if not tvshows:
62-
from json import loads
63-
querystring = '&'.join('{}={}'.format(key, value) for key, value in list(params.items()))
64-
suggest_url = self._VRTNU_SUGGEST_URL + '?' + querystring
65-
log(2, 'URL get: {url}', url=unquote(suggest_url))
66-
tvshows = loads(to_unicode(urlopen(suggest_url).read()))
67-
update_cache(cache_file, tvshows)
6859

69-
return tvshows
60+
querystring = '&'.join('{}={}'.format(key, value) for key, value in list(params.items()))
61+
suggest_url = self._VRTNU_SUGGEST_URL + '?' + querystring
62+
return get_cached_url_json(url=suggest_url, cache=cache_file, ttl=ttl('indirect'))
7063

7164
def list_tvshows(self, category=None, channel=None, feature=None, use_favorites=False):
7265
''' List all TV shows for a given category, channel or feature, optionally filtered by favorites '''
@@ -413,8 +406,7 @@ def get_episode_by_air_date(self, channel_name, start_date, end_date=None):
413406
schedule_date = onairdate
414407
schedule_datestr = schedule_date.isoformat().split('T')[0]
415408
url = 'https://www.vrt.be/bin/epg/schedule.%s.json' % schedule_datestr
416-
from json import loads
417-
schedule_json = loads(to_unicode(urlopen(url).read()))
409+
schedule_json = get_url_json(url)
418410
episodes = schedule_json.get(channel.get('id'), [])
419411
if not episodes:
420412
return None
@@ -569,35 +561,10 @@ def get_episodes(self, program=None, season=None, episodes=None, category=None,
569561
# Construct VRT NU Search API Url and get api data
570562
querystring = '&'.join('{}={}'.format(key, value) for key, value in list(params.items()))
571563
search_url = self._VRTNU_SEARCH_URL + '?' + querystring.replace(' ', '%20') # Only encode spaces to minimize url length
572-
573-
from json import loads
574564
if cache_file:
575-
# Get api data from cache if it is fresh
576-
search_json = get_cache(cache_file, ttl=ttl('indirect'))
577-
if not search_json:
578-
log(2, 'URL get: {url}', url=unquote(search_url))
579-
req = Request(search_url)
580-
try:
581-
search_json = loads(to_unicode(urlopen(req).read()))
582-
except (TypeError, ValueError): # No JSON object could be decoded
583-
return []
584-
except HTTPError as exc:
585-
url_length = len(req.get_selector())
586-
if exc.code == 413 and url_length > 8192:
587-
ok_dialog(heading='HTTP Error 413', message=localize(30967))
588-
log_error('HTTP Error 413: Exceeded maximum url length: '
589-
'VRT Search API url has a length of {length} characters.', length=url_length)
590-
return []
591-
if exc.code == 400 and 7600 <= url_length <= 8192:
592-
ok_dialog(heading='HTTP Error 400', message=localize(30967))
593-
log_error('HTTP Error 400: Probably exceeded maximum url length: '
594-
'VRT Search API url has a length of {length} characters.', length=url_length)
595-
return []
596-
raise
597-
update_cache(cache_file, search_json)
565+
search_json = get_cached_url_json(url=search_url, cache=cache_file, ttl=ttl('indirect'))
598566
else:
599-
log(2, 'URL get: {url}', url=unquote(search_url))
600-
search_json = loads(to_unicode(urlopen(search_url).read()))
567+
search_json = get_url_json(url=search_url)
601568

602569
# Check for multiple seasons
603570
seasons = None
@@ -619,8 +586,9 @@ def get_episodes(self, program=None, season=None, episodes=None, category=None,
619586
if all_items and total_results > api_page_size:
620587
for api_page in range(1, api_pages):
621588
api_page_url = search_url + '&from=' + str(api_page * api_page_size + 1)
622-
api_page_json = loads(to_unicode(urlopen(api_page_url).read()))
623-
episodes += api_page_json.get('results', [{}])
589+
api_page_json = get_url_json(api_page_url)
590+
if api_page_json:
591+
episodes += api_page_json.get('results', [{}])
624592

625593
# Return episodes
626594
return episodes

resources/lib/favorites.py

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
except ImportError: # Python 2
1212
from urllib2 import build_opener, install_opener, ProxyHandler, Request, unquote, urlopen
1313

14-
from kodiutils import (container_refresh, get_cache, get_proxies, get_setting, has_credentials,
15-
input_down, invalidate_caches, localize, log, log_error, multiselect,
16-
notification, ok_dialog, to_unicode, update_cache)
14+
from kodiutils import (container_refresh, get_proxies, get_setting, has_credentials, input_down,
15+
localize, log, log_error, multiselect, notification, ok_dialog)
16+
from utils import get_cache, get_url_json, invalidate_caches, update_cache
1717

1818

1919
class Favorites:
@@ -43,16 +43,8 @@ def refresh(self, ttl=None):
4343
'content-type': 'application/json',
4444
'Referer': 'https://www.vrt.be/vrtnu',
4545
}
46-
req = Request('https://video-user-data.vrt.be/favorites', headers=headers)
47-
log(2, 'URL get: https://video-user-data.vrt.be/favorites')
48-
from json import loads
49-
try:
50-
favorites_json = loads(to_unicode(urlopen(req).read()))
51-
except (TypeError, ValueError): # No JSON object could be decoded
52-
# Force favorites from cache
53-
favorites_json = get_cache('favorites.json', ttl=None)
54-
else:
55-
update_cache('favorites.json', favorites_json)
46+
favorites_url = 'https://video-user-data.vrt.be/favorites'
47+
favorites_json = get_url_json(url=favorites_url, cache='favorites.json', headers=headers)
5648
if favorites_json:
5749
self._favorites = favorites_json
5850

resources/lib/kodiutils.py

Lines changed: 0 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -666,117 +666,6 @@ def delete_cached_thumbnail(url):
666666
return True
667667

668668

669-
def human_delta(seconds):
670-
''' Return a human-readable representation of the TTL '''
671-
from math import floor
672-
days = int(floor(seconds / (24 * 60 * 60)))
673-
seconds = seconds % (24 * 60 * 60)
674-
hours = int(floor(seconds / (60 * 60)))
675-
seconds = seconds % (60 * 60)
676-
if days:
677-
return '%d day%s and %d hour%s' % (days, 's' if days != 1 else '', hours, 's' if hours != 1 else '')
678-
minutes = int(floor(seconds / 60))
679-
seconds = seconds % 60
680-
if hours:
681-
return '%d hour%s and %d minute%s' % (hours, 's' if hours != 1 else '', minutes, 's' if minutes != 1 else '')
682-
if minutes:
683-
return '%d minute%s and %d second%s' % (minutes, 's' if minutes != 1 else '', seconds, 's' if seconds != 1 else '')
684-
return '%d second%s' % (seconds, 's' if seconds != 1 else '')
685-
686-
687-
def get_cache(path, ttl=None): # pylint: disable=redefined-outer-name
688-
''' Get the content from cache, if it's still fresh '''
689-
if get_setting('usehttpcaching', 'true') == 'false':
690-
return None
691-
692-
fullpath = get_cache_path() + path
693-
if not exists(fullpath):
694-
return None
695-
696-
from time import localtime, mktime
697-
mtime = stat_file(fullpath).st_mtime()
698-
now = mktime(localtime())
699-
if ttl and now >= mtime + ttl:
700-
return None
701-
702-
if ttl is None:
703-
log(3, "Cache '{path}' is forced from cache.", path=path)
704-
else:
705-
log(3, "Cache '{path}' is fresh, expires in {time}.", path=path, time=human_delta(mtime + ttl - now))
706-
with open_file(fullpath, 'r') as fdesc:
707-
cache_data = to_unicode(fdesc.read())
708-
if not cache_data:
709-
return None
710-
711-
from json import loads
712-
try:
713-
return loads(cache_data)
714-
except (TypeError, ValueError): # No JSON object could be decoded
715-
return None
716-
717-
718-
def update_cache(path, data):
719-
''' Update the cache, if necessary '''
720-
if get_setting('usehttpcaching', 'true') == 'false':
721-
return
722-
723-
from hashlib import md5
724-
from json import dump, dumps
725-
fullpath = get_cache_path() + path
726-
if exists(fullpath):
727-
with open_file(fullpath) as fdesc:
728-
cachefile = fdesc.read().encode('utf-8')
729-
md5_cache = md5(cachefile)
730-
else:
731-
md5_cache = 0
732-
# Create cache directory if missing
733-
if not exists(get_cache_path()):
734-
mkdirs(get_cache_path())
735-
736-
# Avoid writes if possible (i.e. SD cards)
737-
if md5_cache != md5(dumps(data).encode('utf-8')):
738-
log(3, "Write cache '{path}'.", path=path)
739-
with open_file(fullpath, 'w') as fdesc:
740-
# dump(data, fdesc, encoding='utf-8')
741-
dump(data, fdesc)
742-
else:
743-
# Update timestamp
744-
from os import utime
745-
log(3, "Cache '{path}' has not changed, updating mtime only.", path=path)
746-
utime(path)
747-
748-
749-
def ttl(kind='direct'):
750-
''' Return the HTTP cache ttl in seconds based on kind of relation '''
751-
if kind == 'direct':
752-
return int(get_setting('httpcachettldirect', 5)) * 60
753-
if kind == 'indirect':
754-
return int(get_setting('httpcachettlindirect', 60)) * 60
755-
return 5 * 60
756-
757-
758-
def refresh_caches(cache_file=None):
759-
''' Invalidate the needed caches and refresh container '''
760-
files = ['favorites.json', 'oneoff.json', 'resume_points.json']
761-
if cache_file and cache_file not in files:
762-
files.append(cache_file)
763-
invalidate_caches(*files)
764-
container_refresh()
765-
notification(message=localize(30981))
766-
767-
768-
def invalidate_caches(*caches):
769-
''' Invalidate multiple cache files '''
770-
import fnmatch
771-
_, files = listdir(get_cache_path())
772-
# Invalidate caches related to menu list refreshes
773-
removes = set()
774-
for expr in caches:
775-
removes.update(fnmatch.filter(files, expr))
776-
for filename in removes:
777-
delete(get_cache_path() + filename)
778-
779-
780669
def input_down():
781670
''' Move the cursor down '''
782671
jsonrpc(method='Input.Down')

resources/lib/playerinfo.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,8 @@ def push_position(self, position=0, total=100):
229229

230230
# Do not reload container and rely on Kodi internal watch status when watching a single episode that is partly watched.
231231
# Kodi internal watch status is only updated when the play action is initiated from the GUI, so this only works for single episodes.
232-
if (not self.path.startswith('plugin://plugin.video.vrt.nu/play/upnext') and
233-
ignoresecondsatstart < position < (100 - ignorepercentatend) / 100 * total):
232+
if (not self.path.startswith('plugin://plugin.video.vrt.nu/play/upnext')
233+
and ignoresecondsatstart < position < (100 - ignorepercentatend) / 100 * total):
234234
return
235235

236236
# Do not reload container when playing or not stopped

resources/lib/resumepoints.py

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
from urllib2 import build_opener, install_opener, ProxyHandler, Request, HTTPError, urlopen
1313

1414
from data import SECONDS_MARGIN
15-
from kodiutils import (container_refresh, get_cache, get_proxies, get_setting, has_credentials,
16-
input_down, invalidate_caches, localize, log, log_error, notification,
17-
to_unicode, update_cache)
15+
from kodiutils import (container_refresh, get_proxies, get_setting, has_credentials, input_down,
16+
localize, log, log_error, notification)
17+
from utils import get_cache, get_url_json, invalidate_caches, update_cache
1818

1919

2020
class ResumePoints:
@@ -44,16 +44,8 @@ def refresh(self, ttl=None):
4444
'content-type': 'application/json',
4545
'Referer': 'https://www.vrt.be/vrtnu',
4646
}
47-
req = Request('https://video-user-data.vrt.be/resume_points', headers=headers)
48-
log(2, 'URL get: https://video-user-data.vrt.be/resume_points')
49-
from json import loads
50-
try:
51-
resumepoints_json = loads(to_unicode(urlopen(req).read()))
52-
except (TypeError, ValueError): # No JSON object could be decoded
53-
# Force resumepoints from cache
54-
resumepoints_json = get_cache('resume_points.json', ttl=None)
55-
else:
56-
update_cache('resume_points.json', resumepoints_json)
47+
resumepoints_url = 'https://video-user-data.vrt.be/resume_points'
48+
resumepoints_json = get_url_json(url=resumepoints_url, cache='resume_points.json', headers=headers)
5749
if resumepoints_json:
5850
self._resumepoints = resumepoints_json
5951

resources/lib/search.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
from favorites import Favorites
88
from resumepoints import ResumePoints
99
from kodiutils import (addon_profile, container_refresh, end_of_directory, get_search_string,
10-
get_setting, localize, ok_dialog, open_file, show_listing, ttl, url_for)
10+
get_setting, localize, log_error, ok_dialog, open_file, show_listing, url_for)
11+
from utils import ttl
1112

1213

1314
class Search:
@@ -25,7 +26,9 @@ def read_history(self):
2526
with open_file(self._search_history, 'r') as fdesc:
2627
try:
2728
history = load(fdesc)
28-
except (TypeError, ValueError): # No JSON object could be decoded
29+
except (TypeError, ValueError) as exc: # No JSON object could be decoded
30+
fdesc.seek(0, 0)
31+
log_error('{exc}\nDATA: {data}', exc=exc, data=fdesc.read())
2932
history = []
3033
return history
3134

resources/lib/service.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66
from xbmc import Monitor
77
from apihelper import ApiHelper
88
from favorites import Favorites
9-
from kodiutils import container_refresh, invalidate_caches, log
9+
from kodiutils import container_refresh, log
1010
from playerinfo import PlayerInfo
1111
from resumepoints import ResumePoints
1212
from statichelper import to_unicode
1313
from tokenresolver import TokenResolver
14+
from utils import invalidate_caches
1415

1516

1617
class VrtMonitor(Monitor):

resources/lib/streamservice.py

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
get_proxies, get_setting, has_inputstream_adaptive, kodi_version,
1818
localize, log, log_error, mkdir, ok_dialog, open_settings, supports_drm)
1919
from statichelper import to_unicode
20+
from utils import get_url_json
2021

2122

2223
class StreamService:
@@ -40,9 +41,9 @@ def __init__(self, _tokenresolver):
4041

4142
def _get_vualto_license_url(self):
4243
''' Get Widevine license URL from Vualto API '''
43-
from json import loads
44-
log(2, 'URL get: {url}', url=unquote(self._VUPLAY_API_URL))
45-
self._vualto_license_url = loads(to_unicode(urlopen(self._VUPLAY_API_URL).read())).get('drm_providers', dict()).get('widevine', dict()).get('la_url')
44+
json_data = get_url_json(url=self._VUPLAY_API_URL)
45+
if json_data:
46+
self._vualto_license_url = json_data.get('drm_providers', dict()).get('widevine', dict()).get('la_url')
4647

4748
@staticmethod
4849
def _create_settings_dir():
@@ -154,18 +155,18 @@ def _get_stream_json(self, api_data, roaming=False):
154155
playertoken = self._tokenresolver.get_playertoken(token_url, token_variant='ondemand', roaming=roaming)
155156

156157
# Construct api_url and get video json
157-
stream_json = None
158-
if playertoken:
159-
from json import loads
160-
api_url = api_data.media_api_url + '/videos/' + api_data.publication_id + \
161-
api_data.video_id + '?vrtPlayerToken=' + playertoken + '&client=' + api_data.client
162-
log(2, 'URL get: {url}', url=unquote(api_url))
163-
try:
164-
stream_json = loads(to_unicode(urlopen(api_url).read()))
165-
except HTTPError as exc:
166-
stream_json = loads(to_unicode(exc.read()))
167-
168-
return stream_json
158+
if not playertoken:
159+
return None
160+
api_url = api_data.media_api_url + '/videos/' + api_data.publication_id + \
161+
api_data.video_id + '?vrtPlayerToken=' + playertoken + '&client=' + api_data.client
162+
try:
163+
json_data = get_url_json(url=api_url)
164+
except HTTPError as exc:
165+
from json import load
166+
return load(exc)
167+
if not json_data:
168+
return None
169+
return json_data
169170

170171
@staticmethod
171172
def _fix_virtualsubclip(manifest_url, duration):

0 commit comments

Comments
 (0)