diff --git a/README.md b/README.md index a0baee55..2dd8f582 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![stable version](https://img.shields.io/badge/stable_version-1.7.7-blue.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect/bin/repository.plexkodiconnect/repository.plexkodiconnect-1.0.0.zip) -[![beta version](https://img.shields.io/badge/beta_version-1.7.7-red.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect_BETA/bin-BETA/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.0.zip) +[![beta version](https://img.shields.io/badge/beta_version-1.7.8-red.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect_BETA/bin-BETA/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.0.zip) [![Installation](https://img.shields.io/badge/wiki-installation-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/Installation) [![FAQ](https://img.shields.io/badge/wiki-FAQ-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/faq) diff --git a/addon.xml b/addon.xml index f2cfa0bf..86e458f9 100644 --- a/addon.xml +++ b/addon.xml @@ -1,5 +1,5 @@ - + @@ -50,7 +50,14 @@ Brug på eget ansvar Gebruik op eigen risico 使用風險由您自己承擔 - version 1.7.7 + version 1.7.8 (beta only) +- Fix IMDB id for movies (resync by going to the PKC settings, Advanced, then Repair Local Database) +- Increase timeouts for PMS, should fix some connection issues +- Move translations to new strings.po system +- Fix some TypeErrors +- Some code refactoring + +version 1.7.7 - Chinese Traditional, thanks @old2tan - Chinese Simplified, thanks @everdream - Browse by folder: also sort by Date Added @@ -60,6 +67,37 @@ version 1.7.6 - Hotfix: Revert Cache missing artwork on PKC startup. This should help with slow PKC startup, videos not being started, lagging PKC, etc. version 1.7.5 -- Dutch translation, thanks @mvanbaak +- Dutch translation, thanks @mvanbaak + +version 1.7.4 (beta only) +- Show menu item only for appropriate Kodi library: Be careful to start video content through Videos -> Video Addons -> ... and pictures through Pictures -> Picture Addons -> ... +- Fix playback error popup when using Alexa +- New Italian translations, thanks @nikkux, @chicco83 +- Update translations +- Rewire Kodi ListItem stuff +- Fix TypeError for setting ListItem streams +- Fix Kodi setContent for images +- Fix AttributeError due to missing Kodi sort methods + +version 1.7.3 (beta only) +- Fix KeyError for channels if no media streams +- Move plex node navigation, playback to main thread +- Fix TypeError for malformed browsing xml +- Fix IndexError if we can't get a valid xml from PMS +- Pass 'None' instead of empty string in url args + +version 1.7.2 +- Fix for some channels not starting playback + +version 1.7.1 +- Fix Alexa not doing anything + +version 1.7.0 +- Amazon Alexa support! Be sure to check the Plex Alexa forum first if you encounter issues; there are still many bugs completely unrelated to PKC +- Plex Channels! +- Browse video nodes by folder/path +- Fix IndexError for playqueues +- Update translations +- Code optimization diff --git a/changelog.txt b/changelog.txt index a9c38e21..72cbea82 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,10 @@ +version 1.7.8 (beta only) +- Fix IMDB id for movies (resync by going to the PKC settings, Advanced, then Repair Local Database) +- Increase timeouts for PMS, should fix some connection issues +- Move translations to new strings.po system +- Fix some TypeErrors +- Some code refactoring + version 1.7.7 - Chinese Traditional, thanks @old2tan - Chinese Simplified, thanks @everdream diff --git a/default.py b/default.py index 311ea999..9f30f61c 100644 --- a/default.py +++ b/default.py @@ -123,9 +123,7 @@ class Main(): elif mode in ('manualsync', 'repair'): if window('plex_online') != 'true': # Server is not online, do not run the sync - dialog('ok', - heading=lang(29999), - message=lang(39205)) + dialog('ok', lang(29999), lang(39205)) log.error('Not connected to a PMS.') else: if mode == 'repair': diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index 17f5900e..5bbcc7a3 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -1408,7 +1408,7 @@ msgid "Playback Mode" msgstr "" msgctxt "#39028" -msgid "CAUTION! If you choose \"Native\" mode , you might loose access to certain Plex features such as: Plex trailers and transcoding options. ALL Plex shares need to use direct paths (e.g. smb://myNAS/mymovie.mkv or \\myNAS/mymovie.mkv)!" +msgid "CAUTION! If you choose \"Native\" mode , you might loose access to certain Plex features such as: Plex trailers and transcoding options. ALL Plex shares need to use direct paths (e.g. smb://myNAS/mymovie.mkv or \\\\myNAS/mymovie.mkv)!" msgstr "" msgctxt "#39029" @@ -1428,15 +1428,15 @@ msgid "Please verify the path. You may need to verify your network credentials i msgstr "" msgctxt "#39033" -msgid "Transform Plex UNC library paths \\myNas\mymovie.mkv automatically to smb paths, smb://myNas/mymovie.mkv? (recommended)" +msgid "Transform Plex UNC library paths \\\\myNas\\mymovie.mkv automatically to smb paths, smb://myNas/mymovie.mkv? (recommended)" msgstr "" msgctxt "#39034" -msgid "Replace Plex UNC paths \\myNas with smb://myNas" +msgid "Replace Plex UNC paths \\\\myNas with smb://myNas" msgstr "" msgctxt "#39035" -msgid "Replace Plex paths /volume1/media or \\myserver\media with custom SMB paths smb://NAS/mystuff" +msgid "Replace Plex paths /volume1/media or \\\\myserver\\media with custom SMB paths smb://NAS/mystuff" msgstr "" msgctxt "#39037" diff --git a/resources/lib/PlexAPI.py b/resources/lib/PlexAPI.py index 9036b82f..c80e6992 100644 --- a/resources/lib/PlexAPI.py +++ b/resources/lib/PlexAPI.py @@ -63,10 +63,6 @@ REGEX_TVDB = re_compile(r'''thetvdb:\/\/(.+?)\?''') class PlexAPI(): - # CONSTANTS - # Timeout for POST/GET commands, I guess in seconds - timeout = 10 - def __init__(self): self.g_PMS = {} self.doUtils = DownloadUtils().downloadUrl @@ -259,7 +255,6 @@ class PlexAPI(): """ Checks connection to a Plex server, available at url. Can also be used to check for connection with plex.tv. - Will check up to 3x until reply with False Override SSL to skip the check by setting verifySSL=False if 'None', SSL will be checked (standard requests setting) @@ -288,14 +283,13 @@ class PlexAPI(): url = url + '/library/onDeck' log.debug("Checking connection to server %s with verifySSL=%s" % (url, verifySSL)) - # Check up to 3 times before giving up count = 0 while count < 1: answer = self.doUtils(url, authenticate=False, headerOptions=headerOptions, verifySSL=verifySSL, - timeout=4) + timeout=10) if answer is None: log.debug("Could not connect to %s" % url) count += 1 diff --git a/resources/lib/PlexFunctions.py b/resources/lib/PlexFunctions.py index 9c1284e2..82e54cad 100644 --- a/resources/lib/PlexFunctions.py +++ b/resources/lib/PlexFunctions.py @@ -377,7 +377,7 @@ def GetMachineIdentifier(url): xml = downloadutils.DownloadUtils().downloadUrl('%s/identity' % url, authenticate=False, verifySSL=False, - timeout=4) + timeout=10) try: machineIdentifier = xml.attrib['machineIdentifier'] except (AttributeError, KeyError): diff --git a/resources/lib/connect.py b/resources/lib/connect.py deleted file mode 100644 index aa3082a7..00000000 --- a/resources/lib/connect.py +++ /dev/null @@ -1,257 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################## - -import json -import requests -import logging - -import clientinfo -from utils import window - -################################################################################################## - -# Disable requests logging -from requests.packages.urllib3.exceptions import InsecureRequestWarning, InsecurePlatformWarning -requests.packages.urllib3.disable_warnings(InsecureRequestWarning) -requests.packages.urllib3.disable_warnings(InsecurePlatformWarning) - -log = logging.getLogger("EMBY."+__name__) - -################################################################################################## - - -class ConnectUtils(): - - # Borg - multiple instances, shared state - _shared_state = {} - clientInfo = clientinfo.ClientInfo() - - # Requests session - c = None - timeout = 30 - - - def __init__(self): - - self.__dict__ = self._shared_state - - - def setUserId(self, userId): - # Reserved for userclient only - self.userId = userId - log.debug("Set connect userId: %s" % userId) - - def setServer(self, server): - # Reserved for userclient only - self.server = server - log.debug("Set connect server: %s" % server) - - def setToken(self, token): - # Reserved for userclient only - self.token = token - log.debug("Set connect token: %s" % token) - - - def startSession(self): - - self.deviceId = self.clientInfo.getDeviceId() - - # User is identified from this point - # Attach authenticated header to the session - verify = False - header = self.getHeader() - - # If user enabled host certificate verification - try: - verify = self.sslverify - if self.sslclient is not None: - verify = self.sslclient - except: - log.info("Could not load SSL settings.") - - # Start session - self.c = requests.Session() - self.c.headers = header - self.c.verify = verify - # Retry connections to the server - self.c.mount("http://", requests.adapters.HTTPAdapter(max_retries=1)) - self.c.mount("https://", requests.adapters.HTTPAdapter(max_retries=1)) - - log.info("Requests session started on: %s" % self.server) - - def stopSession(self): - try: - self.c.close() - except Exception: - log.warn("Requests session could not be terminated") - - def getHeader(self, authenticate=True): - - version = self.clientInfo.getVersion() - - if not authenticate: - # If user is not authenticated - header = { - - 'X-Application': "Kodi/%s" % version, - 'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8', - 'Accept': "application/json" - } - log.info("Header: %s" % header) - - else: - token = self.token - # Attached to the requests session - header = { - - 'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8', - 'Accept': "application/json", - 'X-Application': "Kodi/%s" % version, - 'X-Connect-UserToken': token - } - log.info("Header: %s" % header) - - return header - - def doUrl(self, url, data=None, postBody=None, rtype="GET", - parameters=None, authenticate=True, timeout=None): - - log.debug("=== ENTER connectUrl ===") - - default_link = "" - - if timeout is None: - timeout = self.timeout - - # Get requests session - try: - # If connect user is authenticated - if authenticate: - try: - c = self.c - # Replace for the real values - url = url.replace("{server}", self.server) - url = url.replace("{UserId}", self.userId) - - # Prepare request - if rtype == "GET": - r = c.get(url, json=postBody, params=parameters, timeout=timeout) - elif rtype == "POST": - r = c.post(url, data=data, timeout=timeout) - elif rtype == "DELETE": - r = c.delete(url, json=postBody, timeout=timeout) - - except AttributeError: - # request session does not exists - self.server = "https://connect.emby.media/service" - self.userId = window('embyco_currUser') - self.token = window('embyco_accessToken%s' % self.userId) - - header = self.getHeader() - verifyssl = False - - # If user enables ssl verification - try: - verifyssl = self.sslverify - if self.sslclient is not None: - verifyssl = self.sslclient - except AttributeError: - pass - - # Prepare request - if rtype == "GET": - r = requests.get(url, - json=postBody, - params=parameters, - headers=header, - timeout=timeout, - verify=verifyssl) - - elif rtype == "POST": - r = requests.post(url, - data=data, - headers=header, - timeout=timeout, - verify=verifyssl) - # If user is not authenticated - else: - header = self.getHeader(authenticate=False) - verifyssl = False - - # If user enables ssl verification - try: - verifyssl = self.sslverify - if self.sslclient is not None: - verifyssl = self.sslclient - except AttributeError: - pass - - # Prepare request - if rtype == "GET": - r = requests.get(url, - json=postBody, - params=parameters, - headers=header, - timeout=timeout, - verify=verifyssl) - - elif rtype == "POST": - r = requests.post(url, - data=data, - headers=header, - timeout=timeout, - verify=verifyssl) - - ##### THE RESPONSE ##### - log.info(r.url) - log.info(r) - - if r.status_code == 204: - # No body in the response - log.info("====== 204 Success ======") - - elif r.status_code == requests.codes.ok: - - try: - # UNICODE - JSON object - r = r.json() - log.info("====== 200 Success ======") - log.info("Response: %s" % r) - return r - - except: - if r.headers.get('content-type') != "text/html": - log.info("Unable to convert the response for: %s" % url) - else: - r.raise_for_status() - - ##### EXCEPTIONS ##### - - except requests.exceptions.ConnectionError as e: - # Make the addon aware of status - pass - - except requests.exceptions.ConnectTimeout as e: - log.warn("Server timeout at: %s" % url) - - except requests.exceptions.HTTPError as e: - - if r.status_code == 401: - # Unauthorized - pass - - elif r.status_code in (301, 302): - # Redirects - pass - elif r.status_code == 400: - # Bad requests - pass - - except requests.exceptions.SSLError as e: - log.warn("Invalid SSL certificate for: %s" % url) - - except requests.exceptions.RequestException as e: - log.warn("Unknown error connecting to: %s" % url) - - return default_link \ No newline at end of file diff --git a/resources/lib/downloadutils.py b/resources/lib/downloadutils.py index b98619a2..660c68ce 100644 --- a/resources/lib/downloadutils.py +++ b/resources/lib/downloadutils.py @@ -34,11 +34,11 @@ class DownloadUtils(): connectionAttempts = 2 # How many 401 returns before declaring unauthorized? unauthorizedAttempts = 2 + # How long should we wait for an answer from the + timeout = 30.0 def __init__(self): self.__dict__ = self._shared_state - # Requests session - self.timeout = 30.0 def setUsername(self, username): """ diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 2f373308..4e943658 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -768,7 +768,7 @@ def channels(): xml = downloadutils.DownloadUtils().downloadUrl('{server}/channels/all') try: xml[0].attrib - except (ValueError, AttributeError, IndexError): + except (ValueError, AttributeError, IndexError, TypeError): log.error('Could not download Plex Channels') return xbmcplugin.endOfDirectory(HANDLE, False) diff --git a/resources/lib/image_cache_thread.py b/resources/lib/image_cache_thread.py deleted file mode 100644 index b01377a4..00000000 --- a/resources/lib/image_cache_thread.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################# - -import logging -import threading -import requests - -# Disable annoying requests warnings -import requests.packages.urllib3 -requests.packages.urllib3.disable_warnings() -################################################################################################# - -log = logging.getLogger("PLEX."+__name__) - -################################################################################################# - - -class ImageCacheThread(threading.Thread): - - url_to_process = None - is_finished = False - - xbmc_host = "" - xbmc_port = "" - xbmc_username = "" - xbmc_password = "" - - - def __init__(self): - - threading.Thread.__init__(self) - - - def set_url(self, url): - - self.url_to_process = url - - def set_host(self, host, port): - - self.xbmc_host = host - self.xbmc_port = port - - def set_auth(self, username, password): - - self.xbmc_username = username - self.xbmc_password = password - - def run(self): - try: - response = requests.head( - url=("http://%s:%s/image/image://%s" - % (self.xbmc_host, self.xbmc_port, self.url_to_process)), - auth=(self.xbmc_username, self.xbmc_password), - timeout=(5, 5)) - # We don't need the result - except Exception: - pass - self.is_finished = True diff --git a/resources/lib/initialsetup.py b/resources/lib/initialsetup.py index 4e9ad834..9890fb74 100644 --- a/resources/lib/initialsetup.py +++ b/resources/lib/initialsetup.py @@ -407,11 +407,9 @@ class InitialSetup(): # If a Plex server IP has already been set # return only if the right machine identifier is found - getNewIP = False if self.server: log.info("PMS is already set: %s. Checking now..." % self.server) - getNewIP = not self.CheckPMS() - if getNewIP is False: + if self.CheckPMS(): log.info("Using PMS %s with machineIdentifier %s" % (self.server, self.serverid)) self._write_PMS_settings(self.server, self.pms_token) diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 5fa46dc9..301649e3 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -341,7 +341,7 @@ class Movies(Items): WHERE idMovie = ? ''' kodicursor.execute(query, (title, plot, shortplot, tagline, - votecount, rating_id, writer, year, imdb, sorttitle, + votecount, rating_id, writer, year, uniqueid, sorttitle, runtime, mpaa, genre, director, title, studio, trailer, country, playurl, pathid, fileid, year, userdata['UserRating'], movieid)) @@ -372,7 +372,8 @@ class Movies(Items): rating, votecount) # add new uniqueid Kodi 17 - self.kodi_db.add_uniqueid(self.kodi_db.create_entry_uniqueid(), + uniqueid = self.kodi_db.create_entry_uniqueid() + self.kodi_db.add_uniqueid(uniqueid, movieid, v.KODI_TYPE_MOVIE, imdb, @@ -386,8 +387,8 @@ class Movies(Items): ''' kodicursor.execute(query, (movieid, fileid, title, plot, shortplot, tagline, votecount, rating_id, writer, year, - imdb, sorttitle, runtime, mpaa, genre, director, title, - studio, trailer, country, playurl, pathid, year, + uniqueid, sorttitle, runtime, mpaa, genre, director, + title, studio, trailer, country, playurl, pathid, year, userdata['UserRating'])) else: query = '''