# -*- coding: utf-8 -*- ################################################################################################# import json import logging import os import re import unicodedata import urllib from uuid import uuid4 import xbmc import xbmcaddon import xbmcgui import xbmcvfs from . import _ ################################################################################################# LOG = logging.getLogger("EMBY."+__name__) ################################################################################################# def addon_id(): return "plugin.video.emby" def kodi_version(): return xbmc.getInfoLabel('System.BuildVersion')[:2] def window(key, value=None, clear=False, window_id=10000): ''' Get or set window properties. ''' window = xbmcgui.Window(window_id) if clear: LOG.debug("--[ window clear: %s ]", key) window.clearProperty(key) elif value is not None: if key.endswith('.json'): key = key.replace('.json', "") value = json.dumps(value) elif key.endswith('.bool'): key = key.replace('.bool', "") value = "true" if value else "false" window.setProperty(key, value) else: result = window.getProperty(key.replace('.json', "").replace('.bool', "")) if result: if key.endswith('.json'): result = json.loads(result) elif key.endswith('.bool'): result = result in ("true", "1") return result def settings(setting, value=None): ''' Get or add add-on settings. getSetting returns unicode object. ''' addon = xbmcaddon.Addon(addon_id()) if value is not None: if setting.endswith('.bool'): setting = setting.replace('.bool', "") value = "true" if value else "false" addon.setSetting(setting, value) else: result = addon.getSetting(setting.replace('.bool', "")) if result and setting.endswith('.bool'): result = result in ("true", "1") return result def create_id(): return uuid4() def compare_version(a, b): ''' -1 a is smaller 1 a is larger 0 equal ''' a = a.split('.') b = b.split('.') for i in range(0, max(len(a), len(b)), 1): try: aVal = a[i] except IndexError: aVal = 0 try: bVal = b[i] except IndexError: bVal = 0 if aVal < bVal: return -1 if aVal > bVal: return 1 return 0 def find(dict, item): ''' Find value in dictionary. ''' if item in dict: return dict[item] for key,value in sorted(dict.iteritems(), key=lambda (k,v): (v,k)): if re.match(key, item, re.I): return dict[key] def event(method, data=None): ''' Data is a dictionary. ''' data = data or {} xbmc.executebuiltin('NotifyAll(plugin.video.emby, %s, "[%s]")' % (method, json.dumps(data).replace('"', '\\"'))) LOG.debug("---[ event: %s ] %s", method, data) def dialog(dialog_type, *args, **kwargs): d = xbmcgui.Dialog() if "icon" in kwargs: kwargs['icon'] = kwargs['icon'].replace("{emby}", "special://home/addons/plugin.video.emby/icon.png") if "heading" in kwargs: kwargs['heading'] = kwargs['heading'].replace("{emby}", _('addon_name')) types = { 'yesno': d.yesno, 'ok': d.ok, 'notification': d.notification, 'input': d.input, 'select': d.select, 'numeric': d.numeric, 'multi': d.multiselect } return types[dialog_type](*args, **kwargs) def should_stop(): ''' Checkpoint during the sync process. ''' if xbmc.Monitor().waitForAbort(0.00001): return True if window('emby_should_stop.bool'): LOG.info("exiiiiitttinggg") return True if not window('emby_online.bool'): return True return False def get_screensaver(): ''' Get the current screensaver value. ''' result = JSONRPC('Settings.getSettingValue').execute({'setting': "screensaver.mode"}) try: return result['result']['value'] except KeyError: return "" def set_screensaver(value): ''' Toggle the screensaver ''' params = { 'setting': "screensaver.mode", 'value': value } result = JSONRPC('Settings.setSettingValue').execute(params) LOG.info("---[ screensaver/%s ] %s", value, result) class JSONRPC(object): version = 1 jsonrpc = "2.0" def __init__(self, method, **kwargs): self.method = method for arg in kwargs: self.arg = arg def _query(self): query = { 'jsonrpc': self.jsonrpc, 'id': self.version, '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())) def validate(path): ''' Verify if path is accessible. ''' if window('emby_pathverified.bool'): return True path = path if os.path.supports_unicode_filenames else path.encode('utf-8') if not xbmcvfs.exists(path): LOG.info("Could not find %s", path) if dialog("yesno", heading="{emby}", line1="%s %s. %s" % (_(33047), path, _(33048))): return False window('emby_pathverified.bool', True) return True def values(item, keys): ''' Grab the values in the item for a list of keys {key},{key1}.... If the key has no brackets, the key will be passed as is. ''' return (item[key.replace('{', "").replace('}', "")] if type(key) == str and key.startswith('{') else key for key in keys) def indent(elem, level=0): ''' Prettify xml docs. ''' try: 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 except Exception: return def write_xml(content, file): with open(file, 'w') as infile: content = content.replace("'", '"') content = content.replace('?>', ' standalone="yes" ?>', 1) infile.write(content) def delete_folder(path=None): ''' Delete objects from kodi cache ''' LOG.debug("--[ delete folder ]") delete_path = path is not None path = path or xbmc.translatePath('special://temp/emby').decode('utf-8') dirs, files = xbmcvfs.listdir(path) delete_recursive(path, dirs) for file in files: xbmcvfs.delete(os.path.join(path, file.decode('utf-8'))) if delete_path: xbmcvfs.delete(path) LOG.info("DELETE %s", path) def delete_recursive(path, dirs): ''' Delete files and dirs recursively. ''' for directory in dirs: dirs2, files = xbmcvfs.listdir(os.path.join(path, directory.decode('utf-8'))) for file in files: xbmcvfs.delete(os.path.join(path, directory.decode('utf-8'), file.decode('utf-8'))) delete_recursive(os.path.join(path, directory.decode('utf-8')), dirs2) xbmcvfs.rmdir(os.path.join(path, directory.decode('utf-8'))) def unzip(path, dest, folder=None): ''' Unzip file. zipfile module seems to fail on android with badziperror. ''' path = urllib.quote_plus(path) root = "zip://" + path + '/' if folder: xbmcvfs.mkdir(os.path.join(dest, folder)) dest = os.path.join(dest, folder) root = get_zip_directory(root, folder) dirs, files = xbmcvfs.listdir(root) if dirs: unzip_recursive(root, dirs, dest) for file in files: unzip_file(os.path.join(root, file.decode('utf-8')), os.path.join(dest, file.decode('utf-8'))) LOG.info("Unzipped %s", path) def unzip_recursive(path, dirs, dest): for directory in dirs: dirs_dir = os.path.join(path, directory.decode('utf-8')) dest_dir = os.path.join(dest, directory.decode('utf-8')) xbmcvfs.mkdir(dest_dir) dirs2, files = xbmcvfs.listdir(dirs_dir) if dirs2: unzip_recursive(dirs_dir, dirs2, dest_dir) for file in files: unzip_file(os.path.join(dirs_dir, file.decode('utf-8')), os.path.join(dest_dir, file.decode('utf-8'))) def unzip_file(path, dest): ''' Unzip specific file. Path should start with zip:// ''' xbmcvfs.copy(path, dest) LOG.debug("unzip: %s to %s", path, dest) def get_zip_directory(path, folder): dirs, files = xbmcvfs.listdir(path) if folder in dirs: return os.path.join(path, folder) for directory in dirs: result = get_zip_directory(os.path.join(path, directory.decode('utf-8')), folder) if result: return result def copytree(path, dest): ''' Copy folder content from one to another. ''' dirs, files = xbmcvfs.listdir(path) if not xbmcvfs.exists(dest): xbmcvfs.mkdirs(dest) if dirs: copy_recursive(path, dirs, dest) for file in files: copy_file(os.path.join(path, file.decode('utf-8')), os.path.join(dest, file.decode('utf-8'))) LOG.info("Copied %s", path) def copy_recursive(path, dirs, dest): for directory in dirs: dirs_dir = os.path.join(path, directory.decode('utf-8')) dest_dir = os.path.join(dest, directory.decode('utf-8')) xbmcvfs.mkdir(dest_dir) dirs2, files = xbmcvfs.listdir(dirs_dir) if dirs2: copy_recursive(dirs_dir, dirs2, dest_dir) for file in files: copy_file(os.path.join(dirs_dir, file.decode('utf-8')), os.path.join(dest_dir, file.decode('utf-8'))) def copy_file(path, dest): ''' Copy specific file. ''' if path.endswith('.pyo'): return xbmcvfs.copy(path, dest) LOG.debug("copy: %s to %s", path, dest) def normalize_string(text): ''' For theme media, do not modify unless modified in TV Tunes. Remove dots from the last character as windows can not have directories with dots at the end ''' 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() text = text.rstrip('.') text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore') return text def split_list(itemlist, size): ''' Split up list in pieces of size. Will generate a list of lists ''' return [itemlist[i:i+size] for i in range(0, len(itemlist), size)]