Cleanup utils.py
This commit is contained in:
parent
3ff15ba772
commit
13ca30c742
1 changed files with 264 additions and 328 deletions
|
@ -1,9 +1,8 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
import logging
|
||||||
import cProfile
|
import cProfile
|
||||||
import inspect
|
|
||||||
import json
|
import json
|
||||||
import pstats
|
import pstats
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
@ -24,7 +23,86 @@ import xbmcvfs
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
log = logging.getLogger("PLEX."+__name__)
|
||||||
|
|
||||||
addonName = 'PlexKodiConnect'
|
addonName = 'PlexKodiConnect'
|
||||||
|
WINDOW = xbmcgui.Window(10000)
|
||||||
|
ADDON = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Main methods
|
||||||
|
|
||||||
|
|
||||||
|
def window(property, value=None, clear=False, windowid=10000):
|
||||||
|
"""
|
||||||
|
Get or set window property - thread safe!
|
||||||
|
|
||||||
|
Returns unicode.
|
||||||
|
|
||||||
|
Property and value may be string or unicode
|
||||||
|
"""
|
||||||
|
if windowid != 10000:
|
||||||
|
win = xbmcgui.Window(windowid)
|
||||||
|
else:
|
||||||
|
win = WINDOW
|
||||||
|
|
||||||
|
if clear:
|
||||||
|
win.clearProperty(property)
|
||||||
|
elif value is not None:
|
||||||
|
win.setProperty(tryEncode(property), tryEncode(value))
|
||||||
|
else:
|
||||||
|
return tryDecode(win.getProperty(property))
|
||||||
|
|
||||||
|
|
||||||
|
def settings(setting, value=None):
|
||||||
|
"""
|
||||||
|
Get or add addon setting. Returns unicode
|
||||||
|
|
||||||
|
setting and value can either be unicode or string
|
||||||
|
"""
|
||||||
|
if value is not None:
|
||||||
|
# Takes string or unicode by default!
|
||||||
|
ADDON.setSetting(tryEncode(setting), tryEncode(value))
|
||||||
|
else:
|
||||||
|
# Should return unicode by default, but just in case
|
||||||
|
return tryDecode(ADDON.getSetting(setting))
|
||||||
|
|
||||||
|
|
||||||
|
def language(stringid):
|
||||||
|
# Central string retrieval
|
||||||
|
return ADDON.getLocalizedString(stringid)
|
||||||
|
|
||||||
|
|
||||||
|
def tryEncode(uniString, encoding='utf-8'):
|
||||||
|
"""
|
||||||
|
Will try to encode uniString (in unicode) to encoding. This possibly
|
||||||
|
fails with e.g. Android TV's Python, which does not accept arguments for
|
||||||
|
string.encode()
|
||||||
|
"""
|
||||||
|
if isinstance(uniString, str):
|
||||||
|
# already encoded
|
||||||
|
return uniString
|
||||||
|
try:
|
||||||
|
uniString = uniString.encode(encoding, "ignore")
|
||||||
|
except TypeError:
|
||||||
|
uniString = uniString.encode()
|
||||||
|
return uniString
|
||||||
|
|
||||||
|
|
||||||
|
def tryDecode(string, encoding='utf-8'):
|
||||||
|
"""
|
||||||
|
Will try to decode string (encoded) using encoding. This possibly
|
||||||
|
fails with e.g. Android TV's Python, which does not accept arguments for
|
||||||
|
string.encode()
|
||||||
|
"""
|
||||||
|
if isinstance(string, unicode):
|
||||||
|
# already decoded
|
||||||
|
return string
|
||||||
|
try:
|
||||||
|
string = string.decode(encoding, "ignore")
|
||||||
|
except TypeError:
|
||||||
|
string = string.decode()
|
||||||
|
return string
|
||||||
|
|
||||||
|
|
||||||
def DateToKodi(stamp):
|
def DateToKodi(stamp):
|
||||||
|
@ -45,52 +123,6 @@ def DateToKodi(stamp):
|
||||||
return localdate
|
return localdate
|
||||||
|
|
||||||
|
|
||||||
def changePlayState(itemType, kodiId, playCount, lastplayed):
|
|
||||||
"""
|
|
||||||
YET UNUSED
|
|
||||||
|
|
||||||
kodiId: int or str
|
|
||||||
playCount: int or str
|
|
||||||
lastplayed: str or int unix timestamp
|
|
||||||
"""
|
|
||||||
logMsg("changePlayState", "start", 1)
|
|
||||||
lastplayed = DateToKodi(lastplayed)
|
|
||||||
|
|
||||||
kodiId = int(kodiId)
|
|
||||||
playCount = int(playCount)
|
|
||||||
method = {
|
|
||||||
'movie': ' VideoLibrary.SetMovieDetails',
|
|
||||||
'episode': 'VideoLibrary.SetEpisodeDetails',
|
|
||||||
'musicvideo': ' VideoLibrary.SetMusicVideoDetails', # TODO
|
|
||||||
'show': 'VideoLibrary.SetTVShowDetails', # TODO
|
|
||||||
'': 'AudioLibrary.SetAlbumDetails', # TODO
|
|
||||||
'': 'AudioLibrary.SetArtistDetails', # TODO
|
|
||||||
'track': 'AudioLibrary.SetSongDetails'
|
|
||||||
}
|
|
||||||
params = {
|
|
||||||
'movie': {
|
|
||||||
'movieid': kodiId,
|
|
||||||
'playcount': playCount,
|
|
||||||
'lastplayed': lastplayed
|
|
||||||
},
|
|
||||||
'episode': {
|
|
||||||
'episodeid': kodiId,
|
|
||||||
'playcount': playCount,
|
|
||||||
'lastplayed': lastplayed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
query = {
|
|
||||||
"jsonrpc": "2.0",
|
|
||||||
"id": 1,
|
|
||||||
}
|
|
||||||
query['method'] = method[itemType]
|
|
||||||
query['params'] = params[itemType]
|
|
||||||
result = xbmc.executeJSONRPC(json.dumps(query))
|
|
||||||
result = json.loads(result)
|
|
||||||
result = result.get('result')
|
|
||||||
logMsg("changePlayState", "JSON result was: %s" % result, 1)
|
|
||||||
|
|
||||||
|
|
||||||
def IfExists(path):
|
def IfExists(path):
|
||||||
"""
|
"""
|
||||||
Kodi's xbmcvfs.exists is broken - it caches the results for directories.
|
Kodi's xbmcvfs.exists is broken - it caches the results for directories.
|
||||||
|
@ -112,160 +144,6 @@ def IfExists(path):
|
||||||
return answer
|
return answer
|
||||||
|
|
||||||
|
|
||||||
def forEveryMethod(decorator):
|
|
||||||
"""
|
|
||||||
Wrapper for classes to add the decorator "decorator" to all methods of the
|
|
||||||
class
|
|
||||||
"""
|
|
||||||
def decorate(cls):
|
|
||||||
for attr in cls.__dict__: # there's propably a better way to do this
|
|
||||||
if callable(getattr(cls, attr)):
|
|
||||||
setattr(cls, attr, decorator(getattr(cls, attr)))
|
|
||||||
return cls
|
|
||||||
return decorate
|
|
||||||
|
|
||||||
|
|
||||||
def CatchExceptions(warnuser=False):
|
|
||||||
"""
|
|
||||||
Decorator for methods to catch exceptions and log them. Useful for e.g.
|
|
||||||
librarysync threads using itemtypes.py, because otherwise we would not
|
|
||||||
get informed of crashes
|
|
||||||
|
|
||||||
warnuser=True: sets the window flag 'plex_scancrashed' to true
|
|
||||||
which will trigger a Kodi infobox to inform user
|
|
||||||
"""
|
|
||||||
def decorate(func):
|
|
||||||
@wraps(func)
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
try:
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
except Exception as e:
|
|
||||||
logMsg(addonName, '%s has crashed' % func.__name__, -1)
|
|
||||||
logMsg(addonName, e, -1)
|
|
||||||
import traceback
|
|
||||||
logMsg(addonName, "Traceback:\n%s"
|
|
||||||
% traceback.format_exc(), -1)
|
|
||||||
if warnuser:
|
|
||||||
window('plex_scancrashed', value='true')
|
|
||||||
return
|
|
||||||
return wrapper
|
|
||||||
return decorate
|
|
||||||
|
|
||||||
|
|
||||||
def LogTime(func):
|
|
||||||
"""
|
|
||||||
Decorator for functions and methods to log the time it took to run the code
|
|
||||||
"""
|
|
||||||
@wraps(func)
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
starttotal = datetime.now()
|
|
||||||
result = func(*args, **kwargs)
|
|
||||||
elapsedtotal = datetime.now() - starttotal
|
|
||||||
logMsg('%s %s' % (addonName, func.__name__),
|
|
||||||
'It took %s to run the function.' % (elapsedtotal), 1)
|
|
||||||
return result
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
def ThreadMethodsAdditionalStop(windowAttribute):
|
|
||||||
"""
|
|
||||||
Decorator to replace stopThread method to include the Kodi windowAttribute
|
|
||||||
|
|
||||||
Use with any sync threads. @ThreadMethods still required FIRST
|
|
||||||
"""
|
|
||||||
def wrapper(cls):
|
|
||||||
def threadStopped(self):
|
|
||||||
return (self._threadStopped or
|
|
||||||
(window('plex_terminateNow') == "true") or
|
|
||||||
window(windowAttribute) == "true")
|
|
||||||
cls.threadStopped = threadStopped
|
|
||||||
return cls
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
def ThreadMethodsAdditionalSuspend(windowAttribute):
|
|
||||||
"""
|
|
||||||
Decorator to replace threadSuspended(): thread now also suspends if a
|
|
||||||
Kodi windowAttribute is set to 'true', e.g. 'suspend_LibraryThread'
|
|
||||||
|
|
||||||
Use with any library sync threads. @ThreadMethods still required FIRST
|
|
||||||
"""
|
|
||||||
def wrapper(cls):
|
|
||||||
def threadSuspended(self):
|
|
||||||
return (self._threadSuspended or
|
|
||||||
window(windowAttribute) == 'true')
|
|
||||||
cls.threadSuspended = threadSuspended
|
|
||||||
return cls
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
def ThreadMethods(cls):
|
|
||||||
"""
|
|
||||||
Decorator to add the following methods to a threading class:
|
|
||||||
|
|
||||||
suspendThread(): pauses the thread
|
|
||||||
resumeThread(): resumes the thread
|
|
||||||
stopThread(): stopps/kills the thread
|
|
||||||
|
|
||||||
threadSuspended(): returns True if thread is suspend_thread
|
|
||||||
threadStopped(): returns True if thread is stopped (or should stop ;-))
|
|
||||||
ALSO stops if Kodi is exited
|
|
||||||
|
|
||||||
Also adds the following class attributes:
|
|
||||||
_threadStopped
|
|
||||||
_threadSuspended
|
|
||||||
"""
|
|
||||||
# Attach new attributes to class
|
|
||||||
cls._threadStopped = False
|
|
||||||
cls._threadSuspended = False
|
|
||||||
|
|
||||||
# Define new class methods and attach them to class
|
|
||||||
def stopThread(self):
|
|
||||||
self._threadStopped = True
|
|
||||||
cls.stopThread = stopThread
|
|
||||||
|
|
||||||
def suspendThread(self):
|
|
||||||
self._threadSuspended = True
|
|
||||||
cls.suspendThread = suspendThread
|
|
||||||
|
|
||||||
def resumeThread(self):
|
|
||||||
self._threadSuspended = False
|
|
||||||
cls.resumeThread = resumeThread
|
|
||||||
|
|
||||||
def threadSuspended(self):
|
|
||||||
return self._threadSuspended
|
|
||||||
cls.threadSuspended = threadSuspended
|
|
||||||
|
|
||||||
def threadStopped(self):
|
|
||||||
return self._threadStopped or (window('plex_terminateNow') == 'true')
|
|
||||||
cls.threadStopped = threadStopped
|
|
||||||
|
|
||||||
# Return class to render this a decorator
|
|
||||||
return cls
|
|
||||||
|
|
||||||
|
|
||||||
def logging(cls):
|
|
||||||
"""
|
|
||||||
A decorator adding logging capabilities to classes.
|
|
||||||
Also adds self.addonName to the class
|
|
||||||
|
|
||||||
Syntax: self.logMsg(message, loglevel)
|
|
||||||
|
|
||||||
Loglevel: -2 (Error) to 2 (DB debug)
|
|
||||||
"""
|
|
||||||
# Attach new attributes to class
|
|
||||||
cls.addonName = addonName
|
|
||||||
|
|
||||||
# Define new class methods and attach them to class
|
|
||||||
def newFunction(self, msg, lvl=0):
|
|
||||||
title = "%s %s" % (addonName, cls.__name__)
|
|
||||||
logMsg(title, msg, lvl)
|
|
||||||
cls.logMsg = newFunction
|
|
||||||
|
|
||||||
# Return class to render this a decorator
|
|
||||||
return cls
|
|
||||||
|
|
||||||
|
|
||||||
def IntFromStr(string):
|
def IntFromStr(string):
|
||||||
"""
|
"""
|
||||||
Returns an int from string or the int 0 if something happened
|
Returns an int from string or the int 0 if something happened
|
||||||
|
@ -292,85 +170,6 @@ def getUnixTimestamp(secondsIntoTheFuture=None):
|
||||||
return timegm(future.timetuple())
|
return timegm(future.timetuple())
|
||||||
|
|
||||||
|
|
||||||
def logMsg(title, msg, level=1):
|
|
||||||
# Get the logLevel set in UserClient
|
|
||||||
try:
|
|
||||||
logLevel = int(window('plex_logLevel'))
|
|
||||||
except ValueError:
|
|
||||||
logLevel = 0
|
|
||||||
kodiLevel = {
|
|
||||||
-1: xbmc.LOGERROR,
|
|
||||||
0: xbmc.LOGNOTICE,
|
|
||||||
1: xbmc.LOGNOTICE,
|
|
||||||
2: xbmc.LOGNOTICE
|
|
||||||
}
|
|
||||||
if logLevel >= level:
|
|
||||||
if logLevel == 2: # inspect is expensive
|
|
||||||
func = inspect.currentframe().f_back.f_back.f_code
|
|
||||||
try:
|
|
||||||
xbmc.log("%s -> %s : %s" % (
|
|
||||||
title, func.co_name, msg), level=kodiLevel[level])
|
|
||||||
except UnicodeEncodeError:
|
|
||||||
try:
|
|
||||||
xbmc.log("%s -> %s : %s" % (
|
|
||||||
title, func.co_name, tryEncode(msg)),
|
|
||||||
level=kodiLevel[level])
|
|
||||||
except:
|
|
||||||
xbmc.log("%s -> %s : %s" % (
|
|
||||||
title, func.co_name, 'COULDNT LOG'),
|
|
||||||
level=kodiLevel[level])
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
xbmc.log("%s -> %s" % (title, msg), level=kodiLevel[level])
|
|
||||||
except UnicodeEncodeError:
|
|
||||||
try:
|
|
||||||
xbmc.log("%s -> %s" % (title, tryEncode(msg)),
|
|
||||||
level=kodiLevel[level])
|
|
||||||
except:
|
|
||||||
xbmc.log("%s -> %s " % (title, 'COULDNT LOG'),
|
|
||||||
level=kodiLevel[level])
|
|
||||||
|
|
||||||
|
|
||||||
def window(property, value=None, clear=False, windowid=10000):
|
|
||||||
"""
|
|
||||||
Get or set window property - thread safe!
|
|
||||||
|
|
||||||
Returns unicode.
|
|
||||||
|
|
||||||
Property needs to be string; value may be string or unicode
|
|
||||||
"""
|
|
||||||
WINDOW = xbmcgui.Window(windowid)
|
|
||||||
|
|
||||||
#setproperty accepts both string and unicode but utf-8 strings are adviced by kodi devs because some unicode can give issues
|
|
||||||
if clear:
|
|
||||||
WINDOW.clearProperty(property)
|
|
||||||
elif value is not None:
|
|
||||||
WINDOW.setProperty(property, tryEncode(value))
|
|
||||||
else:
|
|
||||||
return tryDecode(WINDOW.getProperty(property))
|
|
||||||
|
|
||||||
def settings(setting, value=None):
|
|
||||||
"""
|
|
||||||
Get or add addon setting. Returns unicode
|
|
||||||
|
|
||||||
Settings needs to be string
|
|
||||||
Value can either be unicode or string
|
|
||||||
"""
|
|
||||||
addon = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
|
|
||||||
|
|
||||||
if value is not None:
|
|
||||||
# Takes string or unicode by default!
|
|
||||||
addon.setSetting(setting, tryEncode(value))
|
|
||||||
else:
|
|
||||||
# Should return unicode by default, but just in case
|
|
||||||
return tryDecode(addon.getSetting(setting))
|
|
||||||
|
|
||||||
def language(stringid):
|
|
||||||
# Central string retrieval
|
|
||||||
addon = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
|
|
||||||
string = addon.getLocalizedString(stringid) #returns unicode object
|
|
||||||
return string
|
|
||||||
|
|
||||||
def kodiSQL(media_type="video"):
|
def kodiSQL(media_type="video"):
|
||||||
|
|
||||||
if media_type == "emby":
|
if media_type == "emby":
|
||||||
|
@ -445,7 +244,8 @@ def setScreensaver(value):
|
||||||
'value': value
|
'value': value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logMsg("PLEX", "Toggling screensaver: %s %s" % (value, xbmc.executeJSONRPC(json.dumps(query))), 1)
|
log.debug("Toggling screensaver: %s %s"
|
||||||
|
% (value, xbmc.executeJSONRPC(json.dumps(query))))
|
||||||
|
|
||||||
def reset():
|
def reset():
|
||||||
|
|
||||||
|
@ -458,7 +258,7 @@ def reset():
|
||||||
window('plex_shouldStop', value="true")
|
window('plex_shouldStop', value="true")
|
||||||
count = 10
|
count = 10
|
||||||
while window('plex_dbScan') == "true":
|
while window('plex_dbScan') == "true":
|
||||||
logMsg("PLEX", "Sync is running, will retry: %s..." % count)
|
log.debug("Sync is running, will retry: %s..." % count)
|
||||||
count -= 1
|
count -= 1
|
||||||
if count == 0:
|
if count == 0:
|
||||||
dialog.ok("Warning", "Could not stop the database from running. Try again.")
|
dialog.ok("Warning", "Could not stop the database from running. Try again.")
|
||||||
|
@ -472,7 +272,7 @@ def reset():
|
||||||
deleteNodes()
|
deleteNodes()
|
||||||
|
|
||||||
# Wipe the kodi databases
|
# Wipe the kodi databases
|
||||||
logMsg("Plex", "Resetting the Kodi video database.", 0)
|
log.info("Resetting the Kodi video database.")
|
||||||
connection = kodiSQL('video')
|
connection = kodiSQL('video')
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
|
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
|
||||||
|
@ -485,7 +285,7 @@ def reset():
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
if settings('enableMusic') == "true":
|
if settings('enableMusic') == "true":
|
||||||
logMsg("Plex", "Resetting the Kodi music database.")
|
log.info("Resetting the Kodi music database.")
|
||||||
connection = kodiSQL('music')
|
connection = kodiSQL('music')
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
|
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
|
||||||
|
@ -498,7 +298,7 @@ def reset():
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
# Wipe the Plex database
|
# Wipe the Plex database
|
||||||
logMsg("Plex", "Resetting the Emby database.", 0)
|
log.info("Resetting the Plex database.")
|
||||||
connection = kodiSQL('emby')
|
connection = kodiSQL('emby')
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
|
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
|
||||||
|
@ -515,7 +315,7 @@ def reset():
|
||||||
# Offer to wipe cached thumbnails
|
# Offer to wipe cached thumbnails
|
||||||
resp = dialog.yesno("Warning", "Remove all cached artwork?")
|
resp = dialog.yesno("Warning", "Remove all cached artwork?")
|
||||||
if resp:
|
if resp:
|
||||||
logMsg("EMBY", "Resetting all cached artwork.", 0)
|
log.info("Resetting all cached artwork.")
|
||||||
# Remove all existing textures first
|
# Remove all existing textures first
|
||||||
path = tryDecode(xbmc.translatePath("special://thumbnails/"))
|
path = tryDecode(xbmc.translatePath("special://thumbnails/"))
|
||||||
if xbmcvfs.exists(path):
|
if xbmcvfs.exists(path):
|
||||||
|
@ -555,7 +355,7 @@ def reset():
|
||||||
addondir = tryDecode(xbmc.translatePath(addon.getAddonInfo('profile')))
|
addondir = tryDecode(xbmc.translatePath(addon.getAddonInfo('profile')))
|
||||||
dataPath = "%ssettings.xml" % addondir
|
dataPath = "%ssettings.xml" % addondir
|
||||||
xbmcvfs.delete(tryEncode(dataPath))
|
xbmcvfs.delete(tryEncode(dataPath))
|
||||||
logMsg("PLEX", "Deleting: settings.xml", 1)
|
log.info("Deleting: settings.xml")
|
||||||
|
|
||||||
dialog.ok(
|
dialog.ok(
|
||||||
heading=addonName,
|
heading=addonName,
|
||||||
|
@ -576,7 +376,7 @@ def profiling(sortby="cumulative"):
|
||||||
s = StringIO.StringIO()
|
s = StringIO.StringIO()
|
||||||
ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
|
ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
|
||||||
ps.print_stats()
|
ps.print_stats()
|
||||||
logMsg("EMBY Profiling", s.getvalue(), 1)
|
log.debug(s.getvalue())
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -785,7 +585,6 @@ def passwordsXML():
|
||||||
# To add network credentials
|
# To add network credentials
|
||||||
path = tryDecode(xbmc.translatePath("special://userdata/"))
|
path = tryDecode(xbmc.translatePath("special://userdata/"))
|
||||||
xmlpath = "%spasswords.xml" % path
|
xmlpath = "%spasswords.xml" % path
|
||||||
logMsg('passwordsXML', 'Path to passwords.xml: %s' % xmlpath, 1)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
xmlparse = etree.parse(xmlpath)
|
xmlparse = etree.parse(xmlpath)
|
||||||
|
@ -813,13 +612,13 @@ def passwordsXML():
|
||||||
for path in paths:
|
for path in paths:
|
||||||
if path.find('.//from').text == "smb://%s/" % credentials:
|
if path.find('.//from').text == "smb://%s/" % credentials:
|
||||||
paths.remove(path)
|
paths.remove(path)
|
||||||
logMsg("passwordsXML",
|
log.info("Successfully removed credentials for: %s"
|
||||||
"Successfully removed credentials for: %s"
|
% credentials)
|
||||||
% credentials, 1)
|
|
||||||
etree.ElementTree(root).write(xmlpath)
|
etree.ElementTree(root).write(xmlpath)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
logMsg("Plex", "Failed to find saved server: %s in passwords.xml" % credentials, 1)
|
log.error("Failed to find saved server: %s in passwords.xml"
|
||||||
|
% credentials)
|
||||||
|
|
||||||
settings('networkCreds', value="")
|
settings('networkCreds', value="")
|
||||||
xbmcgui.Dialog().notification(
|
xbmcgui.Dialog().notification(
|
||||||
|
@ -876,7 +675,7 @@ def passwordsXML():
|
||||||
|
|
||||||
# Add credentials
|
# Add credentials
|
||||||
settings('networkCreds', value="%s" % server)
|
settings('networkCreds', value="%s" % server)
|
||||||
logMsg("PLEX", "Added server: %s to passwords.xml" % server, 1)
|
log.info("Added server: %s to passwords.xml" % server)
|
||||||
# Prettify and write to file
|
# Prettify and write to file
|
||||||
try:
|
try:
|
||||||
indent(root)
|
indent(root)
|
||||||
|
@ -904,15 +703,15 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False):
|
||||||
|
|
||||||
# Create the playlist directory
|
# Create the playlist directory
|
||||||
if not xbmcvfs.exists(tryEncode(path)):
|
if not xbmcvfs.exists(tryEncode(path)):
|
||||||
logMsg("PLEX", "Creating directory: %s" % path, 1)
|
log.info("Creating directory: %s" % path)
|
||||||
xbmcvfs.mkdirs(tryEncode(path))
|
xbmcvfs.mkdirs(tryEncode(path))
|
||||||
|
|
||||||
# Only add the playlist if it doesn't already exists
|
# Only add the playlist if it doesn't already exists
|
||||||
if xbmcvfs.exists(tryEncode(xsppath)):
|
if xbmcvfs.exists(tryEncode(xsppath)):
|
||||||
logMsg('Path %s does exist' % xsppath, 1)
|
log.info('Path %s does exist' % xsppath)
|
||||||
if delete:
|
if delete:
|
||||||
xbmcvfs.delete(tryEncode(xsppath))
|
xbmcvfs.delete(tryEncode(xsppath))
|
||||||
logMsg("PLEX", "Successfully removed playlist: %s." % tagname, 1)
|
log.info("Successfully removed playlist: %s." % tagname)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -922,11 +721,11 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False):
|
||||||
'movie': 'movies',
|
'movie': 'movies',
|
||||||
'show': 'tvshows'
|
'show': 'tvshows'
|
||||||
}
|
}
|
||||||
logMsg("Plex", "Writing playlist file to: %s" % xsppath, 1)
|
log.info("Writing playlist file to: %s" % xsppath)
|
||||||
try:
|
try:
|
||||||
f = xbmcvfs.File(tryEncode(xsppath), 'wb')
|
f = xbmcvfs.File(tryEncode(xsppath), 'wb')
|
||||||
except:
|
except:
|
||||||
logMsg("Plex", "Failed to create playlist: %s" % xsppath, -1)
|
log.error("Failed to create playlist: %s" % xsppath)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
f.write(tryEncode(
|
f.write(tryEncode(
|
||||||
|
@ -940,7 +739,7 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False):
|
||||||
'</smartplaylist>\n'
|
'</smartplaylist>\n'
|
||||||
% (itemtypes.get(mediatype, mediatype), plname, tagname)))
|
% (itemtypes.get(mediatype, mediatype), plname, tagname)))
|
||||||
f.close()
|
f.close()
|
||||||
logMsg("Plex", "Successfully added playlist: %s" % tagname)
|
log.info("Successfully added playlist: %s" % tagname)
|
||||||
|
|
||||||
def deletePlaylists():
|
def deletePlaylists():
|
||||||
|
|
||||||
|
@ -962,43 +761,180 @@ def deleteNodes():
|
||||||
try:
|
try:
|
||||||
shutil.rmtree("%s%s" % (path, tryDecode(dir)))
|
shutil.rmtree("%s%s" % (path, tryDecode(dir)))
|
||||||
except:
|
except:
|
||||||
logMsg("PLEX", "Failed to delete directory: %s"
|
log.error("Failed to delete directory: %s" % tryDecode(dir))
|
||||||
% tryDecode(dir))
|
|
||||||
for file in files:
|
for file in files:
|
||||||
if tryDecode(file).startswith('plex'):
|
if tryDecode(file).startswith('plex'):
|
||||||
try:
|
try:
|
||||||
xbmcvfs.delete(tryEncode("%s%s" % (path, tryDecode(file))))
|
xbmcvfs.delete(tryEncode("%s%s" % (path, tryDecode(file))))
|
||||||
except:
|
except:
|
||||||
logMsg("PLEX", "Failed to file: %s" % tryDecode(file))
|
log.error("Failed to file: %s" % tryDecode(file))
|
||||||
|
|
||||||
|
|
||||||
def tryEncode(uniString, encoding='utf-8'):
|
###############################################################################
|
||||||
|
# WRAPPERS
|
||||||
|
|
||||||
|
def CatchExceptions(warnuser=False):
|
||||||
"""
|
"""
|
||||||
Will try to encode uniString (in unicode) to encoding. This possibly
|
Decorator for methods to catch exceptions and log them. Useful for e.g.
|
||||||
fails with e.g. Android TV's Python, which does not accept arguments for
|
librarysync threads using itemtypes.py, because otherwise we would not
|
||||||
string.encode()
|
get informed of crashes
|
||||||
|
|
||||||
|
warnuser=True: sets the window flag 'plex_scancrashed' to true
|
||||||
|
which will trigger a Kodi infobox to inform user
|
||||||
"""
|
"""
|
||||||
try:
|
def decorate(func):
|
||||||
uniString = uniString.encode(encoding, "ignore")
|
@wraps(func)
|
||||||
except TypeError:
|
def wrapper(*args, **kwargs):
|
||||||
uniString = uniString.encode()
|
try:
|
||||||
except UnicodeDecodeError:
|
return func(*args, **kwargs)
|
||||||
# already encoded
|
except Exception as e:
|
||||||
pass
|
log.error('%s has crashed' % func.__name__)
|
||||||
return uniString
|
log.error(addonName, e)
|
||||||
|
import traceback
|
||||||
|
log.error(addonName, "Traceback:\n%s"
|
||||||
|
% traceback.format_exc())
|
||||||
|
if warnuser:
|
||||||
|
window('plex_scancrashed', value='true')
|
||||||
|
return
|
||||||
|
return wrapper
|
||||||
|
return decorate
|
||||||
|
|
||||||
|
|
||||||
def tryDecode(string, encoding='utf-8'):
|
def LogTime(func):
|
||||||
"""
|
"""
|
||||||
Will try to decode string (encoded) using encoding. This possibly
|
Decorator for functions and methods to log the time it took to run the code
|
||||||
fails with e.g. Android TV's Python, which does not accept arguments for
|
|
||||||
string.encode()
|
|
||||||
"""
|
"""
|
||||||
try:
|
@wraps(func)
|
||||||
string = string.decode(encoding, "ignore")
|
def wrapper(*args, **kwargs):
|
||||||
except TypeError:
|
starttotal = datetime.now()
|
||||||
string = string.decode()
|
result = func(*args, **kwargs)
|
||||||
except UnicodeEncodeError:
|
elapsedtotal = datetime.now() - starttotal
|
||||||
# Already in unicode - e.g. sometimes file paths
|
log.debug('%s %s' % (addonName, func.__name__),
|
||||||
pass
|
'It took %s to run the function.' % (elapsedtotal))
|
||||||
return string
|
return result
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def ThreadMethodsAdditionalStop(windowAttribute):
|
||||||
|
"""
|
||||||
|
Decorator to replace stopThread method to include the Kodi windowAttribute
|
||||||
|
|
||||||
|
Use with any sync threads. @ThreadMethods still required FIRST
|
||||||
|
"""
|
||||||
|
def wrapper(cls):
|
||||||
|
def threadStopped(self):
|
||||||
|
return (self._threadStopped or
|
||||||
|
(window('plex_terminateNow') == "true") or
|
||||||
|
window(windowAttribute) == "true")
|
||||||
|
cls.threadStopped = threadStopped
|
||||||
|
return cls
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def ThreadMethodsAdditionalSuspend(windowAttribute):
|
||||||
|
"""
|
||||||
|
Decorator to replace threadSuspended(): thread now also suspends if a
|
||||||
|
Kodi windowAttribute is set to 'true', e.g. 'suspend_LibraryThread'
|
||||||
|
|
||||||
|
Use with any library sync threads. @ThreadMethods still required FIRST
|
||||||
|
"""
|
||||||
|
def wrapper(cls):
|
||||||
|
def threadSuspended(self):
|
||||||
|
return (self._threadSuspended or
|
||||||
|
window(windowAttribute) == 'true')
|
||||||
|
cls.threadSuspended = threadSuspended
|
||||||
|
return cls
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def ThreadMethods(cls):
|
||||||
|
"""
|
||||||
|
Decorator to add the following methods to a threading class:
|
||||||
|
|
||||||
|
suspendThread(): pauses the thread
|
||||||
|
resumeThread(): resumes the thread
|
||||||
|
stopThread(): stopps/kills the thread
|
||||||
|
|
||||||
|
threadSuspended(): returns True if thread is suspend_thread
|
||||||
|
threadStopped(): returns True if thread is stopped (or should stop ;-))
|
||||||
|
ALSO stops if Kodi is exited
|
||||||
|
|
||||||
|
Also adds the following class attributes:
|
||||||
|
_threadStopped
|
||||||
|
_threadSuspended
|
||||||
|
"""
|
||||||
|
# Attach new attributes to class
|
||||||
|
cls._threadStopped = False
|
||||||
|
cls._threadSuspended = False
|
||||||
|
|
||||||
|
# Define new class methods and attach them to class
|
||||||
|
def stopThread(self):
|
||||||
|
self._threadStopped = True
|
||||||
|
cls.stopThread = stopThread
|
||||||
|
|
||||||
|
def suspendThread(self):
|
||||||
|
self._threadSuspended = True
|
||||||
|
cls.suspendThread = suspendThread
|
||||||
|
|
||||||
|
def resumeThread(self):
|
||||||
|
self._threadSuspended = False
|
||||||
|
cls.resumeThread = resumeThread
|
||||||
|
|
||||||
|
def threadSuspended(self):
|
||||||
|
return self._threadSuspended
|
||||||
|
cls.threadSuspended = threadSuspended
|
||||||
|
|
||||||
|
def threadStopped(self):
|
||||||
|
return self._threadStopped or (window('plex_terminateNow') == 'true')
|
||||||
|
cls.threadStopped = threadStopped
|
||||||
|
|
||||||
|
# Return class to render this a decorator
|
||||||
|
return cls
|
||||||
|
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# UNUSED METHODS
|
||||||
|
|
||||||
|
def changePlayState(itemType, kodiId, playCount, lastplayed):
|
||||||
|
"""
|
||||||
|
YET UNUSED
|
||||||
|
|
||||||
|
kodiId: int or str
|
||||||
|
playCount: int or str
|
||||||
|
lastplayed: str or int unix timestamp
|
||||||
|
"""
|
||||||
|
lastplayed = DateToKodi(lastplayed)
|
||||||
|
|
||||||
|
kodiId = int(kodiId)
|
||||||
|
playCount = int(playCount)
|
||||||
|
method = {
|
||||||
|
'movie': ' VideoLibrary.SetMovieDetails',
|
||||||
|
'episode': 'VideoLibrary.SetEpisodeDetails',
|
||||||
|
'musicvideo': ' VideoLibrary.SetMusicVideoDetails', # TODO
|
||||||
|
'show': 'VideoLibrary.SetTVShowDetails', # TODO
|
||||||
|
'': 'AudioLibrary.SetAlbumDetails', # TODO
|
||||||
|
'': 'AudioLibrary.SetArtistDetails', # TODO
|
||||||
|
'track': 'AudioLibrary.SetSongDetails'
|
||||||
|
}
|
||||||
|
params = {
|
||||||
|
'movie': {
|
||||||
|
'movieid': kodiId,
|
||||||
|
'playcount': playCount,
|
||||||
|
'lastplayed': lastplayed
|
||||||
|
},
|
||||||
|
'episode': {
|
||||||
|
'episodeid': kodiId,
|
||||||
|
'playcount': playCount,
|
||||||
|
'lastplayed': lastplayed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
query = {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
}
|
||||||
|
query['method'] = method[itemType]
|
||||||
|
query['params'] = params[itemType]
|
||||||
|
result = xbmc.executeJSONRPC(json.dumps(query))
|
||||||
|
result = json.loads(result)
|
||||||
|
result = result.get('result')
|
||||||
|
log.debug("JSON result was: %s" % result)
|
||||||
|
|
Loading…
Add table
Reference in a new issue