jellyfin-kodi/resources/lib/utils.py

387 lines
12 KiB
Python
Raw Normal View History

2016-03-31 19:39:32 +00:00
# -*- coding: utf-8 -*-
#################################################################################################
import inspect
import json
import logging
2016-03-31 19:39:32 +00:00
import sqlite3
import StringIO
import os
2016-10-27 10:29:35 +00:00
import sys
2016-03-31 19:39:32 +00:00
import time
import urllib
2016-03-31 19:39:32 +00:00
import unicodedata
import xml.etree.ElementTree as etree
2016-06-22 19:29:53 +00:00
from datetime import datetime
2016-03-31 19:39:32 +00:00
2016-03-31 19:39:32 +00:00
import xbmc
import xbmcaddon
import xbmcgui
2016-10-27 10:29:35 +00:00
import xbmcplugin
2016-03-31 19:39:32 +00:00
import xbmcvfs
#################################################################################################
2016-06-16 05:43:36 +00:00
log = logging.getLogger("EMBY."+__name__)
2016-06-16 05:43:36 +00:00
#################################################################################################
# Main methods
2016-06-16 05:43:36 +00:00
def window(property_, value=None, clear=False, window_id=10000):
2016-06-16 05:43:36 +00:00
# Get or set window property
WINDOW = xbmcgui.Window(window_id)
2016-04-04 00:54:36 +00:00
2016-03-31 19:39:32 +00:00
if clear:
WINDOW.clearProperty(property_)
2016-03-31 19:39:32 +00:00
elif value is not None:
if ".json" in property_:
value = json.dumps(value)
WINDOW.setProperty(property_, value)
2016-06-16 05:43:36 +00:00
else:
result = WINDOW.getProperty(property_)
if result and ".json" in property_:
result = json.loads(result)
return result
2016-03-31 19:39:32 +00:00
def settings(setting, value=None):
# Get or add addon setting
2016-06-16 05:43:36 +00:00
addon = xbmcaddon.Addon(id='plugin.video.emby')
2016-03-31 19:39:32 +00:00
if value is not None:
2016-06-16 05:43:36 +00:00
addon.setSetting(setting, value)
else: # returns unicode object
return addon.getSetting(setting)
2016-03-31 19:39:32 +00:00
2016-06-16 05:43:36 +00:00
def language(string_id):
# Central string retrieval - unicode
2016-07-18 20:47:42 +00:00
return xbmcaddon.Addon(id='plugin.video.emby').getLocalizedString(string_id)
2016-03-31 19:39:32 +00:00
2016-09-18 07:53:52 +00:00
def dialog(type_, *args, **kwargs):
d = xbmcgui.Dialog()
if "icon" in kwargs:
2016-09-07 07:14:02 +00:00
kwargs['icon'] = kwargs['icon'].replace("{emby}",
"special://home/addons/plugin.video.emby/icon.png")
2016-09-09 03:13:25 +00:00
if "heading" in kwargs:
kwargs['heading'] = kwargs['heading'].replace("{emby}", language(29999))
types = {
'yesno': d.yesno,
'ok': d.ok,
'notification': d.notification,
2016-09-09 03:13:25 +00:00
'input': d.input,
'select': d.select,
'numeric': d.numeric
}
2016-09-18 07:53:52 +00:00
return types[type_](*args, **kwargs)
def plugin_path(plugin, params):
return "%s?%s" % (plugin, urllib.urlencode(params))
2016-08-08 00:57:11 +00:00
class JSONRPC(object):
id_ = 1
jsonrpc = "2.0"
def __init__(self, method, **kwargs):
self.method = method
for arg in kwargs: # id_(int), jsonrpc(str)
self.arg = arg
def _query(self):
query = {
'jsonrpc': self.jsonrpc,
'id': self.id_,
'method': self.method,
}
if self.params is not None:
query['params'] = self.params
return json.dumps(query)
def execute(self, params=None):
self.params = params
return json.loads(xbmc.executeJSONRPC(self._query()))
2016-06-16 05:43:36 +00:00
#################################################################################################
# Database related methods
2016-10-10 08:19:00 +00:00
def should_stop():
# Checkpoint during the syncing process
if xbmc.Monitor().abortRequested():
return True
elif window('emby_shouldStop') == "true":
return True
else: # Keep going
return False
2016-06-16 05:43:36 +00:00
#################################################################################################
# Utility methods
2016-03-31 19:39:32 +00:00
def getScreensaver():
# Get the current screensaver value
2016-11-03 02:24:58 +00:00
result = JSONRPC('Settings.getSettingValue').execute({'setting': "screensaver.mode"})
2016-10-27 07:15:09 +00:00
try:
return result['result']['value']
except KeyError:
return ""
2016-03-31 19:39:32 +00:00
def setScreensaver(value):
# Toggle the screensaver
2016-10-27 07:15:09 +00:00
params = {
'setting': "screensaver.mode",
'value': value
2016-03-31 19:39:32 +00:00
}
2016-10-27 07:15:09 +00:00
result = JSONRPC('Settings.setSettingValue').execute(params)
log.info("Toggling screensaver: %s %s" % (value, result))
2016-06-16 05:43:36 +00:00
2016-06-18 03:03:28 +00:00
def convertDate(date):
2016-06-16 05:43:36 +00:00
try:
date = datetime.strptime(date, "%Y-%m-%dT%H:%M:%SZ")
except (ImportError, TypeError):
2016-06-16 05:43:36 +00:00
# TypeError: attribute of type 'NoneType' is not callable
# Known Kodi/python error
date = datetime(*(time.strptime(date, "%Y-%m-%dT%H:%M:%SZ")[0:6]))
return date
def normalize_string(text):
# For theme media, do not modify unless
# modified in TV Tunes
text = text.replace(":", "")
text = text.replace("/", "-")
text = text.replace("\\", "-")
text = text.replace("<", "")
text = text.replace(">", "")
text = text.replace("*", "")
text = text.replace("?", "")
text = text.replace('|', "")
text = text.strip()
# Remove dots from the last character as windows can not have directories
# with dots at the end
text = text.rstrip('.')
text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore')
return text
def indent(elem, level=0):
# Prettify xml trees
i = "\n" + level*" "
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + " "
if not elem.tail or not elem.tail.strip():
elem.tail = i
for elem in elem:
indent(elem, level+1)
if not elem.tail or not elem.tail.strip():
elem.tail = i
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
def profiling(sortby="cumulative"):
# Will print results to Kodi log
def decorator(func):
def wrapper(*args, **kwargs):
import cProfile
import pstats
2016-06-16 05:43:36 +00:00
pr = cProfile.Profile()
pr.enable()
result = func(*args, **kwargs)
pr.disable()
s = StringIO.StringIO()
ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
ps.print_stats()
log.info(s.getvalue())
2016-06-16 05:43:36 +00:00
return result
return wrapper
return decorator
#################################################################################################
# Addon utilities
2016-03-31 19:39:32 +00:00
def sourcesXML():
# To make Master lock compatible
path = xbmc.translatePath("special://profile/").decode('utf-8')
xmlpath = "%ssources.xml" % path
try:
xmlparse = etree.parse(xmlpath)
except: # Document is blank or missing
root = etree.Element('sources')
else:
root = xmlparse.getroot()
2016-04-04 00:54:36 +00:00
2016-03-31 19:39:32 +00:00
video = root.find('video')
if video is None:
video = etree.SubElement(root, 'video')
etree.SubElement(video, 'default', attrib={'pathversion': "1"})
# Add elements
count = 2
for source in root.findall('.//path'):
if source.text == "smb://":
count -= 1
2016-04-04 00:54:36 +00:00
2016-03-31 19:39:32 +00:00
if count == 0:
# sources already set
break
else:
# Missing smb:// occurences, re-add.
for i in range(0, count):
source = etree.SubElement(video, 'source')
etree.SubElement(source, 'name').text = "Emby"
etree.SubElement(source, 'path', attrib={'pathversion': "1"}).text = "smb://"
etree.SubElement(source, 'allowsharing').text = "true"
# Prettify and write to file
try:
indent(root)
except: pass
etree.ElementTree(root).write(xmlpath)
def passwordsXML():
# To add network credentials
path = xbmc.translatePath("special://userdata/").decode('utf-8')
xmlpath = "%spasswords.xml" % path
try:
xmlparse = etree.parse(xmlpath)
except: # Document is blank or missing
root = etree.Element('passwords')
else:
root = xmlparse.getroot()
dialog = xbmcgui.Dialog()
credentials = settings('networkCreds')
if credentials:
# Present user with options
2016-06-21 01:57:29 +00:00
option = dialog.select(language(33075), [language(33076), language(33077)])
2016-03-31 19:39:32 +00:00
if option < 0:
# User cancelled dialog
return
elif option == 1:
# User selected remove
for paths in root.getiterator('passwords'):
for path in paths:
if path.find('.//from').text == "smb://%s/" % credentials:
paths.remove(path)
log.info("Successfully removed credentials for: %s" % credentials)
2016-03-31 19:39:32 +00:00
etree.ElementTree(root).write(xmlpath)
break
else:
log.info("Failed to find saved server: %s in passwords.xml" % credentials)
2016-04-04 00:54:36 +00:00
2016-03-31 19:39:32 +00:00
settings('networkCreds', value="")
xbmcgui.Dialog().notification(
2016-06-21 01:57:29 +00:00
heading=language(29999),
message="%s %s" % (language(33078), credentials),
2016-03-31 19:39:32 +00:00
icon="special://home/addons/plugin.video.emby/icon.png",
time=1000,
sound=False)
return
elif option == 0:
# User selected to modify
2016-06-21 01:57:29 +00:00
server = dialog.input(language(33083), credentials)
2016-03-31 19:39:32 +00:00
if not server:
return
else:
# No credentials added
2016-06-21 01:57:29 +00:00
dialog.ok(heading=language(29999), line1=language(33082))
server = dialog.input(language(33084))
2016-03-31 19:39:32 +00:00
if not server:
return
# Network username
2016-06-21 01:57:29 +00:00
user = dialog.input(language(33079))
2016-03-31 19:39:32 +00:00
if not user:
return
# Network password
2016-06-21 01:57:29 +00:00
password = dialog.input(heading=language(33080), option=xbmcgui.ALPHANUM_HIDE_INPUT)
2016-03-31 19:39:32 +00:00
if not password:
return
# Add elements
for path in root.findall('.//path'):
if path.find('.//from').text.lower() == "smb://%s/" % server.lower():
# Found the server, rewrite credentials
topath = "smb://%s:%s@%s/" % (user, password, server)
path.find('.//to').text = topath.replace("\\", ";")
2016-03-31 19:39:32 +00:00
break
else:
# Server not found, add it.
path = etree.SubElement(root, 'path')
etree.SubElement(path, 'from', attrib={'pathversion': "1"}).text = "smb://%s/" % server
topath = "smb://%s:%s@%s/" % (user, password, server)
etree.SubElement(path, 'to', attrib={'pathversion': "1"}).text = topath.replace("\\", ";")
2016-03-31 19:39:32 +00:00
# Force Kodi to see the credentials without restarting
xbmcvfs.exists(topath)
2016-04-04 00:54:36 +00:00
# Add credentials
settings('networkCreds', value=server)
log.info("Added server: %s to passwords.xml" % server)
2016-03-31 19:39:32 +00:00
# Prettify and write to file
try:
indent(root)
except: pass
etree.ElementTree(root).write(xmlpath)
2016-04-04 00:54:36 +00:00
2016-03-31 19:39:32 +00:00
dialog.notification(
2016-06-21 01:57:29 +00:00
heading=language(29999),
message="%s %s" % (language(33081), server),
2016-03-31 19:39:32 +00:00
icon="special://home/addons/plugin.video.emby/icon.png",
time=1000,
sound=False)
2017-07-28 07:11:41 +00:00
def verify_advancedsettings():
# Track the existance of <cleanonupdate>true</cleanonupdate>
# incompatible with plugin paths
log.info("verifying advanced settings")
if settings('useDirectPaths') != "0": return
path = xbmc.translatePath("special://userdata/").decode('utf-8')
xmlpath = "%sadvancedsettings.xml" % path
try:
xmlparse = etree.parse(xmlpath)
except: # Document is blank or missing
return
else:
root = xmlparse.getroot()
video = root.find('videolibrary')
if video is not None:
cleanonupdate = video.find('cleanonupdate')
if cleanonupdate is not None and cleanonupdate.text == "true":
log.warn("cleanonupdate disabled")
video.remove(cleanonupdate)
try:
indent(root)
except: pass
etree.ElementTree(root).write(xmlpath)
2017-07-29 01:44:40 +00:00
xbmcgui.Dialog().ok(heading=language(29999), line1=language(33097))
2017-07-28 07:11:41 +00:00
xbmc.executebuiltin('RestartApp')
return True
return