From a55a6475b943a804fd76b5181b7d82968c2ea096 Mon Sep 17 00:00:00 2001 From: Leo Moll Date: Mon, 22 Jan 2018 01:01:34 +0100 Subject: [plugin.video.mediathekview] 0.4.2 --- plugin.video.mediathekview/README.md | 35 +++--- plugin.video.mediathekview/addon.py | 25 ++-- plugin.video.mediathekview/addon.xml | 43 +++---- plugin.video.mediathekview/mvupdate | 2 +- .../resources/icons/3sat-m.png | Bin 0 -> 9180 bytes .../resources/icons/ard-m.png | Bin 0 -> 8273 bytes .../resources/icons/arte.de-m.png | Bin 0 -> 4795 bytes .../resources/icons/arte.fr-m.png | Bin 0 -> 4795 bytes .../resources/icons/br-m.png | Bin 0 -> 12519 bytes .../resources/icons/default-m.png | Bin 0 -> 6741 bytes .../resources/icons/dw-m.png | Bin 0 -> 12946 bytes .../resources/icons/hr-m.png | Bin 0 -> 8422 bytes .../resources/icons/kika-m.png | Bin 0 -> 9958 bytes .../resources/icons/mdr-m.png | Bin 0 -> 5710 bytes .../resources/icons/ndr-m.png | Bin 0 -> 7541 bytes .../resources/icons/orf-m.png | Bin 0 -> 4865 bytes .../resources/icons/phoenix-m.png | Bin 0 -> 3998 bytes .../resources/icons/rbb-m.png | Bin 0 -> 4620 bytes .../resources/icons/sr-m.png | Bin 0 -> 9989 bytes .../resources/icons/srf-m.png | Bin 0 -> 5658 bytes .../resources/icons/srf.podcast-m.png | Bin 0 -> 5658 bytes .../resources/icons/swr-m.png | Bin 0 -> 10006 bytes .../resources/icons/wdr-m.png | Bin 0 -> 8160 bytes .../resources/icons/zdf-m.png | Bin 0 -> 7184 bytes .../resources/icons/zdf-tivi-m.png | Bin 0 -> 7184 bytes .../language/resource.language.de_de/strings.po | 22 +++- .../language/resource.language.en_gb/strings.po | 16 +++ .../language/resource.language.it_it/strings.po | 20 ++- .../resources/lib/base/Logger.py | 2 +- .../resources/lib/channelui.py | 22 ++-- .../resources/lib/exceptions.py | 3 + plugin.video.mediathekview/resources/lib/filmui.py | 13 +- .../resources/lib/initialui.py | 18 ++- .../resources/lib/kodi/KodiAddon.py | 52 +++++++- .../resources/lib/kodi/KodiUI.py | 40 +++--- .../resources/lib/mvupdate.py | 6 +- .../resources/lib/mvutils.py | 56 +++++---- .../resources/lib/notifier.py | 15 ++- .../resources/lib/settings.py | 2 + plugin.video.mediathekview/resources/lib/showui.py | 39 ++++-- .../resources/lib/storemysql.py | 136 +++++++++++++++------ .../resources/lib/storesqlite.py | 129 +++++++++++++------ .../resources/lib/ttml2srt.py | 6 +- .../resources/lib/updater.py | 48 ++++---- plugin.video.mediathekview/resources/settings.xml | 7 +- plugin.video.mediathekview/service.py | 28 ++--- 46 files changed, 528 insertions(+), 257 deletions(-) create mode 100644 plugin.video.mediathekview/resources/icons/3sat-m.png create mode 100644 plugin.video.mediathekview/resources/icons/ard-m.png create mode 100644 plugin.video.mediathekview/resources/icons/arte.de-m.png create mode 100644 plugin.video.mediathekview/resources/icons/arte.fr-m.png create mode 100644 plugin.video.mediathekview/resources/icons/br-m.png create mode 100644 plugin.video.mediathekview/resources/icons/default-m.png create mode 100644 plugin.video.mediathekview/resources/icons/dw-m.png create mode 100644 plugin.video.mediathekview/resources/icons/hr-m.png create mode 100644 plugin.video.mediathekview/resources/icons/kika-m.png create mode 100644 plugin.video.mediathekview/resources/icons/mdr-m.png create mode 100644 plugin.video.mediathekview/resources/icons/ndr-m.png create mode 100644 plugin.video.mediathekview/resources/icons/orf-m.png create mode 100644 plugin.video.mediathekview/resources/icons/phoenix-m.png create mode 100644 plugin.video.mediathekview/resources/icons/rbb-m.png create mode 100644 plugin.video.mediathekview/resources/icons/sr-m.png create mode 100644 plugin.video.mediathekview/resources/icons/srf-m.png create mode 100644 plugin.video.mediathekview/resources/icons/srf.podcast-m.png create mode 100644 plugin.video.mediathekview/resources/icons/swr-m.png create mode 100644 plugin.video.mediathekview/resources/icons/wdr-m.png create mode 100644 plugin.video.mediathekview/resources/icons/zdf-m.png create mode 100644 plugin.video.mediathekview/resources/icons/zdf-tivi-m.png diff --git a/plugin.video.mediathekview/README.md b/plugin.video.mediathekview/README.md index 485d456..ab8fee5 100644 --- a/plugin.video.mediathekview/README.md +++ b/plugin.video.mediathekview/README.md @@ -80,9 +80,9 @@ Alternativ-Konfigurationen -------------------------- Ist das Kodi-System zu langsam um eine eigene Datenbank zu verwalten -(z.B. Raspberry PI mit sehr langsamer SD-Karte) oder fehlt das Programm -'xz', so besteht die Möglichkeit das Addon auch mit einer externen -Datenbank (MySQL) zu nutzen. +(z.B. Raspberry PI mit sehr langsamer SD-Karte) oder soll die Datenbank mit +mehreren Kodi Systemen geteilt werden, so besteht die Möglichkeit das Addon +auch mit einem externen Datenbank-Server (MySQL oder MariaDB) zu nutzen. Da viele Kodi-Nutzer über ein eigenes NAS-System verfügen um ihre Medien dem Media-Center zur Verfügung zu stellen, eignet sich dieses in der Regel @@ -113,12 +113,14 @@ folgende zwei Bibliotheken zur Verfügung stehen, sowie das Entpackprogramm 'xz' (optional): * ijson +* defusedxml * mysql-connector Die Installation dieser Bibliotheken erfolgt durch Eingabe folgender Befehle: ```` pip install ijson +pip install defusedxml pip install mysql-connector==2.1.4 ```` @@ -130,7 +132,7 @@ Dies kann entweder durch Herunterladen und Entpacken der Addon-ZIP-Datei erfolgen oder durch Klonen des Addon-Quellcode-Repositories mittels `git` ```` -git clone git@github.com:mediathekview/plugin.video.mediathekview.git +git clone https://github.com/mediathekview/plugin.video.mediathekview.git ```` Durch Angabe des Parameters `-h` bzw. `-h` hinter dem Datenbanktyp, gibt @@ -224,8 +226,9 @@ Alternate Configurations ------------------------ If the Kodi system is too slow to manage its own database (e.g. Raspberry PI -with a very slow SD card) or if the program 'xz' is missing, it is also -possible to use the addon with an external database (MySQL). +with a very slow SD card) or you want to share the database across multiple +Kodi instances, it is also possible to use the addon with an external database +(MySQL or MariaDB). Since many Kodi users have their own NAS system to make their media available to the media center, this is usually also suitable as a MySQL/MariaDB database @@ -236,9 +239,6 @@ created automatically if it does not yet exist on the database server. However, the specified database user must also have sufficient user rights in order to do this. -When you have a running MySQL server avaible, you have only to create the -database by running the SQL script `resources/sql/filmliste-mysql-v1.sql`. - The connection to the database can be configured in the addon settings in the "Database Settings" section. @@ -255,12 +255,14 @@ target system in order to execute the commandline update process. Additionally the following python libraries are required: * ijson +* defusedxml * mysql-connector The required libraries can be installed via pip: ```` pip install ijson +pip install defusedxml pip install mysql-connector==2.1.4 ```` @@ -272,7 +274,7 @@ This can be either done by downloading and unpacking the addon archive or by cloning the source repository with `git` ```` -git clone git@github.com:mediathekview/plugin.video.mediathekview.git +git clone https://github.com/mediathekview/plugin.video.mediathekview.git ```` By specifying the option `-h` itself or after the requested database type, @@ -355,9 +357,10 @@ possibile fare una dichiarazione finale di compatibilità. Configurazioni alternative -------------------------- -Se il sistema Kodi è troppo lento per gestire il proprio database (ad es. -Raspberry PI con una scheda SD molto lenta) o se manca il programma 'xz', -è anche possibile utilizzare l'addon con un database esterno (MySQL). +Se il sistema Kodi è troppo lento per gestire il database interno (ad es. +Raspberry PI con una scheda SD molto lenta) o se si desidera condividere il +database con altri sistemi Kodi, è anche possibile utilizzare l'addon con un +server database esterno (MySQL o MariaDB). Dal momento che molti utenti Kodi hanno il proprio sistema NAS per rendere i loro contenuti mediali disponibili al media center, questo è di solito anche @@ -367,7 +370,7 @@ sistemi operativi NAS offrono l'installazione di un tale database. Se l' addon è configurato per utilizzare un database MySQL/MariaDB, il database verrà creato automaticamente se non esiste ancora sul database server. Tuttavia, anche l'utente del database specificato deve avere i diritti -necessari. +necessari alla creazione di un database. Il collegamento al database può essere effettuato nelle impostazioni dell'addon nella sezione "Impostazioni Banca Dati". @@ -387,12 +390,14 @@ interprete python2, il programma di decompressione 'xz' e le seguenti librerie python: * ijson +* defusedxml * mysql-connector QUeste potranno essere istallate mediante il programma pip: ```` pip install ijson +pip install defusedxml pip install mysql-connector==2.1.4 ```` @@ -404,7 +409,7 @@ Questo sarà possibile sia scaricando l'archivio dell'addon che dovrà essere spacchettato in loco o mediante clonaggio dai sorgenti mediante `git` ```` -git clone git@github.com:mediathekview/plugin.video.mediathekview.git +git clone https://github.com/mediathekview/plugin.video.mediathekview.git ```` Specificando l'opzione `-h` a se stante o a tergo del tipo di database da diff --git a/plugin.video.mediathekview/addon.py b/plugin.video.mediathekview/addon.py index 64e925d..d4f483f 100644 --- a/plugin.video.mediathekview/addon.py +++ b/plugin.video.mediathekview/addon.py @@ -30,6 +30,8 @@ from __future__ import unicode_literals # ,absolute_import, division import os,re,sys,urlparse,datetime import xbmcplugin,xbmcgui,xbmcvfs +import resources.lib.mvutils as mvutils + from contextlib import closing from resources.lib.kodi.KodiAddon import KodiPlugin @@ -190,7 +192,7 @@ class MediathekView( KodiPlugin ): dirname = self.settings.downloadpath + showname + '/' episode = 1 if xbmcvfs.exists( dirname ): - ( dirs, epfiles, ) = xbmcvfs.listdir( dirname ) + ( _, epfiles, ) = xbmcvfs.listdir( dirname ) for epfile in epfiles: match = re.search( '^.* [eE][pP]([0-9]*)\.[^/]*$', epfile ) if match and len( match.groups() ) > 0: @@ -211,22 +213,21 @@ class MediathekView( KodiPlugin ): bgd.Create( self.language( 30974 ), fileepi + extension ) try: bgd.Update( 0 ) - result = mvutils.url_retrieve_vfs( videourl, movname, bgd.UrlRetrieveHook ) + mvutils.url_retrieve_vfs( videourl, movname, bgd.UrlRetrieveHook ) bgd.Close() - if result is not None: - self.notifier.ShowNotification( self.language( 30960 ), self.language( 30976 ).format( videourl ) ) + self.notifier.ShowNotification( 30960, self.language( 30976 ).format( videourl ) ) except Exception as err: bgd.Close() self.error( 'Failure downloading {}: {}', videourl, err ) - self.notifier.ShowError( self.language( 30952 ), self.language( 30975 ).format( videourl, err ) ) + self.notifier.ShowError( 30952, self.language( 30975 ).format( videourl, err ) ) # download subtitles if film.url_sub: bgd = KodiBGDialog() - bgd.Create( self.language( 30978 ), fileepi + u'.ttml' ) + bgd.Create( 30978, fileepi + u'.ttml' ) try: bgd.Update( 0 ) - result = mvutils.url_retrieve_vfs( film.url_sub, ttmname, bgd.UrlRetrieveHook ) + mvutils.url_retrieve_vfs( film.url_sub, ttmname, bgd.UrlRetrieveHook ) try: ttml2srt( xbmcvfs.File( ttmname, 'r' ), xbmcvfs.File( srtname, 'w' ) ) except Exception as err: @@ -239,7 +240,7 @@ class MediathekView( KodiPlugin ): # create NFO Files self._make_nfo_files( film, episode, dirname, nfoname, videourl ) else: - self.notifier.ShowError( self.language( 30952 ), self.language( 30958 ) ) + self.notifier.ShowError( 30952, 30958 ) def doEnqueueFilm( self, filmid ): self.info( 'Enqueue {}', filmid ) @@ -296,18 +297,18 @@ class MediathekView( KodiPlugin ): channel = self.args.get( 'channel', [0] ) self.db.GetRecents( channel[0], FilmUI( self ) ) elif mode[0] == 'recentchannels': - self.db.GetRecentChannels( ChannelUI( self.addon_handle, nextdir = 'recent' ) ) + self.db.GetRecentChannels( ChannelUI( self, nextdir = 'recent' ) ) elif mode[0] == 'channels': - self.db.GetChannels( ChannelUI( self.addon_handle ) ) + self.db.GetChannels( ChannelUI( self, nextdir = 'shows' ) ) elif mode[0] == 'action-dbinfo': self.showDbInfo() elif mode[0] == 'initial': channel = self.args.get( 'channel', [0] ) - self.db.GetInitials( channel[0], InitialUI( self.addon_handle ) ) + self.db.GetInitials( channel[0], InitialUI( self ) ) elif mode[0] == 'shows': channel = self.args.get( 'channel', [0] ) initial = self.args.get( 'initial', [None] ) - self.db.GetShows( channel[0], initial[0], ShowUI( self.addon_handle ) ) + self.db.GetShows( channel[0], initial[0], ShowUI( self ) ) elif mode[0] == 'films': show = self.args.get( 'show', [0] ) self.db.GetFilms( show[0], FilmUI( self ) ) diff --git a/plugin.video.mediathekview/addon.xml b/plugin.video.mediathekview/addon.xml index 596fe4b..dabb8ca 100644 --- a/plugin.video.mediathekview/addon.xml +++ b/plugin.video.mediathekview/addon.xml @@ -1,13 +1,14 @@ + Ermöglicht den Zugriff auf fast alle deutschen Mediatheken der öffentlich Rechtlichen basierend auf der Datenbank von MediathekView.de Gives access to most video-platforms from German public service broadcasters using the database of MediathekView.de Fornisce l'accesso a gran parte delle piattaforme video operate dalle emittenti pubbliche tedesche usando la banca dati di MediathekView.de - v0.4.0 (2018-01-14): + v0.4.2 (2018-01-20): +- Senderlogos werden nun in verschiedenen Situationen angezeigt +- Fehler bei Auflisten der Filme in "Alle Sendungen" aus 0.4.1 behoben +- Fehler bei Datenbankaktualisierung behoben +- Herunterladen von zufälligem Server +v0.4.1 (2018-01-20): +- Alphabetische Anzeige in Listen ist nun nicht mehr Groß-Klein-Sensitiv +- Download von Inhalten funktioniert wieder +- "Suchen nach Titel und Beschreibung" mit MySQL funktioniert wieder +- Freitextsuche kommt nun auch mit " und ' klar +- In seltenen Fällen konnte es bei der Installation oder das Update des Addons zu einem Fehler kommen, der die Datenbank korrumpiert hat. +- Das Programm mvupdate hat nicht funktioniert +- In "Alle Sendungen nach Sender" kommt man nun direkt nach der Auswahl des Senders in die Filmliste +- Die Anzahl Tage die ein Film als "Vor kurzem hinzugefügt" gilt, ist nun einstellbar +- Es ist einstellbar, ob "Vor kurzem hinzugefügt" das Sendedatum oder das Datum des Hinzufügens in der Datenbank berücksichtigt +- Anzahl der neuen Filme in "Vor Kurzem hinzugefügt nach Sender" stimmt nun +- Übersetzungsfehler korrigiert +v0.4.0 (2018-01-14): - Android und Windows Kompatibilität da kein externer Entpacker mehr benötigt wird - Der Datenbanktreiber für MySQL kann nun die Datenbank selbsttätig anlegen - Im Datenbanktreiber für MySQL kann nun auch die Portnummer angegeben werden - Konfigurierbare Begrenzung der Suchergebnisse -v0.3.5 (2018-01-11): -- Anpassungen an die Kodi-Addon Regeln -v0.3.4 (2018-01-11): -- Die Suche sucht nun auch im Sendungs-Name -- "Vor Kurzem hinzugefügt" nach Sendern durchsuchen -- Deutsche und Italienische Übersetzung funktionieren nun -v0.3.3 (2018-01-09): -- Auflösung kann nun beim Download ausgewählt werden -- Kommandozeilenaktualisierer wurde implementiert -- Fehler in der Generierung von tvshow.nfo behoben -- Die README Datei enthält nun alle Sprachen -v0.3.2 (2018-01-08): -- Dateien mit Umlauten im Namen konnten nicht heruntergeladen werden -- Herunterladen bei nicht lokalem Download-Verzeichnis erfolgt nun ohne Zwischenspeicherung -- NFO Dateien enthalten mehr Daten -v0.3.1 (2018-01-07): -- Herunterladen ist nun auch in nicht lokalen (VFS) Download-Verzeichnissen möglich -- Behebung eines sehr seltenen Fehlers der zu einem ENdlos-Update führen konnte -v0.3.0 (2018-01-07): -- Neue Funktion zum Herunterladen von Videos mit Untertiteln und Metadaten (NFO-Dateien) -- Behebung eines Fehlers der das Abspeichern der Untertitel URL in der Datenbank verhinderte all de fr diff --git a/plugin.video.mediathekview/mvupdate b/plugin.video.mediathekview/mvupdate index 56aedc0..ab0657b 100755 --- a/plugin.video.mediathekview/mvupdate +++ b/plugin.video.mediathekview/mvupdate @@ -25,7 +25,7 @@ # -- Imports ------------------------------------------------ from __future__ import unicode_literals -from classes.mvupdate import UpdateApp +from resources.lib.mvupdate import UpdateApp # -- Main Code ---------------------------------------------- if __name__ == '__main__': diff --git a/plugin.video.mediathekview/resources/icons/3sat-m.png b/plugin.video.mediathekview/resources/icons/3sat-m.png new file mode 100644 index 0000000..3c34822 Binary files /dev/null and b/plugin.video.mediathekview/resources/icons/3sat-m.png differ diff --git a/plugin.video.mediathekview/resources/icons/ard-m.png b/plugin.video.mediathekview/resources/icons/ard-m.png new file mode 100644 index 0000000..9053e76 Binary files /dev/null and b/plugin.video.mediathekview/resources/icons/ard-m.png differ diff --git a/plugin.video.mediathekview/resources/icons/arte.de-m.png b/plugin.video.mediathekview/resources/icons/arte.de-m.png new file mode 100644 index 0000000..2d558e7 Binary files /dev/null and b/plugin.video.mediathekview/resources/icons/arte.de-m.png differ diff --git a/plugin.video.mediathekview/resources/icons/arte.fr-m.png b/plugin.video.mediathekview/resources/icons/arte.fr-m.png new file mode 100644 index 0000000..2d558e7 Binary files /dev/null and b/plugin.video.mediathekview/resources/icons/arte.fr-m.png differ diff --git a/plugin.video.mediathekview/resources/icons/br-m.png b/plugin.video.mediathekview/resources/icons/br-m.png new file mode 100644 index 0000000..d8123a8 Binary files /dev/null and b/plugin.video.mediathekview/resources/icons/br-m.png differ diff --git a/plugin.video.mediathekview/resources/icons/default-m.png b/plugin.video.mediathekview/resources/icons/default-m.png new file mode 100644 index 0000000..390d91a Binary files /dev/null and b/plugin.video.mediathekview/resources/icons/default-m.png differ diff --git a/plugin.video.mediathekview/resources/icons/dw-m.png b/plugin.video.mediathekview/resources/icons/dw-m.png new file mode 100644 index 0000000..bd2d979 Binary files /dev/null and b/plugin.video.mediathekview/resources/icons/dw-m.png differ diff --git a/plugin.video.mediathekview/resources/icons/hr-m.png b/plugin.video.mediathekview/resources/icons/hr-m.png new file mode 100644 index 0000000..20ef6aa Binary files /dev/null and b/plugin.video.mediathekview/resources/icons/hr-m.png differ diff --git a/plugin.video.mediathekview/resources/icons/kika-m.png b/plugin.video.mediathekview/resources/icons/kika-m.png new file mode 100644 index 0000000..02c538b Binary files /dev/null and b/plugin.video.mediathekview/resources/icons/kika-m.png differ diff --git a/plugin.video.mediathekview/resources/icons/mdr-m.png b/plugin.video.mediathekview/resources/icons/mdr-m.png new file mode 100644 index 0000000..62c9d2d Binary files /dev/null and b/plugin.video.mediathekview/resources/icons/mdr-m.png differ diff --git a/plugin.video.mediathekview/resources/icons/ndr-m.png b/plugin.video.mediathekview/resources/icons/ndr-m.png new file mode 100644 index 0000000..aef0d47 Binary files /dev/null and b/plugin.video.mediathekview/resources/icons/ndr-m.png differ diff --git a/plugin.video.mediathekview/resources/icons/orf-m.png b/plugin.video.mediathekview/resources/icons/orf-m.png new file mode 100644 index 0000000..7d9645d Binary files /dev/null and b/plugin.video.mediathekview/resources/icons/orf-m.png differ diff --git a/plugin.video.mediathekview/resources/icons/phoenix-m.png b/plugin.video.mediathekview/resources/icons/phoenix-m.png new file mode 100644 index 0000000..4bd530a Binary files /dev/null and b/plugin.video.mediathekview/resources/icons/phoenix-m.png differ diff --git a/plugin.video.mediathekview/resources/icons/rbb-m.png b/plugin.video.mediathekview/resources/icons/rbb-m.png new file mode 100644 index 0000000..138e6b6 Binary files /dev/null and b/plugin.video.mediathekview/resources/icons/rbb-m.png differ diff --git a/plugin.video.mediathekview/resources/icons/sr-m.png b/plugin.video.mediathekview/resources/icons/sr-m.png new file mode 100644 index 0000000..f46dcb4 Binary files /dev/null and b/plugin.video.mediathekview/resources/icons/sr-m.png differ diff --git a/plugin.video.mediathekview/resources/icons/srf-m.png b/plugin.video.mediathekview/resources/icons/srf-m.png new file mode 100644 index 0000000..d1049bf Binary files /dev/null and b/plugin.video.mediathekview/resources/icons/srf-m.png differ diff --git a/plugin.video.mediathekview/resources/icons/srf.podcast-m.png b/plugin.video.mediathekview/resources/icons/srf.podcast-m.png new file mode 100644 index 0000000..d1049bf Binary files /dev/null and b/plugin.video.mediathekview/resources/icons/srf.podcast-m.png differ diff --git a/plugin.video.mediathekview/resources/icons/swr-m.png b/plugin.video.mediathekview/resources/icons/swr-m.png new file mode 100644 index 0000000..c631f5a Binary files /dev/null and b/plugin.video.mediathekview/resources/icons/swr-m.png differ diff --git a/plugin.video.mediathekview/resources/icons/wdr-m.png b/plugin.video.mediathekview/resources/icons/wdr-m.png new file mode 100644 index 0000000..da6c936 Binary files /dev/null and b/plugin.video.mediathekview/resources/icons/wdr-m.png differ diff --git a/plugin.video.mediathekview/resources/icons/zdf-m.png b/plugin.video.mediathekview/resources/icons/zdf-m.png new file mode 100644 index 0000000..dc10c64 Binary files /dev/null and b/plugin.video.mediathekview/resources/icons/zdf-m.png differ diff --git a/plugin.video.mediathekview/resources/icons/zdf-tivi-m.png b/plugin.video.mediathekview/resources/icons/zdf-tivi-m.png new file mode 100644 index 0000000..dc10c64 Binary files /dev/null and b/plugin.video.mediathekview/resources/icons/zdf-tivi-m.png differ diff --git a/plugin.video.mediathekview/resources/language/resource.language.de_de/strings.po b/plugin.video.mediathekview/resources/language/resource.language.de_de/strings.po index d3832b3..fe15710 100644 --- a/plugin.video.mediathekview/resources/language/resource.language.de_de/strings.po +++ b/plugin.video.mediathekview/resources/language/resource.language.de_de/strings.po @@ -32,24 +32,40 @@ msgstr "HD Streams bevorzugen" msgctxt "#30111" msgid "No future videos (usually trailers)" -msgstr "Keine Videos aus der Zukunft (üblicherweise Trailer)" +msgstr "Keine Filme aus der Zukunft (üblicherweise Trailer)" msgctxt "#30112" msgid "Minimum duration in minutes" -msgstr "Minimlae Länge in Minuten" +msgstr "Minimale Länge in Minuten" msgctxt "#30113" msgid "Group shows of different channel" msgstr "Sendungen verschiedener Sender zusammenfassen" msgctxt "#30114" -msgid "Download directory" +msgid "Limit search results" msgstr "Suchergebnisse eingrenzen" msgctxt "#30115" +msgid "Max age of recents in days" +msgstr "Maximales Alter der Neuigkeiten in Tagen" + +msgctxt "#30116" +msgid "Recent calculated by" +msgstr "Neuigkeit berechnet ab" + +msgctxt "#30118" msgid "Download directory" msgstr "Download-Verzeichnis" +msgctxt "#30161" +msgid "Aired Date" +msgstr "Sendedatum" + +msgctxt "#30162" +msgid "Added to Database" +msgstr "Hinzugefügt zur Datenbank" + # Settings Page 2 msgctxt "#30210" msgid "Database type" diff --git a/plugin.video.mediathekview/resources/language/resource.language.en_gb/strings.po b/plugin.video.mediathekview/resources/language/resource.language.en_gb/strings.po index 5c121e8..2d3f7bb 100644 --- a/plugin.video.mediathekview/resources/language/resource.language.en_gb/strings.po +++ b/plugin.video.mediathekview/resources/language/resource.language.en_gb/strings.po @@ -47,9 +47,25 @@ msgid "Limit search results" msgstr "Limit search results" msgctxt "#30115" +msgid "Max age of recents in days" +msgstr "Max age of recents in days" + +msgctxt "#30116" +msgid "Recent calculated by" +msgstr "Recent calculated by" + +msgctxt "#30118" msgid "Download directory" msgstr "Download directory" +msgctxt "#30161" +msgid "Aired Date" +msgstr "Aired Date" + +msgctxt "#30162" +msgid "Added to Database" +msgstr "Added to Database" + # Settings Page 2 msgctxt "#30210" msgid "Database type" diff --git a/plugin.video.mediathekview/resources/language/resource.language.it_it/strings.po b/plugin.video.mediathekview/resources/language/resource.language.it_it/strings.po index f0d9027..9cc1bca 100644 --- a/plugin.video.mediathekview/resources/language/resource.language.it_it/strings.po +++ b/plugin.video.mediathekview/resources/language/resource.language.it_it/strings.po @@ -47,9 +47,25 @@ msgid "Limit search results" msgstr "Limita risultati ricerche" msgctxt "#30115" +msgid "Max age of recents" +msgstr "Età massima delle novità in giorni" + +msgctxt "#30116" +msgid "Recent calculated by" +msgstr "Novità calcolate con" + +msgctxt "#30118" msgid "Download directory" msgstr "Directory di download" +msgctxt "#30161" +msgid "Aired Date" +msgstr "Data di Trasmissione" + +msgctxt "#30162" +msgid "Added to Database" +msgstr "Entrata nel database" + # Settings Page 2 msgctxt "#30210" msgid "Database type" @@ -106,11 +122,11 @@ msgstr "Livestreams" msgctxt "#30904" msgid "Recently Added" -msgstr "Recenti" +msgstr "Novità" msgctxt "#30905" msgid "Recently Added by Channel" -msgstr "Recenti secondo emittenti" +msgstr "Novità secondo emittenti" msgctxt "#30906" msgid "Browse by Show in all Channels" diff --git a/plugin.video.mediathekview/resources/lib/base/Logger.py b/plugin.video.mediathekview/resources/lib/base/Logger.py index 3d74680..850ff44 100644 --- a/plugin.video.mediathekview/resources/lib/base/Logger.py +++ b/plugin.video.mediathekview/resources/lib/base/Logger.py @@ -28,6 +28,6 @@ class Logger( object ): def warn( self, message, *args ): pass - + def error( self, message, *args ): pass diff --git a/plugin.video.mediathekview/resources/lib/channelui.py b/plugin.video.mediathekview/resources/lib/channelui.py index 36743e8..fc9d826 100644 --- a/plugin.video.mediathekview/resources/lib/channelui.py +++ b/plugin.video.mediathekview/resources/lib/channelui.py @@ -3,17 +3,19 @@ # # -- Imports ------------------------------------------------ -import sys, urllib -import xbmcplugin, xbmcgui +import xbmcgui +import xbmcplugin + +import resources.lib.mvutils as mvutils from resources.lib.channel import Channel # -- Classes ------------------------------------------------ class ChannelUI( Channel ): - def __init__( self, handle, sortmethods = None, nextdir = 'initial' ): - self.base_url = sys.argv[0] + def __init__( self, plugin, sortmethods = None, nextdir = 'initial' ): + self.plugin = plugin + self.handle = plugin.addon_handle self.nextdir = nextdir - self.handle = handle self.sortmethods = sortmethods if sortmethods is not None else [ xbmcplugin.SORT_METHOD_TITLE ] self.count = 0 @@ -24,9 +26,14 @@ class ChannelUI( Channel ): def Add( self, altname = None ): resultingname = self.channel if self.count == 0 else '%s (%d)' % ( self.channel, self.count, ) li = xbmcgui.ListItem( label = resultingname if altname is None else altname ) + icon = 'special://home/addons/' + self.plugin.addon_id + '/resources/icons/' + self.channel.lower() + '-m.png' + li.setArt( { + 'thumb': icon, + 'icon': icon + } ) xbmcplugin.addDirectoryItem( handle = self.handle, - url = self.build_url( { + url = mvutils.build_url( { 'mode': self.nextdir, 'channel': self.id } ), @@ -36,6 +43,3 @@ class ChannelUI( Channel ): def End( self ): xbmcplugin.endOfDirectory( self.handle ) - - def build_url( self, query ): - return self.base_url + '?' + urllib.urlencode( query ) diff --git a/plugin.video.mediathekview/resources/lib/exceptions.py b/plugin.video.mediathekview/resources/lib/exceptions.py index 53d7f14..b66fb4f 100644 --- a/plugin.video.mediathekview/resources/lib/exceptions.py +++ b/plugin.video.mediathekview/resources/lib/exceptions.py @@ -7,3 +7,6 @@ class DatabaseCorrupted( RuntimeError ): class DatabaseLost( RuntimeError ): """This exception is raised when the connection to the database is lost during update""" + +class ExitRequested( Exception ): + """This exception is thrown if the addon is shut down by Kodi or by another same addon""" diff --git a/plugin.video.mediathekview/resources/lib/filmui.py b/plugin.video.mediathekview/resources/lib/filmui.py index af04798..23eaf98 100644 --- a/plugin.video.mediathekview/resources/lib/filmui.py +++ b/plugin.video.mediathekview/resources/lib/filmui.py @@ -3,7 +3,8 @@ # # -- Imports ------------------------------------------------ -import xbmcplugin, xbmcgui +import xbmcgui +import xbmcplugin from resources.lib.film import Film from resources.lib.settings import Settings @@ -45,7 +46,7 @@ class FilmUI( Film ): infoLabels = { 'title' : resultingtitle + videohds, - 'sorttitle' : resultingtitle, + 'sorttitle' : resultingtitle.lower(), 'tvshowtitle' : self.show, 'plot' : self.description } @@ -63,9 +64,15 @@ class FilmUI( Film ): infoLabels['aired'] = airedstring infoLabels['dateadded'] = airedstring - li = xbmcgui.ListItem( resultingtitle, self.description ) + icon = 'special://home/addons/' + self.plugin.addon_id + '/resources/icons/' + self.channel.lower() + '-m.png' + + li = xbmcgui.ListItem( resultingtitle ) li.setInfo( type = 'video', infoLabels = infoLabels ) li.setProperty( 'IsPlayable', 'true' ) + li.setArt( { + 'thumb': icon, + 'icon': icon + } ) # create context menu contextmenu = [] diff --git a/plugin.video.mediathekview/resources/lib/initialui.py b/plugin.video.mediathekview/resources/lib/initialui.py index 71d9aef..b59d409 100644 --- a/plugin.video.mediathekview/resources/lib/initialui.py +++ b/plugin.video.mediathekview/resources/lib/initialui.py @@ -3,13 +3,16 @@ # # -- Imports ------------------------------------------------ -import sys, urllib -import xbmcplugin, xbmcgui +import xbmcgui +import xbmcplugin + +import resources.lib.mvutils as mvutils # -- Classes ------------------------------------------------ class InitialUI( object ): - def __init__( self, handle, sortmethods = None ): - self.handle = handle + def __init__( self, plugin, sortmethods = None ): + self.plugin = plugin + self.handle = plugin.addon_handle self.sortmethods = sortmethods if sortmethods is not None else [ xbmcplugin.SORT_METHOD_TITLE ] self.channelid = 0 self.initial = '' @@ -28,7 +31,7 @@ class InitialUI( object ): li = xbmcgui.ListItem( label = resultingname ) xbmcplugin.addDirectoryItem( handle = self.handle, - url = _build_url( { + url = mvutils.build_url( { 'mode': "shows", 'channel': self.channelid, 'initial': self.initial, @@ -40,8 +43,3 @@ class InitialUI( object ): def End( self ): xbmcplugin.endOfDirectory( self.handle ) - -# -- Functions ---------------------------------------------- - -def _build_url( query ): - return sys.argv[0] + '?' + urllib.urlencode( query ) diff --git a/plugin.video.mediathekview/resources/lib/kodi/KodiAddon.py b/plugin.video.mediathekview/resources/lib/kodi/KodiAddon.py index f9c0cd7..4081044 100644 --- a/plugin.video.mediathekview/resources/lib/kodi/KodiAddon.py +++ b/plugin.video.mediathekview/resources/lib/kodi/KodiAddon.py @@ -3,14 +3,19 @@ # # -- Imports ------------------------------------------------ -import sys, urllib -import xbmc, xbmcgui, xbmcaddon, xbmcplugin +import os +import sys +import urllib + +import xbmc +import xbmcgui +import xbmcaddon +import xbmcplugin from resources.lib.kodi.KodiLogger import KodiLogger # -- Classes ------------------------------------------------ class KodiAddon( KodiLogger ): - def __init__( self ): self.addon = xbmcaddon.Addon() self.addon_id = self.addon.getAddonInfo( 'id' ) @@ -29,6 +34,7 @@ class KodiAddon( KodiLogger ): return self.addon.setSetting( setting_id, value ) def doAction( self, action ): + self.debug( 'Triggered action {}', action ) xbmc.executebuiltin( 'Action({})'.format( action ) ) class KodiService( KodiAddon ): @@ -66,3 +72,43 @@ class KodiPlugin( KodiAddon ): def endOfDirectory( self, succeeded = True, updateListing = False, cacheToDisc = True ): xbmcplugin.endOfDirectory( self.addon_handle, succeeded, updateListing, cacheToDisc ) + + +class KodiInterlockedMonitor( xbmc.Monitor ): + def __init__( self, service, setting_id ): + super( KodiInterlockedMonitor, self ).__init__() + self.instance_id = ''.join( format( x, '02x' ) for x in bytearray( os.urandom( 16 ) ) ) + self.setting_id = setting_id + self.service = service + + def RegisterInstance( self, waittime = 1 ): + if self.BadInstance(): + self.service.info( 'Found other instance with id {}', self.instance_id ) + self.service.info( 'Startup delayed by {} second(s) waiting the other instance to shut down', waittime ) + self.service.setSetting( self.setting_id, self.instance_id ) + xbmc.Monitor.waitForAbort( self, waittime ) + else: + self.service.setSetting( self.setting_id, self.instance_id ) + + def UnregisterInstance( self ): + self.service.setSetting( self.setting_id, '' ) + + def BadInstance( self ): + instance_id = self.service.getSetting( self.setting_id ) + return len( instance_id ) > 0 and self.instance_id != instance_id + + def abortRequested( self ): + return self.BadInstance() or xbmc.Monitor.abortRequested( self ) + + def waitForAbort( self, timeout = None ): + if timeout is None: + # infinite wait + while not self.abortRequested(): + if xbmc.Monitor.waitForAbort( self, 1 ): + return True + return True + else: + for _ in range( timeout ): + if self.BadInstance() or xbmc.Monitor.waitForAbort( self, 1 ): + return True + return self.BadInstance() diff --git a/plugin.video.mediathekview/resources/lib/kodi/KodiUI.py b/plugin.video.mediathekview/resources/lib/kodi/KodiUI.py index dee8dbb..138af50 100644 --- a/plugin.video.mediathekview/resources/lib/kodi/KodiUI.py +++ b/plugin.video.mediathekview/resources/lib/kodi/KodiUI.py @@ -3,15 +3,21 @@ # # -- Imports ------------------------------------------------ -import xbmc, xbmcgui +import xbmc +import xbmcgui +import xbmcaddon # -- Classes ------------------------------------------------ class KodiUI( object ): def __init__( self ): - self.bgdialog = None + self.addon = xbmcaddon.Addon() + self.language = self.addon.getLocalizedString + self.bgdialog = KodiBGDialog() - def GetEnteredText( self, deftext = '', heading = '', hidden = False ): + def GetEnteredText( self, deftext = None, heading = None, hidden = False ): + heading = self.language( heading ) if isinstance( heading, int ) else heading if heading is not None else '' + deftext = self.language( deftext ) if isinstance( deftext, int ) else deftext if deftext is not None else '' keyboard = xbmc.Keyboard( deftext, heading, 1 if hidden else 0 ) keyboard.doModal() if keyboard.isConfirmed(): @@ -19,39 +25,39 @@ class KodiUI( object ): return deftext def ShowNotification( self, heading, message, icon = xbmcgui.NOTIFICATION_INFO, time = 5000, sound = True ): + heading = self.language( heading ) if isinstance( heading, int ) else heading + message = self.language( message ) if isinstance( message, int ) else message xbmcgui.Dialog().notification( heading, message, icon, time, sound ) def ShowWarning( self, heading, message, time = 5000, sound = True ): - xbmcgui.Dialog().notification( heading, message, xbmcgui.NOTIFICATION_WARNING, time, sound ) + self.ShowNotification( heading, message, xbmcgui.NOTIFICATION_WARNING, time, sound ) def ShowError( self, heading, message, time = 5000, sound = True ): - xbmcgui.Dialog().notification( heading, message, xbmcgui.NOTIFICATION_ERROR, time, sound ) + self.ShowNotification( heading, message, xbmcgui.NOTIFICATION_ERROR, time, sound ) def ShowBGDialog( self, heading = None, message = None ): - if self.bgdialog is None: - self.bgdialog = xbmcgui.DialogProgressBG() - self.bgdialog.create( heading, message ) - else: - self.bgdialog.update( 0, heading, message ) + self.bgdialog.Create( heading, message ) def UpdateBGDialog( self, percent, heading = None, message = None ): - if self.bgdialog is not None: - self.bgdialog.update( percent, heading, message ) + self.bgdialog.Update( percent, heading, message ) + + def HookBGDialog( self, blockcount, blocksize, totalsize ): + self.bgdialog.UrlRetrieveHook( blockcount, blocksize, totalsize ) def CloseBGDialog( self ): - if self.bgdialog is not None: - self.bgdialog.close() - del self.bgdialog - self.bgdialog = None + self.bgdialog.Close() class KodiBGDialog( object ): def __init__( self ): + self.language = xbmcaddon.Addon().getLocalizedString self.bgdialog= None def __del__( self ): self.Close() def Create( self, heading = None, message = None ): + heading = self.language( heading ) if isinstance( heading, int ) else heading + message = self.language( message ) if isinstance( message, int ) else message if self.bgdialog is None: self.bgdialog = xbmcgui.DialogProgressBG() self.bgdialog.create( heading, message ) @@ -60,6 +66,8 @@ class KodiBGDialog( object ): def Update( self, percent, heading = None, message = None ): if self.bgdialog is not None: + heading = self.language( heading ) if isinstance( heading, int ) else heading + message = self.language( message ) if isinstance( message, int ) else message self.bgdialog.update( percent, heading, message ) def UrlRetrieveHook( self, blockcount, blocksize, totalsize ): diff --git a/plugin.video.mediathekview/resources/lib/mvupdate.py b/plugin.video.mediathekview/resources/lib/mvupdate.py index 7595417..477139e 100644 --- a/plugin.video.mediathekview/resources/lib/mvupdate.py +++ b/plugin.video.mediathekview/resources/lib/mvupdate.py @@ -6,7 +6,7 @@ import os import sys import argparse import datetime -import xml.etree.ElementTree as ET +import defusedxml.ElementTree as ET from resources.lib.base.Logger import Logger from resources.lib.updater import MediathekViewUpdater @@ -24,6 +24,8 @@ class Settings( object ): self.database = args.database self.nofuture = True self.minlength = 0 + self.maxage = 86400 + self.recentmode = 0 self.groupshows = False self.updenabled = True self.updinterval = 3600 @@ -104,6 +106,8 @@ class Notifier( object ): pass def UpdateUpdateProgress( self, percent, count, channels, shows, movies ): pass + def HookDownloadProgress( self, blockcount, blocksize, totalsize ): + pass def CloseUpdateProgress( self ): pass diff --git a/plugin.video.mediathekview/resources/lib/mvutils.py b/plugin.video.mediathekview/resources/lib/mvutils.py index d4c7663..7ccaf3a 100644 --- a/plugin.video.mediathekview/resources/lib/mvutils.py +++ b/plugin.video.mediathekview/resources/lib/mvutils.py @@ -3,12 +3,21 @@ # -- Imports ------------------------------------------------ import os +import sys import stat import string +import urllib import urllib2 -import xbmcvfs from contextlib import closing +from resources.lib.exceptions import ExitRequested + +# -- Kodi Specific Imports ---------------------------------- +try: + import xbmcvfs + is_kodi = True +except ImportError: + is_kodi = False # -- Functions ---------------------------------------------- def dir_exists( name ): @@ -58,30 +67,29 @@ def cleanup_filename( val ): search = ''.join( [ c for c in val if c in cset ] ) return search.strip() -def url_retrieve( url, filename, reporthook, chunk_size = 8192 ): +def url_retrieve( url, filename, reporthook, chunk_size = 8192, aborthook = None ): with closing( urllib2.urlopen( url ) ) as u, closing( open( filename, 'wb' ) ) as f: - total_size = int( u.info().getheader( 'Content-Length' ).strip() ) if u.info() and u.info().getheader( 'Content-Length' ) else 0 - total_chunks = 0 + _chunked_url_copier( u, f, reporthook, chunk_size, aborthook ) + +def url_retrieve_vfs( url, filename, reporthook, chunk_size = 8192, aborthook = None ): + with closing( urllib2.urlopen( url ) ) as u, closing( xbmcvfs.File( filename, 'wb' ) ) as f: + _chunked_url_copier( u, f, reporthook, chunk_size, aborthook ) - while True: - reporthook( total_chunks, chunk_size, total_size ) - chunk = u.read( chunk_size ) - if not chunk: - break - f.write( chunk ) - total_chunks += 1 - return ( filename, [], ) +def build_url( query ): + return sys.argv[0] + '?' + urllib.urlencode( query ) -def url_retrieve_vfs( videourl, filename, reporthook, chunk_size = 8192 ): - with closing( urllib2.urlopen( videourl ) ) as u, closing( xbmcvfs.File( filename, 'wb' ) ) as f: - total_size = int( u.info().getheader( 'Content-Length' ).strip() ) if u.info() and u.info().getheader( 'Content-Length' ) else 0 - total_chunks = 0 +def _chunked_url_copier( u, f, reporthook, chunk_size, aborthook ): + aborthook = aborthook if aborthook is not None else lambda: False + total_size = int( u.info().getheader( 'Content-Length' ).strip() ) if u.info() and u.info().getheader( 'Content-Length' ) else 0 + total_chunks = 0 - while True: - reporthook( total_chunks, chunk_size, total_size ) - chunk = u.read( chunk_size ) - if not chunk: - break - f.write( chunk ) - total_chunks += 1 - return ( filename, [], ) + while not aborthook(): + reporthook( total_chunks, chunk_size, total_size ) + chunk = u.read( chunk_size ) + if not chunk: + # operation has finished + return + f.write( chunk ) + total_chunks += 1 + # abort requested + raise ExitRequested( 'Reception interrupted.' ) diff --git a/plugin.video.mediathekview/resources/lib/notifier.py b/plugin.video.mediathekview/resources/lib/notifier.py index d257a52..517da9e 100644 --- a/plugin.video.mediathekview/resources/lib/notifier.py +++ b/plugin.video.mediathekview/resources/lib/notifier.py @@ -14,28 +14,31 @@ class Notifier( KodiUI ): self.language = xbmcaddon.Addon().getLocalizedString def ShowDatabaseError( self, err ): - self.ShowError( self.language( 30951 ), '{}'.format( err ) ) + self.ShowError( 30951, '{}'.format( err ) ) def ShowDownloadError( self, name, err ): - self.ShowError( self.language( 30952 ), self.language( 30953 ).format( name, err ) ) + self.ShowError( 30952, self.language( 30953 ).format( name, err ) ) def ShowMissingExtractorError( self ): - self.ShowError( self.language( 30952 ), self.language( 30954 ), time = 10000 ) + self.ShowError( 30952, 30954, time = 10000 ) def ShowLimitResults( self, maxresults ): - self.ShowNotification( self.language( 30980 ), self.language( 30981 ).format( maxresults ) ) + self.ShowNotification( 30980, self.language( 30981 ).format( maxresults ) ) def ShowDownloadProgress( self ): - self.ShowBGDialog( self.language( 30955 ) ) + self.ShowBGDialog( 30955 ) def UpdateDownloadProgress( self, percent, message = None ): self.UpdateBGDialog( percent, message = message ) + def HookDownloadProgress( self, blockcount, blocksize, totalsize ): + self.HookBGDialog( blockcount, blocksize, totalsize ) + def CloseDownloadProgress( self ): self.CloseBGDialog() def ShowUpdateProgress( self ): - self.ShowBGDialog( self.language( 30956 ) ) + self.ShowBGDialog( 30956 ) def UpdateUpdateProgress( self, percent, count, channels, shows, movies ): message = self.language( 30957 ) % ( count, channels, shows, movies ) diff --git a/plugin.video.mediathekview/resources/lib/settings.py b/plugin.video.mediathekview/resources/lib/settings.py index 32bd95a..13d86e0 100644 --- a/plugin.video.mediathekview/resources/lib/settings.py +++ b/plugin.video.mediathekview/resources/lib/settings.py @@ -19,6 +19,8 @@ class Settings( object ): self.minlength = int( float( self.addon.getSetting( 'minlength' ) ) ) * 60 self.groupshows = self.addon.getSetting( 'groupshows' ) == 'true' self.maxresults = int( self.addon.getSetting( 'maxresults' ) ) + self.maxage = int( self.addon.getSetting( 'maxage' ) ) * 86400 + self.recentmode = int( self.addon.getSetting( 'recentmode' ) ) self.downloadpath = self.addon.getSetting( 'downloadpath' ) self.type = self.addon.getSetting( 'dbtype' ) self.host = self.addon.getSetting( 'dbhost' ) diff --git a/plugin.video.mediathekview/resources/lib/showui.py b/plugin.video.mediathekview/resources/lib/showui.py index b0bf7f9..64aff20 100644 --- a/plugin.video.mediathekview/resources/lib/showui.py +++ b/plugin.video.mediathekview/resources/lib/showui.py @@ -3,35 +3,55 @@ # # -- Imports ------------------------------------------------ -import sys, urllib -import xbmcplugin, xbmcgui +import xbmcgui +import xbmcplugin + +import resources.lib.mvutils as mvutils from resources.lib.show import Show # -- Classes ------------------------------------------------ class ShowUI( Show ): - def __init__( self, handle, sortmethods = None ): - self.base_url = sys.argv[0] - self.handle = handle + def __init__( self, plugin, sortmethods = None ): + self.plugin = plugin + self.handle = plugin.addon_handle self.sortmethods = sortmethods if sortmethods is not None else [ xbmcplugin.SORT_METHOD_TITLE ] self.querychannelid = 0 def Begin( self, channelid ): - self.querychannelid = channelid + self.querychannelid = int( channelid ) for method in self.sortmethods: xbmcplugin.addSortMethod( self.handle, method ) def Add( self, altname = None ): if altname is not None: resultingname = altname - elif self.querychannelid == '0': + elif self.querychannelid == 0: resultingname = self.show + ' [' + self.channel + ']' else: resultingname = self.show + + infoLabels = { + 'title' : resultingname, + 'sorttitle' : resultingname.lower() + } + + + if self.channel.find( ',' ) == -1: + icon = 'special://home/addons/' + self.plugin.addon_id + '/resources/icons/' + self.channel.lower() + '-m.png' + else: + icon = 'special://home/addons/' + self.plugin.addon_id + '/resources/icons/default-m.png' + li = xbmcgui.ListItem( label = resultingname ) + li.setInfo( type = 'video', infoLabels = infoLabels ) + li.setArt( { + 'thumb': icon, + 'icon': icon + } ) + xbmcplugin.addDirectoryItem( handle = self.handle, - url = self.build_url( { + url = mvutils.build_url( { 'mode': "films", 'show': self.id } ), @@ -41,6 +61,3 @@ class ShowUI( Show ): def End( self ): xbmcplugin.endOfDirectory( self.handle ) - - def build_url( self, query ): - return self.base_url + '?' + urllib.urlencode( query ) diff --git a/plugin.video.mediathekview/resources/lib/storemysql.py b/plugin.video.mediathekview/resources/lib/storemysql.py index e2419cc..9268eec 100644 --- a/plugin.video.mediathekview/resources/lib/storemysql.py +++ b/plugin.video.mediathekview/resources/lib/storemysql.py @@ -20,7 +20,7 @@ class StoreMySQL( object ): # useful query fragments self.sql_query_films = "SELECT film.id,`title`,`show`,`channel`,`description`,TIME_TO_SEC(`duration`) AS `seconds`,`size`,`aired`,`url_sub`,`url_video`,`url_video_sd`,`url_video_hd` FROM `film` LEFT JOIN `show` ON show.id=film.showid LEFT JOIN `channel` ON channel.id=film.channelid" self.sql_query_filmcnt = "SELECT COUNT(*) FROM `film` LEFT JOIN `show` ON show.id=film.showid LEFT JOIN `channel` ON channel.id=film.channelid" - self.sql_cond_recent = "( TIMESTAMPDIFF(HOUR,`aired`,CURRENT_TIMESTAMP()) < 24 )" + self.sql_cond_recent = "( TIMESTAMPDIFF(SECOND,{},CURRENT_TIMESTAMP()) <= {} )".format( "aired" if settings.recentmode == 0 else "film.dtCreated", settings.maxage ) self.sql_cond_nofuture = " AND ( ( `aired` IS NULL ) OR ( TIMESTAMPDIFF(HOUR,`aired`,CURRENT_TIMESTAMP()) > 0 ) )" if settings.nofuture else "" self.sql_cond_minlength = " AND ( ( `duration` IS NULL ) OR ( TIME_TO_SEC(`duration`) >= %d ) )" % settings.minlength if settings.minlength > 0 else "" @@ -48,17 +48,21 @@ class StoreMySQL( object ): self.conn.close() def Search( self, search, filmui ): - self._Search_Condition( '( ( `title` LIKE "%%%s%%" ) OR ( `show` LIKE "%%%s%%" ) )' % ( search, search, ), filmui, True, True, self.settings.maxresults ) + searchmask = '%' + search.decode('utf-8') + '%' + self._Search_Condition( '( ( `title` LIKE %s ) OR ( `show` LIKE %s ) )', ( searchmask, searchmask, ), filmui, True, True, self.settings.maxresults ) def SearchFull( self, search, filmui ): - self._Search_Condition( '( ( `title` LIKE "%%%s%%" ) OR ( `show` LIKE "%%%s%%" ) ) OR ( `description` LIKE "%%%s%%") )' % ( search, search, search ), filmui, True, True, self.settings.maxresults ) + searchmask = '%' + search.decode('utf-8') + '%' + self._Search_Condition( '( ( `title` LIKE %s ) OR ( `show` LIKE %s ) OR ( `description` LIKE %s ) )', ( searchmask, searchmask, searchmask ), filmui, True, True, self.settings.maxresults ) def GetRecents( self, channelid, filmui ): - sql_cond_channel = ' AND ( film.channelid=' + str( channelid ) + ' ) ' if channelid != '0' else '' - self._Search_Condition( self.sql_cond_recent + sql_cond_channel, filmui, True, False, 10000 ) + if channelid != '0': + self._Search_Condition( self.sql_cond_recent + ' AND ( film.channelid=%s )', ( int( channelid ), ), filmui, True, False, 10000 ) + else: + self._Search_Condition( self.sql_cond_recent, (), filmui, True, False, 10000 ) def GetLiveStreams( self, filmui ): - self._Search_Condition( '( show.search="LIVESTREAM" )', filmui, False, False, 10000 ) + self._Search_Condition( '( show.search="LIVESTREAM" )', (), filmui, False, False, 10000 ) def GetChannels( self, channelui ): self._Channels_Condition( None, channelui ) @@ -70,18 +74,30 @@ class StoreMySQL( object ): if self.conn is None: return try: - condition = 'WHERE ( `channelid`=' + str( channelid ) + ' ) ' if channelid != '0' else '' - self.logger.info( 'MySQL Query: {}', - 'SELECT LEFT(`search`,1) AS letter,COUNT(*) AS `count` FROM `show` ' + - condition + - 'GROUP BY LEFT(search,1)' - ) + channelid = int( channelid ) cursor = self.conn.cursor() - cursor.execute( - 'SELECT LEFT(`search`,1) AS letter,COUNT(*) AS `count` FROM `show` ' + - condition + - 'GROUP BY LEFT(`search`,1)' - ) + if channelid != 0: + self.logger.info( + 'MySQL Query: SELECT LEFT(`search`,1) AS letter,COUNT(*) AS `count` FROM `show` WHERE ( `channelid`={} ) GROUP BY LEFT(search,1)', + channelid + ) + cursor.execute( """ + SELECT LEFT(`search`,1) AS `letter`, + COUNT(*) AS `count` + FROM `show` + WHERE ( `channelid`=%s ) + GROUP BY LEFT(`search`,1) + """, ( channelid, ) ) + else: + self.logger.info( + 'MySQL Query: SELECT LEFT(`search`,1) AS letter,COUNT(*) AS `count` FROM `show` GROUP BY LEFT(search,1)' + ) + cursor.execute( """ + SELECT LEFT(`search`,1) AS `letter`, + COUNT(*) AS `count` + FROM `show` + GROUP BY LEFT(`search`,1) + """ ) initialui.Begin( channelid ) for ( initialui.initial, initialui.count ) in cursor: initialui.Add() @@ -95,15 +111,57 @@ class StoreMySQL( object ): if self.conn is None: return try: - if channelid == '0' and self.settings.groupshows: - query = 'SELECT GROUP_CONCAT(show.id),GROUP_CONCAT(`channelid`),`show`,GROUP_CONCAT(`channel`) FROM `show` LEFT JOIN `channel` ON channel.id=show.channelid WHERE ( `show` LIKE "%s%%" ) GROUP BY `show`' % initial - elif channelid == '0': - query = 'SELECT show.id,show.channelid,show.show,channel.channel FROM `show` LEFT JOIN channel ON channel.id=show.channelid WHERE ( `show` LIKE "%s%%" )' % initial - else: - query = 'SELECT show.id,show.channelid,show.show,channel.channel FROM `show` LEFT JOIN channel ON channel.id=show.channelid WHERE ( `channelid`=%s ) AND ( `show` LIKE "%s%%" )' % ( channelid, initial ) - self.logger.info( 'MySQL Query: {}', query ) + channelid = int( channelid ) cursor = self.conn.cursor() - cursor.execute( query ) + if channelid == 0 and self.settings.groupshows: + cursor.execute( """ + SELECT GROUP_CONCAT(show.id), + GROUP_CONCAT(`channelid`), + `show`, + GROUP_CONCAT(`channel`) + FROM `show` + LEFT JOIN `channel` + ON ( channel.id = show.channelid ) + WHERE ( `show` LIKE %s ) + GROUP BY `show` + """, ( initial + '%', ) ) + elif channelid == 0: + cursor.execute( """ + SELECT show.id, + show.channelid, + show.show, + channel.channel + FROM `show` + LEFT JOIN `channel` + ON ( channel.id = show.channelid ) + WHERE ( `show` LIKE %s ) + """, ( initial + '%', ) ) + elif initial: + cursor.execute( """ + SELECT show.id, + show.channelid, + show.show, + channel.channel + FROM `show` + LEFT JOIN `channel` + ON ( channel.id = show.channelid ) + WHERE ( + ( `channelid` = %s ) + AND + ( `show` LIKE %s ) + ) + """, ( channelid, initial + '%', ) ) + else: + cursor.execute( """ + SELECT show.id, + show.channelid, + show.show, + channel.channel + FROM `show` + LEFT JOIN `channel` + ON ( channel.id = show.channelid ) + WHERE ( `channelid` = %s ) + """, ( channelid, ) ) showui.Begin( channelid ) for ( showui.id, showui.channelid, showui.show, showui.channel ) in cursor: showui.Add() @@ -118,13 +176,10 @@ class StoreMySQL( object ): return if showid.find( ',' ) == -1: # only one channel id - condition = '( `showid`=%s )' % showid - showchannels = False + self._Search_Condition( '( `showid` = %s )', ( int( showid ), ), filmui, False, False, 10000 ) else: # multiple channel ids - condition = '( `showid` IN ( %s ) )' % showid - showchannels = True - self._Search_Condition( condition, filmui, False, showchannels, 10000 ) + self._Search_Condition( '( `showid` IN ( {} ) )'.format( showid ), (), filmui, False, True, 10000 ) def _Channels_Condition( self, condition, channelui): if self.conn is None: @@ -132,11 +187,14 @@ class StoreMySQL( object ): try: if condition is None: query = 'SELECT `id`,`channel`,0 AS `count` FROM `channel`' + qtail = '' else: - query = 'SELECT channel.id AS `id`,`channel`,COUNT(*) AS `count` FROM `film` LEFT JOIN `channel` ON channel.id=film.channelid WHERE ' + condition + ' GROUP BY channel.id' - self.logger.info( 'MySQL Query: {}', query ) + query = 'SELECT channel.id AS `id`,`channel`,COUNT(*) AS `count` FROM `film` LEFT JOIN `channel` ON channel.id=film.channelid' + qtail = ' WHERE ' + condition + self.sql_cond_nofuture + self.sql_cond_minlength + ' GROUP BY channel.id' + self.logger.info( 'MySQL Query: {}', query + qtail ) + cursor = self.conn.cursor() - cursor.execute( query ) + cursor.execute( query + qtail ) channelui.Begin() for ( channelui.id, channelui.channel, channelui.count ) in cursor: channelui.Add() @@ -146,7 +204,7 @@ class StoreMySQL( object ): self.logger.error( 'Database error: {}', err ) self.notifier.ShowDatabaseError( err ) - def _Search_Condition( self, condition, filmui, showshows, showchannels, maxresults ): + def _Search_Condition( self, condition, params, filmui, showshows, showchannels, maxresults ): if self.conn is None: return try: @@ -164,7 +222,8 @@ class StoreMySQL( object ): condition + self.sql_cond_nofuture + self.sql_cond_minlength + - ' LIMIT {}'.format( maxresults + 1 ) if maxresults else '' + ' LIMIT {}'.format( maxresults + 1 ) if maxresults else '', + params ) ( results, ) = cursor.fetchone() if maxresults and results > maxresults: @@ -175,7 +234,8 @@ class StoreMySQL( object ): condition + self.sql_cond_nofuture + self.sql_cond_minlength + - ' LIMIT {}'.format( maxresults + 1 ) if maxresults else '' + ' LIMIT {}'.format( maxresults + 1 ) if maxresults else '', + params ) filmui.Begin( showshows, showchannels ) for ( filmui.id, filmui.title, filmui.show, filmui.channel, filmui.description, filmui.seconds, filmui.size, filmui.aired, filmui.url_sub, filmui.url_video, filmui.url_video_sd, filmui.url_video_hd ) in cursor: @@ -518,10 +578,10 @@ class StoreMySQL( object ): cursor = self.conn.cursor() cursor.callproc( 'ftInsertChannel', ( channel, ) ) for result in cursor.stored_results(): - for ( id, added ) in result: + for ( idd, added ) in result: cursor.close() self.conn.commit() - return ( id, added ) + return ( idd, added ) # should never happen cursor.close() self.conn.commit() diff --git a/plugin.video.mediathekview/resources/lib/storesqlite.py b/plugin.video.mediathekview/resources/lib/storesqlite.py index 601db67..90d10b1 100644 --- a/plugin.video.mediathekview/resources/lib/storesqlite.py +++ b/plugin.video.mediathekview/resources/lib/storesqlite.py @@ -23,7 +23,7 @@ class StoreSQLite( object ): # useful query fragments self.sql_query_films = "SELECT film.id,title,show,channel,description,duration,size,datetime(aired, 'unixepoch', 'localtime'),url_sub,url_video,url_video_sd,url_video_hd FROM film LEFT JOIN show ON show.id=film.showid LEFT JOIN channel ON channel.id=film.channelid" self.sql_query_filmcnt = "SELECT COUNT(*) FROM film LEFT JOIN show ON show.id=film.showid LEFT JOIN channel ON channel.id=film.channelid" - self.sql_cond_recent = "( ( UNIX_TIMESTAMP() - aired ) <= 86400 )" + self.sql_cond_recent = "( ( UNIX_TIMESTAMP() - {} ) <= {} )".format( "aired" if settings.recentmode == 0 else "film.dtCreated", settings.maxage ) self.sql_cond_nofuture = " AND ( ( aired IS NULL ) OR ( ( UNIX_TIMESTAMP() - aired ) > 0 ) )" if settings.nofuture else "" self.sql_cond_minlength = " AND ( ( duration IS NULL ) OR ( duration >= %d ) )" % settings.minlength if settings.minlength > 0 else "" @@ -55,17 +55,21 @@ class StoreSQLite( object ): self.conn = None def Search( self, search, filmui ): - self._Search_Condition( '( ( title LIKE "%%%s%%" ) OR ( show LIKE "%%%s%%" ) )' % ( search, search ), filmui, True, True, self.settings.maxresults ) + searchmask = '%' + search.decode('utf-8') + '%' + self._Search_Condition( '( ( title LIKE ? ) OR ( show LIKE ? ) )', ( searchmask, searchmask, ), filmui, True, True, self.settings.maxresults ) def SearchFull( self, search, filmui ): - self._Search_Condition( '( ( title LIKE "%%%s%%" ) OR ( show LIKE "%%%s%%" ) OR ( description LIKE "%%%s%%") )' % ( search, search, search ), filmui, True, True, self.settings.maxresults ) + searchmask = '%' + search.decode('utf-8') + '%' + self._Search_Condition( '( ( title LIKE ? ) OR ( show LIKE ? ) OR ( description LIKE ? ) )', ( searchmask, searchmask, searchmask ), filmui, True, True, self.settings.maxresults ) def GetRecents( self, channelid, filmui ): - sql_cond_channel = ' AND ( film.channelid=' + str( channelid ) + ' ) ' if channelid != '0' else '' - self._Search_Condition( self.sql_cond_recent + sql_cond_channel, filmui, True, False, 10000 ) + if channelid != '0': + self._Search_Condition( self.sql_cond_recent + ' AND ( film.channelid=? )', ( int( channelid ), ), filmui, True, False, 10000 ) + else: + self._Search_Condition( self.sql_cond_recent, (), filmui, True, False, 10000 ) def GetLiveStreams( self, filmui ): - self._Search_Condition( '( show.search="LIVESTREAM" )', filmui, False, False, 10000 ) + self._Search_Condition( '( show.search="LIVESTREAM" )', (), filmui, False, False, 10000 ) def GetChannels( self, channelui ): self._Channels_Condition( None, channelui ) @@ -77,18 +81,28 @@ class StoreSQLite( object ): if self.conn is None: return try: - condition = 'WHERE ( channelid=' + str( channelid ) + ' ) ' if channelid != '0' else '' - self.logger.info( 'SQlite Query: {}', - 'SELECT SUBSTR(search,1,1),COUNT(*) FROM show ' + - condition + - 'GROUP BY LEFT(search,1)' - ) + channelid = int( channelid ) cursor = self.conn.cursor() - cursor.execute( - 'SELECT SUBSTR(search,1,1),COUNT(*) FROM show ' + - condition + - 'GROUP BY SUBSTR(search,1,1)' - ) + if channelid != 0: + self.logger.info( + 'SQlite Query: SELECT SUBSTR(search,1,1),COUNT(*) FROM show WHERE ( channelid={} ) GROUP BY LEFT(search,1)', + channelid + ) + cursor.execute( """ + SELECT SUBSTR(search,1,1),COUNT(*) + FROM show + WHERE ( channelid=? ) + GROUP BY SUBSTR(search,1,1) + """, ( channelid, ) ) + else: + self.logger.info( + 'SQlite Query: SELECT SUBSTR(search,1,1),COUNT(*) FROM show GROUP BY LEFT(search,1)' + ) + cursor.execute( """ + SELECT SUBSTR(search,1,1),COUNT(*) + FROM show + GROUP BY SUBSTR(search,1,1) + """ ) initialui.Begin( channelid ) for ( initialui.initial, initialui.count ) in cursor: initialui.Add() @@ -102,15 +116,57 @@ class StoreSQLite( object ): if self.conn is None: return try: - if channelid == '0' and self.settings.groupshows: - query = 'SELECT GROUP_CONCAT(show.id),GROUP_CONCAT(channelid),show,GROUP_CONCAT(channel) FROM show LEFT JOIN channel ON channel.id=show.channelid WHERE ( show LIKE "%s%%" ) GROUP BY show' % initial - elif channelid == '0': - query = 'SELECT show.id,show.channelid,show.show,channel.channel FROM show LEFT JOIN channel ON channel.id=show.channelid WHERE ( show LIKE "%s%%" )' % initial - else: - query = 'SELECT show.id,show.channelid,show.show,channel.channel FROM show LEFT JOIN channel ON channel.id=show.channelid WHERE ( channelid=%s ) AND ( show LIKE "%s%%" )' % ( channelid, initial ) - self.logger.info( 'SQLite Query: {}', query ) + channelid = int( channelid ) cursor = self.conn.cursor() - cursor.execute( query ) + if channelid == 0 and self.settings.groupshows: + cursor.execute( """ + SELECT GROUP_CONCAT(show.id), + GROUP_CONCAT(channelid), + show, + GROUP_CONCAT(channel) + FROM show + LEFT JOIN channel + ON ( channel.id = show.channelid ) + WHERE ( show LIKE ? ) + GROUP BY show + """, ( initial + '%', ) ) + elif channelid == 0: + cursor.execute( """ + SELECT show.id, + show.channelid, + show.show, + channel.channel + FROM show + LEFT JOIN channel + ON ( channel.id = show.channelid ) + WHERE ( show LIKE ? ) + """, ( initial + '%', ) ) + elif initial: + cursor.execute( """ + SELECT show.id, + show.channelid, + show.show, + channel.channel + FROM show + LEFT JOIN channel + ON ( channel.id = show.channelid ) + WHERE ( + ( channelid=? ) + AND + ( show LIKE ? ) + ) + """, ( channelid, initial + '%', ) ) + else: + cursor.execute( """ + SELECT show.id, + show.channelid, + show.show, + channel.channel + FROM show + LEFT JOIN channel + ON ( channel.id = show.channelid ) + WHERE ( channelid=? ) + """, ( channelid, ) ) showui.Begin( channelid ) for ( showui.id, showui.channelid, showui.show, showui.channel ) in cursor: showui.Add() @@ -125,13 +181,10 @@ class StoreSQLite( object ): return if showid.find( ',' ) == -1: # only one channel id - condition = '( showid=%s )' % showid - showchannels = False + self._Search_Condition( '( showid=? )', ( int( showid ), ), filmui, False, False, 10000 ) else: # multiple channel ids - condition = '( showid IN ( %s ) )' % showid - showchannels = True - self._Search_Condition( condition, filmui, False, showchannels, 10000 ) + self._Search_Condition( '( showid IN ( {} ) )'.format( showid ), (), filmui, False, True, 10000 ) def _Channels_Condition( self, condition, channelui ): if self.conn is None: @@ -139,11 +192,13 @@ class StoreSQLite( object ): try: if condition is None: query = 'SELECT id,channel,0 AS `count` FROM channel' + qtail = '' else: - query = 'SELECT channel.id AS `id`,channel,COUNT(*) AS `count` FROM film LEFT JOIN channel ON channel.id=film.channelid WHERE ' + condition + ' GROUP BY channel' - self.logger.info( 'SQLite Query: {}', query ) + query = 'SELECT channel.id AS `id`,channel,COUNT(*) AS `count` FROM film LEFT JOIN channel ON channel.id=film.channelid' + qtail = ' WHERE ' + condition + ' GROUP BY channel' + self.logger.info( 'SQLite Query: {}', query + qtail ) cursor = self.conn.cursor() - cursor.execute( query ) + cursor.execute( query + qtail ) channelui.Begin() for ( channelui.id, channelui.channel, channelui.count ) in cursor: channelui.Add() @@ -153,7 +208,7 @@ class StoreSQLite( object ): self.logger.error( 'Database error: {}', err ) self.notifier.ShowDatabaseError( err ) - def _Search_Condition( self, condition, filmui, showshows, showchannels, maxresults ): + def _Search_Condition( self, condition, params, filmui, showshows, showchannels, maxresults ): if self.conn is None: return try: @@ -172,7 +227,8 @@ class StoreSQLite( object ): condition + self.sql_cond_nofuture + self.sql_cond_minlength + - ' LIMIT {}'.format( maxresults + 1 ) if maxresults else '' + ' LIMIT {}'.format( maxresults + 1 ) if maxresults else '', + params ) ( results, ) = cursor.fetchone() if maxresults and results > maxresults: @@ -183,7 +239,8 @@ class StoreSQLite( object ): condition + self.sql_cond_nofuture + self.sql_cond_minlength + - ' LIMIT {}'.format( maxresults ) if maxresults else '' + ' LIMIT {}'.format( maxresults ) if maxresults else '', + params ) filmui.Begin( showshows, showchannels ) for ( filmui.id, filmui.title, filmui.show, filmui.channel, filmui.description, filmui.seconds, filmui.size, filmui.aired, filmui.url_sub, filmui.url_video, filmui.url_video_sd, filmui.url_video_hd ) in cursor: diff --git a/plugin.video.mediathekview/resources/lib/ttml2srt.py b/plugin.video.mediathekview/resources/lib/ttml2srt.py index 7bfdc47..5c2c4e3 100644 --- a/plugin.video.mediathekview/resources/lib/ttml2srt.py +++ b/plugin.video.mediathekview/resources/lib/ttml2srt.py @@ -27,7 +27,7 @@ import re import io from datetime import timedelta -from xml.etree import ElementTree as ET +from defusedxml import ElementTree as ET def ttml2srt( infile, outfile ): tree = ET.parse( infile ) @@ -59,7 +59,7 @@ def ttml2srt( infile, outfile ): def parse_time_expression(expression, default_offset=timedelta(0)): offset_time = re.match(r'^([0-9]+(\.[0-9]+)?)(h|m|s|ms|f|t)$', expression) if offset_time: - time_value, fraction, metric = offset_time.groups() + time_value, _, metric = offset_time.groups() time_value = float(time_value) if metric == 'h': return default_offset + timedelta(hours=time_value) @@ -79,7 +79,7 @@ def ttml2srt( infile, outfile ): clock_time = re.match( r'^([0-9]{2,}):([0-9]{2,}):([0-9]{2,}(\.[0-9]+)?)$', expression) if clock_time: - hours, minutes, seconds, fraction = clock_time.groups() + hours, minutes, seconds, _ = clock_time.groups() return timedelta(hours=int(hours), minutes=int(minutes), seconds=float(seconds)) clock_time_frames = re.match( diff --git a/plugin.video.mediathekview/resources/lib/updater.py b/plugin.video.mediathekview/resources/lib/updater.py index afdf840..95c99d0 100644 --- a/plugin.video.mediathekview/resources/lib/updater.py +++ b/plugin.video.mediathekview/resources/lib/updater.py @@ -2,9 +2,15 @@ # Copyright (c) 2017-2018, Leo Moll # -- Imports ------------------------------------------------ -import os, urllib2, subprocess, ijson, datetime, time -import xml.etree.ElementTree as etree - +import os +import time +import ijson +import random +import urllib2 +import datetime +import subprocess + +import defusedxml.ElementTree as etree import resources.lib.mvutils as mvutils from operator import itemgetter @@ -12,6 +18,7 @@ from operator import itemgetter from resources.lib.store import Store from resources.lib.exceptions import DatabaseCorrupted from resources.lib.exceptions import DatabaseLost +from resources.lib.exceptions import ExitRequested # -- Unpacker support --------------------------------------- upd_can_bz2 = False @@ -106,7 +113,7 @@ class MediathekViewUpdater( object ): self.Import( full ) def Import( self, full ): - ( _, compfile, destfile, avgrecsize ) = self._get_update_info( full ) + ( _, _, destfile, avgrecsize ) = self._get_update_info( full ) if not mvutils.file_exists( destfile ): self.logger.error( 'File {} does not exists', destfile ) return False @@ -178,14 +185,14 @@ class MediathekViewUpdater( object ): except DatabaseLost as err: self.logger.error( '{}', err ) self.notifier.CloseUpdateProgress() - except IOError as err: + except Exception as err: self.logger.error( 'Error {} wile processing {}', err, destfile ) self._update_end( full, 'ABORTED' ) self.notifier.CloseUpdateProgress() return False def GetNewestList( self, full ): - ( url, compfile, destfile, avgrecsize ) = self._get_update_info( full ) + ( url, compfile, destfile, _ ) = self._get_update_info( full ) if url is None: self.logger.error( 'No suitable archive extractor available for this system' ) self.notifier.ShowMissingExtractorError() @@ -199,20 +206,18 @@ class MediathekViewUpdater( object ): self.logger.error( 'Failure opening {}', url ) self.notifier.ShowDownloadError( url, err ) return False - root = etree.fromstring ( data ) urls = [] for server in root.findall( 'Server' ): try: URL = server.find( 'URL' ).text Prio = server.find( 'Prio' ).text - urls.append( ( self._get_update_url( URL ), Prio ) ) + urls.append( ( self._get_update_url( URL ), float( Prio ) + random.random() * 1.2 ) ) self.logger.info( 'Found mirror {} (Priority {})', URL, Prio ) except AttributeError: pass urls = sorted( urls, key = itemgetter( 1 ) ) urls = [ url[0] for url in urls ] - result = None # cleanup downloads self.logger.info( 'Cleaning up old downloads...' ) @@ -227,17 +232,23 @@ class MediathekViewUpdater( object ): lasturl = url self.logger.info( 'Trying to download {} from {}...', os.path.basename( compfile ), url ) self.notifier.UpdateDownloadProgress( 0, url ) - result = mvutils.url_retrieve( url, filename = compfile, reporthook = self._reporthook ) + mvutils.url_retrieve( url, filename = compfile, reporthook = self.notifier.HookDownloadProgress, aborthook = self.monitor.abortRequested ) break except urllib2.URLError as err: self.logger.error( 'Failure downloading {}', url ) + self.notifier.CloseDownloadProgress() + self.notifier.ShowDownloadError( lasturl, err ) + return False + except ExitRequested as err: + self.logger.error( 'Immediate exit requested. Aborting download of {}', url ) + self.notifier.CloseDownloadProgress() + self.notifier.ShowDownloadError( lasturl, err ) + return False except Exception as err: self.logger.error( 'Failure writng {}', url ) - if result is None: - self.logger.info( 'No file downloaded' ) - self.notifier.CloseDownloadProgress() - self.notifier.ShowDownloadError( lasturl, err ) - return False + self.notifier.CloseDownloadProgress() + self.notifier.ShowDownloadError( lasturl, err ) + return False # decompress filmliste if self.use_xz is True: @@ -304,13 +315,6 @@ class MediathekViewUpdater( object ): self.logger.error( 'Failed to remove {}: error {}', name, err ) return False - def _reporthook( self, blockcount, blocksize, totalsize ): - downloaded = blockcount * blocksize - if totalsize > 0: - percent = int( (downloaded * 100) / totalsize ) - self.notifier.UpdateDownloadProgress( percent ) - self.logger.debug( 'Downloading blockcount={}, blocksize={}, totalsize={}', blockcount, blocksize, totalsize ) - def _update_start( self, full ): self.logger.info( 'Initializing update...' ) self.add_chn = 0 diff --git a/plugin.video.mediathekview/resources/settings.xml b/plugin.video.mediathekview/resources/settings.xml index a1ef62b..b61a0cc 100644 --- a/plugin.video.mediathekview/resources/settings.xml +++ b/plugin.video.mediathekview/resources/settings.xml @@ -2,12 +2,16 @@ + - + + + + @@ -18,7 +22,6 @@ - diff --git a/plugin.video.mediathekview/service.py b/plugin.video.mediathekview/service.py index 933ce55..536b5cb 100644 --- a/plugin.video.mediathekview/service.py +++ b/plugin.video.mediathekview/service.py @@ -23,26 +23,20 @@ # SOFTWARE. # -- Imports ------------------------------------------------ -from __future__ import unicode_literals # ,absolute_import, division - -import xbmc +from __future__ import unicode_literals from resources.lib.kodi.KodiAddon import KodiService +from resources.lib.kodi.KodiAddon import KodiInterlockedMonitor from resources.lib.notifier import Notifier from resources.lib.settings import Settings from resources.lib.updater import MediathekViewUpdater # -- Classes ------------------------------------------------ -class MediathekViewMonitor( xbmc.Monitor ): - def __init__( self, service ): - super( MediathekViewMonitor, self ).__init__() - self.service = service +class MediathekViewMonitor( KodiInterlockedMonitor ): + def __init__( self, service, setting_id ): + super( MediathekViewMonitor, self ).__init__( service, setting_id ) self.logger = service.getNewLogger( 'Monitor' ) - self.logger.info( 'Startup' ) - - def __del__( self ): - self.logger.info( 'Shutdown' ) def onSettingsChanged( self ): self.service.ReloadSettings() @@ -53,15 +47,16 @@ class MediathekViewService( KodiService ): self.setTopic( 'Service' ) self.settings = Settings() self.notifier = Notifier() - self.monitor = MediathekViewMonitor( self ) + self.monitor = MediathekViewMonitor( self, 'instanceid' ) self.updater = MediathekViewUpdater( self.getNewLogger( 'Updater' ), self.notifier, self.settings, self.monitor ) def Init( self ): - self.info( 'Init' ) + self.info( 'Init (instance id: {})', self.monitor.instance_id ) + self.monitor.RegisterInstance() self.updater.Init() def Run( self ): - self.info( 'Starting up...' ) + self.info( 'Starting up... (instance id: {})', self.monitor.instance_id ) while not self.monitor.abortRequested(): updateop = self.updater.GetCurrentUpdateOperation() if updateop == 1: @@ -76,11 +71,12 @@ class MediathekViewService( KodiService ): if self.monitor.waitForAbort( 60 ): # Abort was requested while waiting. We should exit break - self.info( 'Shutting down...' ) + self.info( 'Shutting down... (instance id: {})', self.monitor.instance_id ) def Exit( self ): - self.info( 'Exit' ) + self.info( 'Exit (instance id: {})', self.monitor.instance_id ) self.updater.Exit() + self.monitor.UnregisterInstance() def ReloadSettings( self ): # TODO: support online reconfiguration -- cgit v1.2.3