diff --git a/contextmenu.py b/contextmenu.py
index d462807a..bc640ef0 100644
--- a/contextmenu.py
+++ b/contextmenu.py
@@ -147,7 +147,7 @@ if __name__ == '__main__':
doUtils = downloadutils.DownloadUtils()
url = "{server}/emby/Items/%s?format=json" % embyid
logMsg("Deleting request: %s" % embyid, 0)
- doUtils.downloadUrl(url, type="DELETE")
+ doUtils.downloadUrl(url, action_type="DELETE")
'''if utils.settings('skipContextMenu') != "true":
if xbmcgui.Dialog().yesno(
@@ -156,8 +156,7 @@ if __name__ == '__main__':
"also delete the file(s) from disk!")):
import downloadutils
doUtils = downloadutils.DownloadUtils()
- url = "{server}/emby/Items/%s?format=json" % embyid
- doUtils.downloadUrl(url, type="DELETE")'''
+ doUtils.downloadUrl("{server}/emby/Items/%s?format=json" % embyid, action_type="DELETE")'''
xbmc.sleep(500)
xbmc.executebuiltin("Container.Update")
\ No newline at end of file
diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml
index 003324f8..348a9994 100644
--- a/resources/language/English/strings.xml
+++ b/resources/language/English/strings.xml
@@ -328,7 +328,7 @@
Gathering tv shows from:
Gathering:
Detected the database needs to be recreated for this version of Emby for Kodi. Proceed?
- Emby for Kod may not work correctly until the database is reset.
+ Emby for Kodi may not work correctly until the database is reset.
Cancelling the database syncing process. The current Kodi version is unsupported.
completed in:
Comparing movies from:
diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py
index 10113064..7cff323c 100644
--- a/resources/lib/artwork.py
+++ b/resources/lib/artwork.py
@@ -28,7 +28,7 @@ class Artwork():
xbmc_port = None
xbmc_username = None
xbmc_password = None
-
+
imageCacheThreads = []
imageCacheLimitThreads = 0
@@ -36,9 +36,9 @@ class Artwork():
self.enableTextureCache = utils.settings('enableTextureCache') == "true"
self.imageCacheLimitThreads = int(utils.settings("imageCacheLimit"))
- self.imageCacheLimitThreads = int(self.imageCacheLimitThreads * 5);
+ self.imageCacheLimitThreads = int(self.imageCacheLimitThreads * 5)
utils.logMsg("Using Image Cache Thread Count: " + str(self.imageCacheLimitThreads), 1)
-
+
if not self.xbmc_port and self.enableTextureCache:
self.setKodiWebServerDetails()
@@ -48,11 +48,11 @@ class Artwork():
def double_urlencode(self, text):
text = self.single_urlencode(text)
text = self.single_urlencode(text)
-
+
return text
def single_urlencode(self, text):
-
+
text = urllib.urlencode({'blahblahblah':text.encode("utf-8")}) #urlencode needs a utf- string
text = text[13:]
@@ -74,9 +74,9 @@ class Artwork():
result = json.loads(result)
try:
xbmc_webserver_enabled = result['result']['value']
- except KeyError, TypeError:
+ except (KeyError, TypeError):
xbmc_webserver_enabled = False
-
+
if not xbmc_webserver_enabled:
# Enable the webserver, it is disabled
web_port = {
@@ -159,7 +159,7 @@ class Artwork():
self.xbmc_password = result['result']['value']
except TypeError:
pass
-
+
def FullTextureCacheSync(self):
# This method will sync all Kodi artwork to textures13.db
# and cache them locally. This takes diskspace!
@@ -169,12 +169,12 @@ class Artwork():
if not xbmcgui.Dialog().yesno(
"Image Texture Cache", string(39250).encode('utf-8')):
return
-
+
self.logMsg("Doing Image Cache Sync", 1)
-
+
dialog = xbmcgui.DialogProgress()
dialog.create("Emby for Kodi", "Image Cache Sync")
-
+
# ask to rest all existing or not
if xbmcgui.Dialog().yesno(
"Image Texture Cache", string(39251).encode('utf-8'), ""):
@@ -190,7 +190,7 @@ class Artwork():
xbmcvfs.delete(os.path.join(path+dir.decode('utf-8'),file.decode('utf-8')))
else:
xbmcvfs.delete(os.path.join(path.encode('utf-8')+dir,file))
-
+
# remove all existing data from texture DB
textureconnection = utils.kodiSQL('texture')
texturecursor = textureconnection.cursor()
@@ -209,8 +209,8 @@ class Artwork():
cursor.execute("SELECT url FROM art WHERE media_type != 'actor'") # dont include actors
result = cursor.fetchall()
total = len(result)
- count = 1
- percentage = 0
+ count = 1
+ percentage = 0
self.logMsg("Image cache sync about to process " + str(total) + " images", 1)
for url in result:
if dialog.iscanceled():
@@ -221,26 +221,26 @@ class Artwork():
self.CacheTexture(url[0])
count += 1
cursor.close()
-
+
# Cache all entries in music DB
connection = utils.kodiSQL('music')
cursor = connection.cursor()
cursor.execute("SELECT url FROM art")
result = cursor.fetchall()
total = len(result)
- count = 1
+ count = 1
percentage = 0
self.logMsg("Image cache sync about to process " + str(total) + " images", 1)
for url in result:
if dialog.iscanceled():
- break
+ break
percentage = int((float(count) / float(total))*100)
textMessage = str(count) + " of " + str(total)
dialog.update(percentage, "Updating Image Cache: " + textMessage)
self.CacheTexture(url[0])
count += 1
cursor.close()
-
+
dialog.update(100, "Waiting for all threads to exit: " + str(len(self.imageCacheThreads)))
self.logMsg("Waiting for all threads to exit", 1)
while len(self.imageCacheThreads) > 0:
@@ -250,16 +250,16 @@ class Artwork():
dialog.update(100, "Waiting for all threads to exit: " + str(len(self.imageCacheThreads)))
self.logMsg("Waiting for all threads to exit: " + str(len(self.imageCacheThreads)), 1)
xbmc.sleep(500)
-
+
dialog.close()
def addWorkerImageCacheThread(self, urlToAdd):
-
+
while(True):
# removed finished
for thread in self.imageCacheThreads:
if thread.isFinished:
- self.imageCacheThreads.remove(thread)
+ self.imageCacheThreads.remove(thread)
# add a new thread or wait and retry if we hit our limit
if(len(self.imageCacheThreads) < self.imageCacheLimitThreads):
@@ -273,14 +273,14 @@ class Artwork():
else:
self.logMsg("Waiting for empty queue spot: " + str(len(self.imageCacheThreads)), 2)
xbmc.sleep(50)
-
-
+
+
def CacheTexture(self, url):
# Cache a single image url to the texture cache
if url and self.enableTextureCache:
if(self.imageCacheLimitThreads == 0 or self.imageCacheLimitThreads == None):
#Add image to texture cache by simply calling it at the http endpoint
-
+
url = self.double_urlencode(url)
try: # Extreme short timeouts so we will have a exception.
response = requests.head(
@@ -291,7 +291,7 @@ class Artwork():
timeout=(0.01, 0.01))
# We don't need the result
except: pass
-
+
else:
self.addWorkerImageCacheThread(url)
@@ -349,13 +349,13 @@ class Artwork():
mediaType=mediaType,
imageType="%s%s" % ("fanart", index),
cursor=cursor)
-
+
if backdropsNumber > 1:
try: # Will only fail on the first try, str to int.
index += 1
except TypeError:
index = 1
-
+
elif art == "Primary":
# Primary art is processed as thumb and poster for Kodi.
for artType in kodiart[art]:
@@ -365,7 +365,7 @@ class Artwork():
mediaType=mediaType,
imageType=artType,
cursor=cursor)
-
+
elif kodiart.get(art):
# Process the rest artwork type that Kodi can use
self.addOrUpdateArt(
@@ -391,9 +391,11 @@ class Artwork():
cursor.execute(query, (kodiId, mediaType, imageType,))
try: # Update the artwork
url = cursor.fetchone()[0]
-
+
except TypeError: # Add the artwork
cacheimage = True
+ self.logMsg("Adding Art Link for kodiId: %s (%s)" % (kodiId, imageUrl), 2)
+
query = (
'''
INSERT INTO art(media_id, media_type, type, url)
@@ -402,17 +404,21 @@ class Artwork():
'''
)
cursor.execute(query, (kodiId, mediaType, imageType, imageUrl))
-
+
else: # Only cache artwork if it changed
if url != imageUrl:
cacheimage = True
-
+
# Only for the main backdrop, poster
if (utils.window('emby_initialScan') != "true" and
imageType in ("fanart", "poster")):
# Delete current entry before updating with the new one
self.deleteCachedArtwork(url)
+ self.logMsg(
+ "Updating Art url for %s kodiId: %s (%s) -> (%s)"
+ % (imageType, kodiId, url, imageUrl), 1)
+
query = ' '.join((
"UPDATE art",
@@ -422,7 +428,7 @@ class Artwork():
"AND type = ?"
))
cursor.execute(query, (imageUrl, kodiId, mediaType, imageType))
-
+
# Cache fanart and poster in Kodi texture cache
if cacheimage and imageType in ("fanart", "poster", "thumb"):
self.CacheTexture(imageUrl)
@@ -453,24 +459,24 @@ class Artwork():
try:
cursor.execute("SELECT cachedurl FROM texture WHERE url = ?", (url,))
cachedurl = cursor.fetchone()[0]
-
+
except TypeError:
self.logMsg("Could not find cached url.", 1)
except OperationalError:
self.logMsg("Database is locked. Skip deletion process.", 1)
-
+
else: # Delete thumbnail as well as the entry
thumbnails = xbmc.translatePath("special://thumbnails/%s" % cachedurl).decode('utf-8')
self.logMsg("Deleting cached thumbnail: %s" % thumbnails, 1)
xbmcvfs.delete(thumbnails)
-
+
try:
cursor.execute("DELETE FROM texture WHERE url = ?", (url,))
connection.commit()
except OperationalError:
self.logMsg("Issue deleting url from cache. Skipping.", 2)
-
+
finally:
cursor.close()
@@ -487,7 +493,7 @@ class Artwork():
"%s/emby/Items/%s/Images/Primary?"
"MaxWidth=400&MaxHeight=400&Index=0&Tag=%s"
% (self.server, personId, tag))
-
+
person['imageurl'] = image
return people
@@ -501,8 +507,6 @@ class Artwork():
def getAllArtwork(self, item, parentInfo=False):
- server = self.server
-
itemid = item['Id']
artworks = item['ImageTags']
backdrops = item.get('BackdropImageTags',[])
@@ -527,13 +531,13 @@ def getAllArtwork(self, item, parentInfo=False):
'Disc': "",
'Backdrop': []
}
-
+
# Process backdrops
for index, tag in enumerate(backdrops):
artwork = (
"%s/emby/Items/%s/Images/Backdrop/%s?"
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
- % (server, itemid, index, maxWidth, maxHeight, tag, customquery))
+ % (self.server, itemid, index, maxWidth, maxHeight, tag, customquery))
allartworks['Backdrop'].append(artwork)
# Process the rest of the artwork
@@ -544,15 +548,15 @@ def getAllArtwork(self, item, parentInfo=False):
artwork = (
"%s/emby/Items/%s/Images/%s/0?"
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
- % (server, itemid, art, maxWidth, maxHeight, tag, customquery))
+ % (self.server, itemid, art, maxWidth, maxHeight, tag, customquery))
allartworks[art] = artwork
# Process parent items if the main item is missing artwork
if parentInfo:
-
+
# Process parent backdrops
if not allartworks['Backdrop']:
-
+
parentId = item.get('ParentBackdropItemId')
if parentId:
# If there is a parentId, go through the parent backdrop list
@@ -562,7 +566,7 @@ def getAllArtwork(self, item, parentInfo=False):
artwork = (
"%s/emby/Items/%s/Images/Backdrop/%s?"
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
- % (server, parentId, index, maxWidth, maxHeight, tag, customquery))
+ % (self.server, parentId, index, maxWidth, maxHeight, tag, customquery))
allartworks['Backdrop'].append(artwork)
# Process the rest of the artwork
@@ -570,15 +574,15 @@ def getAllArtwork(self, item, parentInfo=False):
for parentart in parentartwork:
if not allartworks[parentart]:
-
+
parentId = item.get('Parent%sItemId' % parentart)
if parentId:
-
+
parentTag = item['Parent%sImageTag' % parentart]
artwork = (
"%s/emby/Items/%s/Images/%s/0?"
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
- % (server, parentId, parentart,
+ % (self.server, parentId, parentart,
maxWidth, maxHeight, parentTag, customquery))
allartworks[parentart] = artwork
@@ -587,12 +591,12 @@ def getAllArtwork(self, item, parentInfo=False):
parentId = item.get('AlbumId')
if parentId and item.get('AlbumPrimaryImageTag'):
-
+
parentTag = item['AlbumPrimaryImageTag']
artwork = (
"%s/emby/Items/%s/Images/Primary/0?"
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
- % (server, parentId, maxWidth, maxHeight, parentTag, customquery))
+ % (self.server, parentId, maxWidth, maxHeight, parentTag, customquery))
allartworks['Primary'] = artwork
- return allartworks
\ No newline at end of file
+ return allartworks
diff --git a/resources/lib/connect.py b/resources/lib/connect.py
index 05b563a3..2bd5c05d 100644
--- a/resources/lib/connect.py
+++ b/resources/lib/connect.py
@@ -21,7 +21,7 @@ requests.packages.urllib3.disable_warnings(InsecurePlatformWarning)
class ConnectUtils():
-
+
# Borg - multiple instances, shared state
_shared_state = {}
clientInfo = clientinfo.ClientInfo()
@@ -60,8 +60,6 @@ class ConnectUtils():
def startSession(self):
- log = self.logMsg
-
self.deviceId = self.clientInfo.getDeviceId()
# User is identified from this point
@@ -75,8 +73,8 @@ class ConnectUtils():
if self.sslclient is not None:
verify = self.sslclient
except:
- log("Could not load SSL settings.", 1)
-
+ self.logMsg("Could not load SSL settings.", 1)
+
# Start session
self.c = requests.Session()
self.c.headers = header
@@ -85,7 +83,7 @@ class ConnectUtils():
self.c.mount("http://", requests.adapters.HTTPAdapter(max_retries=1))
self.c.mount("https://", requests.adapters.HTTPAdapter(max_retries=1))
- log("Requests session started on: %s" % self.server, 1)
+ self.logMsg("Requests session started on: %s" % self.server, 1)
def stopSession(self):
try:
@@ -95,8 +93,7 @@ class ConnectUtils():
def getHeader(self, authenticate=True):
- clientInfo = self.clientInfo
- version = clientInfo.getVersion()
+ version = self.clientInfo.getVersion()
if not authenticate:
# If user is not authenticated
@@ -105,9 +102,9 @@ class ConnectUtils():
'X-Application': "Kodi/%s" % version,
'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Accept': "application/json"
- }
+ }
self.logMsg("Header: %s" % header, 1)
-
+
else:
token = self.token
# Attached to the requests session
@@ -117,18 +114,17 @@ class ConnectUtils():
'Accept': "application/json",
'X-Application': "Kodi/%s" % version,
'X-Connect-UserToken': token
- }
+ }
self.logMsg("Header: %s" % header, 1)
-
+
return header
def doUrl(self, url, data=None, postBody=None, rtype="GET",
parameters=None, authenticate=True, timeout=None):
- log = self.logMsg
window = utils.window
- log("=== ENTER connectUrl ===", 2)
+ self.logMsg("=== ENTER connectUrl ===", 2)
default_link = ""
if timeout is None:
timeout = self.timeout
@@ -137,7 +133,7 @@ class ConnectUtils():
try:
# If connect user is authenticated
if authenticate:
- try:
+ try:
c = self.c
# Replace for the real values
url = url.replace("{server}", self.server)
@@ -167,7 +163,7 @@ class ConnectUtils():
verifyssl = self.sslclient
except AttributeError:
pass
-
+
# Prepare request
if rtype == "GET":
r = requests.get(url,
@@ -195,7 +191,7 @@ class ConnectUtils():
verifyssl = self.sslclient
except AttributeError:
pass
-
+
# Prepare request
if rtype == "GET":
r = requests.get(url,
@@ -213,28 +209,28 @@ class ConnectUtils():
verify=verifyssl)
##### THE RESPONSE #####
- log(r.url, 1)
- log(r, 1)
+ self.logMsg(r.url, 1)
+ self.logMsg(r, 1)
if r.status_code == 204:
# No body in the response
- log("====== 204 Success ======", 1)
+ self.logMsg("====== 204 Success ======", 1)
elif r.status_code == requests.codes.ok:
-
- try:
+
+ try:
# UNICODE - JSON object
r = r.json()
- log("====== 200 Success ======", 1)
- log("Response: %s" % r, 1)
+ self.logMsg("====== 200 Success ======", 1)
+ self.logMsg("Response: %s" % r, 1)
return r
except:
if r.headers.get('content-type') != "text/html":
- log("Unable to convert the response for: %s" % url, 1)
+ self.logMsg("Unable to convert the response for: %s" % url, 1)
else:
r.raise_for_status()
-
+
##### EXCEPTIONS #####
except requests.exceptions.ConnectionError as e:
@@ -242,8 +238,8 @@ class ConnectUtils():
pass
except requests.exceptions.ConnectTimeout as e:
- log("Server timeout at: %s" % url, 0)
- log(e, 1)
+ self.logMsg("Server timeout at: %s" % url, 0)
+ self.logMsg(e, 1)
except requests.exceptions.HTTPError as e:
@@ -259,11 +255,11 @@ class ConnectUtils():
pass
except requests.exceptions.SSLError as e:
- log("Invalid SSL certificate for: %s" % url, 0)
- log(e, 1)
+ self.logMsg("Invalid SSL certificate for: %s" % url, 0)
+ self.logMsg(e, 1)
except requests.exceptions.RequestException as e:
- log("Unknown error connecting to: %s" % url, 0)
- log(e, 1)
+ self.logMsg("Unknown error connecting to: %s" % url, 0)
+ self.logMsg(e, 1)
return default_link
diff --git a/resources/lib/embydb_functions.py b/resources/lib/embydb_functions.py
index d6485fe3..b73030df 100644
--- a/resources/lib/embydb_functions.py
+++ b/resources/lib/embydb_functions.py
@@ -34,7 +34,6 @@ class Embydb_Functions():
def getViews(self):
- embycursor = self.embycursor
views = []
query = ' '.join((
@@ -42,8 +41,8 @@ class Embydb_Functions():
"SELECT view_id",
"FROM view"
))
- embycursor.execute(query)
- rows = embycursor.fetchall()
+ self.embycursor.execute(query)
+ rows = self.embycursor.fetchall()
for row in rows:
views.append(row[0])
return views
@@ -68,7 +67,6 @@ class Embydb_Functions():
def getView_byId(self, viewid):
- embycursor = self.embycursor
query = ' '.join((
@@ -76,13 +74,13 @@ class Embydb_Functions():
"FROM view",
"WHERE view_id = ?"
))
- embycursor.execute(query, (viewid,))
- view = embycursor.fetchone()
+ self.embycursor.execute(query, (viewid,))
+ view = self.embycursor.fetchone()
+
return view
def getView_byType(self, mediatype):
- embycursor = self.embycursor
views = []
query = ' '.join((
@@ -91,8 +89,8 @@ class Embydb_Functions():
"FROM view",
"WHERE media_type = ?"
))
- embycursor.execute(query, (mediatype,))
- rows = embycursor.fetchall()
+ self.embycursor.execute(query, (mediatype,))
+ rows = self.embycursor.fetchall()
for row in rows:
views.append({
@@ -105,17 +103,16 @@ class Embydb_Functions():
def getView_byName(self, tagname):
- embycursor = self.embycursor
-
query = ' '.join((
"SELECT view_id",
"FROM view",
"WHERE view_name = ?"
))
- embycursor.execute(query, (tagname,))
+ self.embycursor.execute(query, (tagname,))
try:
- view = embycursor.fetchone()[0]
+ view = self.embycursor.fetchone()[0]
+
except TypeError:
view = None
@@ -190,8 +187,6 @@ class Embydb_Functions():
def getItem_byId(self, embyid):
- embycursor = self.embycursor
-
query = ' '.join((
"SELECT kodi_id, kodi_fileid, kodi_pathid, parent_id, media_type, emby_type",
@@ -199,40 +194,32 @@ class Embydb_Functions():
"WHERE emby_id = ?"
))
try:
- embycursor.execute(query, (embyid,))
- item = embycursor.fetchone()
+ self.embycursor.execute(query, (embyid,))
+ item = self.embycursor.fetchone()
return item
except: return None
def getItem_byWildId(self, embyid):
- embycursor = self.embycursor
-
query = ' '.join((
"SELECT kodi_id, media_type",
"FROM emby",
"WHERE emby_id LIKE ?"
))
- embycursor.execute(query, (embyid+"%",))
- items = embycursor.fetchall()
-
- return items
+ self.embycursor.execute(query, (embyid+"%",))
+ return self.embycursor.fetchall()
def getItem_byView(self, mediafolderid):
- embycursor = self.embycursor
-
query = ' '.join((
"SELECT kodi_id",
"FROM emby",
"WHERE media_folder = ?"
))
- embycursor.execute(query, (mediafolderid,))
- items = embycursor.fetchall()
-
- return items
+ self.embycursor.execute(query, (mediafolderid,))
+ return self.embycursor.fetchall()
def getPlexId(self, kodiid, mediatype):
"""
@@ -253,8 +240,6 @@ class Embydb_Functions():
def getItem_byKodiId(self, kodiid, mediatype):
- embycursor = self.embycursor
-
query = ' '.join((
"SELECT emby_id, parent_id",
@@ -262,15 +247,11 @@ class Embydb_Functions():
"WHERE kodi_id = ?",
"AND media_type = ?"
))
- embycursor.execute(query, (kodiid, mediatype,))
- item = embycursor.fetchone()
-
- return item
+ self.embycursor.execute(query, (kodiid, mediatype,))
+ return self.embycursor.fetchone()
def getItem_byParentId(self, parentid, mediatype):
- embycursor = self.embycursor
-
query = ' '.join((
"SELECT emby_id, kodi_id, kodi_fileid",
@@ -278,15 +259,11 @@ class Embydb_Functions():
"WHERE parent_id = ?",
"AND media_type = ?"
))
- embycursor.execute(query, (parentid, mediatype,))
- items = embycursor.fetchall()
-
- return items
+ self.embycursor.execute(query, (parentid, mediatype,))
+ return self.embycursor.fetchall()
def getItemId_byParentId(self, parentid, mediatype):
- embycursor = self.embycursor
-
query = ' '.join((
"SELECT emby_id, kodi_id",
@@ -294,39 +271,32 @@ class Embydb_Functions():
"WHERE parent_id = ?",
"AND media_type = ?"
))
- embycursor.execute(query, (parentid, mediatype,))
- items = embycursor.fetchall()
-
- return items
+ self.embycursor.execute(query, (parentid, mediatype,))
+ return self.embycursor.fetchall()
def getChecksum(self, mediatype):
- embycursor = self.embycursor
-
query = ' '.join((
"SELECT emby_id, checksum",
"FROM emby",
"WHERE emby_type = ?"
))
- embycursor.execute(query, (mediatype,))
- items = embycursor.fetchall()
-
- return items
+ self.embycursor.execute(query, (mediatype,))
+ return self.embycursor.fetchall()
def getMediaType_byId(self, embyid):
- embycursor = self.embycursor
-
query = ' '.join((
"SELECT emby_type",
"FROM emby",
"WHERE emby_id = ?"
))
- embycursor.execute(query, (embyid,))
+ self.embycursor.execute(query, (embyid,))
try:
- itemtype = embycursor.fetchone()[0]
+ itemtype = self.embycursor.fetchone()[0]
+
except TypeError:
itemtype = None
diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py
index 2e5bd59e..8e42492b 100644
--- a/resources/lib/entrypoint.py
+++ b/resources/lib/entrypoint.py
@@ -328,12 +328,12 @@ def doMainListing():
if not path:
path = utils.window('Emby.nodes.%s.content' % i)
label = utils.window('Emby.nodes.%s.title' % i)
- type = utils.window('Emby.nodes.%s.type' % i)
+ node_type = utils.window('Emby.nodes.%s.type' % i)
#because we do not use seperate entrypoints for each content type, we need to figure out which items to show in each listing.
#for now we just only show picture nodes in the picture library video nodes in the video library and all nodes in any other window
- if path and xbmc.getCondVisibility("Window.IsActive(Pictures)") and type == "photos":
+ if path and xbmc.getCondVisibility("Window.IsActive(Pictures)") and node_type == "photos":
addDirectoryItem(label, path)
- elif path and xbmc.getCondVisibility("Window.IsActive(VideoLibrary)") and type != "photos":
+ elif path and xbmc.getCondVisibility("Window.IsActive(VideoLibrary)") and node_type != "photos":
addDirectoryItem(label, path)
elif path and not xbmc.getCondVisibility("Window.IsActive(VideoLibrary) | Window.IsActive(Pictures) | Window.IsActive(MusicLibrary)"):
addDirectoryItem(label, path)
@@ -431,7 +431,7 @@ def deleteItem():
doUtils = downloadutils.DownloadUtils()
url = "{server}/emby/Items/%s?format=json" % embyid
utils.logMsg("EMBY delete", "Deleting request: %s" % embyid, 0)
- doUtils.downloadUrl(url, type="DELETE")
+ doUtils.downloadUrl(url, action_type="DELETE")
##### ADD ADDITIONAL USERS #####
def addUser():
@@ -486,7 +486,7 @@ def addUser():
selected = additionalUsername[resp]
selected_userId = additionalUserlist[selected]
url = "{server}/emby/Sessions/%s/Users/%s" % (sessionId, selected_userId)
- doUtils.downloadUrl(url, postBody={}, type="DELETE")
+ doUtils.downloadUrl(url, postBody={}, action_type="DELETE")
dialog.notification(
heading="Success!",
message="%s removed from viewing session" % selected,
@@ -519,7 +519,7 @@ def addUser():
selected = users[resp]
selected_userId = userlist[selected]
url = "{server}/emby/Sessions/%s/Users/%s" % (sessionId, selected_userId)
- doUtils.downloadUrl(url, postBody={}, type="POST")
+ doUtils.downloadUrl(url, postBody={}, action_type="POST")
dialog.notification(
heading="Success!",
message="%s added to viewing session" % selected,
@@ -759,22 +759,22 @@ def GetSubFolders(nodeindex):
title = utils.window('Emby.nodes.%s%s.title' %(nodeindex,node))
if title:
path = utils.window('Emby.nodes.%s%s.content' %(nodeindex,node))
- type = utils.window('Emby.nodes.%s%s.type' %(nodeindex,node))
addDirectoryItem(title, path)
xbmcplugin.endOfDirectory(int(sys.argv[1]))
##### BROWSE EMBY NODES DIRECTLY #####
-def BrowseContent(viewname, type="", folderid=""):
+def BrowseContent(viewname, browse_type="", folderid=""):
+
emby = embyserver.Read_EmbyServer()
art = artwork.Artwork()
doUtils = downloadutils.DownloadUtils()
#folderid used as filter ?
if folderid in ["recent","recentepisodes","inprogress","inprogressepisodes","unwatched","nextepisodes","sets","genres","random","recommended"]:
- filter = folderid
+ filter_type = folderid
folderid = ""
else:
- filter = ""
+ filter_type = ""
xbmcplugin.setPluginCategory(int(sys.argv[1]), viewname)
#get views for root level
@@ -783,33 +783,35 @@ def BrowseContent(viewname, type="", folderid=""):
for view in views:
if view.get("name") == viewname.decode('utf-8'):
folderid = view.get("id")
+ break
- utils.logMsg("BrowseContent","viewname: %s - type: %s - folderid: %s - filter: %s" %(viewname.decode('utf-8'), type.decode('utf-8'), folderid.decode('utf-8'), filter.decode('utf-8')))
+ if viewname is not None:
+ utils.logMsg("BrowseContent","viewname: %s - type: %s - folderid: %s - filter: %s" %(viewname.decode('utf-8'), browse_type.decode('utf-8'), folderid.decode('utf-8'), filter_type.decode('utf-8')))
#set the correct params for the content type
#only proceed if we have a folderid
if folderid:
- if type.lower() == "homevideos":
+ if browse_type.lower() == "homevideos":
xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
itemtype = "Video,Folder,PhotoAlbum"
- elif type.lower() == "photos":
+ elif browse_type.lower() == "photos":
xbmcplugin.setContent(int(sys.argv[1]), 'files')
itemtype = "Photo,PhotoAlbum,Folder"
else:
itemtype = ""
#get the actual listing
- if type == "recordings":
+ if browse_type == "recordings":
listing = emby.getTvRecordings(folderid)
- elif type == "tvchannels":
+ elif browse_type == "tvchannels":
listing = emby.getTvChannels()
- elif filter == "recent":
+ elif filter_type == "recent":
listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[0], sortby="DateCreated", recursive=True, limit=25, sortorder="Descending")
- elif filter == "random":
+ elif filter_type == "random":
listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[0], sortby="Random", recursive=True, limit=150, sortorder="Descending")
- elif filter == "recommended":
- listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[0], sortby="SortName", recursive=True, limit=25, sortorder="Ascending", filter="IsFavorite")
- elif filter == "sets":
- listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[1], sortby="SortName", recursive=True, limit=25, sortorder="Ascending", filter="IsFavorite")
+ elif filter_type == "recommended":
+ listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[0], sortby="SortName", recursive=True, limit=25, sortorder="Ascending", filter_type="IsFavorite")
+ elif filter_type == "sets":
+ listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[1], sortby="SortName", recursive=True, limit=25, sortorder="Ascending", filter_type="IsFavorite")
else:
listing = emby.getFilteredSection(folderid, itemtype=itemtype, recursive=False)
@@ -819,14 +821,14 @@ def BrowseContent(viewname, type="", folderid=""):
li = createListItemFromEmbyItem(item,art,doUtils)
if item.get("IsFolder") == True:
#for folders we add an additional browse request, passing the folderId
- path = "%s?id=%s&mode=browsecontent&type=%s&folderid=%s" % (sys.argv[0].decode('utf-8'), viewname.decode('utf-8'), type.decode('utf-8'), item.get("Id").decode('utf-8'))
+ path = "%s?id=%s&mode=browsecontent&type=%s&folderid=%s" % (sys.argv[0].decode('utf-8'), viewname.decode('utf-8'), browse_type.decode('utf-8'), item.get("Id").decode('utf-8'))
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=path, listitem=li, isFolder=True)
else:
#playable item, set plugin path and mediastreams
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=li.getProperty("path"), listitem=li)
- if filter == "recent":
+ if filter_type == "recent":
xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_DATE)
else:
xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_TITLE)
diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py
index 8683f106..0f0bbc2f 100644
--- a/resources/lib/itemtypes.py
+++ b/resources/lib/itemtypes.py
@@ -309,10 +309,9 @@ class Movies(Items):
count = 0
for boxset in items:
- title = boxset['Name']
if pdialog:
percentage = int((float(count) / float(total))*100)
- pdialog.update(percentage, message=title)
+ pdialog.update(percentage, message=boxset['Name'])
count += 1
self.add_updateBoxset(boxset)
@@ -333,7 +332,6 @@ class Movies(Items):
# Process single movie
kodicursor = self.kodicursor
emby_db = self.emby_db
- kodi_db = self.kodi_db
artwork = self.artwork
API = PlexAPI.API(item)
@@ -517,23 +515,23 @@ class Movies(Items):
kodi_db.addCountries(movieid, countries, "movie")
# Process cast
people = API.getPeopleList()
- kodi_db.addPeople(movieid, people, "movie")
+ self.kodi_db.addPeople(movieid, people, "movie")
# Process genres
- kodi_db.addGenres(movieid, genres, "movie")
+ self.kodi_db.addGenres(movieid, genres, "movie")
# Process artwork
allartworks = API.getAllArtwork()
artwork.addArtwork(allartworks, movieid, "movie", kodicursor)
# Process stream details
streams = API.getMediaStreams()
- kodi_db.addStreams(fileid, streams, runtime)
+ self.kodi_db.addStreams(fileid, streams, runtime)
# Process studios
- kodi_db.addStudios(movieid, studios, "movie")
+ self.kodi_db.addStudios(movieid, studios, "movie")
# Process tags: view, Plex collection tags
tags = [viewtag]
tags.extend(collections)
if userdata['Favorite']:
tags.append("Favorite movies")
- kodi_db.addTags(movieid, tags, "movie")
+ self.kodi_db.addTags(movieid, tags, "movie")
# Process playstates
kodi_db.addPlaystate(fileid, resume, runtime, playcount, dateplayed)
@@ -603,7 +601,6 @@ class MusicVideos(Items):
# Process single music video
kodicursor = self.kodicursor
emby_db = self.emby_db
- kodi_db = self.kodi_db
artwork = self.artwork
API = api.API(item)
@@ -794,32 +791,31 @@ class MusicVideos(Items):
artist['Type'] = "Artist"
people.extend(artists)
people = artwork.getPeopleArtwork(people)
- kodi_db.addPeople(mvideoid, people, "musicvideo")
+ self.kodi_db.addPeople(mvideoid, people, "musicvideo")
# Process genres
- kodi_db.addGenres(mvideoid, genres, "musicvideo")
+ self.kodi_db.addGenres(mvideoid, genres, "musicvideo")
# Process artwork
artwork.addArtwork(artwork.getAllArtwork(item), mvideoid, "musicvideo", kodicursor)
# Process stream details
streams = API.getMediaStreams()
- kodi_db.addStreams(fileid, streams, runtime)
+ self.kodi_db.addStreams(fileid, streams, runtime)
# Process studios
- kodi_db.addStudios(mvideoid, studios, "musicvideo")
+ self.kodi_db.addStudios(mvideoid, studios, "musicvideo")
# Process tags: view, emby tags
tags = [viewtag]
tags.extend(item['Tags'])
if userdata['Favorite']:
tags.append("Favorite musicvideos")
- kodi_db.addTags(mvideoid, tags, "musicvideo")
+ self.kodi_db.addTags(mvideoid, tags, "musicvideo")
# Process playstates
resume = API.adjustResume(userdata['Resume'])
total = round(float(runtime), 6)
- kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed)
+ self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed)
def updateUserdata(self, item):
# This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks
# Poster with progress bar
emby_db = self.emby_db
- kodi_db = self.kodi_db
API = api.API(item)
# Get emby information
@@ -841,9 +837,9 @@ class MusicVideos(Items):
# Process favorite tags
if userdata['Favorite']:
- kodi_db.addTag(mvideoid, "Favorite musicvideos", "musicvideo")
+ self.kodi_db.addTag(mvideoid, "Favorite musicvideos", "musicvideo")
else:
- kodi_db.removeTag(mvideoid, "Favorite musicvideos", "musicvideo")
+ self.kodi_db.removeTag(mvideoid, "Favorite musicvideos", "musicvideo")
# Process playstates
playcount = userdata['PlayCount']
@@ -851,7 +847,7 @@ class MusicVideos(Items):
resume = API.adjustResume(userdata['Resume'])
total = round(float(runtime), 6)
- kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed)
+ self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed)
emby_db.updateReference(itemid, checksum)
def remove(self, itemid):
@@ -878,8 +874,7 @@ class MusicVideos(Items):
"AND media_type = 'musicvideo'"
))
kodicursor.execute(query, (mvideoid,))
- rows = kodicursor.fetchall()
- for row in rows:
+ for row in kodicursor.fetchall():
url = row[0]
imagetype = row[1]
@@ -959,7 +954,6 @@ class TVShows(Items):
# Process single tvshow
kodicursor = self.kodicursor
emby_db = self.emby_db
- kodi_db = self.kodi_db
artwork = self.artwork
API = PlexAPI.API(item)
@@ -1079,7 +1073,7 @@ class TVShows(Items):
kodicursor.execute(query, (toplevelpath, "tvshows", "metadata.local", 1, toppathid))
# Add path
- pathid = kodi_db.addPath(path)
+ pathid = self.kodi_db.addPath(path)
# Create the tvshow entry
query = (
@@ -1112,18 +1106,18 @@ class TVShows(Items):
# Process cast
people = API.getPeopleList()
- kodi_db.addPeople(showid, people, "tvshow")
+ self.kodi_db.addPeople(showid, people, "tvshow")
# Process genres
- kodi_db.addGenres(showid, genres, "tvshow")
+ self.kodi_db.addGenres(showid, genres, "tvshow")
# Process artwork
allartworks = API.getAllArtwork()
artwork.addArtwork(allartworks, showid, "tvshow", kodicursor)
# Process studios
- kodi_db.addStudios(showid, studios, "tvshow")
+ self.kodi_db.addStudios(showid, studios, "tvshow")
# Process tags: view, PMS collection tags
tags = [viewtag]
tags.extend(collections)
- kodi_db.addTags(showid, tags, "tvshow")
+ self.kodi_db.addTags(showid, tags, "tvshow")
if force_episodes:
# We needed to recreate the show entry. Re-add episodes now.
@@ -1154,7 +1148,6 @@ class TVShows(Items):
return
kodicursor = self.kodicursor
emby_db = self.emby_db
- kodi_db = self.kodi_db
artwork = self.artwork
seasonnum = API.getIndex()
# Get parent tv show Plex id
@@ -1210,10 +1203,8 @@ class TVShows(Items):
viewtag and viewid are irrelevant!
"""
# Process single episode
- kodiversion = self.kodiversion
kodicursor = self.kodicursor
emby_db = self.emby_db
- kodi_db = self.kodi_db
artwork = self.artwork
API = PlexAPI.API(item)
@@ -1310,7 +1301,7 @@ class TVShows(Items):
# self.logMsg("Skipping: %s. Unable to add series: %s." % (itemid, seriesId), -1)
self.logMsg("Parent tvshow now found, skip item", 2)
return False
- seasonid = kodi_db.addSeason(showid, season)
+ seasonid = self.kodi_db.addSeason(showid, season)
# GET THE FILE AND PATH #####
doIndirect = not self.directpath
@@ -1368,7 +1359,7 @@ class TVShows(Items):
self.logMsg("UPDATE episode itemid: %s" % (itemid), 1)
# Update the movie entry
- if kodiversion in (16, 17):
+ if self.kodiversion in (16, 17):
# Kodi Jarvis, Krypton
query = ' '.join((
@@ -1400,10 +1391,9 @@ class TVShows(Items):
##### OR ADD THE EPISODE #####
else:
- self.logMsg("ADD episode itemid: %s" % (itemid), 1)
-
+ self.logMsg("ADD episode itemid: %s - Title: %s" % (itemid, title), 1)
# Create the episode entry
- if kodiversion in (16, 17):
+ if self.kodiversion in (16, 17):
# Kodi Jarvis, Krypton
query = (
'''
@@ -1454,7 +1444,7 @@ class TVShows(Items):
kodicursor.execute(query, (pathid, filename, dateadded, fileid))
# Process cast
people = API.getPeopleList()
- kodi_db.addPeople(episodeid, people, "episode")
+ self.kodi_db.addPeople(episodeid, people, "episode")
# Process artwork
# Wide "screenshot" of particular episode
poster = item.attrib.get('thumb')
@@ -1473,13 +1463,13 @@ class TVShows(Items):
# Process stream details
streams = API.getMediaStreams()
- kodi_db.addStreams(fileid, streams, runtime)
+ self.kodi_db.addStreams(fileid, streams, runtime)
# Process playstates
- kodi_db.addPlaystate(fileid, resume, runtime, playcount, dateplayed)
+ self.kodi_db.addPlaystate(fileid, resume, runtime, playcount, dateplayed)
if not self.directpath and resume:
# Create additional entry for widgets. This is only required for plugin/episode.
- temppathid = kodi_db.getPath("plugin://plugin.video.plexkodiconnect.tvshows/")
- tempfileid = kodi_db.addFile(filename, temppathid)
+ temppathid = self.kodi_db.getPath("plugin://plugin.video.plexkodiconnect.tvshows/")
+ tempfileid = self.kodi_db.addFile(filename, temppathid)
query = ' '.join((
"UPDATE files",
@@ -1487,7 +1477,7 @@ class TVShows(Items):
"WHERE idFile = ?"
))
kodicursor.execute(query, (temppathid, filename, dateadded, tempfileid))
- kodi_db.addPlaystate(tempfileid, resume, runtime, playcount, dateplayed)
+ self.kodi_db.addPlaystate(tempfileid, resume, runtime, playcount, dateplayed)
self.kodiconn.commit()
self.embyconn.commit()
@@ -1600,27 +1590,23 @@ class TVShows(Items):
def removeShow(self, kodiid):
kodicursor = self.kodicursor
- artwork = self.artwork
-
- artwork.deleteArtwork(kodiid, "tvshow", kodicursor)
+ self.artwork.deleteArtwork(kodiid, "tvshow", kodicursor)
kodicursor.execute("DELETE FROM tvshow WHERE idShow = ?", (kodiid,))
self.logMsg("Removed tvshow: %s." % kodiid, 2)
def removeSeason(self, kodiid):
kodicursor = self.kodicursor
- artwork = self.artwork
- artwork.deleteArtwork(kodiid, "season", kodicursor)
+ self.artwork.deleteArtwork(kodiid, "season", kodicursor)
kodicursor.execute("DELETE FROM seasons WHERE idSeason = ?", (kodiid,))
self.logMsg("Removed season: %s." % kodiid, 2)
def removeEpisode(self, kodiid, fileid):
kodicursor = self.kodicursor
- artwork = self.artwork
- artwork.deleteArtwork(kodiid, "episode", kodicursor)
+ self.artwork.deleteArtwork(kodiid, "episode", kodicursor)
kodicursor.execute("DELETE FROM episode WHERE idEpisode = ?", (kodiid,))
kodicursor.execute("DELETE FROM files WHERE idFile = ?", (fileid,))
self.logMsg("Removed episode: %s." % kodiid, 2)
@@ -1656,10 +1642,9 @@ class Music(Items):
count = 0
for artist in items:
- title = artist['Name']
if pdialog:
percentage = int((float(count) / float(total))*100)
- pdialog.update(percentage, message=title)
+ pdialog.update(percentage, message=artist['Name'])
count += 1
self.add_updateArtist(artist)
# Add albums
@@ -1672,10 +1657,9 @@ class Music(Items):
count = 0
for album in items:
- title = album['Name']
if pdialog:
percentage = int((float(count) / float(total))*100)
- pdialog.update(percentage, message=title)
+ pdialog.update(percentage, message=album['Name'])
count += 1
self.add_updateAlbum(album)
# Add songs
@@ -1688,14 +1672,13 @@ class Music(Items):
count = 0
for song in items:
- title = song['Name']
if pdialog:
percentage = int((float(count) / float(total))*100)
- pdialog.update(percentage, message=title)
+ pdialog.update(percentage, message=song['Name'])
count += 1
self.add_updateSong(song)
if not pdialog and self.contentmsg:
- self.contentPop(title, self.newmusic_time)
+ self.contentPop(song['Name'], self.newmusic_time)
def add_updateArtist(self, item, viewtag=None, viewid=None, artisttype="MusicArtist"):
try:
@@ -1715,7 +1698,6 @@ class Music(Items):
artisttype="MusicArtist"):
kodicursor = self.kodicursor
emby_db = self.emby_db
- kodi_db = self.kodi_db
artwork = self.artwork
API = PlexAPI.API(item)
@@ -1764,7 +1746,7 @@ class Music(Items):
# multiple times.
# Kodi doesn't allow that. In case that happens we just merge the
# artist entries.
- artistid = kodi_db.addArtist(name, musicBrainzId)
+ artistid = self.kodi_db.addArtist(name, musicBrainzId)
# Create the reference in emby table
emby_db.addReference(
itemid, artistid, artisttype, "artist", checksum=checksum)
@@ -1811,10 +1793,8 @@ class Music(Items):
return
def run_add_updateAlbum(self, item, viewtag=None, viewid=None):
- kodiversion = self.kodiversion
kodicursor = self.kodicursor
emby_db = self.emby_db
- kodi_db = self.kodi_db
artwork = self.artwork
API = PlexAPI.API(item)
@@ -1875,13 +1855,13 @@ class Music(Items):
# multiple times.
# Kodi doesn't allow that. In case that happens we just merge the
# artist entries.
- albumid = kodi_db.addAlbum(name, musicBrainzId)
+ albumid = self.kodi_db.addAlbum(name, musicBrainzId)
# Create the reference in emby table
emby_db.addReference(
itemid, albumid, "MusicAlbum", "album", checksum=checksum)
# Process the album info
- if kodiversion == 17:
+ if self.kodiversion == 17:
# Kodi Krypton
query = ' '.join((
@@ -1894,7 +1874,7 @@ class Music(Items):
kodicursor.execute(query, (artistname, year, genre, bio, thumb,
rating, lastScraped, "album", studio,
albumid))
- elif kodiversion == 16:
+ elif self.kodiversion == 16:
# Kodi Jarvis
query = ' '.join((
@@ -1907,7 +1887,7 @@ class Music(Items):
kodicursor.execute(query, (artistname, year, genre, bio, thumb,
rating, lastScraped, "album", studio,
albumid))
- elif kodiversion == 15:
+ elif self.kodiversion == 15:
# Kodi Isengard
query = ' '.join((
@@ -1998,7 +1978,7 @@ class Music(Items):
# Update emby reference with parentid
emby_db.updateParentId(artistId, albumid)
# Add genres
- kodi_db.addMusicGenres(albumid, genres, "album")
+ self.kodi_db.addMusicGenres(albumid, genres, "album")
# Update artwork
artwork.addArtwork(artworks, albumid, "album", kodicursor)
self.embyconn.commit()
@@ -2020,11 +2000,9 @@ class Music(Items):
def run_add_updateSong(self, item, viewtag=None, viewid=None):
# Process single song
- kodiversion = self.kodiversion
kodicursor = self.kodicursor
emby = self.emby
emby_db = self.emby_db
- kodi_db = self.kodi_db
artwork = self.artwork
API = PlexAPI.API(item)
@@ -2136,7 +2114,7 @@ class Music(Items):
self.logMsg("ADD song itemid: %s - Title: %s" % (itemid, title), 1)
# Add path
- pathid = kodi_db.addPath(path, strHash="123")
+ pathid = self.kodi_db.addPath(path, strHash="123")
try:
# Get the album
@@ -2148,7 +2126,7 @@ class Music(Items):
album_name = item.get('parentTitle')
if album_name:
self.logMsg("Creating virtual music album for song: %s." % itemid, 1)
- albumid = kodi_db.addAlbum(album_name, API.getProvider('MusicBrainzAlbum'))
+ albumid = self.kodi_db.addAlbum(album_name, API.getProvider('MusicBrainzAlbum'))
emby_db.addReference("%salbum%s" % (itemid, albumid), albumid, "MusicAlbum_", "album")
else:
# No album Id associated to the song.
@@ -2173,7 +2151,7 @@ class Music(Items):
self.logMsg("Failed to add album. Creating singles.", 1)
kodicursor.execute("select coalesce(max(idAlbum),0) from album")
albumid = kodicursor.fetchone()[0] + 1
- if kodiversion == 16:
+ if self.kodiversion == 16:
# Kodi Jarvis
query = (
'''
@@ -2183,7 +2161,7 @@ class Music(Items):
'''
)
kodicursor.execute(query, (albumid, genre, year, "single"))
- elif kodiversion == 15:
+ elif self.kodiversion == 15:
# Kodi Isengard
query = (
'''
@@ -2316,11 +2294,11 @@ class Music(Items):
result = kodicursor.fetchone()
if result and result[0] != album_artists:
# Field is empty
- if kodiversion in (16, 17):
+ if self.kodiversion in (16, 17):
# Kodi Jarvis, Krypton
query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?"
kodicursor.execute(query, (album_artists, albumid))
- elif kodiversion == 15:
+ elif self.kodiversion == 15:
# Kodi Isengard
query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?"
kodicursor.execute(query, (album_artists, albumid))
@@ -2330,7 +2308,7 @@ class Music(Items):
kodicursor.execute(query, (album_artists, albumid))
# Add genres
- kodi_db.addMusicGenres(songid, genres, "song")
+ self.kodi_db.addMusicGenres(songid, genres, "song")
# Update artwork
allart = API.getAllArtwork(parentInfo=True)
@@ -2372,10 +2350,9 @@ class Music(Items):
self.removeSong(kodiid)
# This should only address single song scenario, where server doesn't actually
# create an album for the song.
- customitems = emby_db.getItem_byWildId(itemid)
emby_db.removeWildItem(itemid)
- for item in customitems:
+ for item in emby_db.getItem_byWildId(itemid):
item_kid = item[0]
item_mediatype = item[1]
@@ -2431,23 +2408,16 @@ class Music(Items):
def removeSong(self, kodiid):
kodicursor = self.kodicursor
- artwork = self.artwork
- artwork.deleteArtwork(kodiid, "song", kodicursor)
- kodicursor.execute("DELETE FROM song WHERE idSong = ?", (kodiid,))
+ self.artwork.deleteArtwork(kodiid, "song", self.kodicursor)
+ self.kodicursor.execute("DELETE FROM song WHERE idSong = ?", (kodiid,))
def removeAlbum(self, kodiid):
- kodicursor = self.kodicursor
- artwork = self.artwork
-
- artwork.deleteArtwork(kodiid, "album", kodicursor)
- kodicursor.execute("DELETE FROM album WHERE idAlbum = ?", (kodiid,))
+ self.artwork.deleteArtwork(kodiid, "album", self.kodicursor)
+ self.kodicursor.execute("DELETE FROM album WHERE idAlbum = ?", (kodiid,))
def removeArtist(self, kodiid):
- kodicursor = self.kodicursor
- artwork = self.artwork
-
- artwork.deleteArtwork(kodiid, "artist", kodicursor)
- kodicursor.execute("DELETE FROM artist WHERE idArtist = ?", (kodiid,))
+ self.artwork.deleteArtwork(kodiid, "artist", self.kodicursor)
+ self.kodicursor.execute("DELETE FROM artist WHERE idArtist = ?", (kodiid,))
diff --git a/resources/lib/kodidb_functions.py b/resources/lib/kodidb_functions.py
index 4ee18755..29eee017 100644
--- a/resources/lib/kodidb_functions.py
+++ b/resources/lib/kodidb_functions.py
@@ -100,19 +100,18 @@ class Kodidb_Functions():
# SQL won't return existing paths otherwise
if path is None:
path = ""
- cursor = self.cursor
query = ' '.join((
"SELECT idPath",
"FROM path",
"WHERE strPath = ?"
))
- cursor.execute(query, (path,))
+ self.cursor.execute(query, (path,))
try:
- pathid = cursor.fetchone()[0]
+ pathid = self.cursor.fetchone()[0]
except TypeError:
- cursor.execute("select coalesce(max(idPath),0) from path")
- pathid = cursor.fetchone()[0] + 1
+ self.cursor.execute("select coalesce(max(idPath),0) from path")
+ pathid = self.cursor.fetchone()[0] + 1
if strHash is None:
query = (
'''
@@ -122,7 +121,7 @@ class Kodidb_Functions():
VALUES (?, ?)
'''
)
- cursor.execute(query, (pathid, path))
+ self.cursor.execute(query, (pathid, path))
else:
query = (
'''
@@ -132,23 +131,21 @@ class Kodidb_Functions():
VALUES (?, ?, ?)
'''
)
- cursor.execute(query, (pathid, path, strHash))
+ self.cursor.execute(query, (pathid, path, strHash))
return pathid
def getPath(self, path):
- cursor = self.cursor
-
query = ' '.join((
"SELECT idPath",
"FROM path",
"WHERE strPath = ?"
))
- cursor.execute(query, (path,))
+ self.cursor.execute(query, (path,))
try:
- pathid = cursor.fetchone()[0]
+ pathid = self.cursor.fetchone()[0]
except TypeError:
pathid = None
@@ -156,8 +153,6 @@ class Kodidb_Functions():
def addFile(self, filename, pathid):
- cursor = self.cursor
-
query = ' '.join((
"SELECT idFile",
@@ -165,12 +160,12 @@ class Kodidb_Functions():
"WHERE strFilename = ?",
"AND idPath = ?"
))
- cursor.execute(query, (filename, pathid,))
+ self.cursor.execute(query, (filename, pathid,))
try:
- fileid = cursor.fetchone()[0]
+ fileid = self.cursor.fetchone()[0]
except TypeError:
- cursor.execute("select coalesce(max(idFile),0) from files")
- fileid = cursor.fetchone()[0] + 1
+ self.cursor.execute("select coalesce(max(idFile),0) from files")
+ fileid = self.cursor.fetchone()[0] + 1
query = (
'''
INSERT INTO files(
@@ -179,23 +174,21 @@ class Kodidb_Functions():
VALUES (?, ?)
'''
)
- cursor.execute(query, (fileid, filename))
+ self.cursor.execute(query, (fileid, filename))
return fileid
def getFile(self, fileid):
- cursor = self.cursor
-
query = ' '.join((
"SELECT strFilename",
"FROM files",
"WHERE idFile = ?"
))
- cursor.execute(query, (fileid,))
+ self.cursor.execute(query, (fileid,))
try:
- filename = cursor.fetchone()[0]
+ filename = self.cursor.fetchone()[0]
except TypeError:
filename = ""
@@ -216,8 +209,6 @@ class Kodidb_Functions():
def addCountries(self, kodiid, countries, mediatype):
- cursor = self.cursor
-
if self.kodiversion in (15, 16, 17):
# Kodi Isengard, Jarvis, Krypton
for country in countries:
@@ -228,18 +219,18 @@ class Kodidb_Functions():
"WHERE name = ?",
"COLLATE NOCASE"
))
- cursor.execute(query, (country,))
+ self.cursor.execute(query, (country,))
try:
- country_id = cursor.fetchone()[0]
+ country_id = self.cursor.fetchone()[0]
except TypeError:
# Country entry does not exists
- cursor.execute("select coalesce(max(country_id),0) from country")
- country_id = cursor.fetchone()[0] + 1
+ self.cursor.execute("select coalesce(max(country_id),0) from country")
+ country_id = self.cursor.fetchone()[0] + 1
query = "INSERT INTO country(country_id, name) values(?, ?)"
- cursor.execute(query, (country_id, country))
+ self.cursor.execute(query, (country_id, country))
self.logMsg("Add country to media, processing: %s" % country, 2)
finally: # Assign country to content
@@ -251,7 +242,7 @@ class Kodidb_Functions():
VALUES (?, ?, ?)
'''
)
- cursor.execute(query, (country_id, kodiid, mediatype))
+ self.cursor.execute(query, (country_id, kodiid, mediatype))
else:
# Kodi Helix
for country in countries:
@@ -262,18 +253,18 @@ class Kodidb_Functions():
"WHERE strCountry = ?",
"COLLATE NOCASE"
))
- cursor.execute(query, (country,))
+ self.cursor.execute(query, (country,))
try:
- idCountry = cursor.fetchone()[0]
+ idCountry = self.cursor.fetchone()[0]
except TypeError:
# Country entry does not exists
- cursor.execute("select coalesce(max(idCountry),0) from country")
- idCountry = cursor.fetchone()[0] + 1
+ self.cursor.execute("select coalesce(max(idCountry),0) from country")
+ idCountry = self.cursor.fetchone()[0] + 1
query = "INSERT INTO country(idCountry, strCountry) values(?, ?)"
- cursor.execute(query, (idCountry, country))
+ self.cursor.execute(query, (idCountry, country))
self.logMsg("Add country to media, processing: %s" % country, 2)
finally:
@@ -287,23 +278,19 @@ class Kodidb_Functions():
VALUES (?, ?)
'''
)
- cursor.execute(query, (idCountry, kodiid))
+ self.cursor.execute(query, (idCountry, kodiid))
def addPeople(self, kodiid, people, mediatype):
- cursor = self.cursor
- artwork = self.artwork
- kodiversion = self.kodiversion
-
castorder = 1
for person in people:
name = person['Name']
- type = person['Type']
+ person_type = person['Type']
thumb = person['imageurl']
# Kodi Isengard, Jarvis, Krypton
- if kodiversion in (15, 16, 17):
+ if self.kodiversion in (15, 16, 17):
query = ' '.join((
"SELECT actor_id",
@@ -311,22 +298,23 @@ class Kodidb_Functions():
"WHERE name = ?",
"COLLATE NOCASE"
))
- cursor.execute(query, (name,))
+ self.cursor.execute(query, (name,))
try:
- actorid = cursor.fetchone()[0]
+ actorid = self.cursor.fetchone()[0]
except TypeError:
# Cast entry does not exists
- cursor.execute("select coalesce(max(actor_id),0) from actor")
- actorid = cursor.fetchone()[0] + 1
+ self.cursor.execute("select coalesce(max(actor_id),0) from actor")
+ actorid = self.cursor.fetchone()[0] + 1
query = "INSERT INTO actor(actor_id, name) values(?, ?)"
- cursor.execute(query, (actorid, name))
+ self.cursor.execute(query, (actorid, name))
+ self.logMsg("Add people to media, processing: %s" % name, 2)
finally:
# Link person to content
- if "Actor" in type:
+ if "Actor" in person_type:
role = person.get('Role')
query = (
'''
@@ -336,10 +324,10 @@ class Kodidb_Functions():
VALUES (?, ?, ?, ?, ?)
'''
)
- cursor.execute(query, (actorid, kodiid, mediatype, role, castorder))
+ self.cursor.execute(query, (actorid, kodiid, mediatype, role, castorder))
castorder += 1
- elif "Director" in type:
+ elif "Director" in person_type:
query = (
'''
INSERT OR REPLACE INTO director_link(
@@ -348,9 +336,9 @@ class Kodidb_Functions():
VALUES (?, ?, ?)
'''
)
- cursor.execute(query, (actorid, kodiid, mediatype))
+ self.cursor.execute(query, (actorid, kodiid, mediatype))
- elif type in ("Writing", "Writer"):
+ elif person_type in ("Writing", "Writer"):
query = (
'''
INSERT OR REPLACE INTO writer_link(
@@ -359,9 +347,9 @@ class Kodidb_Functions():
VALUES (?, ?, ?)
'''
)
- cursor.execute(query, (actorid, kodiid, mediatype))
+ self.cursor.execute(query, (actorid, kodiid, mediatype))
- elif "Artist" in type:
+ elif "Artist" in person_type:
query = (
'''
INSERT OR REPLACE INTO actor_link(
@@ -370,7 +358,7 @@ class Kodidb_Functions():
VALUES (?, ?, ?)
'''
)
- cursor.execute(query, (actorid, kodiid, mediatype))
+ self.cursor.execute(query, (actorid, kodiid, mediatype))
# Kodi Helix
else:
query = ' '.join((
@@ -380,22 +368,23 @@ class Kodidb_Functions():
"WHERE strActor = ?",
"COLLATE NOCASE"
))
- cursor.execute(query, (name,))
+ self.cursor.execute(query, (name,))
try:
- actorid = cursor.fetchone()[0]
+ actorid = self.cursor.fetchone()[0]
except TypeError:
# Cast entry does not exists
- cursor.execute("select coalesce(max(idActor),0) from actors")
- actorid = cursor.fetchone()[0] + 1
+ self.cursor.execute("select coalesce(max(idActor),0) from actors")
+ actorid = self.cursor.fetchone()[0] + 1
query = "INSERT INTO actors(idActor, strActor) values(?, ?)"
- cursor.execute(query, (actorid, name))
+ self.cursor.execute(query, (actorid, name))
+ self.logMsg("Add people to media, processing: %s" % name, 2)
finally:
# Link person to content
- if "Actor" in type:
+ if "Actor" in person_type:
role = person.get('Role')
if "movie" in mediatype:
@@ -427,10 +416,10 @@ class Kodidb_Functions():
)
else: return # Item is invalid
- cursor.execute(query, (actorid, kodiid, role, castorder))
+ self.cursor.execute(query, (actorid, kodiid, role, castorder))
castorder += 1
- elif "Director" in type:
+ elif "Director" in person_type:
if "movie" in mediatype:
query = (
'''
@@ -470,9 +459,9 @@ class Kodidb_Functions():
)
else: return # Item is invalid
- cursor.execute(query, (actorid, kodiid))
+ self.cursor.execute(query, (actorid, kodiid))
- elif type in ("Writing", "Writer"):
+ elif person_type in ("Writing", "Writer"):
if "movie" in mediatype:
query = (
'''
@@ -493,9 +482,9 @@ class Kodidb_Functions():
)
else: return # Item is invalid
- cursor.execute(query, (actorid, kodiid))
+ self.cursor.execute(query, (actorid, kodiid))
- elif "Artist" in type:
+ elif "Artist" in person_type:
query = (
'''
INSERT OR REPLACE INTO artistlinkmusicvideo(
@@ -504,20 +493,19 @@ class Kodidb_Functions():
VALUES (?, ?)
'''
)
- cursor.execute(query, (actorid, kodiid))
+ self.cursor.execute(query, (actorid, kodiid))
# Add person image to art table
if thumb:
- arttype = type.lower()
+ arttype = person_type.lower()
if "writing" in arttype:
arttype = "writer"
- artwork.addOrUpdateArt(thumb, actorid, arttype, "thumb", cursor)
+ self.artwork.addOrUpdateArt(thumb, actorid, arttype, "thumb", self.cursor)
def addGenres(self, kodiid, genres, mediatype):
- cursor = self.cursor
# Kodi Isengard, Jarvis, Krypton
if self.kodiversion in (15, 16, 17):
@@ -528,7 +516,7 @@ class Kodidb_Functions():
"WHERE media_id = ?",
"AND media_type = ?"
))
- cursor.execute(query, (kodiid, mediatype,))
+ self.cursor.execute(query, (kodiid, mediatype,))
# Add genres
for genre in genres:
@@ -540,19 +528,20 @@ class Kodidb_Functions():
"WHERE name = ?",
"COLLATE NOCASE"
))
- cursor.execute(query, (genre,))
+ self.cursor.execute(query, (genre,))
try:
- genre_id = cursor.fetchone()[0]
+ genre_id = self.cursor.fetchone()[0]
except TypeError:
# Create genre in database
- cursor.execute("select coalesce(max(genre_id),0) from genre")
- genre_id = cursor.fetchone()[0] + 1
+ self.cursor.execute("select coalesce(max(genre_id),0) from genre")
+ genre_id = self.cursor.fetchone()[0] + 1
query = "INSERT INTO genre(genre_id, name) values(?, ?)"
- cursor.execute(query, (genre_id, genre))
-
+ self.cursor.execute(query, (genre_id, genre))
+ self.logMsg("Add Genres to media, processing: %s" % genre, 2)
+
finally:
# Assign genre to item
query = (
@@ -563,16 +552,16 @@ class Kodidb_Functions():
VALUES (?, ?, ?)
'''
)
- cursor.execute(query, (genre_id, kodiid, mediatype))
+ self.cursor.execute(query, (genre_id, kodiid, mediatype))
else:
# Kodi Helix
# Delete current genres for clean slate
if "movie" in mediatype:
- cursor.execute("DELETE FROM genrelinkmovie WHERE idMovie = ?", (kodiid,))
+ self.cursor.execute("DELETE FROM genrelinkmovie WHERE idMovie = ?", (kodiid,))
elif "tvshow" in mediatype:
- cursor.execute("DELETE FROM genrelinktvshow WHERE idShow = ?", (kodiid,))
+ self.cursor.execute("DELETE FROM genrelinktvshow WHERE idShow = ?", (kodiid,))
elif "musicvideo" in mediatype:
- cursor.execute("DELETE FROM genrelinkmusicvideo WHERE idMVideo = ?", (kodiid,))
+ self.cursor.execute("DELETE FROM genrelinkmusicvideo WHERE idMVideo = ?", (kodiid,))
# Add genres
for genre in genres:
@@ -584,19 +573,20 @@ class Kodidb_Functions():
"WHERE strGenre = ?",
"COLLATE NOCASE"
))
- cursor.execute(query, (genre,))
+ self.cursor.execute(query, (genre,))
try:
- idGenre = cursor.fetchone()[0]
+ idGenre = self.cursor.fetchone()[0]
except TypeError:
# Create genre in database
- cursor.execute("select coalesce(max(idGenre),0) from genre")
- idGenre = cursor.fetchone()[0] + 1
+ self.cursor.execute("select coalesce(max(idGenre),0) from genre")
+ idGenre = self.cursor.fetchone()[0] + 1
query = "INSERT INTO genre(idGenre, strGenre) values(?, ?)"
- cursor.execute(query, (idGenre, genre))
-
+ self.cursor.execute(query, (idGenre, genre))
+ self.logMsg("Add Genres to media, processing: %s" % genre, 2)
+
finally:
# Assign genre to item
if "movie" in mediatype:
@@ -628,16 +618,13 @@ class Kodidb_Functions():
)
else: return # Item is invalid
- cursor.execute(query, (idGenre, kodiid))
+ self.cursor.execute(query, (idGenre, kodiid))
def addStudios(self, kodiid, studios, mediatype):
- cursor = self.cursor
- kodiversion = self.kodiversion
-
for studio in studios:
- if kodiversion in (15, 16, 17):
+ if self.kodiversion in (15, 16, 17):
# Kodi Isengard, Jarvis, Krypton
query = ' '.join((
@@ -646,17 +633,18 @@ class Kodidb_Functions():
"WHERE name = ?",
"COLLATE NOCASE"
))
- cursor.execute(query, (studio,))
+ self.cursor.execute(query, (studio,))
try:
- studioid = cursor.fetchone()[0]
+ studioid = self.cursor.fetchone()[0]
except TypeError:
# Studio does not exists.
- cursor.execute("select coalesce(max(studio_id),0) from studio")
- studioid = cursor.fetchone()[0] + 1
+ self.cursor.execute("select coalesce(max(studio_id),0) from studio")
+ studioid = self.cursor.fetchone()[0] + 1
query = "INSERT INTO studio(studio_id, name) values(?, ?)"
- cursor.execute(query, (studioid, studio))
+ self.cursor.execute(query, (studioid, studio))
+ self.logMsg("Add Studios to media, processing: %s" % studio, 2)
finally: # Assign studio to item
query = (
@@ -666,7 +654,7 @@ class Kodidb_Functions():
VALUES (?, ?, ?)
''')
- cursor.execute(query, (studioid, kodiid, mediatype))
+ self.cursor.execute(query, (studioid, kodiid, mediatype))
else:
# Kodi Helix
query = ' '.join((
@@ -676,17 +664,18 @@ class Kodidb_Functions():
"WHERE strstudio = ?",
"COLLATE NOCASE"
))
- cursor.execute(query, (studio,))
+ self.cursor.execute(query, (studio,))
try:
- studioid = cursor.fetchone()[0]
+ studioid = self.cursor.fetchone()[0]
except TypeError:
# Studio does not exists.
- cursor.execute("select coalesce(max(idstudio),0) from studio")
- studioid = cursor.fetchone()[0] + 1
+ self.cursor.execute("select coalesce(max(idstudio),0) from studio")
+ studioid = self.cursor.fetchone()[0] + 1
query = "INSERT INTO studio(idstudio, strstudio) values(?, ?)"
- cursor.execute(query, (studioid, studio))
+ self.cursor.execute(query, (studioid, studio))
+ self.logMsg("Add Studios to media, processing: %s" % studio, 2)
finally: # Assign studio to item
if "movie" in mediatype:
@@ -713,14 +702,12 @@ class Kodidb_Functions():
INSERT OR REPLACE INTO studiolinkepisode(idstudio, idEpisode)
VALUES (?, ?)
''')
- cursor.execute(query, (studioid, kodiid))
+ self.cursor.execute(query, (studioid, kodiid))
def addStreams(self, fileid, streamdetails, runtime):
- cursor = self.cursor
-
# First remove any existing entries
- cursor.execute("DELETE FROM streamdetails WHERE idFile = ?", (fileid,))
+ self.cursor.execute("DELETE FROM streamdetails WHERE idFile = ?", (fileid,))
if streamdetails:
# Video details
for videotrack in streamdetails['video']:
@@ -733,7 +720,7 @@ class Kodidb_Functions():
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
'''
)
- cursor.execute(query, (fileid, 0, videotrack['codec'],
+ self.cursor.execute(query, (fileid, 0, videotrack['codec'],
videotrack['aspect'], videotrack['width'], videotrack['height'],
runtime ,videotrack['video3DFormat']))
@@ -747,7 +734,7 @@ class Kodidb_Functions():
VALUES (?, ?, ?, ?, ?)
'''
)
- cursor.execute(query, (fileid, 1, audiotrack['codec'],
+ self.cursor.execute(query, (fileid, 1, audiotrack['codec'],
audiotrack['channels'], audiotrack['language']))
# Subtitles details
@@ -760,7 +747,7 @@ class Kodidb_Functions():
VALUES (?, ?, ?)
'''
)
- cursor.execute(query, (fileid, 2, subtitletrack))
+ self.cursor.execute(query, (fileid, 2, subtitletrack))
def getResumes(self):
"""
@@ -926,35 +913,27 @@ class Kodidb_Functions():
return int(runtime)
def addPlaystate(self, fileid, resume_seconds, total_seconds, playcount, dateplayed):
- cursor = self.cursor
# Delete existing resume point
query = ' '.join((
"DELETE FROM bookmark",
"WHERE idFile = ?"
))
- cursor.execute(query, (fileid,))
+ self.cursor.execute(query, (fileid,))
# Set watched count
- if playcount is None:
- query = ' '.join((
- "UPDATE files",
- "SET lastPlayed = ?",
- "WHERE idFile = ?"
- ))
- cursor.execute(query, (dateplayed, fileid))
- else:
- query = ' '.join((
- "UPDATE files",
- "SET playCount = ?, lastPlayed = ?",
- "WHERE idFile = ?"
- ))
- cursor.execute(query, (playcount, dateplayed, fileid))
+ query = ' '.join((
+
+ "UPDATE files",
+ "SET playCount = ?, lastPlayed = ?",
+ "WHERE idFile = ?"
+ ))
+ self.cursor.execute(query, (playcount, dateplayed, fileid))
# Set the resume bookmark
if resume_seconds:
- cursor.execute("select coalesce(max(idBookmark),0) from bookmark")
- bookmarkId = cursor.fetchone()[0] + 1
+ self.cursor.execute("select coalesce(max(idBookmark),0) from bookmark")
+ bookmarkId = self.cursor.fetchone()[0] + 1
query = (
'''
INSERT INTO bookmark(
@@ -963,12 +942,10 @@ class Kodidb_Functions():
VALUES (?, ?, ?, ?, ?, ?)
'''
)
- cursor.execute(query, (bookmarkId, fileid, resume_seconds, total_seconds,
+ self.cursor.execute(query, (bookmarkId, fileid, resume_seconds, total_seconds,
"DVDPlayer", 1))
def addTags(self, kodiid, tags, mediatype):
-
- cursor = self.cursor
# First, delete any existing tags associated to the id
if self.kodiversion in (15, 16, 17):
@@ -979,7 +956,7 @@ class Kodidb_Functions():
"WHERE media_id = ?",
"AND media_type = ?"
))
- cursor.execute(query, (kodiid, mediatype))
+ self.cursor.execute(query, (kodiid, mediatype))
else:
# Kodi Helix
query = ' '.join((
@@ -988,16 +965,15 @@ class Kodidb_Functions():
"WHERE idMedia = ?",
"AND media_type = ?"
))
- cursor.execute(query, (kodiid, mediatype))
+ self.cursor.execute(query, (kodiid, mediatype))
# Add tags
+ self.logMsg("Adding Tags: %s" % tags, 2)
for tag in tags:
self.addTag(kodiid, tag, mediatype)
def addTag(self, kodiid, tag, mediatype):
- cursor = self.cursor
-
if self.kodiversion in (15, 16, 17):
# Kodi Isengard, Jarvis, Krypton
query = ' '.join((
@@ -1007,9 +983,9 @@ class Kodidb_Functions():
"WHERE name = ?",
"COLLATE NOCASE"
))
- cursor.execute(query, (tag,))
+ self.cursor.execute(query, (tag,))
try:
- tag_id = cursor.fetchone()[0]
+ tag_id = self.cursor.fetchone()[0]
except TypeError:
# Create the tag, because it does not exist
@@ -1026,7 +1002,7 @@ class Kodidb_Functions():
VALUES (?, ?, ?)
'''
)
- cursor.execute(query, (tag_id, kodiid, mediatype))
+ self.cursor.execute(query, (tag_id, kodiid, mediatype))
else:
# Kodi Helix
query = ' '.join((
@@ -1036,9 +1012,9 @@ class Kodidb_Functions():
"WHERE strTag = ?",
"COLLATE NOCASE"
))
- cursor.execute(query, (tag,))
+ self.cursor.execute(query, (tag,))
try:
- tag_id = cursor.fetchone()[0]
+ tag_id = self.cursor.fetchone()[0]
except TypeError:
# Create the tag
@@ -1055,12 +1031,10 @@ class Kodidb_Functions():
VALUES (?, ?, ?)
'''
)
- cursor.execute(query, (tag_id, kodiid, mediatype))
+ self.cursor.execute(query, (tag_id, kodiid, mediatype))
def createTag(self, name):
- cursor = self.cursor
-
# This will create and return the tag_id
if self.kodiversion in (15, 16, 17):
# Kodi Isengard, Jarvis, Krypton
@@ -1071,16 +1045,16 @@ class Kodidb_Functions():
"WHERE name = ?",
"COLLATE NOCASE"
))
- cursor.execute(query, (name,))
+ self.cursor.execute(query, (name,))
try:
- tag_id = cursor.fetchone()[0]
+ tag_id = self.cursor.fetchone()[0]
except TypeError:
- cursor.execute("select coalesce(max(tag_id),0) from tag")
- tag_id = cursor.fetchone()[0] + 1
+ self.cursor.execute("select coalesce(max(tag_id),0) from tag")
+ tag_id = self.cursor.fetchone()[0] + 1
query = "INSERT INTO tag(tag_id, name) values(?, ?)"
- cursor.execute(query, (tag_id, name))
+ self.cursor.execute(query, (tag_id, name))
self.logMsg("Create tag_id: %s name: %s" % (tag_id, name), 2)
else:
# Kodi Helix
@@ -1091,23 +1065,22 @@ class Kodidb_Functions():
"WHERE strTag = ?",
"COLLATE NOCASE"
))
- cursor.execute(query, (name,))
+ self.cursor.execute(query, (name,))
try:
- tag_id = cursor.fetchone()[0]
+ tag_id = self.cursor.fetchone()[0]
except TypeError:
- cursor.execute("select coalesce(max(idTag),0) from tag")
- tag_id = cursor.fetchone()[0] + 1
+ self.cursor.execute("select coalesce(max(idTag),0) from tag")
+ tag_id = self.cursor.fetchone()[0] + 1
query = "INSERT INTO tag(idTag, strTag) values(?, ?)"
- cursor.execute(query, (tag_id, name))
+ self.cursor.execute(query, (tag_id, name))
self.logMsg("Create idTag: %s name: %s" % (tag_id, name), 2)
return tag_id
def updateTag(self, oldtag, newtag, kodiid, mediatype):
- cursor = self.cursor
self.logMsg("Updating: %s with %s for %s: %s" % (oldtag, newtag, mediatype, kodiid), 2)
if self.kodiversion in (15, 16, 17):
@@ -1121,7 +1094,7 @@ class Kodidb_Functions():
"AND media_type = ?",
"AND tag_id = ?"
))
- cursor.execute(query, (newtag, kodiid, mediatype, oldtag,))
+ self.cursor.execute(query, (newtag, kodiid, mediatype, oldtag,))
except Exception as e:
# The new tag we are going to apply already exists for this item
# delete current tag instead
@@ -1133,7 +1106,7 @@ class Kodidb_Functions():
"AND media_type = ?",
"AND tag_id = ?"
))
- cursor.execute(query, (kodiid, mediatype, oldtag,))
+ self.cursor.execute(query, (kodiid, mediatype, oldtag,))
else:
# Kodi Helix
try:
@@ -1145,7 +1118,7 @@ class Kodidb_Functions():
"AND media_type = ?",
"AND idTag = ?"
))
- cursor.execute(query, (newtag, kodiid, mediatype, oldtag,))
+ self.cursor.execute(query, (newtag, kodiid, mediatype, oldtag,))
except Exception as e:
# The new tag we are going to apply already exists for this item
# delete current tag instead
@@ -1157,12 +1130,10 @@ class Kodidb_Functions():
"AND media_type = ?",
"AND idTag = ?"
))
- cursor.execute(query, (kodiid, mediatype, oldtag,))
+ self.cursor.execute(query, (kodiid, mediatype, oldtag,))
def removeTag(self, kodiid, tagname, mediatype):
- cursor = self.cursor
-
if self.kodiversion in (15, 16, 17):
# Kodi Isengard, Jarvis, Krypton
query = ' '.join((
@@ -1172,9 +1143,9 @@ class Kodidb_Functions():
"WHERE name = ?",
"COLLATE NOCASE"
))
- cursor.execute(query, (tagname,))
+ self.cursor.execute(query, (tagname,))
try:
- tag_id = cursor.fetchone()[0]
+ tag_id = self.cursor.fetchone()[0]
except TypeError:
return
else:
@@ -1185,7 +1156,7 @@ class Kodidb_Functions():
"AND media_type = ?",
"AND tag_id = ?"
))
- cursor.execute(query, (kodiid, mediatype, tag_id,))
+ self.cursor.execute(query, (kodiid, mediatype, tag_id,))
else:
# Kodi Helix
query = ' '.join((
@@ -1195,9 +1166,9 @@ class Kodidb_Functions():
"WHERE strTag = ?",
"COLLATE NOCASE"
))
- cursor.execute(query, (tagname,))
+ self.cursor.execute(query, (tagname,))
try:
- tag_id = cursor.fetchone()[0]
+ tag_id = self.cursor.fetchone()[0]
except TypeError:
return
else:
@@ -1208,11 +1179,10 @@ class Kodidb_Functions():
"AND media_type = ?",
"AND idTag = ?"
))
- cursor.execute(query, (kodiid, mediatype, tag_id,))
+ self.cursor.execute(query, (kodiid, mediatype, tag_id,))
def createBoxset(self, boxsetname):
- cursor = self.cursor
self.logMsg("Adding boxset: %s" % boxsetname, 2)
query = ' '.join((
@@ -1221,16 +1191,16 @@ class Kodidb_Functions():
"WHERE strSet = ?",
"COLLATE NOCASE"
))
- cursor.execute(query, (boxsetname,))
+ self.cursor.execute(query, (boxsetname,))
try:
- setid = cursor.fetchone()[0]
+ setid = self.cursor.fetchone()[0]
except TypeError:
- cursor.execute("select coalesce(max(idSet),0) from sets")
- setid = cursor.fetchone()[0] + 1
+ self.cursor.execute("select coalesce(max(idSet),0) from sets")
+ setid = self.cursor.fetchone()[0] + 1
query = "INSERT INTO sets(idSet, strSet) values(?, ?)"
- cursor.execute(query, (setid, boxsetname))
+ self.cursor.execute(query, (setid, boxsetname))
return setid
@@ -1256,8 +1226,6 @@ class Kodidb_Functions():
def addSeason(self, showid, seasonnumber):
- cursor = self.cursor
-
query = ' '.join((
"SELECT idSeason",
@@ -1265,30 +1233,28 @@ class Kodidb_Functions():
"WHERE idShow = ?",
"AND season = ?"
))
- cursor.execute(query, (showid, seasonnumber,))
+ self.cursor.execute(query, (showid, seasonnumber,))
try:
- seasonid = cursor.fetchone()[0]
+ seasonid = self.cursor.fetchone()[0]
except TypeError:
- cursor.execute("select coalesce(max(idSeason),0) from seasons")
- seasonid = cursor.fetchone()[0] + 1
+ self.cursor.execute("select coalesce(max(idSeason),0) from seasons")
+ seasonid = self.cursor.fetchone()[0] + 1
query = "INSERT INTO seasons(idSeason, idShow, season) values(?, ?, ?)"
- cursor.execute(query, (seasonid, showid, seasonnumber))
+ self.cursor.execute(query, (seasonid, showid, seasonnumber))
return seasonid
def addArtist(self, name, musicbrainz):
- cursor = self.cursor
-
query = ' '.join((
"SELECT idArtist, strArtist",
"FROM artist",
"WHERE strMusicBrainzArtistID = ?"
))
- cursor.execute(query, (musicbrainz,))
+ self.cursor.execute(query, (musicbrainz,))
try:
- result = cursor.fetchone()
+ result = self.cursor.fetchone()
artistid = result[0]
artistname = result[1]
@@ -1301,12 +1267,12 @@ class Kodidb_Functions():
"WHERE strArtist = ?",
"COLLATE NOCASE"
))
- cursor.execute(query, (name,))
+ self.cursor.execute(query, (name,))
try:
- artistid = cursor.fetchone()[0]
+ artistid = self.cursor.fetchone()[0]
except TypeError:
- cursor.execute("select coalesce(max(idArtist),0) from artist")
- artistid = cursor.fetchone()[0] + 1
+ self.cursor.execute("select coalesce(max(idArtist),0) from artist")
+ artistid = self.cursor.fetchone()[0] + 1
query = (
'''
INSERT INTO artist(idArtist, strArtist, strMusicBrainzArtistID)
@@ -1314,33 +1280,30 @@ class Kodidb_Functions():
VALUES (?, ?, ?)
'''
)
- cursor.execute(query, (artistid, name, musicbrainz))
+ self.cursor.execute(query, (artistid, name, musicbrainz))
else:
if artistname != name:
query = "UPDATE artist SET strArtist = ? WHERE idArtist = ?"
- cursor.execute(query, (name, artistid,))
+ self.cursor.execute(query, (name, artistid,))
return artistid
def addAlbum(self, name, musicbrainz):
- kodiversion = self.kodiversion
- cursor = self.cursor
-
query = ' '.join((
"SELECT idAlbum",
"FROM album",
"WHERE strMusicBrainzAlbumID = ?"
))
- cursor.execute(query, (musicbrainz,))
+ self.cursor.execute(query, (musicbrainz,))
try:
- albumid = cursor.fetchone()[0]
+ albumid = self.cursor.fetchone()[0]
except TypeError:
# Create the album
- cursor.execute("select coalesce(max(idAlbum),0) from album")
- albumid = cursor.fetchone()[0] + 1
- if kodiversion in (15, 16, 17):
+ self.cursor.execute("select coalesce(max(idAlbum),0) from album")
+ albumid = self.cursor.fetchone()[0] + 1
+ if self.kodiversion in (15, 16, 17):
query = (
'''
INSERT INTO album(idAlbum, strAlbum, strMusicBrainzAlbumID, strReleaseType)
@@ -1348,7 +1311,7 @@ class Kodidb_Functions():
VALUES (?, ?, ?, ?)
'''
)
- cursor.execute(query, (albumid, name, musicbrainz, "album"))
+ self.cursor.execute(query, (albumid, name, musicbrainz, "album"))
else: # Helix
query = (
'''
@@ -1357,14 +1320,12 @@ class Kodidb_Functions():
VALUES (?, ?, ?)
'''
)
- cursor.execute(query, (albumid, name, musicbrainz))
+ self.cursor.execute(query, (albumid, name, musicbrainz))
return albumid
def addMusicGenres(self, kodiid, genres, mediatype):
- cursor = self.cursor
-
if mediatype == "album":
# Delete current genres for clean slate
@@ -1373,7 +1334,7 @@ class Kodidb_Functions():
"DELETE FROM album_genre",
"WHERE idAlbum = ?"
))
- cursor.execute(query, (kodiid,))
+ self.cursor.execute(query, (kodiid,))
for genre in genres:
query = ' '.join((
@@ -1383,18 +1344,18 @@ class Kodidb_Functions():
"WHERE strGenre = ?",
"COLLATE NOCASE"
))
- cursor.execute(query, (genre,))
+ self.cursor.execute(query, (genre,))
try:
- genreid = cursor.fetchone()[0]
+ genreid = self.cursor.fetchone()[0]
except TypeError:
# Create the genre
- cursor.execute("select coalesce(max(idGenre),0) from genre")
- genreid = cursor.fetchone()[0] + 1
+ self.cursor.execute("select coalesce(max(idGenre),0) from genre")
+ genreid = self.cursor.fetchone()[0] + 1
query = "INSERT INTO genre(idGenre, strGenre) values(?, ?)"
- cursor.execute(query, (genreid, genre))
+ self.cursor.execute(query, (genreid, genre))
query = "INSERT OR REPLACE INTO album_genre(idGenre, idAlbum) values(?, ?)"
- cursor.execute(query, (genreid, kodiid))
+ self.cursor.execute(query, (genreid, kodiid))
elif mediatype == "song":
@@ -1404,7 +1365,7 @@ class Kodidb_Functions():
"DELETE FROM song_genre",
"WHERE idSong = ?"
))
- cursor.execute(query, (kodiid,))
+ self.cursor.execute(query, (kodiid,))
for genre in genres:
query = ' '.join((
@@ -1414,15 +1375,15 @@ class Kodidb_Functions():
"WHERE strGenre = ?",
"COLLATE NOCASE"
))
- cursor.execute(query, (genre,))
+ self.cursor.execute(query, (genre,))
try:
- genreid = cursor.fetchone()[0]
+ genreid = self.cursor.fetchone()[0]
except TypeError:
# Create the genre
- cursor.execute("select coalesce(max(idGenre),0) from genre")
- genreid = cursor.fetchone()[0] + 1
+ self.cursor.execute("select coalesce(max(idGenre),0) from genre")
+ genreid = self.cursor.fetchone()[0] + 1
query = "INSERT INTO genre(idGenre, strGenre) values(?, ?)"
- cursor.execute(query, (genreid, genre))
+ self.cursor.execute(query, (genreid, genre))
query = "INSERT OR REPLACE INTO song_genre(idGenre, idSong) values(?, ?)"
- cursor.execute(query, (genreid, kodiid))
\ No newline at end of file
+ self.cursor.execute(query, (genreid, kodiid))
\ No newline at end of file
diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py
index 768c3b27..3ea4953c 100644
--- a/resources/lib/kodimonitor.py
+++ b/resources/lib/kodimonitor.py
@@ -82,13 +82,13 @@ class KodiMonitor(xbmc.Monitor):
item = data.get('item')
try:
kodiid = item['id']
- type = item['type']
+ item_type = item['type']
except (KeyError, TypeError):
self.logMsg("Item is invalid for playstate update.", 1)
else:
# Send notification to the server.
with embydb.GetEmbyDB() as emby_db:
- emby_dbitem = emby_db.getItem_byKodiId(kodiid, type)
+ emby_dbitem = emby_db.getItem_byKodiId(kodiid, item_type)
try:
itemid = emby_dbitem[0]
except TypeError:
@@ -137,7 +137,7 @@ class KodiMonitor(xbmc.Monitor):
url = "{server}/emby/Items/%s?format=json" % itemid
self.logMsg("Deleting request: %s" % itemid)
- doUtils.downloadUrl(url, type="DELETE")
+ doUtils.downloadUrl(url, action_type="DELETE")
finally:
embycursor.close()'''
diff --git a/resources/lib/musicutils.py b/resources/lib/musicutils.py
index 1f977781..3a7dc294 100644
--- a/resources/lib/musicutils.py
+++ b/resources/lib/musicutils.py
@@ -193,6 +193,7 @@ def getSongTags(file):
if pic.type == 3 and pic.data:
#the file has an embedded cover
hasEmbeddedCover = True
+ break
if audio.get("rating"):
rating = float(audio.get("rating")[0])
#flac rating is 0-100 and needs to be converted to 0-5 range
diff --git a/resources/lib/playbackutils.py b/resources/lib/playbackutils.py
index f96d2aaa..c691cebf 100644
--- a/resources/lib/playbackutils.py
+++ b/resources/lib/playbackutils.py
@@ -44,7 +44,6 @@ class PlaybackUtils():
def play(self, itemid, dbid=None):
- log = self.logMsg
window = utils.window
settings = utils.settings
@@ -56,7 +55,7 @@ class PlaybackUtils():
listitem = xbmcgui.ListItem()
playutils = putils.PlayUtils(item[0])
- log("Play called.", 1)
+ self.logMsg("Play called.", 1)
playurl = playutils.getPlayUrl()
if not playurl:
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
@@ -101,9 +100,9 @@ class PlaybackUtils():
introsPlaylist = False
dummyPlaylist = False
- log("Playlist start position: %s" % startPos, 1)
- log("Playlist plugin position: %s" % self.currentPosition, 1)
- log("Playlist size: %s" % sizePlaylist, 1)
+ self.logMsg("Playlist start position: %s" % startPos, 2)
+ self.logMsg("Playlist plugin position: %s" % currentPosition, 2)
+ self.logMsg("Playlist size: %s" % sizePlaylist, 2)
############### RESUME POINT ################
@@ -114,11 +113,11 @@ class PlaybackUtils():
if not propertiesPlayback:
window('emby_playbackProps', value="true")
- log("Setting up properties in playlist.", 1)
+ self.logMsg("Setting up properties in playlist.", 1)
if (not homeScreen and not seektime and
window('emby_customPlaylist') != "true"):
- log("Adding dummy file to playlist.", 2)
+ self.logMsg("Adding dummy file to playlist.", 2)
dummyPlaylist = True
playlist.add(playurl, listitem, index=startPos)
# Remove the original item from playlist
@@ -181,13 +180,13 @@ class PlaybackUtils():
if dummyPlaylist:
# Added a dummy file to the playlist,
# because the first item is going to fail automatically.
- log("Processed as a playlist. First item is skipped.", 1)
+ self.logMsg("Processed as a playlist. First item is skipped.", 1)
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
# We just skipped adding properties. Reset flag for next time.
elif propertiesPlayback:
- log("Resetting properties playback flag.", 2)
+ self.logMsg("Resetting properties playback flag.", 2)
window('emby_playbackProps', clear=True)
#self.pl.verifyPlaylist()
@@ -206,18 +205,18 @@ class PlaybackUtils():
############### PLAYBACK ################
if homeScreen and seektime and window('emby_customPlaylist') != "true":
- log("Play as a widget item.", 1)
+ self.logMsg("Play as a widget item.", 1)
API.CreateListItemFromPlexItem(listitem)
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
elif ((introsPlaylist and window('emby_customPlaylist') == "true") or
(homeScreen and not sizePlaylist)):
# Playlist was created just now, play it.
- log("Play playlist.", 1)
+ self.logMsg("Play playlist.", 1)
xbmc.Player().play(playlist, startpos=startPos)
else:
- log("Play as a regular item.", 1)
+ self.logMsg("Play as a regular item.", 1)
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
def AddTrailers(self, xml):
diff --git a/resources/lib/player.py b/resources/lib/player.py
index 4f0f5483..d6304675 100644
--- a/resources/lib/player.py
+++ b/resources/lib/player.py
@@ -51,15 +51,13 @@ class Player(xbmc.Player):
"""
Window values need to have been set in Kodimonitor.py
"""
- log = self.logMsg
window = utils.window
# Will be called when xbmc starts playing a file
- xbmcplayer = self.xbmcplayer
self.stopAll()
# Get current file (in utf-8!)
try:
- currentFile = xbmcplayer.getPlayingFile()
+ currentFile = self.xbmcplayer.getPlayingFile()
xbmc.sleep(300)
except:
currentFile = ""
@@ -67,11 +65,11 @@ class Player(xbmc.Player):
while not currentFile:
xbmc.sleep(100)
try:
- currentFile = xbmcplayer.getPlayingFile()
+ currentFile = self.xbmcplayer.getPlayingFile()
except:
pass
if count == 20:
- log("Cancelling playback report...", 1)
+ self.logMsg("Cancelling playback report...", 1)
break
else:
count += 1
@@ -220,6 +218,8 @@ class Player(xbmc.Player):
except ValueError:
runtime = xbmcplayer.getTotalTime()
log("Runtime is missing, Kodi runtime: %s" % runtime, 1)
+ runtime = self.xbmcplayer.getTotalTime()
+ self.logMsg("Runtime is missing, Kodi runtime: %s" % runtime, 1)
playQueueVersion = window('playQueueVersion')
playQueueID = window('playQueueID')
@@ -263,7 +263,7 @@ class Player(xbmc.Player):
if not self.doNotify:
return
- log = self.logMsg
+ self.logMsg("reportPlayback Called", 2)
log("reportPlayback Called", 2)
@@ -382,7 +382,7 @@ class Player(xbmc.Player):
if mapping: # Set in PlaybackUtils.py
- log("Mapping for external subtitles index: %s" % mapping, 2)
+ self.logMsg("Mapping for external subtitles index: %s" % mapping, 2)
externalIndex = json.loads(mapping)
if externalIndex.get(str(indexSubs)):
@@ -404,7 +404,7 @@ class Player(xbmc.Player):
# postdata = json.dumps(postdata)
# self.ws.sendProgressUpdate(postdata)
self.doUtils(
- "{server}/:/timeline?" + urlencode(postdata), type="GET")
+ "{server}/:/timeline?" + urlencode(postdata), action_type="GET")
def onPlayBackPaused(self):
@@ -440,7 +440,6 @@ class Player(xbmc.Player):
def onPlayBackStopped(self):
# Will be called when user stops xbmc playing a file
- log = self.logMsg
window = utils.window
log("ONPLAYBACK_STOPPED", 1)
@@ -459,31 +458,28 @@ class Player(xbmc.Player):
def stopAll(self):
- log = self.logMsg
lang = utils.language
settings = utils.settings
- doUtils = self.doUtils
-
if not self.played_info:
return
- log("Played_information: %s" % self.played_info, 1)
+ self.logMsg("Played_information: %s" % self.played_info, 1)
# Process each items
for item in self.played_info:
data = self.played_info.get(item)
if data:
- log("Item path: %s" % item, 2)
- log("Item data: %s" % data, 2)
+ self.logMsg("Item path: %s" % item, 2)
+ self.logMsg("Item data: %s" % data, 2)
runtime = data['runtime']
currentPosition = data['currentPosition']
itemid = data['item_id']
refresh_id = data['refresh_id']
currentFile = data['currentfile']
- type = data['Type']
+ media_type = data['Type']
playMethod = data['playmethod']
# Prevent manually mark as watched in Kodi monitor
@@ -497,15 +493,15 @@ class Player(xbmc.Player):
percentComplete = 0
markPlayedAt = float(settings('markPlayed')) / 100
- log("Percent complete: %s Mark played at: %s"
+ self.logMsg("Percent complete: %s Mark played at: %s"
% (percentComplete, markPlayedAt), 1)
# Send the delete action to the server.
offerDelete = False
- if type == "Episode" and settings('deleteTV') == "true":
+ if media_type == "Episode" and settings('deleteTV') == "true":
offerDelete = True
- elif type == "Movie" and settings('deleteMovies') == "true":
+ elif media_type == "Movie" and settings('deleteMovies') == "true":
offerDelete = True
if settings('offerDelete') != "true":
@@ -520,7 +516,7 @@ class Player(xbmc.Player):
lang(33015),
autoclose=120000)
if not resp:
- log("User skipped deletion.", 1)
+ self.logMsg("User skipped deletion.", 1)
continue
url = "{server}/emby/Items/%s?format=json" % itemid
@@ -567,4 +563,4 @@ class Player(xbmc.Player):
'duration': int(duration)
}
url = url + urlencode(args)
- self.doUtils(url, type="GET")
+ self.doUtils(url, action_type="GET")
diff --git a/resources/lib/playlist.py b/resources/lib/playlist.py
index b3f9c154..7376d55e 100644
--- a/resources/lib/playlist.py
+++ b/resources/lib/playlist.py
@@ -27,7 +27,6 @@ class Playlist():
self.emby = embyserver.Read_EmbyServer()
def playAll(self, itemids, startat):
- log = self.logMsg
window = utils.window
embyconn = utils.kodiSQL('emby')
@@ -38,8 +37,8 @@ class Playlist():
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
playlist.clear()
- log("---*** PLAY ALL ***---", 1)
- log("Items: %s and start at: %s" % (itemids, startat), 1)
+ self.logMsg("---*** PLAY ALL ***---", 1)
+ self.logMsg("Items: %s and start at: %s" % (itemids, startat), 1)
started = False
window('emby_customplaylist', value="true")
@@ -76,14 +75,12 @@ class Playlist():
def modifyPlaylist(self, itemids):
- log = self.logMsg
-
embyconn = utils.kodiSQL('emby')
embycursor = embyconn.cursor()
emby_db = embydb.Embydb_Functions(embycursor)
- log("---*** ADD TO PLAYLIST ***---", 1)
- log("Items: %s" % itemids, 1)
+ self.logMsg("---*** ADD TO PLAYLIST ***---", 1)
+ self.logMsg("Items: %s" % itemids, 1)
# player = xbmc.Player()
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
@@ -101,7 +98,7 @@ class Playlist():
# Add to playlist
self.addtoPlaylist(dbid, mediatype)
- log("Adding %s to playlist." % itemid, 1)
+ self.logMsg("Adding %s to playlist." % itemid, 1)
self.verifyPlaylist()
embycursor.close()
@@ -124,8 +121,7 @@ class Playlist():
else:
pl['params']['item'] = {'file': url}
- result = xbmc.executeJSONRPC(json.dumps(pl))
- self.logMsg(result, 2)
+ self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)
def addtoPlaylist_xbmc(self, playlist, item):
path = "plugin://plugin.video.plexkodiconnect.movies/"
@@ -160,8 +156,7 @@ class Playlist():
else:
pl['params']['item'] = {'file': url}
- result = xbmc.executeJSONRPC(json.dumps(pl))
- self.logMsg(result, 2)
+ self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)
def verifyPlaylist(self):
@@ -176,8 +171,7 @@ class Playlist():
'properties': ['title', 'file']
}
}
- result = xbmc.executeJSONRPC(json.dumps(pl))
- self.logMsg(result, 2)
+ self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)
def removefromPlaylist(self, position):
@@ -192,5 +186,4 @@ class Playlist():
'position': position
}
}
- result = xbmc.executeJSONRPC(json.dumps(pl))
- self.logMsg(result, 2)
\ No newline at end of file
+ self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)
diff --git a/resources/lib/playutils.py b/resources/lib/playutils.py
index f5e5dd34..09da276f 100644
--- a/resources/lib/playutils.py
+++ b/resources/lib/playutils.py
@@ -56,7 +56,7 @@ class PlayUtils():
utils.window('emby_%s.playmethod' % playurl, "DirectStream")
elif self.isTranscoding():
- log("File is transcoding.", 1)
+ self.logMsg("File is transcoding.", 1)
quality = {
'maxVideoBitrate': self.getBitrate(),
'videoResolution': self.getResolution(),
@@ -73,16 +73,14 @@ class PlayUtils():
def httpPlay(self):
# Audio, Video, Photo
- item = self.item
- server = self.server
- itemid = item['Id']
- mediatype = item['MediaType']
+ itemid = self.item['Id']
+ mediatype = self.item['MediaType']
if mediatype == "Audio":
- playurl = "%s/emby/Audio/%s/stream" % (server, itemid)
+ playurl = "%s/emby/Audio/%s/stream" % (self.server, itemid)
else:
- playurl = "%s/emby/Videos/%s/stream?static=true" % (server, itemid)
+ playurl = "%s/emby/Videos/%s/stream?static=true" % (self.server, itemid)
return playurl
@@ -117,20 +115,16 @@ class PlayUtils():
def directPlay(self):
- item = self.item
-
try:
- playurl = item['MediaSources'][0]['Path']
+ playurl = self.item['MediaSources'][0]['Path']
except (IndexError, KeyError):
- playurl = item['Path']
+ playurl = self.item['Path']
- if item.get('VideoType'):
+ if self.item.get('VideoType'):
# Specific format modification
- type = item['VideoType']
-
- if type == "Dvd":
+ if self.item['VideoType'] == "Dvd":
playurl = "%s/VIDEO_TS/VIDEO_TS.IFO" % playurl
- elif type == "BluRay":
+ elif self.item['VideoType'] == "BluRay":
playurl = "%s/BDMV/index.bdmv" % playurl
# Assign network protocol
@@ -146,26 +140,24 @@ class PlayUtils():
def fileExists(self):
- log = self.logMsg
-
if 'Path' not in self.item:
# File has no path defined in server
return False
# Convert path to direct play
path = self.directPlay()
- log("Verifying path: %s" % path, 1)
+ self.logMsg("Verifying path: %s" % path, 1)
if xbmcvfs.exists(path):
- log("Path exists.", 1)
+ self.logMsg("Path exists.", 1)
return True
elif ":" not in path:
- log("Can't verify path, assumed linux. Still try to direct play.", 1)
+ self.logMsg("Can't verify path, assumed linux. Still try to direct play.", 1)
return True
else:
- log("Failed to find file.", 1)
+ self.logMsg("Failed to find file.", 1)
return False
def h265enabled(self):
@@ -197,6 +189,8 @@ class PlayUtils():
if utils.settings('playType') == "2":
# User forcing to play via HTTP
self.logMsg("User chose to transcode", 1)
+ self.logMsg("Resolution is: %sP, transcode for resolution: %sP+"
+ canDirectStream = self.item['MediaSources'][0]['SupportsDirectStream']
return False
if self.h265enabled():
return False
@@ -244,26 +238,19 @@ class PlayUtils():
return True
def isTranscoding(self):
- # I hope Plex transcodes everything
- return True
- item = self.item
-
- canTranscode = item['MediaSources'][0]['SupportsTranscoding']
# Make sure the server supports it
- if not canTranscode:
+ if not self.item['MediaSources'][0]['SupportsTranscoding']:
return False
return True
def transcoding(self):
- item = self.item
-
- if 'Path' in item and item['Path'].endswith('.strm'):
+ if 'Path' in self.item and self.item['Path'].endswith('.strm'):
# Allow strm loading when transcoding
playurl = self.directPlay()
else:
- itemid = item['Id']
+ itemid = self.item['Id']
deviceId = self.clientInfo.getDeviceId()
playurl = (
"%s/emby/Videos/%s/master.m3u8?MediaSourceId=%s"
diff --git a/resources/lib/read_embyserver.py b/resources/lib/read_embyserver.py
index 775fe600..1b58c025 100644
--- a/resources/lib/read_embyserver.py
+++ b/resources/lib/read_embyserver.py
@@ -31,8 +31,7 @@ class Read_EmbyServer():
# This will return the full item
item = {}
- url = "{server}/emby/Users/{UserId}/Items/%s?format=json" % itemid
- result = self.doUtils(url)
+ result = self.doUtils("{server}/emby/Users/{UserId}/Items/%s?format=json" % itemid)
if result:
item = result
@@ -45,13 +44,12 @@ class Read_EmbyServer():
itemlists = self.split_list(itemlist, 50)
for itemlist in itemlists:
# Will return basic information
- url = "{server}/emby/Users/{UserId}/Items?&format=json"
params = {
'Ids': ",".join(itemlist),
'Fields': "Etag"
}
- result = self.doUtils(url, parameters=params)
+ result = self.doUtils("{server}/emby/Users/{UserId}/Items?&format=json", parameters=params)
if result:
items.extend(result['Items'])
@@ -64,7 +62,6 @@ class Read_EmbyServer():
itemlists = self.split_list(itemlist, 50)
for itemlist in itemlists:
- url = "{server}/emby/Users/{UserId}/Items?format=json"
params = {
"Ids": ",".join(itemlist),
@@ -75,10 +72,10 @@ class Read_EmbyServer():
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers,"
- "MediaSources"
+ "MediaSources,VoteCount"
)
}
- result = self.doUtils(url, parameters=params)
+ result = self.doUtils("{server}/emby/Users/{UserId}/Items?format=json", parameters=params)
if result:
items.extend(result['Items'])
@@ -87,13 +84,10 @@ class Read_EmbyServer():
def getView_embyId(self, itemid):
# Returns ancestors using embyId
viewId = None
- url = "{server}/emby/Items/%s/Ancestors?UserId={UserId}&format=json" % itemid
- result = self.doUtils(url)
- for view in result:
+ for view in self.doUtils("{server}/emby/Items/%s/Ancestors?UserId={UserId}&format=json" % itemid):
- viewtype = view['Type']
- if viewtype == "CollectionFolder":
+ if view['Type'] == "CollectionFolder":
# Found view
viewId = view['Id']
@@ -120,8 +114,6 @@ class Read_EmbyServer():
return [viewName, viewId, mediatype]
def getFilteredSection(self, parentid, itemtype=None, sortby="SortName", recursive=True, limit=None, sortorder="Ascending", filter=""):
- doUtils = self.doUtils
- url = "{server}/emby/Users/{UserId}/Items?format=json"
params = {
'ParentId': parentid,
@@ -140,11 +132,9 @@ class Read_EmbyServer():
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers")
}
- return doUtils(url, parameters=params)
+ return self.doUtils("{server}/emby/Users/{UserId}/Items?format=json", parameters=params)
def getTvChannels(self):
- doUtils = self.doUtils
- url = "{server}/emby/LiveTv/Channels/?userid={UserId}&format=json"
params = {
'EnableImages': True,
@@ -154,11 +144,9 @@ class Read_EmbyServer():
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers")
}
- return doUtils(url, parameters=params)
+ return self.doUtils("{server}/emby/LiveTv/Channels/?userid={UserId}&format=json", parameters=params)
def getTvRecordings(self, groupid):
- doUtils = self.doUtils
- url = "{server}/emby/LiveTv/Recordings/?userid={UserId}&format=json"
if groupid == "root": groupid = ""
params = {
@@ -170,13 +158,10 @@ class Read_EmbyServer():
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers")
}
- return doUtils(url, parameters=params)
+ return self.doUtils("{server}/emby/LiveTv/Recordings/?userid={UserId}&format=json", parameters=params)
def getSection(self, parentid, itemtype=None, sortby="SortName", basic=False, dialog=None):
- log = self.logMsg
-
- doUtils = self.doUtils
items = {
'Items': [],
@@ -195,13 +180,13 @@ class Read_EmbyServer():
'Recursive': True,
'Limit': 1
}
- result = doUtils(url, parameters=params)
+ result = self.doUtils(url, parameters=params)
try:
total = result['TotalRecordCount']
items['TotalRecordCount'] = total
except TypeError: # Failed to retrieve
- log("%s:%s Failed to retrieve the server response." % (url, params), 2)
+ self.logMsg("%s:%s Failed to retrieve the server response." % (url, params), 2)
else:
index = 0
@@ -234,36 +219,36 @@ class Read_EmbyServer():
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers,"
- "MediaSources"
+ "MediaSources,VoteCount"
)
- result = doUtils(url, parameters=params)
+ result = self.doUtils(url, parameters=params)
try:
items['Items'].extend(result['Items'])
except TypeError:
# Something happened to the connection
if not throttled:
throttled = True
- log("Throttle activated.", 1)
+ self.logMsg("Throttle activated.", 1)
if jump == highestjump:
# We already tried with the highestjump, but it failed. Reset value.
- log("Reset highest value.", 1)
+ self.logMsg("Reset highest value.", 1)
highestjump = 0
# Lower the number by half
if highestjump:
throttled = False
jump = highestjump
- log("Throttle deactivated.", 1)
+ self.logMsg("Throttle deactivated.", 1)
else:
jump = int(jump/4)
- log("Set jump limit to recover: %s" % jump, 2)
+ self.logMsg("Set jump limit to recover: %s" % jump, 2)
retry = 0
while utils.window('emby_online') != "true":
# Wait server to come back online
if retry == 5:
- log("Unable to reconnect to server. Abort process.", 1)
+ self.logMsg("Unable to reconnect to server. Abort process.", 1)
return items
retry += 1
@@ -291,12 +276,11 @@ class Read_EmbyServer():
increment = 10
jump += increment
- log("Increase jump limit to: %s" % jump, 1)
+ self.logMsg("Increase jump limit to: %s" % jump, 1)
return items
def getViews(self, mediatype="", root=False, sortedlist=False):
# Build a list of user views
- doUtils = self.doUtils
views = []
mediatype = mediatype.lower()
@@ -305,7 +289,7 @@ class Read_EmbyServer():
else: # Views ungrouped
url = "{server}/emby/Users/{UserId}/Items?Sortby=SortName&format=json"
- result = doUtils(url)
+ result = self.doUtils(url)
try:
items = result['Items']
except TypeError:
@@ -313,11 +297,8 @@ class Read_EmbyServer():
else:
for item in items:
- name = item['Name']
- itemId = item['Id']
- viewtype = item['Type']
-
- if viewtype == "Channel":
+ item['Name'] = item['Name']
+ if item['Type'] == "Channel":
# Filter view types
continue
@@ -328,20 +309,20 @@ class Read_EmbyServer():
# Assumed missing is mixed then.
'''if itemtype is None:
url = "{server}/emby/Library/MediaFolders?format=json"
- result = doUtils(url)
+ result = self.doUtils(url)
for folder in result['Items']:
- if itemId == folder['Id']:
+ if item['Id'] == folder['Id']:
itemtype = folder.get('CollectionType', "mixed")'''
- if name not in ('Collections', 'Trailers'):
+ if item['Name'] not in ('Collections', 'Trailers'):
if sortedlist:
views.append({
- 'name': name,
+ 'name': item['Name'],
'type': itemtype,
- 'id': itemId
+ 'id': item['Id']
})
elif (itemtype == mediatype or
@@ -349,9 +330,9 @@ class Read_EmbyServer():
views.append({
- 'name': name,
+ 'name': item['Name'],
'type': itemtype,
- 'id': itemId
+ 'id': item['Id']
})
return views
@@ -359,8 +340,6 @@ class Read_EmbyServer():
def verifyView(self, parentid, itemid):
belongs = False
-
- url = "{server}/emby/Users/{UserId}/Items?format=json"
params = {
'ParentId': parentid,
@@ -370,7 +349,7 @@ class Read_EmbyServer():
'Recursive': True,
'Ids': itemid
}
- result = self.doUtils(url, parameters=params)
+ result = self.doUtils("{server}/emby/Users/{UserId}/Items?format=json", parameters=params)
try:
total = result['TotalRecordCount']
except TypeError:
@@ -383,40 +362,23 @@ class Read_EmbyServer():
return belongs
def getMovies(self, parentId, basic=False, dialog=None):
-
- items = self.getSection(parentId, "Movie", basic=basic, dialog=dialog)
-
- return items
+ return self.getSection(parentId, "Movie", basic=basic, dialog=dialog)
def getBoxset(self, dialog=None):
-
- items = self.getSection(None, "BoxSet", dialog=dialog)
-
- return items
+ return self.getSection(None, "BoxSet", dialog=dialog)
def getMovies_byBoxset(self, boxsetid):
-
- items = self.getSection(boxsetid, "Movie")
-
- return items
+ return self.getSection(boxsetid, "Movie")
def getMusicVideos(self, parentId, basic=False, dialog=None):
-
- items = self.getSection(parentId, "MusicVideo", basic=basic, dialog=dialog)
-
- return items
+ return self.getSection(parentId, "MusicVideo", basic=basic, dialog=dialog)
def getHomeVideos(self, parentId):
- items = self.getSection(parentId, "Video")
-
- return items
+ return self.getSection(parentId, "Video")
def getShows(self, parentId, basic=False, dialog=None):
-
- items = self.getSection(parentId, "Series", basic=basic, dialog=dialog)
-
- return items
+ return self.getSection(parentId, "Series", basic=basic, dialog=dialog)
def getSeasons(self, showId):
@@ -426,13 +388,12 @@ class Read_EmbyServer():
'TotalRecordCount': 0
}
- url = "{server}/emby/Shows/%s/Seasons?UserId={UserId}&format=json" % showId
params = {
'IsVirtualUnaired': False,
'Fields': "Etag"
}
- result = self.doUtils(url, parameters=params)
+ result = self.doUtils("{server}/emby/Shows/%s/Seasons?UserId={UserId}&format=json" % showId, parameters=params)
if result:
items = result
@@ -440,25 +401,19 @@ class Read_EmbyServer():
def getEpisodes(self, parentId, basic=False, dialog=None):
- items = self.getSection(parentId, "Episode", basic=basic, dialog=dialog)
-
- return items
+ return self.getSection(parentId, "Episode", basic=basic, dialog=dialog)
def getEpisodesbyShow(self, showId):
- items = self.getSection(showId, "Episode")
-
- return items
+ return self.getSection(showId, "Episode")
def getEpisodesbySeason(self, seasonId):
- items = self.getSection(seasonId, "Episode")
+ return self.getSection(seasonId, "Episode")
- return items
def getArtists(self, dialog=None):
- doUtils = self.doUtils
items = {
'Items': [],
@@ -472,7 +427,7 @@ class Read_EmbyServer():
'Recursive': True,
'Limit': 1
}
- result = doUtils(url, parameters=params)
+ result = self.doUtils(url, parameters=params)
try:
total = result['TotalRecordCount']
items['TotalRecordCount'] = total
@@ -502,7 +457,7 @@ class Read_EmbyServer():
"AirTime,DateCreated,MediaStreams,People,ProviderIds,Overview"
)
}
- result = doUtils(url, parameters=params)
+ result = self.doUtils(url, parameters=params)
items['Items'].extend(result['Items'])
index += jump
@@ -512,28 +467,17 @@ class Read_EmbyServer():
return items
def getAlbums(self, basic=False, dialog=None):
-
- items = self.getSection(None, "MusicAlbum", sortby="DateCreated", basic=basic, dialog=dialog)
-
- return items
+ return self.getSection(None, "MusicAlbum", sortby="DateCreated", basic=basic, dialog=dialog)
def getAlbumsbyArtist(self, artistId):
-
- items = self.getSection(artistId, "MusicAlbum", sortby="DateCreated")
-
- return items
+ return self.getSection(artistId, "MusicAlbum", sortby="DateCreated")
def getSongs(self, basic=False, dialog=None):
-
- items = self.getSection(None, "Audio", basic=basic, dialog=dialog)
-
- return items
+ return self.getSection(None, "Audio", basic=basic, dialog=dialog)
def getSongsbyAlbum(self, albumId):
+ return self.getSection(albumId, "Audio")
- items = self.getSection(albumId, "Audio")
-
- return items
def getAdditionalParts(self, itemId):
@@ -543,8 +487,7 @@ class Read_EmbyServer():
'TotalRecordCount': 0
}
- url = "{server}/emby/Videos/%s/AdditionalParts?UserId={UserId}&format=json" % itemId
- result = self.doUtils(url)
+ result = self.doUtils("{server}/emby/Videos/%s/AdditionalParts?UserId={UserId}&format=json" % itemId)
if result:
items = result
@@ -566,25 +509,21 @@ class Read_EmbyServer():
def updateUserRating(self, itemid, like=None, favourite=None, deletelike=False):
# Updates the user rating to Emby
- doUtils = self.doUtils
if favourite:
- url = "{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid
- doUtils(url, type="POST")
+ self.doUtils("{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid, action_type="POST")
elif favourite == False:
- url = "{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid
- doUtils(url, type="DELETE")
+ self.doUtils("{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid, action_type="DELETE")
if not deletelike and like:
- url = "{server}/emby/Users/{UserId}/Items/%s/Rating?Likes=true&format=json" % itemid
- doUtils(url, type="POST")
- elif not deletelike and like == False:
- url = "{server}/emby/Users/{UserId}/Items/%s/Rating?Likes=false&format=json" % itemid
- doUtil(url, type="POST")
+ self.doUtils("{server}/emby/Users/{UserId}/Items/%s/Rating?Likes=true&format=json" % itemid, action_type="POST")
+ elif not deletelike and like is False:
+ self.doUtils("{server}/emby/Users/{UserId}/Items/%s/Rating?Likes=false&format=json" % itemid, action_type="POST")
elif deletelike:
- url = "{server}/emby/Users/{UserId}/Items/%s/Rating?format=json" % itemid
- doUtils(url, type="DELETE")
+ self.doUtils("{server}/emby/Users/{UserId}/Items/%s/Rating?format=json" % itemid, action_type="DELETE")
+ else:
+ self.logMsg("Error processing user rating.", 1)
self.logMsg("Update user rating to emby for itemid: %s "
"| like: %s | favourite: %s | deletelike: %s"
- % (itemid, like, favourite, deletelike), 1)
\ No newline at end of file
+ % (itemid, like, favourite, deletelike), 1)
diff --git a/resources/lib/utils.py b/resources/lib/utils.py
index 091a044b..1e4a9bd9 100644
--- a/resources/lib/utils.py
+++ b/resources/lib/utils.py
@@ -299,7 +299,7 @@ def window(property, value=None, clear=False, windowid=10000):
Property needs to be string; value may be string or unicode
"""
WINDOW = xbmcgui.Window(windowid)
-
+
#setproperty accepts both string and unicode but utf-8 strings are adviced by kodi devs because some unicode can give issues
'''if isinstance(property, unicode):
property = property.encode("utf-8")
@@ -334,23 +334,22 @@ def language(stringid):
string = addon.getLocalizedString(stringid) #returns unicode object
return string
-def kodiSQL(type="video"):
-
- if type == "emby":
+def kodiSQL(media_type="video"):
+
+ if media_type == "emby":
dbPath = xbmc.translatePath("special://database/emby.db").decode('utf-8')
- elif type == "music":
+ elif media_type == "music":
dbPath = getKodiMusicDBPath()
- elif type == "texture":
+ elif media_type == "texture":
dbPath = xbmc.translatePath("special://database/Textures13.db").decode('utf-8')
else:
dbPath = getKodiVideoDBPath()
-
+
connection = sqlite3.connect(dbPath)
return connection
def getKodiVideoDBPath():
- kodibuild = xbmc.getInfoLabel('System.BuildVersion')[:2]
dbVersion = {
"13": 78, # Gotham
@@ -358,16 +357,16 @@ def getKodiVideoDBPath():
"15": 93, # Isengard
"16": 99, # Jarvis
"17":104 # Krypton
+ "17": 104 # Krypton
}
dbPath = xbmc.translatePath(
"special://database/MyVideos%s.db"
- % dbVersion.get(kodibuild, "")).decode('utf-8')
+ % dbVersion.get(xbmc.getInfoLabel('System.BuildVersion')[:2], "")).decode('utf-8')
return dbPath
def getKodiMusicDBPath():
- kodibuild = xbmc.getInfoLabel('System.BuildVersion')[:2]
dbVersion = {
"13": 46, # Gotham
@@ -379,7 +378,7 @@ def getKodiMusicDBPath():
dbPath = xbmc.translatePath(
"special://database/MyMusic%s.db"
- % dbVersion.get(kodibuild, "")).decode('utf-8')
+ % dbVersion.get(xbmc.getInfoLabel('System.BuildVersion')[:2], "")).decode('utf-8')
return dbPath
def getScreensaver():
@@ -394,11 +393,7 @@ def getScreensaver():
'setting': "screensaver.mode"
}
}
- result = xbmc.executeJSONRPC(json.dumps(query))
- result = json.loads(result)
- screensaver = result['result']['value']
-
- return screensaver
+ return json.loads(xbmc.executeJSONRPC(json.dumps(query)))['result']['value']
def setScreensaver(value):
# Toggle the screensaver
@@ -413,15 +408,13 @@ def setScreensaver(value):
'value': value
}
}
- result = xbmc.executeJSONRPC(json.dumps(query))
- logMsg("PLEX", "Toggling screensaver: %s %s" % (value, result), 1)
+ logMsg("PLEX", "Toggling screensaver: %s %s" % (value, xbmc.executeJSONRPC(json.dumps(query))), 1)
def reset():
dialog = xbmcgui.Dialog()
- resp = dialog.yesno("Warning", "Are you sure you want to reset your local Kodi database?")
- if resp == 0:
+ if dialog.yesno("Warning", "Are you sure you want to reset your local Kodi database?") == 0:
return
# first stop any db sync
@@ -483,7 +476,7 @@ def reset():
cursor.close()
# Offer to wipe cached thumbnails
- resp = dialog.yesno("Warning", "Removed all cached artwork?")
+ resp = dialog.yesno("Warning", "Remove all cached artwork?")
if resp:
logMsg("EMBY", "Resetting all cached artwork.", 0)
# Remove all existing textures first
@@ -497,7 +490,7 @@ def reset():
xbmcvfs.delete(os.path.join(path+dir.decode('utf-8'),file.decode('utf-8')))
else:
xbmcvfs.delete(os.path.join(path.encode('utf-8')+dir,file))
-
+
# remove all existing data from texture DB
connection = kodiSQL('texture')
cursor = connection.cursor()
@@ -509,8 +502,8 @@ def reset():
cursor.execute("DELETE FROM " + tableName)
connection.commit()
cursor.close()
-
- # reset the install run flag
+
+ # reset the install run flag
settings('SyncInstallRunDone', value="false")
# Remove emby info
@@ -532,7 +525,7 @@ def profiling(sortby="cumulative"):
# Will print results to Kodi log
def decorator(func):
def wrapper(*args, **kwargs):
-
+
pr = cProfile.Profile()
pr.enable()
@@ -576,7 +569,7 @@ def normalize_nodes(text):
# with dots at the end
text = text.rstrip('.')
text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore')
-
+
return text
def normalize_string(text):
@@ -717,7 +710,7 @@ def sourcesXML():
root = etree.Element('sources')
else:
root = xmlparse.getroot()
-
+
video = root.find('video')
if video is None:
@@ -729,7 +722,7 @@ def sourcesXML():
for source in root.findall('.//path'):
if source.text == "smb://":
count -= 1
-
+
if count == 0:
# sources already set
break
@@ -775,9 +768,7 @@ def passwordsXML():
elif option == 1:
# User selected remove
- iterator = root.getiterator('passwords')
-
- for paths in iterator:
+ for paths in root.getiterator('passwords'):
for path in paths:
if path.find('.//from').text == "smb://%s/" % credentials:
paths.remove(path)
@@ -788,7 +779,7 @@ def passwordsXML():
break
else:
logMsg("EMBY", "Failed to find saved server: %s in passwords.xml" % credentials, 1)
-
+
settings('networkCreds', value="")
xbmcgui.Dialog().notification(
heading='PlexKodiConnect',
@@ -842,7 +833,7 @@ def passwordsXML():
# Force Kodi to see the credentials without restarting
xbmcvfs.exists(topath)
- # Add credentials
+ # Add credentials
settings('networkCreds', value="%s" % server)
logMsg("PLEX", "Added server: %s to passwords.xml" % server, 1)
# Prettify and write to file
@@ -850,7 +841,7 @@ def passwordsXML():
indent(root)
except: pass
etree.ElementTree(root).write(xmlpath)
-
+
# dialog.notification(
# heading="PlexKodiConnect",
# message="Added to passwords.xml",
@@ -881,7 +872,7 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False):
if delete:
xbmcvfs.delete(xsppath.encode('utf-8'))
logMsg("PLEX", "Successfully removed playlist: %s." % tagname, 1)
-
+
return
# Using write process since there's no guarantee the xml declaration works with etree
@@ -949,4 +940,4 @@ def try_decode(text, encoding="utf-8"):
try:
return text.decode(encoding,"ignore")
except:
- return text
\ No newline at end of file
+ return text
diff --git a/resources/lib/videonodes.py b/resources/lib/videonodes.py
index 253223a5..fce5c169 100644
--- a/resources/lib/videonodes.py
+++ b/resources/lib/videonodes.py
@@ -54,7 +54,6 @@ class VideoNodes(object):
mediatype = mediatypes[mediatype]
window = utils.window
- kodiversion = self.kodiversion
if viewtype == "mixed":
dirname = "%s-%s" % (viewid, mediatype)
@@ -231,7 +230,7 @@ class VideoNodes(object):
# Custom query
path = ("plugin://plugin.video.plexkodiconnect/?id=%s&mode=recentepisodes&type=%s&tagname=%s&limit=%s"
% (viewid, mediatype, tagname, limit))
- elif kodiversion == 14 and nodetype == "inprogressepisodes":
+ elif self.kodiversion == 14 and nodetype == "inprogressepisodes":
# Custom query
path = "plugin://plugin.video.plexkodiconnect/?id=%s&mode=inprogressepisodes&limit=%s" % (tagname, limit)
elif nodetype == 'ondeck':
diff --git a/resources/lib/websocket.py b/resources/lib/websocket.py
index 3d777a97..e35d1966 100644
--- a/resources/lib/websocket.py
+++ b/resources/lib/websocket.py
@@ -1,912 +1,911 @@
-"""
-websocket - WebSocket client library for Python
-
-Copyright (C) 2010 Hiroki Ohtani(liris)
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library; if not, write to the Free Software
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
-"""
-
-
-import socket
-
-try:
- import ssl
- from ssl import SSLError
- HAVE_SSL = True
-except ImportError:
- # dummy class of SSLError for ssl none-support environment.
- class SSLError(Exception):
- pass
-
- HAVE_SSL = False
-
-from urlparse import urlparse
-import os
-import array
-import struct
-import uuid
-import hashlib
-import base64
-import threading
-import time
-import logging
-import traceback
-import sys
-
-"""
-websocket python client.
-=========================
-
-This version support only hybi-13.
-Please see http://tools.ietf.org/html/rfc6455 for protocol.
-"""
-
-
-# websocket supported version.
-VERSION = 13
-
-# closing frame status codes.
-STATUS_NORMAL = 1000
-STATUS_GOING_AWAY = 1001
-STATUS_PROTOCOL_ERROR = 1002
-STATUS_UNSUPPORTED_DATA_TYPE = 1003
-STATUS_STATUS_NOT_AVAILABLE = 1005
-STATUS_ABNORMAL_CLOSED = 1006
-STATUS_INVALID_PAYLOAD = 1007
-STATUS_POLICY_VIOLATION = 1008
-STATUS_MESSAGE_TOO_BIG = 1009
-STATUS_INVALID_EXTENSION = 1010
-STATUS_UNEXPECTED_CONDITION = 1011
-STATUS_TLS_HANDSHAKE_ERROR = 1015
-
-logger = logging.getLogger()
-
-
-class WebSocketException(Exception):
- """
- websocket exeception class.
- """
- pass
-
-
-class WebSocketConnectionClosedException(WebSocketException):
- """
- If remote host closed the connection or some network error happened,
- this exception will be raised.
- """
- pass
-
-class WebSocketTimeoutException(WebSocketException):
- """
- WebSocketTimeoutException will be raised at socket timeout during read/write data.
- """
- pass
-
-default_timeout = None
-traceEnabled = False
-
-
-def enableTrace(tracable):
- """
- turn on/off the tracability.
-
- tracable: boolean value. if set True, tracability is enabled.
- """
- global traceEnabled
- traceEnabled = tracable
- if tracable:
- if not logger.handlers:
- logger.addHandler(logging.StreamHandler())
- logger.setLevel(logging.DEBUG)
-
-
-def setdefaulttimeout(timeout):
- """
- Set the global timeout setting to connect.
-
- timeout: default socket timeout time. This value is second.
- """
- global default_timeout
- default_timeout = timeout
-
-
-def getdefaulttimeout():
- """
- Return the global timeout setting(second) to connect.
- """
- return default_timeout
-
-
-def _parse_url(url):
- """
- parse url and the result is tuple of
- (hostname, port, resource path and the flag of secure mode)
-
- url: url string.
- """
- if ":" not in url:
- raise ValueError("url is invalid")
-
- scheme, url = url.split(":", 1)
-
- parsed = urlparse(url, scheme="http")
- if parsed.hostname:
- hostname = parsed.hostname
- else:
- raise ValueError("hostname is invalid")
- port = 0
- if parsed.port:
- port = parsed.port
-
- is_secure = False
- if scheme == "ws":
- if not port:
- port = 80
- elif scheme == "wss":
- is_secure = True
- if not port:
- port = 443
- else:
- raise ValueError("scheme %s is invalid" % scheme)
-
- if parsed.path:
- resource = parsed.path
- else:
- resource = "/"
-
- if parsed.query:
- resource += "?" + parsed.query
-
- return (hostname, port, resource, is_secure)
-
-
-def create_connection(url, timeout=None, **options):
- """
- connect to url and return websocket object.
-
- Connect to url and return the WebSocket object.
- Passing optional timeout parameter will set the timeout on the socket.
- If no timeout is supplied, the global default timeout setting returned by getdefauttimeout() is used.
- You can customize using 'options'.
- If you set "header" list object, you can set your own custom header.
-
- >>> conn = create_connection("ws://echo.websocket.org/",
- ... header=["User-Agent: MyProgram",
- ... "x-custom: header"])
-
-
- timeout: socket timeout time. This value is integer.
- if you set None for this value, it means "use default_timeout value"
-
- options: current support option is only "header".
- if you set header as dict value, the custom HTTP headers are added.
- """
- sockopt = options.get("sockopt", [])
- sslopt = options.get("sslopt", {})
- websock = WebSocket(sockopt=sockopt, sslopt=sslopt)
- websock.settimeout(timeout if timeout is not None else default_timeout)
- websock.connect(url, **options)
- return websock
-
-_MAX_INTEGER = (1 << 32) -1
-_AVAILABLE_KEY_CHARS = range(0x21, 0x2f + 1) + range(0x3a, 0x7e + 1)
-_MAX_CHAR_BYTE = (1<<8) -1
-
-# ref. Websocket gets an update, and it breaks stuff.
-# http://axod.blogspot.com/2010/06/websocket-gets-update-and-it-breaks.html
-
-
-def _create_sec_websocket_key():
- uid = uuid.uuid4()
- return base64.encodestring(uid.bytes).strip()
-
-
-_HEADERS_TO_CHECK = {
- "upgrade": "websocket",
- "connection": "upgrade",
- }
-
-
-class ABNF(object):
- """
- ABNF frame class.
- see http://tools.ietf.org/html/rfc5234
- and http://tools.ietf.org/html/rfc6455#section-5.2
- """
-
- # operation code values.
- OPCODE_CONT = 0x0
- OPCODE_TEXT = 0x1
- OPCODE_BINARY = 0x2
- OPCODE_CLOSE = 0x8
- OPCODE_PING = 0x9
- OPCODE_PONG = 0xa
-
- # available operation code value tuple
- OPCODES = (OPCODE_CONT, OPCODE_TEXT, OPCODE_BINARY, OPCODE_CLOSE,
- OPCODE_PING, OPCODE_PONG)
-
- # opcode human readable string
- OPCODE_MAP = {
- OPCODE_CONT: "cont",
- OPCODE_TEXT: "text",
- OPCODE_BINARY: "binary",
- OPCODE_CLOSE: "close",
- OPCODE_PING: "ping",
- OPCODE_PONG: "pong"
- }
-
- # data length threashold.
- LENGTH_7 = 0x7d
- LENGTH_16 = 1 << 16
- LENGTH_63 = 1 << 63
-
- def __init__(self, fin=0, rsv1=0, rsv2=0, rsv3=0,
- opcode=OPCODE_TEXT, mask=1, data=""):
- """
- Constructor for ABNF.
- please check RFC for arguments.
- """
- self.fin = fin
- self.rsv1 = rsv1
- self.rsv2 = rsv2
- self.rsv3 = rsv3
- self.opcode = opcode
- self.mask = mask
- self.data = data
- self.get_mask_key = os.urandom
-
- def __str__(self):
- return "fin=" + str(self.fin) \
- + " opcode=" + str(self.opcode) \
- + " data=" + str(self.data)
-
- @staticmethod
- def create_frame(data, opcode):
- """
- create frame to send text, binary and other data.
-
- data: data to send. This is string value(byte array).
- if opcode is OPCODE_TEXT and this value is uniocde,
- data value is conveted into unicode string, automatically.
-
- opcode: operation code. please see OPCODE_XXX.
- """
- if opcode == ABNF.OPCODE_TEXT and isinstance(data, unicode):
- data = data.encode("utf-8")
- # mask must be set if send data from client
- return ABNF(1, 0, 0, 0, opcode, 1, data)
-
- def format(self):
- """
- format this object to string(byte array) to send data to server.
- """
- if any(x not in (0, 1) for x in [self.fin, self.rsv1, self.rsv2, self.rsv3]):
- raise ValueError("not 0 or 1")
- if self.opcode not in ABNF.OPCODES:
- raise ValueError("Invalid OPCODE")
- length = len(self.data)
- if length >= ABNF.LENGTH_63:
- raise ValueError("data is too long")
-
- frame_header = chr(self.fin << 7
- | self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4
- | self.opcode)
- if length < ABNF.LENGTH_7:
- frame_header += chr(self.mask << 7 | length)
- elif length < ABNF.LENGTH_16:
- frame_header += chr(self.mask << 7 | 0x7e)
- frame_header += struct.pack("!H", length)
- else:
- frame_header += chr(self.mask << 7 | 0x7f)
- frame_header += struct.pack("!Q", length)
-
- if not self.mask:
- return frame_header + self.data
- else:
- mask_key = self.get_mask_key(4)
- return frame_header + self._get_masked(mask_key)
-
- def _get_masked(self, mask_key):
- s = ABNF.mask(mask_key, self.data)
- return mask_key + "".join(s)
-
- @staticmethod
- def mask(mask_key, data):
- """
- mask or unmask data. Just do xor for each byte
-
- mask_key: 4 byte string(byte).
-
- data: data to mask/unmask.
- """
- _m = array.array("B", mask_key)
- _d = array.array("B", data)
- for i in xrange(len(_d)):
- _d[i] ^= _m[i % 4]
- return _d.tostring()
-
-
-class WebSocket(object):
- """
- Low level WebSocket interface.
- This class is based on
- The WebSocket protocol draft-hixie-thewebsocketprotocol-76
- http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
-
- We can connect to the websocket server and send/recieve data.
- The following example is a echo client.
-
- >>> import websocket
- >>> ws = websocket.WebSocket()
- >>> ws.connect("ws://echo.websocket.org")
- >>> ws.send("Hello, Server")
- >>> ws.recv()
- 'Hello, Server'
- >>> ws.close()
-
- get_mask_key: a callable to produce new mask keys, see the set_mask_key
- function's docstring for more details
- sockopt: values for socket.setsockopt.
- sockopt must be tuple and each element is argument of sock.setscokopt.
- sslopt: dict object for ssl socket option.
- """
-
- def __init__(self, get_mask_key=None, sockopt=None, sslopt=None):
- """
- Initalize WebSocket object.
- """
- if sockopt is None:
- sockopt = []
- if sslopt is None:
- sslopt = {}
- self.connected = False
- self.sock = socket.socket()
- for opts in sockopt:
- self.sock.setsockopt(*opts)
- self.sslopt = sslopt
- self.get_mask_key = get_mask_key
- # Buffers over the packets from the layer beneath until desired amount
- # bytes of bytes are received.
- self._recv_buffer = []
- # These buffer over the build-up of a single frame.
- self._frame_header = None
- self._frame_length = None
- self._frame_mask = None
- self._cont_data = None
-
- def fileno(self):
- return self.sock.fileno()
-
- def set_mask_key(self, func):
- """
- set function to create musk key. You can custumize mask key generator.
- Mainly, this is for testing purpose.
-
- func: callable object. the fuct must 1 argument as integer.
- The argument means length of mask key.
- This func must be return string(byte array),
- which length is argument specified.
- """
- self.get_mask_key = func
-
- def gettimeout(self):
- """
- Get the websocket timeout(second).
- """
- return self.sock.gettimeout()
-
- def settimeout(self, timeout):
- """
- Set the timeout to the websocket.
-
- timeout: timeout time(second).
- """
- self.sock.settimeout(timeout)
-
- timeout = property(gettimeout, settimeout)
-
- def connect(self, url, **options):
- """
- Connect to url. url is websocket url scheme. ie. ws://host:port/resource
- You can customize using 'options'.
- If you set "header" dict object, you can set your own custom header.
-
- >>> ws = WebSocket()
- >>> ws.connect("ws://echo.websocket.org/",
- ... header={"User-Agent: MyProgram",
- ... "x-custom: header"})
-
- timeout: socket timeout time. This value is integer.
- if you set None for this value,
- it means "use default_timeout value"
-
- options: current support option is only "header".
- if you set header as dict value,
- the custom HTTP headers are added.
-
- """
- hostname, port, resource, is_secure = _parse_url(url)
- # TODO: we need to support proxy
- self.sock.connect((hostname, port))
- if is_secure:
- if HAVE_SSL:
- if self.sslopt is None:
- sslopt = {}
- else:
- sslopt = self.sslopt
- self.sock = ssl.wrap_socket(self.sock, **sslopt)
- else:
- raise WebSocketException("SSL not available.")
-
- self._handshake(hostname, port, resource, **options)
-
- def _handshake(self, host, port, resource, **options):
- sock = self.sock
- headers = []
- headers.append("GET %s HTTP/1.1" % resource)
- headers.append("Upgrade: websocket")
- headers.append("Connection: Upgrade")
- if port == 80:
- hostport = host
- else:
- hostport = "%s:%d" % (host, port)
- headers.append("Host: %s" % hostport)
-
- if "origin" in options:
- headers.append("Origin: %s" % options["origin"])
- else:
- headers.append("Origin: http://%s" % hostport)
-
- key = _create_sec_websocket_key()
- headers.append("Sec-WebSocket-Key: %s" % key)
- headers.append("Sec-WebSocket-Version: %s" % VERSION)
- if "header" in options:
- headers.extend(options["header"])
-
- headers.append("")
- headers.append("")
-
- header_str = "\r\n".join(headers)
- self._send(header_str)
- if traceEnabled:
- logger.debug("--- request header ---")
- logger.debug(header_str)
- logger.debug("-----------------------")
-
- status, resp_headers = self._read_headers()
- if status != 101:
- self.close()
- raise WebSocketException("Handshake Status %d" % status)
-
- success = self._validate_header(resp_headers, key)
- if not success:
- self.close()
- raise WebSocketException("Invalid WebSocket Header")
-
- self.connected = True
-
- def _validate_header(self, headers, key):
- for k, v in _HEADERS_TO_CHECK.iteritems():
- r = headers.get(k, None)
- if not r:
- return False
- r = r.lower()
- if v != r:
- return False
-
- result = headers.get("sec-websocket-accept", None)
- if not result:
- return False
- result = result.lower()
-
- value = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
- hashed = base64.encodestring(hashlib.sha1(value).digest()).strip().lower()
- return hashed == result
-
- def _read_headers(self):
- status = None
- headers = {}
- if traceEnabled:
- logger.debug("--- response header ---")
-
- while True:
- line = self._recv_line()
- if line == "\r\n":
- break
- line = line.strip()
- if traceEnabled:
- logger.debug(line)
- if not status:
- status_info = line.split(" ", 2)
- status = int(status_info[1])
- else:
- kv = line.split(":", 1)
- if len(kv) == 2:
- key, value = kv
- headers[key.lower()] = value.strip().lower()
- else:
- raise WebSocketException("Invalid header")
-
- if traceEnabled:
- logger.debug("-----------------------")
-
- return status, headers
-
- def send(self, payload, opcode=ABNF.OPCODE_TEXT):
- """
- Send the data as string.
-
- payload: Payload must be utf-8 string or unicoce,
- if the opcode is OPCODE_TEXT.
- Otherwise, it must be string(byte array)
-
- opcode: operation code to send. Please see OPCODE_XXX.
- """
- frame = ABNF.create_frame(payload, opcode)
- if self.get_mask_key:
- frame.get_mask_key = self.get_mask_key
- data = frame.format()
- length = len(data)
- if traceEnabled:
- logger.debug("send: " + repr(data))
- while data:
- l = self._send(data)
- data = data[l:]
- return length
-
- def send_binary(self, payload):
- return self.send(payload, ABNF.OPCODE_BINARY)
-
- def ping(self, payload=""):
- """
- send ping data.
-
- payload: data payload to send server.
- """
- self.send(payload, ABNF.OPCODE_PING)
-
- def pong(self, payload):
- """
- send pong data.
-
- payload: data payload to send server.
- """
- self.send(payload, ABNF.OPCODE_PONG)
-
- def recv(self):
- """
- Receive string data(byte array) from the server.
-
- return value: string(byte array) value.
- """
- opcode, data = self.recv_data()
- return data
-
- def recv_data(self):
- """
- Recieve data with operation code.
-
- return value: tuple of operation code and string(byte array) value.
- """
- while True:
- frame = self.recv_frame()
- if not frame:
- # handle error:
- # 'NoneType' object has no attribute 'opcode'
- raise WebSocketException("Not a valid frame %s" % frame)
- elif frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY, ABNF.OPCODE_CONT):
- if frame.opcode == ABNF.OPCODE_CONT and not self._cont_data:
- raise WebSocketException("Illegal frame")
- if self._cont_data:
- self._cont_data[1] += frame.data
- else:
- self._cont_data = [frame.opcode, frame.data]
-
- if frame.fin:
- data = self._cont_data
- self._cont_data = None
- return data
- elif frame.opcode == ABNF.OPCODE_CLOSE:
- self.send_close()
- return (frame.opcode, None)
- elif frame.opcode == ABNF.OPCODE_PING:
- self.pong(frame.data)
-
- def recv_frame(self):
- """
- recieve data as frame from server.
-
- return value: ABNF frame object.
- """
- # Header
- if self._frame_header is None:
- self._frame_header = self._recv_strict(2)
- b1 = ord(self._frame_header[0])
- fin = b1 >> 7 & 1
- rsv1 = b1 >> 6 & 1
- rsv2 = b1 >> 5 & 1
- rsv3 = b1 >> 4 & 1
- opcode = b1 & 0xf
- b2 = ord(self._frame_header[1])
- has_mask = b2 >> 7 & 1
- # Frame length
- if self._frame_length is None:
- length_bits = b2 & 0x7f
- if length_bits == 0x7e:
- length_data = self._recv_strict(2)
- self._frame_length = struct.unpack("!H", length_data)[0]
- elif length_bits == 0x7f:
- length_data = self._recv_strict(8)
- self._frame_length = struct.unpack("!Q", length_data)[0]
- else:
- self._frame_length = length_bits
- # Mask
- if self._frame_mask is None:
- self._frame_mask = self._recv_strict(4) if has_mask else ""
- # Payload
- payload = self._recv_strict(self._frame_length)
- if has_mask:
- payload = ABNF.mask(self._frame_mask, payload)
- # Reset for next frame
- self._frame_header = None
- self._frame_length = None
- self._frame_mask = None
- return ABNF(fin, rsv1, rsv2, rsv3, opcode, has_mask, payload)
-
-
- def send_close(self, status=STATUS_NORMAL, reason=""):
- """
- send close data to the server.
-
- status: status code to send. see STATUS_XXX.
-
- reason: the reason to close. This must be string.
- """
- if status < 0 or status >= ABNF.LENGTH_16:
- raise ValueError("code is invalid range")
- self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE)
-
- def close(self, status=STATUS_NORMAL, reason=""):
- """
- Close Websocket object
-
- status: status code to send. see STATUS_XXX.
-
- reason: the reason to close. This must be string.
- """
-
- try:
- self.sock.shutdown(socket.SHUT_RDWR)
- except:
- pass
-
- '''
- if self.connected:
- if status < 0 or status >= ABNF.LENGTH_16:
- raise ValueError("code is invalid range")
-
- try:
- self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE)
- timeout = self.sock.gettimeout()
- self.sock.settimeout(3)
- try:
- frame = self.recv_frame()
- if logger.isEnabledFor(logging.ERROR):
- recv_status = struct.unpack("!H", frame.data)[0]
- if recv_status != STATUS_NORMAL:
- logger.error("close status: " + repr(recv_status))
- except:
- pass
- self.sock.settimeout(timeout)
- self.sock.shutdown(socket.SHUT_RDWR)
- except:
- pass
- '''
- self._closeInternal()
-
- def _closeInternal(self):
- self.connected = False
- self.sock.close()
-
- def _send(self, data):
- try:
- return self.sock.send(data)
- except socket.timeout as e:
- raise WebSocketTimeoutException(e.args[0])
- except Exception as e:
- if "timed out" in e.args[0]:
- raise WebSocketTimeoutException(e.args[0])
- else:
- raise e
-
- def _recv(self, bufsize):
- try:
- bytes = self.sock.recv(bufsize)
- except socket.timeout as e:
- raise WebSocketTimeoutException(e.args[0])
- except SSLError as e:
- if e.args[0] == "The read operation timed out":
- raise WebSocketTimeoutException(e.args[0])
- else:
- raise
- if not bytes:
- raise WebSocketConnectionClosedException()
- return bytes
-
-
- def _recv_strict(self, bufsize):
- shortage = bufsize - sum(len(x) for x in self._recv_buffer)
- while shortage > 0:
- bytes = self._recv(shortage)
- self._recv_buffer.append(bytes)
- shortage -= len(bytes)
- unified = "".join(self._recv_buffer)
- if shortage == 0:
- self._recv_buffer = []
- return unified
- else:
- self._recv_buffer = [unified[bufsize:]]
- return unified[:bufsize]
-
-
- def _recv_line(self):
- line = []
- while True:
- c = self._recv(1)
- line.append(c)
- if c == "\n":
- break
- return "".join(line)
-
-
-class WebSocketApp(object):
- """
- Higher level of APIs are provided.
- The interface is like JavaScript WebSocket object.
- """
- def __init__(self, url, header=[],
- on_open=None, on_message=None, on_error=None,
- on_close=None, keep_running=True, get_mask_key=None):
- """
- url: websocket url.
- header: custom header for websocket handshake.
- on_open: callable object which is called at opening websocket.
- this function has one argument. The arugment is this class object.
- on_message: callbale object which is called when recieved data.
- on_message has 2 arguments.
- The 1st arugment is this class object.
- The passing 2nd arugment is utf-8 string which we get from the server.
- on_error: callable object which is called when we get error.
- on_error has 2 arguments.
- The 1st arugment is this class object.
- The passing 2nd arugment is exception object.
- on_close: callable object which is called when closed the connection.
- this function has one argument. The arugment is this class object.
- keep_running: a boolean flag indicating whether the app's main loop should
- keep running, defaults to True
- get_mask_key: a callable to produce new mask keys, see the WebSocket.set_mask_key's
- docstring for more information
- """
- self.url = url
- self.header = header
- self.on_open = on_open
- self.on_message = on_message
- self.on_error = on_error
- self.on_close = on_close
- self.keep_running = keep_running
- self.get_mask_key = get_mask_key
- self.sock = None
-
- def send(self, data, opcode=ABNF.OPCODE_TEXT):
- """
- send message.
- data: message to send. If you set opcode to OPCODE_TEXT, data must be utf-8 string or unicode.
- opcode: operation code of data. default is OPCODE_TEXT.
- """
- if self.sock.send(data, opcode) == 0:
- raise WebSocketConnectionClosedException()
-
- def close(self):
- """
- close websocket connection.
- """
- self.keep_running = False
- if(self.sock != None):
- self.sock.close()
-
- def _send_ping(self, interval):
- while True:
- for i in range(interval):
- time.sleep(1)
- if not self.keep_running:
- return
- self.sock.ping()
-
- def run_forever(self, sockopt=None, sslopt=None, ping_interval=0):
- """
- run event loop for WebSocket framework.
- This loop is infinite loop and is alive during websocket is available.
- sockopt: values for socket.setsockopt.
- sockopt must be tuple and each element is argument of sock.setscokopt.
- sslopt: ssl socket optional dict.
- ping_interval: automatically send "ping" command every specified period(second)
- if set to 0, not send automatically.
- """
- if sockopt is None:
- sockopt = []
- if sslopt is None:
- sslopt = {}
- if self.sock:
- raise WebSocketException("socket is already opened")
- thread = None
- self.keep_running = True
-
- try:
- self.sock = WebSocket(self.get_mask_key, sockopt=sockopt, sslopt=sslopt)
- self.sock.settimeout(default_timeout)
- self.sock.connect(self.url, header=self.header)
- self._callback(self.on_open)
-
- if ping_interval:
- thread = threading.Thread(target=self._send_ping, args=(ping_interval,))
- thread.setDaemon(True)
- thread.start()
-
- while self.keep_running:
-
- try:
- data = self.sock.recv()
-
- if data is None or self.keep_running == False:
- break
- self._callback(self.on_message, data)
-
- except Exception, e:
- #print str(e.args[0])
- if "timed out" not in e.args[0]:
- raise e
-
- except Exception, e:
- self._callback(self.on_error, e)
- finally:
- if thread:
- self.keep_running = False
- self.sock.close()
- self._callback(self.on_close)
- self.sock = None
-
- def _callback(self, callback, *args):
- if callback:
- try:
- callback(self, *args)
- except Exception, e:
- logger.error(e)
- if True:#logger.isEnabledFor(logging.DEBUG):
- _, _, tb = sys.exc_info()
- traceback.print_tb(tb)
-
-
-if __name__ == "__main__":
- enableTrace(True)
- ws = create_connection("ws://echo.websocket.org/")
- print("Sending 'Hello, World'...")
- ws.send("Hello, World")
- print("Sent")
- print("Receiving...")
- result = ws.recv()
- print("Received '%s'" % result)
- ws.close()
+"""
+websocket - WebSocket client library for Python
+
+Copyright (C) 2010 Hiroki Ohtani(liris)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+
+
+import socket
+
+try:
+ import ssl
+ from ssl import SSLError
+ HAVE_SSL = True
+except ImportError:
+ # dummy class of SSLError for ssl none-support environment.
+ class SSLError(Exception):
+ pass
+
+ HAVE_SSL = False
+
+from urlparse import urlparse
+import os
+import array
+import struct
+import uuid
+import hashlib
+import base64
+import threading
+import time
+import logging
+import traceback
+import sys
+
+"""
+websocket python client.
+=========================
+
+This version support only hybi-13.
+Please see http://tools.ietf.org/html/rfc6455 for protocol.
+"""
+
+
+# websocket supported version.
+VERSION = 13
+
+# closing frame status codes.
+STATUS_NORMAL = 1000
+STATUS_GOING_AWAY = 1001
+STATUS_PROTOCOL_ERROR = 1002
+STATUS_UNSUPPORTED_DATA_TYPE = 1003
+STATUS_STATUS_NOT_AVAILABLE = 1005
+STATUS_ABNORMAL_CLOSED = 1006
+STATUS_INVALID_PAYLOAD = 1007
+STATUS_POLICY_VIOLATION = 1008
+STATUS_MESSAGE_TOO_BIG = 1009
+STATUS_INVALID_EXTENSION = 1010
+STATUS_UNEXPECTED_CONDITION = 1011
+STATUS_TLS_HANDSHAKE_ERROR = 1015
+
+logger = logging.getLogger()
+
+
+class WebSocketException(Exception):
+ """
+ websocket exeception class.
+ """
+ pass
+
+
+class WebSocketConnectionClosedException(WebSocketException):
+ """
+ If remote host closed the connection or some network error happened,
+ this exception will be raised.
+ """
+ pass
+
+class WebSocketTimeoutException(WebSocketException):
+ """
+ WebSocketTimeoutException will be raised at socket timeout during read/write data.
+ """
+ pass
+
+default_timeout = None
+traceEnabled = False
+
+
+def enableTrace(tracable):
+ """
+ turn on/off the tracability.
+
+ tracable: boolean value. if set True, tracability is enabled.
+ """
+ global traceEnabled
+ traceEnabled = tracable
+ if tracable:
+ if not logger.handlers:
+ logger.addHandler(logging.StreamHandler())
+ logger.setLevel(logging.DEBUG)
+
+
+def setdefaulttimeout(timeout):
+ """
+ Set the global timeout setting to connect.
+
+ timeout: default socket timeout time. This value is second.
+ """
+ global default_timeout
+ default_timeout = timeout
+
+
+def getdefaulttimeout():
+ """
+ Return the global timeout setting(second) to connect.
+ """
+ return default_timeout
+
+
+def _parse_url(url):
+ """
+ parse url and the result is tuple of
+ (hostname, port, resource path and the flag of secure mode)
+
+ url: url string.
+ """
+ if ":" not in url:
+ raise ValueError("url is invalid")
+
+ scheme, url = url.split(":", 1)
+
+ parsed = urlparse(url, scheme="http")
+ if parsed.hostname:
+ hostname = parsed.hostname
+ else:
+ raise ValueError("hostname is invalid")
+ port = 0
+ if parsed.port:
+ port = parsed.port
+
+ is_secure = False
+ if scheme == "ws":
+ if not port:
+ port = 80
+ elif scheme == "wss":
+ is_secure = True
+ if not port:
+ port = 443
+ else:
+ raise ValueError("scheme %s is invalid" % scheme)
+
+ if parsed.path:
+ resource = parsed.path
+ else:
+ resource = "/"
+
+ if parsed.query:
+ resource += "?" + parsed.query
+
+ return (hostname, port, resource, is_secure)
+
+
+def create_connection(url, timeout=None, **options):
+ """
+ connect to url and return websocket object.
+
+ Connect to url and return the WebSocket object.
+ Passing optional timeout parameter will set the timeout on the socket.
+ If no timeout is supplied, the global default timeout setting returned by getdefauttimeout() is used.
+ You can customize using 'options'.
+ If you set "header" list object, you can set your own custom header.
+
+ >>> conn = create_connection("ws://echo.websocket.org/",
+ ... header=["User-Agent: MyProgram",
+ ... "x-custom: header"])
+
+
+ timeout: socket timeout time. This value is integer.
+ if you set None for this value, it means "use default_timeout value"
+
+ options: current support option is only "header".
+ if you set header as dict value, the custom HTTP headers are added.
+ """
+ sockopt = options.get("sockopt", [])
+ sslopt = options.get("sslopt", {})
+ websock = WebSocket(sockopt=sockopt, sslopt=sslopt)
+ websock.settimeout(timeout if timeout is not None else default_timeout)
+ websock.connect(url, **options)
+ return websock
+
+_MAX_INTEGER = (1 << 32) -1
+_AVAILABLE_KEY_CHARS = range(0x21, 0x2f + 1) + range(0x3a, 0x7e + 1)
+_MAX_CHAR_BYTE = (1<<8) -1
+
+# ref. Websocket gets an update, and it breaks stuff.
+# http://axod.blogspot.com/2010/06/websocket-gets-update-and-it-breaks.html
+
+
+def _create_sec_websocket_key():
+ uid = uuid.uuid4()
+ return base64.encodestring(uid.bytes).strip()
+
+
+_HEADERS_TO_CHECK = {
+ "upgrade": "websocket",
+ "connection": "upgrade",
+ }
+
+
+class ABNF(object):
+ """
+ ABNF frame class.
+ see http://tools.ietf.org/html/rfc5234
+ and http://tools.ietf.org/html/rfc6455#section-5.2
+ """
+
+ # operation code values.
+ OPCODE_CONT = 0x0
+ OPCODE_TEXT = 0x1
+ OPCODE_BINARY = 0x2
+ OPCODE_CLOSE = 0x8
+ OPCODE_PING = 0x9
+ OPCODE_PONG = 0xa
+
+ # available operation code value tuple
+ OPCODES = (OPCODE_CONT, OPCODE_TEXT, OPCODE_BINARY, OPCODE_CLOSE,
+ OPCODE_PING, OPCODE_PONG)
+
+ # opcode human readable string
+ OPCODE_MAP = {
+ OPCODE_CONT: "cont",
+ OPCODE_TEXT: "text",
+ OPCODE_BINARY: "binary",
+ OPCODE_CLOSE: "close",
+ OPCODE_PING: "ping",
+ OPCODE_PONG: "pong"
+ }
+
+ # data length threashold.
+ LENGTH_7 = 0x7d
+ LENGTH_16 = 1 << 16
+ LENGTH_63 = 1 << 63
+
+ def __init__(self, fin=0, rsv1=0, rsv2=0, rsv3=0,
+ opcode=OPCODE_TEXT, mask=1, data=""):
+ """
+ Constructor for ABNF.
+ please check RFC for arguments.
+ """
+ self.fin = fin
+ self.rsv1 = rsv1
+ self.rsv2 = rsv2
+ self.rsv3 = rsv3
+ self.opcode = opcode
+ self.mask = mask
+ self.data = data
+ self.get_mask_key = os.urandom
+
+ def __str__(self):
+ return "fin=" + str(self.fin) \
+ + " opcode=" + str(self.opcode) \
+ + " data=" + str(self.data)
+
+ @staticmethod
+ def create_frame(data, opcode):
+ """
+ create frame to send text, binary and other data.
+
+ data: data to send. This is string value(byte array).
+ if opcode is OPCODE_TEXT and this value is uniocde,
+ data value is conveted into unicode string, automatically.
+
+ opcode: operation code. please see OPCODE_XXX.
+ """
+ if opcode == ABNF.OPCODE_TEXT and isinstance(data, unicode):
+ data = data.encode("utf-8")
+ # mask must be set if send data from client
+ return ABNF(1, 0, 0, 0, opcode, 1, data)
+
+ def format(self):
+ """
+ format this object to string(byte array) to send data to server.
+ """
+ if any(x not in (0, 1) for x in [self.fin, self.rsv1, self.rsv2, self.rsv3]):
+ raise ValueError("not 0 or 1")
+ if self.opcode not in ABNF.OPCODES:
+ raise ValueError("Invalid OPCODE")
+ length = len(self.data)
+ if length >= ABNF.LENGTH_63:
+ raise ValueError("data is too long")
+
+ frame_header = chr(self.fin << 7
+ | self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4
+ | self.opcode)
+ if length < ABNF.LENGTH_7:
+ frame_header += chr(self.mask << 7 | length)
+ elif length < ABNF.LENGTH_16:
+ frame_header += chr(self.mask << 7 | 0x7e)
+ frame_header += struct.pack("!H", length)
+ else:
+ frame_header += chr(self.mask << 7 | 0x7f)
+ frame_header += struct.pack("!Q", length)
+
+ if not self.mask:
+ return frame_header + self.data
+ else:
+ mask_key = self.get_mask_key(4)
+ return frame_header + self._get_masked(mask_key)
+
+ def _get_masked(self, mask_key):
+ s = ABNF.mask(mask_key, self.data)
+ return mask_key + "".join(s)
+
+ @staticmethod
+ def mask(mask_key, data):
+ """
+ mask or unmask data. Just do xor for each byte
+
+ mask_key: 4 byte string(byte).
+
+ data: data to mask/unmask.
+ """
+ _m = array.array("B", mask_key)
+ _d = array.array("B", data)
+ for i in xrange(len(_d)):
+ _d[i] ^= _m[i % 4]
+ return _d.tostring()
+
+
+class WebSocket(object):
+ """
+ Low level WebSocket interface.
+ This class is based on
+ The WebSocket protocol draft-hixie-thewebsocketprotocol-76
+ http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
+
+ We can connect to the websocket server and send/recieve data.
+ The following example is a echo client.
+
+ >>> import websocket
+ >>> ws = websocket.WebSocket()
+ >>> ws.connect("ws://echo.websocket.org")
+ >>> ws.send("Hello, Server")
+ >>> ws.recv()
+ 'Hello, Server'
+ >>> ws.close()
+
+ get_mask_key: a callable to produce new mask keys, see the set_mask_key
+ function's docstring for more details
+ sockopt: values for socket.setsockopt.
+ sockopt must be tuple and each element is argument of sock.setscokopt.
+ sslopt: dict object for ssl socket option.
+ """
+
+ def __init__(self, get_mask_key=None, sockopt=None, sslopt=None):
+ """
+ Initalize WebSocket object.
+ """
+ if sockopt is None:
+ sockopt = []
+ if sslopt is None:
+ sslopt = {}
+ self.connected = False
+ self.sock = socket.socket()
+ for opts in sockopt:
+ self.sock.setsockopt(*opts)
+ self.sslopt = sslopt
+ self.get_mask_key = get_mask_key
+ # Buffers over the packets from the layer beneath until desired amount
+ # bytes of bytes are received.
+ self._recv_buffer = []
+ # These buffer over the build-up of a single frame.
+ self._frame_header = None
+ self._frame_length = None
+ self._frame_mask = None
+ self._cont_data = None
+
+ def fileno(self):
+ return self.sock.fileno()
+
+ def set_mask_key(self, func):
+ """
+ set function to create musk key. You can custumize mask key generator.
+ Mainly, this is for testing purpose.
+
+ func: callable object. the fuct must 1 argument as integer.
+ The argument means length of mask key.
+ This func must be return string(byte array),
+ which length is argument specified.
+ """
+ self.get_mask_key = func
+
+ def gettimeout(self):
+ """
+ Get the websocket timeout(second).
+ """
+ return self.sock.gettimeout()
+
+ def settimeout(self, timeout):
+ """
+ Set the timeout to the websocket.
+
+ timeout: timeout time(second).
+ """
+ self.sock.settimeout(timeout)
+
+ timeout = property(gettimeout, settimeout)
+
+ def connect(self, url, **options):
+ """
+ Connect to url. url is websocket url scheme. ie. ws://host:port/resource
+ You can customize using 'options'.
+ If you set "header" dict object, you can set your own custom header.
+
+ >>> ws = WebSocket()
+ >>> ws.connect("ws://echo.websocket.org/",
+ ... header={"User-Agent: MyProgram",
+ ... "x-custom: header"})
+
+ timeout: socket timeout time. This value is integer.
+ if you set None for this value,
+ it means "use default_timeout value"
+
+ options: current support option is only "header".
+ if you set header as dict value,
+ the custom HTTP headers are added.
+
+ """
+ hostname, port, resource, is_secure = _parse_url(url)
+ # TODO: we need to support proxy
+ self.sock.connect((hostname, port))
+ if is_secure:
+ if HAVE_SSL:
+ if self.sslopt is None:
+ sslopt = {}
+ else:
+ sslopt = self.sslopt
+ self.sock = ssl.wrap_socket(self.sock, **sslopt)
+ else:
+ raise WebSocketException("SSL not available.")
+
+ self._handshake(hostname, port, resource, **options)
+
+ def _handshake(self, host, port, resource, **options):
+ headers = []
+ headers.append("GET %s HTTP/1.1" % resource)
+ headers.append("Upgrade: websocket")
+ headers.append("Connection: Upgrade")
+ if port == 80:
+ hostport = host
+ else:
+ hostport = "%s:%d" % (host, port)
+ headers.append("Host: %s" % hostport)
+
+ if "origin" in options:
+ headers.append("Origin: %s" % options["origin"])
+ else:
+ headers.append("Origin: http://%s" % hostport)
+
+ key = _create_sec_websocket_key()
+ headers.append("Sec-WebSocket-Key: %s" % key)
+ headers.append("Sec-WebSocket-Version: %s" % VERSION)
+ if "header" in options:
+ headers.extend(options["header"])
+
+ headers.append("")
+ headers.append("")
+
+ header_str = "\r\n".join(headers)
+ self._send(header_str)
+ if traceEnabled:
+ logger.debug("--- request header ---")
+ logger.debug(header_str)
+ logger.debug("-----------------------")
+
+ status, resp_headers = self._read_headers()
+ if status != 101:
+ self.close()
+ raise WebSocketException("Handshake Status %d" % status)
+
+ success = self._validate_header(resp_headers, key)
+ if not success:
+ self.close()
+ raise WebSocketException("Invalid WebSocket Header")
+
+ self.connected = True
+
+ def _validate_header(self, headers, key):
+ for k, v in _HEADERS_TO_CHECK.iteritems():
+ r = headers.get(k, None)
+ if not r:
+ return False
+ r = r.lower()
+ if v != r:
+ return False
+
+ result = headers.get("sec-websocket-accept", None)
+ if not result:
+ return False
+ result = result.lower()
+
+ value = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+ hashed = base64.encodestring(hashlib.sha1(value).digest()).strip().lower()
+ return hashed == result
+
+ def _read_headers(self):
+ status = None
+ headers = {}
+ if traceEnabled:
+ logger.debug("--- response header ---")
+
+ while True:
+ line = self._recv_line()
+ if line == "\r\n":
+ break
+ line = line.strip()
+ if traceEnabled:
+ logger.debug(line)
+ if not status:
+ status_info = line.split(" ", 2)
+ status = int(status_info[1])
+ else:
+ kv = line.split(":", 1)
+ if len(kv) == 2:
+ key, value = kv
+ headers[key.lower()] = value.strip().lower()
+ else:
+ raise WebSocketException("Invalid header")
+
+ if traceEnabled:
+ logger.debug("-----------------------")
+
+ return status, headers
+
+ def send(self, payload, opcode=ABNF.OPCODE_TEXT):
+ """
+ Send the data as string.
+
+ payload: Payload must be utf-8 string or unicoce,
+ if the opcode is OPCODE_TEXT.
+ Otherwise, it must be string(byte array)
+
+ opcode: operation code to send. Please see OPCODE_XXX.
+ """
+ frame = ABNF.create_frame(payload, opcode)
+ if self.get_mask_key:
+ frame.get_mask_key = self.get_mask_key
+ data = frame.format()
+ length = len(data)
+ if traceEnabled:
+ logger.debug("send: " + repr(data))
+ while data:
+ l = self._send(data)
+ data = data[l:]
+ return length
+
+ def send_binary(self, payload):
+ return self.send(payload, ABNF.OPCODE_BINARY)
+
+ def ping(self, payload=""):
+ """
+ send ping data.
+
+ payload: data payload to send server.
+ """
+ self.send(payload, ABNF.OPCODE_PING)
+
+ def pong(self, payload):
+ """
+ send pong data.
+
+ payload: data payload to send server.
+ """
+ self.send(payload, ABNF.OPCODE_PONG)
+
+ def recv(self):
+ """
+ Receive string data(byte array) from the server.
+
+ return value: string(byte array) value.
+ """
+ opcode, data = self.recv_data()
+ return data
+
+ def recv_data(self):
+ """
+ Recieve data with operation code.
+
+ return value: tuple of operation code and string(byte array) value.
+ """
+ while True:
+ frame = self.recv_frame()
+ if not frame:
+ # handle error:
+ # 'NoneType' object has no attribute 'opcode'
+ raise WebSocketException("Not a valid frame %s" % frame)
+ elif frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY, ABNF.OPCODE_CONT):
+ if frame.opcode == ABNF.OPCODE_CONT and not self._cont_data:
+ raise WebSocketException("Illegal frame")
+ if self._cont_data:
+ self._cont_data[1] += frame.data
+ else:
+ self._cont_data = [frame.opcode, frame.data]
+
+ if frame.fin:
+ data = self._cont_data
+ self._cont_data = None
+ return data
+ elif frame.opcode == ABNF.OPCODE_CLOSE:
+ self.send_close()
+ return (frame.opcode, None)
+ elif frame.opcode == ABNF.OPCODE_PING:
+ self.pong(frame.data)
+
+ def recv_frame(self):
+ """
+ recieve data as frame from server.
+
+ return value: ABNF frame object.
+ """
+ # Header
+ if self._frame_header is None:
+ self._frame_header = self._recv_strict(2)
+ b1 = ord(self._frame_header[0])
+ fin = b1 >> 7 & 1
+ rsv1 = b1 >> 6 & 1
+ rsv2 = b1 >> 5 & 1
+ rsv3 = b1 >> 4 & 1
+ opcode = b1 & 0xf
+ b2 = ord(self._frame_header[1])
+ has_mask = b2 >> 7 & 1
+ # Frame length
+ if self._frame_length is None:
+ length_bits = b2 & 0x7f
+ if length_bits == 0x7e:
+ length_data = self._recv_strict(2)
+ self._frame_length = struct.unpack("!H", length_data)[0]
+ elif length_bits == 0x7f:
+ length_data = self._recv_strict(8)
+ self._frame_length = struct.unpack("!Q", length_data)[0]
+ else:
+ self._frame_length = length_bits
+ # Mask
+ if self._frame_mask is None:
+ self._frame_mask = self._recv_strict(4) if has_mask else ""
+ # Payload
+ payload = self._recv_strict(self._frame_length)
+ if has_mask:
+ payload = ABNF.mask(self._frame_mask, payload)
+ # Reset for next frame
+ self._frame_header = None
+ self._frame_length = None
+ self._frame_mask = None
+ return ABNF(fin, rsv1, rsv2, rsv3, opcode, has_mask, payload)
+
+
+ def send_close(self, status=STATUS_NORMAL, reason=""):
+ """
+ send close data to the server.
+
+ status: status code to send. see STATUS_XXX.
+
+ reason: the reason to close. This must be string.
+ """
+ if status < 0 or status >= ABNF.LENGTH_16:
+ raise ValueError("code is invalid range")
+ self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE)
+
+ def close(self, status=STATUS_NORMAL, reason=""):
+ """
+ Close Websocket object
+
+ status: status code to send. see STATUS_XXX.
+
+ reason: the reason to close. This must be string.
+ """
+
+ try:
+ self.sock.shutdown(socket.SHUT_RDWR)
+ except:
+ pass
+
+ '''
+ if self.connected:
+ if status < 0 or status >= ABNF.LENGTH_16:
+ raise ValueError("code is invalid range")
+
+ try:
+ self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE)
+ timeout = self.sock.gettimeout()
+ self.sock.settimeout(3)
+ try:
+ frame = self.recv_frame()
+ if logger.isEnabledFor(logging.ERROR):
+ recv_status = struct.unpack("!H", frame.data)[0]
+ if recv_status != STATUS_NORMAL:
+ logger.error("close status: " + repr(recv_status))
+ except:
+ pass
+ self.sock.settimeout(timeout)
+ self.sock.shutdown(socket.SHUT_RDWR)
+ except:
+ pass
+ '''
+ self._closeInternal()
+
+ def _closeInternal(self):
+ self.connected = False
+ self.sock.close()
+
+ def _send(self, data):
+ try:
+ return self.sock.send(data)
+ except socket.timeout as e:
+ raise WebSocketTimeoutException(e.args[0])
+ except Exception as e:
+ if "timed out" in e.args[0]:
+ raise WebSocketTimeoutException(e.args[0])
+ else:
+ raise e
+
+ def _recv(self, bufsize):
+ try:
+ bytes = self.sock.recv(bufsize)
+ except socket.timeout as e:
+ raise WebSocketTimeoutException(e.args[0])
+ except SSLError as e:
+ if e.args[0] == "The read operation timed out":
+ raise WebSocketTimeoutException(e.args[0])
+ else:
+ raise
+ if not bytes:
+ raise WebSocketConnectionClosedException()
+ return bytes
+
+
+ def _recv_strict(self, bufsize):
+ shortage = bufsize - sum(len(x) for x in self._recv_buffer)
+ while shortage > 0:
+ bytes = self._recv(shortage)
+ self._recv_buffer.append(bytes)
+ shortage -= len(bytes)
+ unified = "".join(self._recv_buffer)
+ if shortage == 0:
+ self._recv_buffer = []
+ return unified
+ else:
+ self._recv_buffer = [unified[bufsize:]]
+ return unified[:bufsize]
+
+
+ def _recv_line(self):
+ line = []
+ while True:
+ c = self._recv(1)
+ line.append(c)
+ if c == "\n":
+ break
+ return "".join(line)
+
+
+class WebSocketApp(object):
+ """
+ Higher level of APIs are provided.
+ The interface is like JavaScript WebSocket object.
+ """
+ def __init__(self, url, header=[],
+ on_open=None, on_message=None, on_error=None,
+ on_close=None, keep_running=True, get_mask_key=None):
+ """
+ url: websocket url.
+ header: custom header for websocket handshake.
+ on_open: callable object which is called at opening websocket.
+ this function has one argument. The arugment is this class object.
+ on_message: callbale object which is called when recieved data.
+ on_message has 2 arguments.
+ The 1st arugment is this class object.
+ The passing 2nd arugment is utf-8 string which we get from the server.
+ on_error: callable object which is called when we get error.
+ on_error has 2 arguments.
+ The 1st arugment is this class object.
+ The passing 2nd arugment is exception object.
+ on_close: callable object which is called when closed the connection.
+ this function has one argument. The arugment is this class object.
+ keep_running: a boolean flag indicating whether the app's main loop should
+ keep running, defaults to True
+ get_mask_key: a callable to produce new mask keys, see the WebSocket.set_mask_key's
+ docstring for more information
+ """
+ self.url = url
+ self.header = header
+ self.on_open = on_open
+ self.on_message = on_message
+ self.on_error = on_error
+ self.on_close = on_close
+ self.keep_running = keep_running
+ self.get_mask_key = get_mask_key
+ self.sock = None
+
+ def send(self, data, opcode=ABNF.OPCODE_TEXT):
+ """
+ send message.
+ data: message to send. If you set opcode to OPCODE_TEXT, data must be utf-8 string or unicode.
+ opcode: operation code of data. default is OPCODE_TEXT.
+ """
+ if self.sock.send(data, opcode) == 0:
+ raise WebSocketConnectionClosedException()
+
+ def close(self):
+ """
+ close websocket connection.
+ """
+ self.keep_running = False
+ if(self.sock != None):
+ self.sock.close()
+
+ def _send_ping(self, interval):
+ while True:
+ for i in range(interval):
+ time.sleep(1)
+ if not self.keep_running:
+ return
+ self.sock.ping()
+
+ def run_forever(self, sockopt=None, sslopt=None, ping_interval=0):
+ """
+ run event loop for WebSocket framework.
+ This loop is infinite loop and is alive during websocket is available.
+ sockopt: values for socket.setsockopt.
+ sockopt must be tuple and each element is argument of sock.setscokopt.
+ sslopt: ssl socket optional dict.
+ ping_interval: automatically send "ping" command every specified period(second)
+ if set to 0, not send automatically.
+ """
+ if sockopt is None:
+ sockopt = []
+ if sslopt is None:
+ sslopt = {}
+ if self.sock:
+ raise WebSocketException("socket is already opened")
+ thread = None
+ self.keep_running = True
+
+ try:
+ self.sock = WebSocket(self.get_mask_key, sockopt=sockopt, sslopt=sslopt)
+ self.sock.settimeout(default_timeout)
+ self.sock.connect(self.url, header=self.header)
+ self._callback(self.on_open)
+
+ if ping_interval:
+ thread = threading.Thread(target=self._send_ping, args=(ping_interval,))
+ thread.setDaemon(True)
+ thread.start()
+
+ while self.keep_running:
+
+ try:
+ data = self.sock.recv()
+
+ if data is None or self.keep_running == False:
+ break
+ self._callback(self.on_message, data)
+
+ except Exception, e:
+ #print str(e.args[0])
+ if "timed out" not in e.args[0]:
+ raise e
+
+ except Exception, e:
+ self._callback(self.on_error, e)
+ finally:
+ if thread:
+ self.keep_running = False
+ self.sock.close()
+ self._callback(self.on_close)
+ self.sock = None
+
+ def _callback(self, callback, *args):
+ if callback:
+ try:
+ callback(self, *args)
+ except Exception, e:
+ logger.error(e)
+ if True:#logger.isEnabledFor(logging.DEBUG):
+ _, _, tb = sys.exc_info()
+ traceback.print_tb(tb)
+
+
+if __name__ == "__main__":
+ enableTrace(True)
+ ws = create_connection("ws://echo.websocket.org/")
+ print("Sending 'Hello, World'...")
+ ws.send("Hello, World")
+ print("Sent")
+ print("Receiving...")
+ result = ws.recv()
+ print("Received '%s'" % result)
+ ws.close()