Skip to content

Commit

Permalink
Fix IOexception errors on VERO (#117)
Browse files Browse the repository at this point in the history
* Fix "IOException error" and HTTP timeouts.
* Better output during re-caching.
* Fix re-caching to also cache full description urls.
  • Loading branch information
TermeHansen authored Jan 29, 2025
1 parent 6103910 commit 04edd25
Show file tree
Hide file tree
Showing 12 changed files with 96 additions and 52 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@


## Known issues
*Inputstream.adaptive* in Matrix (kodi 19) has a bug with subtitles, which is why it is advised to use the `subtitles from file` setting that is a legacy way of handling subtitles. On Nexus (kodi 20) this has been fixed in *Inputstream.adaptive*, and `subtitles from file` should be switched off.
Fetching data for especially the alphabet section can be very slow, this can be greatly improved by activating the re-cache cron job if kodi anyhow is running on a device that is always on.

With version 6.2.0 I have made a big change in settings handling backend, and if kodi has not been restarted after this upgrade, some of the fields in settings can have empty naming. Just restart kodi and it should be fixed.
9 changes: 7 additions & 2 deletions addon.xml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<addon id="plugin.video.drnu" version="6.4.6" name="DR TV" provider-name="TermeHansen">
<addon id="plugin.video.drnu" version="6.5.0" name="DR TV" provider-name="TermeHansen">
<requires>
<import addon="xbmc.python" version="3.0.1"/>
<import addon="script.module.dateutil" version="2.8.2" />
<import addon="script.module.requests" version="2.31.0" />
<import addon="script.module.requests-cache" version="0.5.2+matrix.2"/>
<import addon="service.cronxbmc" version="0.1.6"/>
<import addon="service.cronxbmc" version="0.1.7"/>
</requires>
<extension point="xbmc.python.pluginsource" library="default.py">
<provides>video</provides>
Expand All @@ -31,6 +31,11 @@
<screenshot>resources/media/Screenshot3.jpg</screenshot>
<screenshot>resources/media/Screenshot4.jpg</screenshot>
</assets>
<news>[B]Version 6.5.0 - 2024-01-22[/B]
- Fix "IOException error" and HTTP timeouts.
- Better output during re-caching.
- Fix re-caching to also cache full description urls.
</news>
<news>[B]Version 6.4.6 - 2024-11-01[/B]
- Fix "unsupported_subCode" issue.
- Fix time offset in live tv EPG.
Expand Down
5 changes: 5 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
[B]Version 6.5.0 - 2024-01-22[/B]
- Fix "IOException error" and HTTP timeouts.
- Better output during re-caching.
- Fix re-caching to also cache full description urls.

[B]Version 6.4.6 - 2024-11-01[/B]
- Fix "unsupported_subCode" issue.
- Fix time offset in live tv EPG.
Expand Down
4 changes: 4 additions & 0 deletions resources/language/resource.language.da_dk/strings.po
Original file line number Diff line number Diff line change
Expand Up @@ -297,3 +297,7 @@ msgstr "Rapporter gerne fejlen på github"
msgctxt "#30907"
msgid "https://github.com/xbmc-danish-addons/plugin.video.drnu/issues"
msgstr "https://github.com/xbmc-danish-addons/plugin.video.drnu/issues"

msgctxt "#30908"
msgid "Hent fuld beskrivelse af episoder"
msgstr ""
4 changes: 4 additions & 0 deletions resources/language/resource.language.en_gb/strings.po
Original file line number Diff line number Diff line change
Expand Up @@ -300,3 +300,7 @@ msgstr ""
msgctxt "#30907"
msgid "https://github.com/xbmc-danish-addons/plugin.video.drnu/issues"
msgstr ""

msgctxt "#30908"
msgid "Fetch full descriptions of items"
msgstr ""
5 changes: 4 additions & 1 deletion resources/lib/addon.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def kodi_version_major():


def version(s):
return [int(item) for item in s.split('.')]
return [int(item) for item in s.split('-')[0].split('.')]


class DrDkTvAddon(object):
Expand Down Expand Up @@ -615,12 +615,15 @@ def route(self, query):
self.list_entries('/gensyn')

except tvapi.ApiException as ex:
log(['API exception', query], level=1)
self.displayError(str(ex))

except IOError as ex:
log(['IO exception', query], level=1)
self.displayIOError(str(ex))

except Exception as ex:
log(['Exception', query], level=1)
stack = traceback.format_exc()
heading = 'drnu addon crash'
xbmcgui.Dialog().ok(heading, '\n'.join([tr(30906), tr(30907), str(stack)]))
Expand Down
69 changes: 47 additions & 22 deletions resources/lib/tvapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@
import time
from dateutil import parser
from datetime import datetime, timezone, timedelta
from urllib.parse import urlparse, parse_qs, parse_qsl, urlencode, urlunparse

from urllib.parse import urlparse, parse_qs, parse_qsl, urlencode
from requests.adapters import HTTPAdapter
from urllib3.util import Retry

CHANNEL_IDS = [20875, 20876, 192099, 192100, 20892]
CHANNEL_PRESET = {
Expand All @@ -41,7 +42,7 @@
'DRTV Ekstra': 5
}
URL = 'https://production.dr-massive.com/api'
GET_TIMEOUT = 5
GET_TIMEOUT = 10


def cache_path(path):
Expand All @@ -51,15 +52,18 @@ def cache_path(path):
return True


def fix_query(url, remove={}, add={}):
o = list(urlparse(url))
qs = dict(parse_qsl(o[4]))
def fix_query(url, remove={}, add={}, remove_keys=[]):
o = urlparse(url)
qs = dict(parse_qsl(o.query))
for k in remove_keys:
if k in qs:
del qs[k]
for k,v in remove.items():
if qs.get(k) == v:
del qs[k]
qs.update(add)
o[4] = urlencode(qs)
return urlunparse(o)
qs = dict(sorted(qs.items()))
return o._replace(query=urlencode(qs)).geturl()


def full_login(user, password):
Expand Down Expand Up @@ -150,7 +154,10 @@ def __init__(self, cachePath, getLocalizedString, get_setting):
self.cleanup_every = int(get_setting('recache.cleanup'))
self.expire_hours = int(get_setting('recache.expiration'))
self.caching = get_setting('recache.enabled') == 'true'
self.fetch_full_plot = get_setting('fetch.full_plot') == 'true'
self.expire_seconds = 3600*self.expire_hours if self.expire_hours >= 0 else None
retry = Retry(total=3, backoff_factor=2, status_forcelist=[429, 500, 502, 503, 504])
self.adapter = HTTPAdapter(max_retries=retry)
self.init_sqlite_db()

self.token_file = Path(f'{self.cachePath}/token.p')
Expand All @@ -167,6 +174,7 @@ def init_sqlite_db(self):
request_fname = str(self.cachePath/'requests.cache')
self.session = requests_cache.CachedSession(
request_fname, backend='sqlite', expire_after=self.expire_seconds)
self.session.mount('https://', self.adapter)

if (self.cachePath/'requests_cleaned').exists():
if (time.time() - (self.cachePath/'requests_cleaned').stat().st_mtime)/3600/24 < self.cleanup_every:
Expand All @@ -181,6 +189,7 @@ def init_sqlite_db(self):
(self.cachePath/'requests.cache.sqlite').unlink()
self.session = requests_cache.CachedSession(
request_fname, backend='sqlite', expire_after=self.expire_seconds)
self.session.mount('https://', self.adapter)
(self.cachePath/'requests_cleaned').write_text(str(datetime.now()))

def read_tokens(self, tokens):
Expand Down Expand Up @@ -273,6 +282,7 @@ def _request_get(self, url, params=None, headers=None, use_cache=True):
u = self.session.get(url, params=params, headers=headers, timeout=GET_TIMEOUT)
else:
u = requests.get(url, params=params, headers=headers, timeout=GET_TIMEOUT)

if u.status_code == 200:
return u.json()
else:
Expand All @@ -295,7 +305,8 @@ def get_item(self, id, use_cache=True):

def get_next(self, path, use_cache=True, headers=None):
remove = {'sub':'Emergency'}
url = URL + fix_query(path, remove=remove)
remove_keys = ['lang', 'segments', 'isDeviceAbroad', 'isLive2VodSupported']
url = URL + fix_query(path, remove=remove, remove_keys=remove_keys)
return self._request_get(url, headers=headers, use_cache=use_cache)

def get_list(self, id, param, use_cache=True):
Expand Down Expand Up @@ -371,7 +382,7 @@ def get_profile(self, use_cache=False):
url = URL + '/account/profile'
headers = {'X-Authorization': 'Bearer ' + self.profile_token()}
return self._request_get(url, headers=headers, use_cache=use_cache)

def kids_item(self, item):
if 'classification' in item:
if item['classification']['code'] in ['DR-Ramasjang', 'DR-Minisjang']:
Expand All @@ -382,12 +393,21 @@ def kids_item(self, item):
return True
return False

def unfold_list(self, item, filter_kids=False, headers=None):
def unfold_list(self, item, filter_kids=False, headers=None, progress=None):
items = item['items']
if 'next' in item['paging']:
if progress is not None:
if progress.iscanceled():
return items
progress.update(self.progress_prc, self.msg + f"page {item['paging']['page']} of {item['paging']['total']}")

next_js = self.get_next(item['paging']['next'], headers=headers)
items += next_js['items']
while 'next' in next_js['paging']:
if progress is not None:
if progress.iscanceled():
return items
progress.update(self.progress_prc, self.msg + f"page {next_js['paging']['page']} of {next_js['paging']['total']}")
next_js = self.get_next(next_js['paging']['next'], headers=headers)
items += next_js['items']
if filter_kids:
Expand Down Expand Up @@ -449,22 +469,23 @@ def recache_items(self, progress=None, clear_expired=False):
i = 0
for item in js['entries']:
if item['type'] == 'ListEntry':
st2 = time.time()
self.unfold_list(item['list'])
msg = f"{self.tr(30523)}'{item['title']}'\n{time.time() - st2:.1f}s"
if progress is not None:
if progress.iscanceled():
return
progress.update(int(100*(i+1)/maxidx), msg)
self.msg = f"{self.tr(30523)}'{item['title']}'\n"
self.progress_prc = int(100 * (i + 1) / maxidx)
for sub_item in self.unfold_list(item['list'], progress=progress):
if self.fetch_full_plot:
if progress is not None:
if progress.iscanceled():
return
progress.update(self.progress_prc, self.msg + 'updating descriptions...')
self.fix_item_description(sub_item)
i += 1

for channel in ['dr-ramasjang', 'dr-minisjang', 'dr-ultra']:
self.get_children_front_items(channel)
msg = f"{self.tr(30523)}'{channel}'\n{time.time() - st2:.1f}s"
msg = f"{self.tr(30523)}'{channel}'\n"
if progress is not None:
if progress.iscanceled():
return
progress.update(int(100*(i+1)/maxidx), msg)
self.get_children_front_items(channel)
i += 1

def get_children_front_items(self, channel):
Expand Down Expand Up @@ -576,13 +597,17 @@ def get_title(self, item):
title += f" ({item['contextualTitle']})"
return title

def set_info(self, item, tag, title):
def fix_item_description(self, item):
if len(item.get('shortDescription', '')) >= 255 and item.get('description', '') == '':
resumetime_save = float(item.get('ResumeTime', 0.0))
item = self.get_item(item['id'])
if resumetime_save > 0:
item['ResumeTime'] = resumetime_save
return item

def set_info(self, item, tag, title):
if self.fetch_full_plot:
item = self.fix_item_description(item)
tag.setTitle(title)
if item.get('shortDescription', '') and item['shortDescription'] != 'LinkItem':
tag.setPlot(item['shortDescription'])
Expand Down
4 changes: 4 additions & 0 deletions resources/settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@
<level>0</level> <default>false</default> <control type="toggle"/>
</setting>

<setting id="fetch.full_plot" label="30908" type="boolean">
<level>0</level> <default>true</default> <control type="toggle"/>
</setting>

<setting id="use.simpleareaitem" label="30210" type="boolean">
<level>0</level> <default>false</default> <control type="toggle"/>
</setting>
Expand Down
2 changes: 1 addition & 1 deletion tests/test_routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ def test_a_aa(capsys):

handle.route(a_aa[0]['url'])
a = [iteminfo(item) for item in get_items().values()]
myEqual(len(a), 102)
myEqual(len(a), 116)

myEqual(a[0]['label'], 'A Storm Foretold - det amerikanske oprør')
# handle.route(a[0]['url']) # test playing
Expand Down
14 changes: 7 additions & 7 deletions tests/userdata/menudata/daglige_forslag.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"info": {
"resume": null,
"title": "Tæt på sandheden med Jonatan Spang: Programmet fra himlen",
"plot": "Vi er slet ikke færdige med at tale om mink, da det er kommet frem, at erstatningen til minkavlere formentlig vil stige med omkring seks milliarder kroner. Og så er der et nyt lovforslag under behandling. Det handler om, hvilket nationalflag du må have i din flagstang. Vi tager også et kig på prisstigningen for at rejse med bus, tog eller metro. Og så har vi også nået vores klimamål for 2025 - eller har vi nu også det? Udløber: 10. feb 2025",
"plot": "Vi er slet ikke færdige med at tale om mink, da det er kommet frem, at erstatningen til minkavlere formentlig vil stige med omkring seks milliarder kroner. Og så er der et nyt lovforslag under behandling. Det handler om, hvilket nationalflag du må have i ",
"plotoutline": "Et satirisk blik på ugens nyheder og tendenser",
"aired": "2024-02-10",
"year": 2024,
Expand Down Expand Up @@ -42,7 +42,7 @@
"info": {
"resume": null,
"title": "De udvalgte - Dansk Melodi Grand Prix 2024",
"plot": "Mød artisterne der skal stå på scenen til Dansk Melodi Grand Prix 2024, når Jesper Groth og Laurits Emanuel fra Fyr og Flamme tager os med bag kulisserne til dette års store Grand Prix show. Vi følger artisterne fra de bliver udvalgt og under alle de prøver der liger forud for showet. Det bliver en unik mulighed for at lære artisterne og deres musik bedre at kende inden de ugen efter skal kæmpe om, hvem der skal repræsentere Danmark ved Eurovision Song Contest. Udløber: 10. feb 2025",
"plot": "Mød artisterne der skal stå på scenen til Dansk Melodi Grand Prix 2024, når Jesper Groth og Laurits Emanuel fra Fyr og Flamme tager os med bag kulisserne til dette års store Grand Prix show. Vi følger artisterne fra de bliver udvalgt og under alle de prøv",
"plotoutline": "Kom med bag kulisserne til dette års store Grand Prix-show",
"aired": "2024-02-10",
"year": 2024,
Expand Down Expand Up @@ -73,7 +73,7 @@
"info": {
"resume": null,
"title": "Signe Molde på udebane: De rige bliver mobbet",
"plot": "Bliver de rige dæmoniseret, hetzet og faktisk direkte mobbet, bare fordi de er rige? Signe Molde møder milliardæren Niklas Nikolajsen, der hverken vil kaldes grisk eller egoistisk - også selvom han er flyttet til Schweiz. I Monaco møder Signe ejendomsmægleren Helle Skovby, der er træt af alle fordommene om fyrstendømmet. Udløber: 10. feb 2026",
"plot": "Bliver de rige dæmoniseret, hetzet og faktisk direkte mobbet, bare fordi de er rige? Signe Molde møder milliardæren Niklas Nikolajsen, der hverken vil kaldes grisk eller egoistisk - også selvom han er flyttet til Schweiz. I Monaco møder Signe ejendomsmægl",
"plotoutline": "Bliver de rige dæmoniseret, hetzet og mistænkeliggjort?",
"aired": "2024-02-10",
"year": 2024,
Expand Down Expand Up @@ -105,7 +105,7 @@
"info": {
"resume": null,
"title": "SEX MED P3: Fantasi: Expectations vs. Reality",
"plot": "Trekanter, dominans eller gangbangs? Måske deler du sexfantasier med mange flere end du tror. Er du nysgerrig på at høre om andres frække fantasier? Og skal din fantasi udleves eller blive inde i hovedet? Vi giver dig den ultimative guide til, hvad du skal stille op med din fantasi, og til at udleve den, hvis det er det, du drømmer om. Vi dykker også ned i, hvad dine fantasier i virkeligheden handler om og hvad du kan gøre, hvis du har en sexfantasi, som du gerne vil af med. Udløber: 9. feb 2026",
"plot": "Trekanter, dominans eller gangbangs? Måske deler du sexfantasier med mange flere end du tror. Er du nysgerrig på at høre om andres frække fantasier? Og skal din fantasi udleves eller blive inde i hovedet? Vi giver dig den ultimative guide til, hvad du ska",
"plotoutline": "Episode 2:8 | Den ultimative guide til, hvad du skal stille op med din fantasi",
"episode": 22,
"mediatype": "episode"
Expand Down Expand Up @@ -135,7 +135,7 @@
"info": {
"resume": null,
"title": "Fanget på havets bund",
"plot": "Dette er den sande historie om to mænds heroiske, men tilsyneladende umulige kamp for at overleve. En skibskok er strandet i en luftlomme i vraget af et sunket skib 34 meter under havets overflade. Med næsten ingen ilt og uden, at nogen aner, at han er i live. Men nogen kommer ham alligevel til undsætning. Tre dage efter forliset ankommer et redningsskib for at bjærge de døde fra vraget. En nyuddannet dykker bliver som den første sendt ind, og til sin skræk og store overraskelse finder han skibskokken i live dybt inde i det sunkne skib. Men det er først nu, at problemerne virkelig begynder, og de er begge i livsfare. Helt unikt blev hele den mirakuløse redningsaktion filmet af dykkernes hjelmkameraer og al deres kommunikation blev optaget. I tre år har Lasse Spang Olsen rejst verden rundt og fundet de involverede som - for første gang - nu selv fortæller om deres oplevelser på bunden af Atlanterhavet i en af verdens mest besynderlige redningsaktioner. Udløber: 29. dec 2028",
"plot": "Dette er den sande historie om to mænds heroiske, men tilsyneladende umulige kamp for at overleve. En skibskok er strandet i en luftlomme i vraget af et sunket skib 34 meter under havets overflade. Med næsten ingen ilt og uden, at nogen aner, at han er i ",
"plotoutline": "Skibskokken Harrison fanges i et vrag på 34 meters dybde",
"aired": "2024-02-10",
"year": 2024,
Expand Down Expand Up @@ -314,7 +314,7 @@
"info": {
"resume": null,
"title": "Danmarks bedste portrætmaler: Det helstøbte portræt",
"plot": "Vi er nået til finalen i Danmarks bedste portrætmaler, som tradition tro finder sted på Ny Carlsberg Glyptotek i København. Det er nu, de tre finalister skal vise alt, hvad de har lært gennem konkurrencen, ved at lave det helstøbte portræt af modellen Sofie Gråbøl. Men hvem imponerer de tre dommere mest og får dermed titlen som Danmarks bedste portrætmaler? Udløber: 31. dec 2027",
"plot": "Vi er nået til finalen i Danmarks bedste portrætmaler, som tradition tro finder sted på Ny Carlsberg Glyptotek i København. Det er nu, de tre finalister skal vise alt, hvad de har lært gennem konkurrencen, ved at lave det helstøbte portræt af modellen Sof",
"plotoutline": "Episode 6:6 | Modellen er Sofie Gråbøl. Hvilken kunstner løber med sejren?",
"aired": "2024-02-06",
"year": 2024,
Expand Down Expand Up @@ -346,7 +346,7 @@
"info": {
"resume": null,
"title": "I hus til halsen: Spøgelseshus nær tyren Svend Otto",
"plot": "Om to måneder slipper pengene op for Jørgen og Susanne. Så går de 5.000 kr. i minus hver måned, hvis ikke huset bliver solgt. \nMen landsbyen passer på sine børn, så alle der kan kravle og gå tropper op for at hjælpe. Designer Kalle Thesbjerg har en vild plan og bygger sofa et mystisk sted, ejendomsmægler Line Franck besøger slægtsgården, og tømrer Dennis Lindeqiust viser, hvordan man fikser et badeværelse for meget få penge. Udløber: 7. feb 2026",
"plot": "Om to måneder slipper pengene op for Jørgen og Susanne. Så går de 5.000 kr. i minus hver måned, hvis ikke huset bliver solgt. \nMen landsbyen passer på sine børn, så alle der kan kravle og gå tropper op for at hjælpe. Designer Kalle Thesbjerg har en vild",
"plotoutline": "En hel landsby hjælper Jørgen og Susanne, så huset kan sælges",
"episode": 7,
"mediatype": "episode"
Expand Down
Loading

0 comments on commit 04edd25

Please sign in to comment.