Merge branch 'stable-version'
This commit is contained in:
commit
0d77102117
3 changed files with 282 additions and 288 deletions
31
default.py
31
default.py
|
@ -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():
|
||||||
|
|
|
@ -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
|
||||||
|
@ -347,21 +361,24 @@ def getInProgressEpisodes(tagname, limit):
|
||||||
'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,12 +408,11 @@ 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
|
||||||
|
@ -405,14 +421,14 @@ def getRecentEpisodes(viewid, mediatype, tagname, limit):
|
||||||
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,7 +646,7 @@ 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,
|
||||||
|
@ -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)
|
||||||
|
|
|
@ -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,18 +201,20 @@ 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
|
||||||
|
@ -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):
|
||||||
|
@ -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()
|
||||||
|
|
Loading…
Reference in a new issue