Merge branch 'stable-version' into beta-version

This commit is contained in:
Croneter 2018-06-15 14:41:15 +02:00
commit 12f1486f53
3 changed files with 282 additions and 288 deletions

View file

@ -67,32 +67,27 @@ class Main():
self.play() self.play()
elif mode == 'ondeck': elif mode == 'ondeck':
entrypoint.getOnDeck(itemid, entrypoint.on_deck_episodes(itemid,
params.get('type'), params.get('tagname'),
params.get('tagname'), int(params.get('limit')))
int(params.get('limit')))
elif mode == 'recentepisodes': elif mode == 'recentepisodes':
entrypoint.getRecentEpisodes(itemid, entrypoint.recent_episodes(params.get('type'),
params.get('type'), params.get('tagname'),
params.get('tagname'), int(params.get('limit')))
int(params.get('limit')))
elif mode == 'nextup': elif mode == 'nextup':
entrypoint.getNextUpEpisodes(params['tagname'], entrypoint.next_up_episodes(params['tagname'],
int(params['limit'])) int(params['limit']))
elif mode == 'inprogressepisodes': elif mode == 'inprogressepisodes':
entrypoint.getInProgressEpisodes(params['tagname'], entrypoint.in_progress_episodes(params['tagname'],
int(params['limit'])) int(params['limit']))
elif mode == 'browseplex': elif mode == 'browseplex':
entrypoint.browse_plex(key=params.get('key'), entrypoint.browse_plex(key=params.get('key'),
plex_section_id=params.get('id')) plex_section_id=params.get('id'))
elif mode == 'getsubfolders':
entrypoint.GetSubFolders(itemid)
elif mode == 'watchlater': elif mode == 'watchlater':
entrypoint.watchlater() entrypoint.watchlater()
@ -106,22 +101,22 @@ class Main():
executebuiltin('Addon.OpenSettings(%s)' % v.ADDON_ID) executebuiltin('Addon.OpenSettings(%s)' % v.ADDON_ID)
elif mode == 'enterPMS': elif mode == 'enterPMS':
entrypoint.enterPMS() entrypoint.create_new_pms()
elif mode == 'reset': elif mode == 'reset':
reset() reset()
elif mode == 'togglePlexTV': elif mode == 'togglePlexTV':
entrypoint.togglePlexTV() entrypoint.toggle_plex_tv_sign_in()
elif mode == 'resetauth': elif mode == 'resetauth':
entrypoint.resetAuth() entrypoint.reset_authorization()
elif mode == 'passwords': elif mode == 'passwords':
passwords_xml() passwords_xml()
elif mode == 'switchuser': elif mode == 'switchuser':
entrypoint.switchPlexUser() entrypoint.switch_plex_user()
elif mode in ('manualsync', 'repair'): elif mode in ('manualsync', 'repair'):
if window('plex_online') != 'true': if window('plex_online') != 'true':
@ -141,7 +136,7 @@ class Main():
plex_command('RUN_LIB_SCAN', 'textures') plex_command('RUN_LIB_SCAN', 'textures')
elif mode == 'chooseServer': elif mode == 'chooseServer':
entrypoint.chooseServer() entrypoint.choose_pms_server()
elif mode == 'refreshplaylist': elif mode == 'refreshplaylist':
log.info('Requesting playlist/nodes refresh') log.info('Requesting playlist/nodes refresh')
@ -157,17 +152,17 @@ class Main():
elif '/extrafanart' in argv[0]: elif '/extrafanart' in argv[0]:
plexpath = argv[2][1:] plexpath = argv[2][1:]
plexid = itemid plexid = itemid
entrypoint.getExtraFanArt(plexid, plexpath) entrypoint.extra_fanart(plexid, plexpath)
entrypoint.getVideoFiles(plexid, plexpath) entrypoint.get_video_files(plexid, plexpath)
# Called by e.g. 3rd party plugin video extras # Called by e.g. 3rd party plugin video extras
elif ('/Extras' in argv[0] or '/VideoFiles' in argv[0] or elif ('/Extras' in argv[0] or '/VideoFiles' in argv[0] or
'/Extras' in argv[2]): '/Extras' in argv[2]):
plexId = itemid or None plexId = itemid or None
entrypoint.getVideoFiles(plexId, params) entrypoint.get_video_files(plexId, params)
else: else:
entrypoint.doMainListing(content_type=params.get('content_type')) entrypoint.show_main_menu(content_type=params.get('content_type'))
@staticmethod @staticmethod
def play(): def play():

View file

@ -1,4 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
#
# Loads of different functions called in SEPARATE Python instances through
# e.g. plugin://... calls. Hence be careful to only rely on window variables.
#
############################################################################### ###############################################################################
from logging import getLogger from logging import getLogger
from shutil import copyfile from shutil import copyfile
@ -22,7 +26,7 @@ import json_rpc as js
import variables as v import variables as v
############################################################################### ###############################################################################
log = getLogger("PLEX."+__name__) LOG = getLogger("PLEX." + __name__)
try: try:
HANDLE = int(argv[1]) HANDLE = int(argv[1])
@ -32,25 +36,25 @@ except IndexError:
############################################################################### ###############################################################################
def chooseServer(): def choose_pms_server():
""" """
Lets user choose from list of PMS Lets user choose from list of PMS
""" """
log.info("Choosing PMS server requested, starting") LOG.info("Choosing PMS server requested, starting")
import initialsetup import initialsetup
setup = initialsetup.InitialSetup() setup = initialsetup.InitialSetup()
server = setup.pick_pms(showDialog=True) server = setup.pick_pms(showDialog=True)
if server is None: if server is None:
log.error('We did not connect to a new PMS, aborting') LOG.error('We did not connect to a new PMS, aborting')
plex_command('SUSPEND_USER_CLIENT', 'False') plex_command('SUSPEND_USER_CLIENT', 'False')
plex_command('SUSPEND_LIBRARY_THREAD', 'False') plex_command('SUSPEND_LIBRARY_THREAD', 'False')
return return
log.info("User chose server %s" % server['name']) LOG.info("User chose server %s", server['name'])
setup.write_pms_to_settings(server) setup.write_pms_to_settings(server)
if not __LogOut(): if not _log_out():
return return
from utils import wipe_database from utils import wipe_database
@ -58,8 +62,8 @@ def chooseServer():
wipe_database() wipe_database()
# Log in again # Log in again
__LogIn() _log_in()
log.info("Choosing new PMS complete") LOG.info("Choosing new PMS complete")
# '<PMS> connected' # '<PMS> connected'
dialog('notification', dialog('notification',
lang(29999), lang(29999),
@ -69,9 +73,13 @@ def chooseServer():
sound=False) sound=False)
def togglePlexTV(): def toggle_plex_tv_sign_in():
"""
Signs out of Plex.tv if there was a token saved and thus deletes the token.
Or signs in to plex.tv if the user was not logged in before.
"""
if settings('plexToken'): if settings('plexToken'):
log.info('Reseting plex.tv credentials in settings') LOG.info('Reseting plex.tv credentials in settings')
settings('plexLogin', value="") settings('plexLogin', value="")
settings('plexToken', value="") settings('plexToken', value="")
settings('plexid', value="") settings('plexid', value="")
@ -83,7 +91,7 @@ def togglePlexTV():
plex_command('PLEX_TOKEN', '') plex_command('PLEX_TOKEN', '')
plex_command('PLEX_USERNAME', '') plex_command('PLEX_USERNAME', '')
else: else:
log.info('Login to plex.tv') LOG.info('Login to plex.tv')
import initialsetup import initialsetup
initialsetup.InitialSetup().plex_tv_sign_in() initialsetup.InitialSetup().plex_tv_sign_in()
dialog('notification', dialog('notification',
@ -94,27 +102,40 @@ def togglePlexTV():
sound=False) sound=False)
##### DO RESET AUTH ##### def reset_authorization():
def resetAuth(): """
# User tried login and failed too many times User tried login and failed too many times. Reset # of logins
"""
resp = dialog('yesno', heading="{plex}", line1=lang(39206)) resp = dialog('yesno', heading="{plex}", line1=lang(39206))
if resp == 1: if resp == 1:
log.info("Reset login attempts.") LOG.info("Reset login attempts.")
plex_command('PMS_STATUS', 'Auth') plex_command('PMS_STATUS', 'Auth')
else: else:
executebuiltin('Addon.OpenSettings(plugin.video.plexkodiconnect)') executebuiltin('Addon.OpenSettings(plugin.video.plexkodiconnect)')
def addDirectoryItem(label, path, folder=True): def directory_item(label, path, folder=True):
li = ListItem(label, path=path) """
li.setThumbnailImage("special://home/addons/plugin.video.plexkodiconnect/icon.png") Adds a xbmcplugin.addDirectoryItem() directory itemlistitem
li.setArt({"fanart":"special://home/addons/plugin.video.plexkodiconnect/fanart.jpg"}) """
li.setArt({"landscape":"special://home/addons/plugin.video.plexkodiconnect/fanart.jpg"}) listitem = ListItem(label, path=path)
xbmcplugin.addDirectoryItem(handle=HANDLE, url=path, listitem=li, isFolder=folder) listitem.setThumbnailImage(
"special://home/addons/plugin.video.plexkodiconnect/icon.png")
listitem.setArt(
{"fanart": "special://home/addons/plugin.video.plexkodiconnect/fanart.jpg"})
listitem.setArt(
{"landscape":"special://home/addons/plugin.video.plexkodiconnect/fanart.jpg"})
xbmcplugin.addDirectoryItem(handle=HANDLE,
url=path,
listitem=listitem,
isFolder=folder)
def doMainListing(content_type=None): def show_main_menu(content_type=None):
log.debug('Do main listing with content_type: %s' % content_type) """
Shows the main PKC menu listing with all libraries, Channel, settings, etc.
"""
LOG.debug('Do main listing with content_type: %s', content_type)
xbmcplugin.setContent(HANDLE, 'files') xbmcplugin.setContent(HANDLE, 'files')
# Get emby nodes from the window props # Get emby nodes from the window props
plexprops = window('Plex.nodes.total') plexprops = window('Plex.nodes.total')
@ -133,33 +154,33 @@ def doMainListing(content_type=None):
# now we just only show picture nodes in the picture library video # now we just only show picture nodes in the picture library video
# nodes in the video library and all nodes in any other window # nodes in the video library and all nodes in any other window
if node_type == 'photos' and content_type == 'image': if node_type == 'photos' and content_type == 'image':
addDirectoryItem(label, path) directory_item(label, path)
elif (node_type != 'photos' and elif (node_type != 'photos' and
content_type not in ('image', 'audio')): content_type not in ('image', 'audio')):
addDirectoryItem(label, path) directory_item(label, path)
# Plex Watch later # Plex Watch later
if content_type not in ('image', 'audio'): if content_type not in ('image', 'audio'):
addDirectoryItem(lang(39211), directory_item(lang(39211),
"plugin://%s?mode=watchlater" % v.ADDON_ID) "plugin://%s?mode=watchlater" % v.ADDON_ID)
# Plex Channels # Plex Channels
addDirectoryItem(lang(30173), directory_item(lang(30173),
"plugin://%s?mode=channels" % v.ADDON_ID) "plugin://%s?mode=channels" % v.ADDON_ID)
# Plex user switch # Plex user switch
addDirectoryItem('%s%s' % (lang(39200), settings('username')), directory_item('%s%s' % (lang(39200), settings('username')),
"plugin://%s?mode=switchuser" % v.ADDON_ID) "plugin://%s?mode=switchuser" % v.ADDON_ID)
# some extra entries for settings and stuff # some extra entries for settings and stuff
addDirectoryItem(lang(39201), directory_item(lang(39201),
"plugin://%s?mode=settings" % v.ADDON_ID) "plugin://%s?mode=settings" % v.ADDON_ID)
addDirectoryItem(lang(39203), directory_item(lang(39203),
"plugin://%s?mode=refreshplaylist" % v.ADDON_ID) "plugin://%s?mode=refreshplaylist" % v.ADDON_ID)
addDirectoryItem(lang(39204), directory_item(lang(39204),
"plugin://%s?mode=manualsync" % v.ADDON_ID) "plugin://%s?mode=manualsync" % v.ADDON_ID)
xbmcplugin.endOfDirectory(HANDLE) xbmcplugin.endOfDirectory(HANDLE)
def switchPlexUser(): def switch_plex_user():
""" """
Signs out currently logged in user (if applicable). Triggers sign-in of a Signs out currently logged in user (if applicable). Triggers sign-in of a
new user new user
@ -168,42 +189,30 @@ def switchPlexUser():
# Delete any userimages. Since there's always only 1 user: position = 0 # Delete any userimages. Since there's always only 1 user: position = 0
# position = 0 # position = 0
# window('EmbyAdditionalUserImage.%s' % position, clear=True) # window('EmbyAdditionalUserImage.%s' % position, clear=True)
log.info("Plex home user switch requested") LOG.info("Plex home user switch requested")
if not __LogOut(): if not _log_out():
return return
# First remove playlists of old user # First remove playlists of old user
from utils import delete_playlists, delete_nodes from utils import delete_playlists, delete_nodes
delete_playlists() delete_playlists()
# Remove video nodes # Remove video nodes
delete_nodes() delete_nodes()
__LogIn() _log_in()
#### SHOW SUBFOLDERS FOR NODE ##### def create_listitem(item, append_show_title=False, append_sxxexx=False):
def GetSubFolders(nodeindex): """
nodetypes = ["",".recent",".recentepisodes",".inprogress",".inprogressepisodes",".unwatched",".nextepisodes",".sets",".genres",".random",".recommended"] Feed with a Kodi json item response to get a xbmcgui.ListItem() with
for node in nodetypes: everything set and ready.
title = window('Plex.nodes.%s%s.title' %(nodeindex,node)) """
if title:
path = window('Plex.nodes.%s%s.content' %(nodeindex,node))
addDirectoryItem(title, path)
xbmcplugin.endOfDirectory(HANDLE)
##### LISTITEM SETUP FOR VIDEONODES #####
def createListItem(item, append_show_title=False, append_sxxexx=False):
log.debug('createListItem called with append_show_title %s, append_sxxexx '
'%s, item: %s', append_show_title, append_sxxexx, item)
title = item['title'] title = item['title']
li = ListItem(title) listitem = ListItem(title)
li.setProperty('IsPlayable', 'true') listitem.setProperty('IsPlayable', 'true')
metadata = { metadata = {
'duration': str(item['runtime']/60), 'duration': str(item['runtime'] / 60),
'Plot': item['plot'], 'Plot': item['plot'],
'Playcount': item['playcount'] 'Playcount': item['playcount']
} }
if 'episode' in item: if 'episode' in item:
episode = item['episode'] episode = item['episode']
metadata['Episode'] = episode metadata['Episode'] = episode
@ -211,7 +220,7 @@ def createListItem(item, append_show_title=False, append_sxxexx=False):
season = item['season'] season = item['season']
metadata['Season'] = season metadata['Season'] = season
if season and episode: if season and episode:
li.setProperty('episodeno', 's%.2de%.2d' % (season, episode)) listitem.setProperty('episodeno', 's%.2de%.2d' % (season, episode))
if append_sxxexx is True: if append_sxxexx is True:
title = 'S%.2dE%.2d - %s' % (season, episode, title) title = 'S%.2dE%.2d - %s' % (season, episode, title)
if 'firstaired' in item: if 'firstaired' in item:
@ -239,27 +248,30 @@ def createListItem(item, append_show_title=False, append_sxxexx=False):
metadata['Title'] = title metadata['Title'] = title
metadata['mediatype'] = 'episode' metadata['mediatype'] = 'episode'
metadata['dbid'] = str(item['episodeid']) metadata['dbid'] = str(item['episodeid'])
li.setLabel(title) listitem.setLabel(title)
li.setInfo(type='Video', infoLabels=metadata) listitem.setInfo(type='Video', infoLabels=metadata)
li.setProperty('resumetime', str(item['resume']['position'])) listitem.setProperty('resumetime', str(item['resume']['position']))
li.setProperty('totaltime', str(item['resume']['total'])) listitem.setProperty('totaltime', str(item['resume']['total']))
li.setArt(item['art']) listitem.setArt(item['art'])
li.setThumbnailImage(item['art'].get('thumb', '')) listitem.setThumbnailImage(item['art'].get('thumb', ''))
li.setArt({'icon': 'DefaultTVShows.png'}) listitem.setArt({'icon': 'DefaultTVShows.png'})
li.setProperty('fanart_image', item['art'].get('tvshow.fanart', '')) listitem.setProperty('fanart_image', item['art'].get('tvshow.fanart', ''))
try: try:
li.addContextMenuItems([(lang(30032), 'XBMC.Action(Info)',)]) listitem.addContextMenuItems([(lang(30032), 'XBMC.Action(Info)',)])
except TypeError: except TypeError:
# Kodi fuck-up # Kodi fuck-up
pass pass
for key, value in item['streamdetails'].iteritems(): for key, value in item['streamdetails'].iteritems():
for stream in value: for stream in value:
li.addStreamInfo(key, stream) listitem.addStreamInfo(key, stream)
return li return listitem
##### GET NEXTUP EPISODES FOR TAGNAME #####
def getNextUpEpisodes(tagname, limit): def next_up_episodes(tagname, limit):
"""
List the next up episodes for tagname.
"""
count = 0 count = 0
# if the addon is called with nextup parameter, # if the addon is called with nextup parameter,
# we return the nextepisodes list of the given tagname # we return the nextepisodes list of the given tagname
@ -314,15 +326,17 @@ def getNextUpEpisodes(tagname, limit):
for episode in js.get_episodes(params): for episode in js.get_episodes(params):
xbmcplugin.addDirectoryItem(handle=HANDLE, xbmcplugin.addDirectoryItem(handle=HANDLE,
url=episode['file'], url=episode['file'],
listitem=createListItem(episode)) listitem=create_listitem(episode))
count += 1 count += 1
if count == limit: if count == limit:
break break
xbmcplugin.endOfDirectory(handle=HANDLE) xbmcplugin.endOfDirectory(handle=HANDLE)
##### GET INPROGRESS EPISODES FOR TAGNAME ##### def in_progress_episodes(tagname, limit):
def getInProgressEpisodes(tagname, limit): """
List the episodes that are in progress for tagname
"""
count = 0 count = 0
# if the addon is called with inprogressepisodes parameter, # if the addon is called with inprogressepisodes parameter,
# we return the inprogressepisodes list of the given tagname # we return the inprogressepisodes list of the given tagname
@ -346,22 +360,25 @@ def getInProgressEpisodes(tagname, limit):
'field': "inprogress", 'field': "inprogress",
'value': ""}, 'value': ""},
'properties': ["title", "playcount", "season", "episode", 'properties': ["title", "playcount", "season", "episode",
"showtitle", "plot", "file", "rating", "resume", "showtitle", "plot", "file", "rating", "resume",
"tvshowid", "art", "cast", "streamdetails", "firstaired", "tvshowid", "art", "cast", "streamdetails",
"runtime", "writer", "dateadded", "lastplayed"] "firstaired", "runtime", "writer", "dateadded",
"lastplayed"]
} }
for episode in js.get_episodes(params): for episode in js.get_episodes(params):
xbmcplugin.addDirectoryItem(handle=HANDLE, xbmcplugin.addDirectoryItem(handle=HANDLE,
url=episode['file'], url=episode['file'],
listitem=createListItem(episode)) listitem=create_listitem(episode))
count += 1 count += 1
if count == limit: if count == limit:
break break
xbmcplugin.endOfDirectory(handle=HANDLE) xbmcplugin.endOfDirectory(handle=HANDLE)
##### GET RECENT EPISODES FOR TAGNAME #####
# def getRecentEpisodes(tagname, limit): def recent_episodes(mediatype, tagname, limit):
def getRecentEpisodes(viewid, mediatype, tagname, limit): """
List the recently added episodes for tagname
"""
count = 0 count = 0
# if the addon is called with recentepisodes parameter, # if the addon is called with recentepisodes parameter,
# we return the recentepisodes list of the given tagname # we return the recentepisodes list of the given tagname
@ -369,13 +386,13 @@ def getRecentEpisodes(viewid, mediatype, tagname, limit):
append_show_title = settings('RecentTvAppendShow') == 'true' append_show_title = settings('RecentTvAppendShow') == 'true'
append_sxxexx = settings('RecentTvAppendSeason') == 'true' append_sxxexx = settings('RecentTvAppendSeason') == 'true'
# First we get a list of all the TV shows - filtered by tag # First we get a list of all the TV shows - filtered by tag
allshowsIds = set() show_ids = set()
params = { params = {
'sort': {'order': "descending", 'method': "dateadded"}, 'sort': {'order': "descending", 'method': "dateadded"},
'filter': {'operator': "is", 'field': "tag", 'value': "%s" % tagname}, 'filter': {'operator': "is", 'field': "tag", 'value': "%s" % tagname},
} }
for tv_show in js.get_tv_shows(params): for tv_show in js.get_tv_shows(params):
allshowsIds.add(tv_show['tvshowid']) show_ids.add(tv_show['tvshowid'])
params = { params = {
'sort': {'order': "descending", 'method': "dateadded"}, 'sort': {'order': "descending", 'method': "dateadded"},
'properties': ["title", "playcount", "season", "episode", "showtitle", 'properties': ["title", "playcount", "season", "episode", "showtitle",
@ -391,28 +408,27 @@ def getRecentEpisodes(viewid, mediatype, tagname, limit):
'value': "1" 'value': "1"
} }
for episode in js.get_episodes(params): for episode in js.get_episodes(params):
if episode['tvshowid'] in allshowsIds: if episode['tvshowid'] in show_ids:
listitem = createListItem(episode, listitem = create_listitem(episode,
append_show_title=append_show_title, append_show_title=append_show_title,
append_sxxexx=append_sxxexx) append_sxxexx=append_sxxexx)
xbmcplugin.addDirectoryItem( xbmcplugin.addDirectoryItem(handle=HANDLE,
handle=HANDLE, url=episode['file'],
url=episode['file'], listitem=listitem)
listitem=listitem)
count += 1 count += 1
if count == limit: if count == limit:
break break
xbmcplugin.endOfDirectory(handle=HANDLE) xbmcplugin.endOfDirectory(handle=HANDLE)
def getVideoFiles(plexId, params): def get_video_files(plex_id, params):
""" """
GET VIDEO EXTRAS FOR LISTITEM GET VIDEO EXTRAS FOR LISTITEM
returns the video files for the item as plugin listing, can be used for returns the video files for the item as plugin listing, can be used for
browsing the actual files or videoextras etc. browsing the actual files or videoextras etc.
""" """
if plexId is None: if plex_id is None:
filename = params.get('filename') filename = params.get('filename')
if filename is not None: if filename is not None:
filename = filename[0] filename = filename[0]
@ -420,19 +436,19 @@ def getVideoFiles(plexId, params):
regex = re.compile(r'''library/metadata/(\d+)''') regex = re.compile(r'''library/metadata/(\d+)''')
filename = regex.findall(filename) filename = regex.findall(filename)
try: try:
plexId = filename[0] plex_id = filename[0]
except IndexError: except IndexError:
pass pass
if plexId is None: if plex_id is None:
log.info('No Plex ID found, abort getting Extras') LOG.info('No Plex ID found, abort getting Extras')
return xbmcplugin.endOfDirectory(HANDLE) return xbmcplugin.endOfDirectory(HANDLE)
item = GetPlexMetadata(plexId) item = GetPlexMetadata(plex_id)
try: try:
path = item[0][0][0].attrib['file'] path = item[0][0][0].attrib['file']
except: except (TypeError, IndexError, AttributeError, KeyError):
log.error('Could not get file path for item %s' % plexId) LOG.error('Could not get file path for item %s', plex_id)
return xbmcplugin.endOfDirectory(HANDLE) return xbmcplugin.endOfDirectory(HANDLE)
# Assign network protocol # Assign network protocol
if path.startswith('\\\\'): if path.startswith('\\\\'):
@ -447,82 +463,80 @@ def getVideoFiles(plexId, params):
for root, dirs, files in walk(path): for root, dirs, files in walk(path):
for directory in dirs: for directory in dirs:
item_path = try_encode(join(root, directory)) item_path = try_encode(join(root, directory))
li = ListItem(item_path, path=item_path) listitem = ListItem(item_path, path=item_path)
xbmcplugin.addDirectoryItem(handle=HANDLE, xbmcplugin.addDirectoryItem(handle=HANDLE,
url=item_path, url=item_path,
listitem=li, listitem=listitem,
isFolder=True) isFolder=True)
for file in files: for file in files:
item_path = try_encode(join(root, file)) item_path = try_encode(join(root, file))
li = ListItem(item_path, path=item_path) listitem = ListItem(item_path, path=item_path)
xbmcplugin.addDirectoryItem(handle=HANDLE, xbmcplugin.addDirectoryItem(handle=HANDLE,
url=file, url=file,
listitem=li) listitem=listitem)
break break
else: else:
log.error('Kodi cannot access folder %s' % path) LOG.error('Kodi cannot access folder %s', path)
xbmcplugin.endOfDirectory(HANDLE) xbmcplugin.endOfDirectory(HANDLE)
@catch_exceptions(warnuser=False) @catch_exceptions(warnuser=False)
def getExtraFanArt(plexid, plexPath): def extra_fanart(plex_id, plex_path):
""" """
Get extrafanart for listitem Get extrafanart for listitem
will be called by skinhelper script to get the extrafanart will be called by skinhelper script to get the extrafanart
for tvshows we get the plexid just from the path for tvshows we get the plex_id just from the path
""" """
log.debug('Called with plexid: %s, plexPath: %s' % (plexid, plexPath)) LOG.debug('Called with plex_id: %s, plex_path: %s', plex_id, plex_path)
if not plexid: if not plex_id:
if "plugin.video.plexkodiconnect" in plexPath: if "plugin.video.plexkodiconnect" in plex_path:
plexid = plexPath.split("/")[-2] plex_id = plex_path.split("/")[-2]
if not plexid: if not plex_id:
log.error('Could not get a plexid, aborting') LOG.error('Could not get a plex_id, aborting')
return xbmcplugin.endOfDirectory(HANDLE) return xbmcplugin.endOfDirectory(HANDLE)
# We need to store the images locally for this to work # We need to store the images locally for this to work
# because of the caching system in xbmc # because of the caching system in xbmc
fanartDir = try_decode(translatePath( fanart_dir = try_decode(translatePath(
"special://thumbnails/plex/%s/" % plexid)) "special://thumbnails/plex/%s/" % plex_id))
if not exists_dir(fanartDir): if not exists_dir(fanart_dir):
# Download the images to the cache directory # Download the images to the cache directory
makedirs(fanartDir) makedirs(fanart_dir)
xml = GetPlexMetadata(plexid) xml = GetPlexMetadata(plex_id)
if xml is None: if xml is None:
log.error('Could not download metadata for %s' % plexid) LOG.error('Could not download metadata for %s', plex_id)
return xbmcplugin.endOfDirectory(HANDLE) return xbmcplugin.endOfDirectory(HANDLE)
api = API(xml[0]) api = API(xml[0])
backdrops = api.artwork()['Backdrop'] backdrops = api.artwork()['Backdrop']
for count, backdrop in enumerate(backdrops): for count, backdrop in enumerate(backdrops):
# Same ordering as in artwork # Same ordering as in artwork
fanartFile = try_encode(join(fanartDir, "fanart%.3d.jpg" % count)) art_file = try_encode(join(fanart_dir, "fanart%.3d.jpg" % count))
li = ListItem("%.3d" % count, path=fanartFile) listitem = ListItem("%.3d" % count, path=art_file)
xbmcplugin.addDirectoryItem( xbmcplugin.addDirectoryItem(
handle=HANDLE, handle=HANDLE,
url=fanartFile, url=art_file,
listitem=li) listitem=listitem)
copyfile(backdrop, try_decode(fanartFile)) copyfile(backdrop, try_decode(art_file))
else: else:
log.info("Found cached backdrop.") LOG.info("Found cached backdrop.")
# Use existing cached images # Use existing cached images
for root, dirs, files in walk(fanartDir): for root, _, files in walk(fanart_dir):
for file in files: for file in files:
fanartFile = try_encode(join(root, file)) art_file = try_encode(join(root, file))
li = ListItem(file, path=fanartFile) listitem = ListItem(file, path=art_file)
xbmcplugin.addDirectoryItem(handle=HANDLE, xbmcplugin.addDirectoryItem(handle=HANDLE,
url=fanartFile, url=art_file,
listitem=li) listitem=listitem)
xbmcplugin.endOfDirectory(HANDLE) xbmcplugin.endOfDirectory(HANDLE)
def getOnDeck(viewid, mediatype, tagname, limit): def on_deck_episodes(viewid, tagname, limit):
""" """
Retrieves Plex On Deck items, currently only for TV shows Retrieves Plex On Deck items, currently only for TV shows
Input: Input:
viewid: Plex id of the library section, e.g. '1' viewid: Plex id of the library section, e.g. '1'
mediatype: Kodi mediatype, e.g. 'tvshows', 'movies',
'homevideos', 'photos'
tagname: Name of the Plex library, e.g. "My Movies" tagname: Name of the Plex library, e.g. "My Movies"
limit: Max. number of items to retrieve, e.g. 50 limit: Max. number of items to retrieve, e.g. 50
""" """
@ -536,7 +550,7 @@ def getOnDeck(viewid, mediatype, tagname, limit):
while window('plex_authenticated') != 'true': while window('plex_authenticated') != 'true':
counter += 1 counter += 1
if counter == 300: if counter == 300:
log.error('Aborting On Deck view, we were not authenticated ' LOG.error('Aborting On Deck view, we were not authenticated '
'for the PMS') 'for the PMS')
xbmcplugin.endOfDirectory(HANDLE, False) xbmcplugin.endOfDirectory(HANDLE, False)
return return
@ -544,7 +558,7 @@ def getOnDeck(viewid, mediatype, tagname, limit):
xml = downloadutils.DownloadUtils().downloadUrl( xml = downloadutils.DownloadUtils().downloadUrl(
'{server}/library/sections/%s/onDeck' % viewid) '{server}/library/sections/%s/onDeck' % viewid)
if xml in (None, 401): if xml in (None, 401):
log.error('Could not download PMS xml for view %s' % viewid) LOG.error('Could not download PMS xml for view %s', viewid)
xbmcplugin.endOfDirectory(HANDLE, False) xbmcplugin.endOfDirectory(HANDLE, False)
return return
direct_paths = settings('useDirectPaths') == '1' direct_paths = settings('useDirectPaths') == '1'
@ -632,9 +646,9 @@ def getOnDeck(viewid, mediatype, tagname, limit):
continue continue
for episode in episodes: for episode in episodes:
# There will always be only 1 episode ('limit=1') # There will always be only 1 episode ('limit=1')
listitem = createListItem(episode, listitem = create_listitem(episode,
append_show_title=append_show_title, append_show_title=append_show_title,
append_sxxexx=append_sxxexx) append_sxxexx=append_sxxexx)
xbmcplugin.addDirectoryItem(handle=HANDLE, xbmcplugin.addDirectoryItem(handle=HANDLE,
url=episode['file'], url=episode['file'],
listitem=listitem, listitem=listitem,
@ -650,10 +664,10 @@ def watchlater():
Listing for plex.tv Watch Later section (if signed in to plex.tv) Listing for plex.tv Watch Later section (if signed in to plex.tv)
""" """
if window('plex_token') == '': if window('plex_token') == '':
log.error('No watch later - not signed in to plex.tv') LOG.error('No watch later - not signed in to plex.tv')
return xbmcplugin.endOfDirectory(HANDLE, False) return xbmcplugin.endOfDirectory(HANDLE, False)
if window('plex_restricteduser') == 'true': if window('plex_restricteduser') == 'true':
log.error('No watch later - restricted user') LOG.error('No watch later - restricted user')
return xbmcplugin.endOfDirectory(HANDLE, False) return xbmcplugin.endOfDirectory(HANDLE, False)
xml = downloadutils.DownloadUtils().downloadUrl( xml = downloadutils.DownloadUtils().downloadUrl(
@ -661,10 +675,10 @@ def watchlater():
authenticate=False, authenticate=False,
headerOptions={'X-Plex-Token': window('plex_token')}) headerOptions={'X-Plex-Token': window('plex_token')})
if xml in (None, 401): if xml in (None, 401):
log.error('Could not download watch later list from plex.tv') LOG.error('Could not download watch later list from plex.tv')
return xbmcplugin.endOfDirectory(HANDLE, False) return xbmcplugin.endOfDirectory(HANDLE, False)
log.info('Displaying watch later plex.tv items') LOG.info('Displaying watch later plex.tv items')
xbmcplugin.setContent(HANDLE, 'movies') xbmcplugin.setContent(HANDLE, 'movies')
direct_paths = settings('useDirectPaths') == '1' direct_paths = settings('useDirectPaths') == '1'
for item in xml: for item in xml:
@ -683,10 +697,10 @@ def channels():
try: try:
xml[0].attrib xml[0].attrib
except (ValueError, AttributeError, IndexError, TypeError): except (ValueError, AttributeError, IndexError, TypeError):
log.error('Could not download Plex Channels') LOG.error('Could not download Plex Channels')
return xbmcplugin.endOfDirectory(HANDLE, False) return xbmcplugin.endOfDirectory(HANDLE, False)
log.info('Displaying Plex Channels') LOG.info('Displaying Plex Channels')
xbmcplugin.setContent(HANDLE, 'files') xbmcplugin.setContent(HANDLE, 'files')
for method in v.SORT_METHODS_DIRECTORY: for method in v.SORT_METHODS_DIRECTORY:
xbmcplugin.addSortMethod(HANDLE, getattr(xbmcplugin, method)) xbmcplugin.addSortMethod(HANDLE, getattr(xbmcplugin, method))
@ -709,7 +723,7 @@ def browse_plex(key=None, plex_section_id=None):
try: try:
xml[0].attrib xml[0].attrib
except (ValueError, AttributeError, IndexError, TypeError): except (ValueError, AttributeError, IndexError, TypeError):
log.error('Could not browse to %s' % key) LOG.error('Could not browse to %s', key)
return xbmcplugin.endOfDirectory(HANDLE, False) return xbmcplugin.endOfDirectory(HANDLE, False)
photos = False photos = False
@ -855,19 +869,19 @@ def extras(plex_id):
xbmcplugin.endOfDirectory(HANDLE) xbmcplugin.endOfDirectory(HANDLE)
def enterPMS(): def create_new_pms():
""" """
Opens dialogs for the user the plug in the PMS details Opens dialogs for the user the plug in the PMS details
""" """
# "Enter your Plex Media Server's IP or URL. Examples are:" # "Enter your Plex Media Server's IP or URL. Examples are:"
dialog('ok', lang(29999), lang(39215), '192.168.1.2', 'plex.myServer.org') dialog('ok', lang(29999), lang(39215), '192.168.1.2', 'plex.myServer.org')
ip = dialog('input', "Enter PMS IP or URL") address = dialog('input', "Enter PMS IP or URL")
if ip == '': if address == '':
return return
port = dialog('input', "Enter PMS port", '32400', type='{numeric}') port = dialog('input', "Enter PMS port", '32400', type='{numeric}')
if port == '': if port == '':
return return
url = '%s:%s' % (ip, port) url = '%s:%s' % (address, port)
# "Does your Plex Media Server support SSL connections? # "Does your Plex Media Server support SSL connections?
# (https instead of http)" # (https instead of http)"
https = dialog('yesno', lang(29999), lang(39217)) https = dialog('yesno', lang(29999), lang(39217))
@ -876,9 +890,8 @@ def enterPMS():
else: else:
url = 'http://%s' % url url = 'http://%s' % url
https = 'true' if https else 'false' https = 'true' if https else 'false'
machine_identifier = GetMachineIdentifier(url)
machineIdentifier = GetMachineIdentifier(url) if machine_identifier is None:
if machineIdentifier is None:
# "Error contacting url # "Error contacting url
# Abort (Yes) or save address anyway (No)" # Abort (Yes) or save address anyway (No)"
if dialog('yesno', if dialog('yesno',
@ -888,22 +901,22 @@ def enterPMS():
else: else:
settings('plex_machineIdentifier', '') settings('plex_machineIdentifier', '')
else: else:
settings('plex_machineIdentifier', machineIdentifier) settings('plex_machineIdentifier', machine_identifier)
log.info('Set new PMS to https %s, ip %s, port %s, machineIdentifier %s' LOG.info('Set new PMS to https %s, address %s, port %s, machineId %s',
% (https, ip, port, machineIdentifier)) https, address, port, machine_identifier)
settings('https', value=https) settings('https', value=https)
settings('ipaddress', value=ip) settings('ipaddress', value=address)
settings('port', value=port) settings('port', value=port)
# Chances are this is a local PMS, so disable SSL certificate check # Chances are this is a local PMS, so disable SSL certificate check
settings('sslverify', value='false') settings('sslverify', value='false')
# Sign out to trigger new login # Sign out to trigger new login
if __LogOut(): if _log_out():
# Only login again if logout was successful # Only login again if logout was successful
__LogIn() _log_in()
def __LogIn(): def _log_in():
""" """
Resets (clears) window properties to enable (re-)login Resets (clears) window properties to enable (re-)login
@ -915,7 +928,7 @@ def __LogIn():
plex_command('SUSPEND_USER_CLIENT', 'False') plex_command('SUSPEND_USER_CLIENT', 'False')
def __LogOut(): def _log_out():
""" """
Finishes lib scans, logs out user. Finishes lib scans, logs out user.
@ -938,11 +951,11 @@ def __LogOut():
dialog('ok', lang(29999), lang(39208)) dialog('ok', lang(29999), lang(39208))
# Resuming threads, just in case # Resuming threads, just in case
plex_command('SUSPEND_LIBRARY_THREAD', 'False') plex_command('SUSPEND_LIBRARY_THREAD', 'False')
log.error("Could not stop library sync, aborting") LOG.error("Could not stop library sync, aborting")
return False return False
counter += 1 counter += 1
sleep(50) sleep(50)
log.debug("Successfully stopped library sync") LOG.debug("Successfully stopped library sync")
counter = 0 counter = 0
# Log out currently signed in user: # Log out currently signed in user:
@ -953,7 +966,7 @@ def __LogOut():
if counter > 100: if counter > 100:
# 'Failed to reset PKC. Try to restart Kodi.' # 'Failed to reset PKC. Try to restart Kodi.'
dialog('ok', lang(29999), lang(39208)) dialog('ok', lang(29999), lang(39208))
log.error("Could not sign out user, aborting") LOG.error("Could not sign out user, aborting")
return False return False
counter += 1 counter += 1
sleep(50) sleep(50)

View file

@ -27,8 +27,10 @@ try:
from ssl import SSLError from ssl import SSLError
HAVE_SSL = True HAVE_SSL = True
except ImportError: except ImportError:
# dummy class of SSLError for ssl none-support environment.
class SSLError(Exception): class SSLError(Exception):
"""
Dummy class of SSLError for ssl none-support environment.
"""
pass pass
HAVE_SSL = False HAVE_SSL = False
@ -50,7 +52,7 @@ import utils
############################################################################### ###############################################################################
log = logging.getLogger("PLEX."+__name__) LOG = logging.getLogger("PLEX." + __name__)
############################################################################### ###############################################################################
@ -95,28 +97,31 @@ class WebSocketConnectionClosedException(WebSocketException):
""" """
pass pass
class WebSocketTimeoutException(WebSocketException): class WebSocketTimeoutException(WebSocketException):
""" """
WebSocketTimeoutException will be raised at socket timeout during read/write data. WebSocketTimeoutException will be raised at socket timeout during read and
write data.
""" """
pass pass
default_timeout = None
traceEnabled = False DEFAULT_TIMEOUT = None
TRACE_ENABLED = False
def enableTrace(tracable): def enable_trace(tracable):
""" """
turn on/off the tracability. turn on/off the tracability.
tracable: boolean value. if set True, tracability is enabled. tracable: boolean value. if set True, tracability is enabled.
""" """
global traceEnabled global TRACE_ENABLED
traceEnabled = tracable TRACE_ENABLED = tracable
if tracable: if tracable:
if not log.handlers: if not LOG.handlers:
log.addHandler(logging.StreamHandler()) LOG.addHandler(logging.StreamHandler())
log.setLevel(logging.DEBUG) LOG.setLevel(logging.DEBUG)
def setdefaulttimeout(timeout): def setdefaulttimeout(timeout):
@ -125,15 +130,15 @@ def setdefaulttimeout(timeout):
timeout: default socket timeout time. This value is second. timeout: default socket timeout time. This value is second.
""" """
global default_timeout global DEFAULT_TIMEOUT
default_timeout = timeout DEFAULT_TIMEOUT = timeout
def getdefaulttimeout(): def getdefaulttimeout():
""" """
Return the global timeout setting(second) to connect. Return the global timeout setting(second) to connect.
""" """
return default_timeout return DEFAULT_TIMEOUT
def _parse_url(url): def _parse_url(url):
@ -185,7 +190,8 @@ def create_connection(url, timeout=None, **options):
Connect to url and return the WebSocket object. Connect to url and return the WebSocket object.
Passing optional timeout parameter will set the timeout on the socket. 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. If no timeout is supplied, the global default timeout setting returned by
getdefauttimeout() is used.
You can customize using 'options'. You can customize using 'options'.
If you set "header" list object, you can set your own custom header. If you set "header" list object, you can set your own custom header.
@ -195,21 +201,23 @@ def create_connection(url, timeout=None, **options):
timeout: socket timeout time. This value is integer. timeout: socket timeout time. This value is integer.
if you set None for this value, it means "use default_timeout value" if you set None for this value, it means "use DEFAULT_TIMEOUT
value"
options: current support option is only "header". options: current support option is only "header".
if you set header as dict value, the custom HTTP headers are added. if you set header as dict value, the custom HTTP headers are added
""" """
sockopt = options.get("sockopt", []) sockopt = options.get("sockopt", [])
sslopt = options.get("sslopt", {}) sslopt = options.get("sslopt", {})
websock = WebSocket(sockopt=sockopt, sslopt=sslopt) websock = WebSocket(sockopt=sockopt, sslopt=sslopt)
websock.settimeout(timeout if timeout is not None else default_timeout) websock.settimeout(timeout if timeout is not None else DEFAULT_TIMEOUT)
websock.connect(url, **options) websock.connect(url, **options)
return websock return websock
_MAX_INTEGER = (1 << 32) -1
_MAX_INTEGER = (1 << 32) - 1
_AVAILABLE_KEY_CHARS = range(0x21, 0x2f + 1) + range(0x3a, 0x7e + 1) _AVAILABLE_KEY_CHARS = range(0x21, 0x2f + 1) + range(0x3a, 0x7e + 1)
_MAX_CHAR_BYTE = (1<<8) -1 _MAX_CHAR_BYTE = (1 << 8) - 1
# ref. Websocket gets an update, and it breaks stuff. # ref. Websocket gets an update, and it breaks stuff.
# http://axod.blogspot.com/2010/06/websocket-gets-update-and-it-breaks.html # http://axod.blogspot.com/2010/06/websocket-gets-update-and-it-breaks.html
@ -220,10 +228,7 @@ def _create_sec_websocket_key():
return base64.encodestring(uid.bytes).strip() return base64.encodestring(uid.bytes).strip()
_HEADERS_TO_CHECK = { _HEADERS_TO_CHECK = {"upgrade": "websocket", "connection": "upgrade"}
"upgrade": "websocket",
"connection": "upgrade",
}
class ABNF(object): class ABNF(object):
@ -234,16 +239,16 @@ class ABNF(object):
""" """
# operation code values. # operation code values.
OPCODE_CONT = 0x0 OPCODE_CONT = 0x0
OPCODE_TEXT = 0x1 OPCODE_TEXT = 0x1
OPCODE_BINARY = 0x2 OPCODE_BINARY = 0x2
OPCODE_CLOSE = 0x8 OPCODE_CLOSE = 0x8
OPCODE_PING = 0x9 OPCODE_PING = 0x9
OPCODE_PONG = 0xa OPCODE_PONG = 0xa
# available operation code value tuple # available operation code value tuple
OPCODES = (OPCODE_CONT, OPCODE_TEXT, OPCODE_BINARY, OPCODE_CLOSE, OPCODES = (OPCODE_CONT, OPCODE_TEXT, OPCODE_BINARY, OPCODE_CLOSE,
OPCODE_PING, OPCODE_PONG) OPCODE_PING, OPCODE_PONG)
# opcode human readable string # opcode human readable string
OPCODE_MAP = { OPCODE_MAP = {
@ -253,10 +258,10 @@ class ABNF(object):
OPCODE_CLOSE: "close", OPCODE_CLOSE: "close",
OPCODE_PING: "ping", OPCODE_PING: "ping",
OPCODE_PONG: "pong" OPCODE_PONG: "pong"
} }
# data length threashold. # data length threashold.
LENGTH_7 = 0x7d LENGTH_7 = 0x7d
LENGTH_16 = 1 << 16 LENGTH_16 = 1 << 16
LENGTH_63 = 1 << 63 LENGTH_63 = 1 << 63
@ -277,8 +282,8 @@ class ABNF(object):
def __str__(self): def __str__(self):
return "fin=" + str(self.fin) \ return "fin=" + str(self.fin) \
+ " opcode=" + str(self.opcode) \ + " opcode=" + str(self.opcode) \
+ " data=" + str(self.data) + " data=" + str(self.data)
@staticmethod @staticmethod
def create_frame(data, opcode): def create_frame(data, opcode):
@ -308,9 +313,9 @@ class ABNF(object):
if length >= ABNF.LENGTH_63: if length >= ABNF.LENGTH_63:
raise ValueError("data is too long") raise ValueError("data is too long")
frame_header = chr(self.fin << 7 frame_header = chr(self.fin << 7 |
| self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4 self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4 |
| self.opcode) self.opcode)
if length < ABNF.LENGTH_7: if length < ABNF.LENGTH_7:
frame_header += chr(self.mask << 7 | length) frame_header += chr(self.mask << 7 | length)
elif length < ABNF.LENGTH_16: elif length < ABNF.LENGTH_16:
@ -395,6 +400,9 @@ class WebSocket(object):
self._cont_data = None self._cont_data = None
def fileno(self): def fileno(self):
"""
Returns sock.fileno()
"""
return self.sock.fileno() return self.sock.fileno()
def set_mask_key(self, func): def set_mask_key(self, func):
@ -438,7 +446,7 @@ class WebSocket(object):
timeout: socket timeout time. This value is integer. timeout: socket timeout time. This value is integer.
if you set None for this value, if you set None for this value,
it means "use default_timeout value" it means "use DEFAULT_TIMEOUT value"
options: current support option is only "header". options: current support option is only "header".
if you set header as dict value, if you set header as dict value,
@ -487,10 +495,10 @@ class WebSocket(object):
header_str = "\r\n".join(headers) header_str = "\r\n".join(headers)
self._send(header_str) self._send(header_str)
if traceEnabled: if TRACE_ENABLED:
log.debug("--- request header ---") LOG.debug("--- request header ---")
log.debug(header_str) LOG.debug(header_str)
log.debug("-----------------------") LOG.debug("-----------------------")
status, resp_headers = self._read_headers() status, resp_headers = self._read_headers()
if status != 101: if status != 101:
@ -526,16 +534,16 @@ class WebSocket(object):
def _read_headers(self): def _read_headers(self):
status = None status = None
headers = {} headers = {}
if traceEnabled: if TRACE_ENABLED:
log.debug("--- response header ---") LOG.debug("--- response header ---")
while True: while True:
line = self._recv_line() line = self._recv_line()
if line == "\r\n": if line == "\r\n":
break break
line = line.strip() line = line.strip()
if traceEnabled: if TRACE_ENABLED:
log.debug(line) LOG.debug(line)
if not status: if not status:
status_info = line.split(" ", 2) status_info = line.split(" ", 2)
status = int(status_info[1]) status = int(status_info[1])
@ -547,8 +555,8 @@ class WebSocket(object):
else: else:
raise WebSocketException("Invalid header") raise WebSocketException("Invalid header")
if traceEnabled: if TRACE_ENABLED:
log.debug("-----------------------") LOG.debug("-----------------------")
return status, headers return status, headers
@ -567,14 +575,17 @@ class WebSocket(object):
frame.get_mask_key = self.get_mask_key frame.get_mask_key = self.get_mask_key
data = frame.format() data = frame.format()
length = len(data) length = len(data)
if traceEnabled: if TRACE_ENABLED:
log.debug("send: " + repr(data)) LOG.debug("send: %s", repr(data))
while data: while data:
l = self._send(data) l = self._send(data)
data = data[l:] data = data[l:]
return length return length
def send_binary(self, payload): def send_binary(self, payload):
"""
send the payload
"""
return self.send(payload, ABNF.OPCODE_BINARY) return self.send(payload, ABNF.OPCODE_BINARY)
def ping(self, payload=""): def ping(self, payload=""):
@ -693,34 +704,10 @@ class WebSocket(object):
reason: the reason to close. This must be string. reason: the reason to close. This must be string.
""" """
try: try:
self.sock.shutdown(socket.SHUT_RDWR) self.sock.shutdown(socket.SHUT_RDWR)
except: except:
pass 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 log.isEnabledFor(logging.ERROR):
recv_status = struct.unpack("!H", frame.data)[0]
if recv_status != STATUS_NORMAL:
log.error("close status: " + repr(recv_status))
except:
pass
self.sock.settimeout(timeout)
self.sock.shutdown(socket.SHUT_RDWR)
except:
pass
'''
self._closeInternal() self._closeInternal()
def _closeInternal(self): def _closeInternal(self):
@ -752,7 +739,6 @@ class WebSocket(object):
raise WebSocketConnectionClosedException() raise WebSocketConnectionClosedException()
return bytes_ return bytes_
def _recv_strict(self, bufsize): def _recv_strict(self, bufsize):
shortage = bufsize - sum(len(x) for x in self._recv_buffer) shortage = bufsize - sum(len(x) for x in self._recv_buffer)
while shortage > 0: while shortage > 0:
@ -767,7 +753,6 @@ class WebSocket(object):
self._recv_buffer = [unified[bufsize:]] self._recv_buffer = [unified[bufsize:]]
return unified[:bufsize] return unified[:bufsize]
def _recv_line(self): def _recv_line(self):
line = [] line = []
while True: while True:
@ -846,9 +831,11 @@ class WebSocketApp(object):
run event loop for WebSocket framework. run event loop for WebSocket framework.
This loop is infinite loop and is alive during websocket is available. This loop is infinite loop and is alive during websocket is available.
sockopt: values for socket.setsockopt. sockopt: values for socket.setsockopt.
sockopt must be tuple and each element is argument of sock.setscokopt. sockopt must be tuple and each element is argument of
sock.setscokopt.
sslopt: ssl socket optional dict. sslopt: ssl socket optional dict.
ping_interval: automatically send "ping" command every specified period(second) ping_interval: automatically send "ping" command every specified
period(second)
if set to 0, not send automatically. if set to 0, not send automatically.
""" """
if sockopt is None: if sockopt is None:
@ -861,26 +848,26 @@ class WebSocketApp(object):
self.keep_running = True self.keep_running = True
try: try:
self.sock = WebSocket(self.get_mask_key, sockopt=sockopt, sslopt=sslopt) self.sock = WebSocket(self.get_mask_key,
self.sock.settimeout(default_timeout) sockopt=sockopt,
sslopt=sslopt)
self.sock.settimeout(DEFAULT_TIMEOUT)
self.sock.connect(self.url, header=self.header) self.sock.connect(self.url, header=self.header)
self._callback(self.on_open) self._callback(self.on_open)
if ping_interval: if ping_interval:
thread = threading.Thread(target=self._send_ping, args=(ping_interval,)) thread = threading.Thread(target=self._send_ping,
args=(ping_interval,))
thread.setDaemon(True) thread.setDaemon(True)
thread.start() thread.start()
while self.keep_running: while self.keep_running:
try: try:
data = self.sock.recv() data = self.sock.recv()
if data is None or self.keep_running is False:
if data is None or self.keep_running == False:
break break
self._callback(self.on_message, data) self._callback(self.on_message, data)
except Exception, e: except Exception, e:
#print str(e.args[0])
if "timed out" not in e.args[0]: if "timed out" not in e.args[0]:
raise e raise e
@ -898,19 +885,18 @@ class WebSocketApp(object):
try: try:
callback(self, *args) callback(self, *args)
except Exception, e: except Exception, e:
log.error(e) LOG.error(e)
if True:#log.isEnabledFor(logging.DEBUG): _, _, tb = sys.exc_info()
_, _, tb = sys.exc_info() traceback.print_tb(tb)
traceback.print_tb(tb)
if __name__ == "__main__": if __name__ == "__main__":
enableTrace(True) enable_trace(True)
ws = create_connection("ws://echo.websocket.org/") WEBSOCKET = create_connection("ws://echo.websocket.org/")
print("Sending 'Hello, World'...") LOG.info("Sending 'Hello, World'...")
ws.send("Hello, World") WEBSOCKET.send("Hello, World")
print("Sent") LOG.info("Sent")
print("Receiving...") LOG.info("Receiving...")
result = ws.recv() RESULT = WEBSOCKET.recv()
print("Received '%s'" % result) LOG.info("Received '%s'", RESULT)
ws.close() WEBSOCKET.close()