версия для Kodi 19

This commit is contained in:
roman committed 2022-03-14 20:34:22 +03:00
1 parent 9aefb9451b
commit 5d2aff9bc5
60 files changed
+5831

No files matched your search

+26
View File
@@ -0,0 +1,26 @@
Copyright (c) 2011 Mikel Azkolain
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of copyright holders nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR CONTRIBUTORS
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
+372
View File
@@ -0,0 +1,372 @@
"""
Created on 09/08/2011
@author: mikel
"""
__all__ = ["fonts", "includes"]
import logging
import os
import re
import shutil
import sys
import time
import xml.etree.ElementTree as ET
from datetime import datetime
from os import listdir
from os.path import isdir, dirname, basename
import xbmc
import xbmcgui
class SkinUtilsError(Exception):
pass
def reload_skin():
xbmc.executebuiltin("XBMC.ReloadSkin()")
def setup_logging():
# Keep comaptibility with Python2.6
if hasattr(logging, 'NullHandler'):
logger = logging.getLogger('skinutils')
logger.addHandler(logging.NullHandler())
def get_logger():
return logging.getLogger('skinutils')
def debug_log(msg):
get_logger().debug(msg)
def get_sha1_obj():
# SHA1 lib 2.4 compatibility
try:
from hashlib import sha1
return sha1()
except:
import sha
return sha.new()
def sha1_file(file, block_size=2 ** 20):
f = open(file, 'rb')
sha1 = get_sha1_obj()
while True:
data = f.read(block_size)
if not data:
break
sha1.update(data)
f.close()
return sha1.hexdigest()
def try_remove_file(file, wait=0.5, tries=10):
removed = False
num_try = 0
while num_try < tries and not removed:
try:
os.remove(file)
return True
except OSError:
num_try += 1
time.sleep(wait)
return False
def case_file_exists(file):
if not os.path.isfile(file):
return False
else:
file_dir = dirname(file)
if not isdir(file_dir):
return False
else:
dir_contents = listdir(file_dir)
return basename(file) in dir_contents
def get_current_skin_path():
return os.path.normpath(xbmc.translatePath("special://skin/"))
def get_skin_name():
return os.path.basename(get_current_skin_path())
def get_local_skin_path():
user_addons_path = xbmc.translatePath("special://home/addons")
return os.path.normpath(
os.path.join(user_addons_path, get_skin_name())
)
def copy_skin_to_userdata(ask_user=True):
# Warn user before doing this weird thing
d = xbmcgui.Dialog()
msg1 = "This addon needs to install some extra resources."
msg2 = "This installation requires a manual XBMC restart."
msg3 = "Begin installation now? After that it will exit."
make_copy = (
not ask_user or
d.yesno("Notice", msg1, msg2, msg3)
)
if make_copy:
# Get skin dest name
local_skin_path = get_local_skin_path()
# If it was not copied before...
if not os.path.exists(local_skin_path):
shutil.copytree(get_current_skin_path(), local_skin_path)
return make_copy
def is_invalid_local_skin():
# Get skin paths
current_skin_path = get_current_skin_path()
local_skin_path = get_local_skin_path()
# If the local path does not exist
if not os.path.isdir(local_skin_path):
return False
else:
# Get addon xml paths
current_xml = os.path.join(current_skin_path, 'addon.xml')
local_xml = os.path.join(local_skin_path, 'addon.xml')
# Both files must exist
if not os.path.isfile(current_xml) or not os.path.isfile(local_xml):
return True
# If sum of both files mismatch, got it!
elif sha1_file(current_xml) != sha1_file(local_xml):
return True
# Otherwise everything is ok
else:
return False
def fix_invalid_local_skin():
local_skin_path = get_local_skin_path()
time_suffix = datetime.now().strftime('%Y%m%d%H%M%S')
backup_skin_path = local_skin_path + '-skinutils-' + time_suffix
# Just move the skin, if it already exists someone is trolling us...
shutil.move(local_skin_path, backup_skin_path)
# And now do the real copy
copy_skin_to_userdata(ask_user=False)
# Inform the user about the operation...
d = xbmcgui.Dialog()
l1 = "Your local skin is not in use (probably outdated)."
l2 = "Press OK to apply a fix (archiving the old skin)."
l3 = "You will need to restart XBMC once more."
d.ok("Notice", l1 + '\n' + '\n' + l2 + '\n' + l3)
sys.exit()
# Skin was copied but XBMC was not restarted
def check_needs_restart():
# Get skin paths
current_skin_path = get_current_skin_path()
local_skin_path = get_local_skin_path()
# Local skin exists and does not match current skin path
if os.path.isdir(local_skin_path) and current_skin_path != local_skin_path:
# Check if the local skin is a leftover from a previous XBMC install
if is_invalid_local_skin():
fix_invalid_local_skin()
# Local skin is correct, a restart is needed
else:
d = xbmcgui.Dialog()
d.ok("Notice", "Restart XBMC to complete the installation.")
sys.exit()
def do_write_test(path):
test_file = os.path.join(path, 'write_test.txt')
get_logger().debug('performing write test: %s' % test_file)
try:
# Open and cleanup
open(test_file, 'w').close()
os.remove(test_file)
return True
except Exception:
return False
def skin_is_local():
return get_current_skin_path() == get_local_skin_path()
def check_skin_writability():
# Some debug info
debug_log("-- skinutils debug info --")
debug_log("current skin path: %s\n" % get_current_skin_path())
debug_log("local path should be: %s" % get_local_skin_path())
# Check if XBMC needs a restart
check_needs_restart()
# Get the current skin's path
skin_path = get_local_skin_path()
# Check if it's local or not (contained in userdata)
if not skin_is_local():
copy_skin_to_userdata()
sys.exit()
# Check if this path is writable
elif not os.access(skin_path, os.W_OK) or not do_write_test(skin_path):
raise IOError("Skin directory is not writable.")
def make_backup(path):
backup_path = path + '-skinutilsbackup'
# If the backup already exists, don't overwrite it
if not os.path.exists(backup_path):
shutil.copy(path, backup_path)
def restore_backup(path):
backup_path = path + '-skinutilsbackup'
# Do nothing if no backup exists
if os.path.exists(backup_path):
# os.rename is atomic on unix, and it will overwrite silently
if os.name != 'nt':
os.rename(backup_path, path)
# Windows will complain if the file exists
else:
os.remove(path)
os.rename(backup_path, path)
def is_invalid_xml(file):
contents = open(file, 'r').read()
# Check for invalid comments
pattern = re.compile('<!--(.*?)-->', re.MULTILINE | re.DOTALL)
group_pattern = re.compile('^-|--|-$')
for match in re.finditer(pattern, contents):
if re.match(group_pattern, match.group(1)) is not None:
return True
# Check also for whitespace prior to declaration
whitespace_pattern = re.compile('^\s+', re.MULTILINE)
return whitespace_pattern.match(contents) is not None
def sanitize_xml(file):
contents = open(file, 'r').read()
# Remove leading whitespace to declaration
contents = contents.lstrip()
# Strip invalid comments
p = re.compile('<!--.*?-->', re.MULTILINE | re.DOTALL)
clean_contents, num_repl = re.subn(p, '', contents)
open(file, 'w').write(clean_contents)
def install_resources():
pass
class DocumentCache:
__cached_docs = None
def __init__(self):
self.__cached_docs = {}
def _check_file_exists(self, file):
if not os.path.isfile(file):
raise IOError('File not found: %s' % file)
def contains(self, file):
return file in self.__cached_docs
def _check_file_known(self, file):
if not self.contains(file):
raise KeyError('Unknown file: %s' % file)
def list_files(self):
return self.__cached_docs.keys()
def items(self):
return self.__cached_docs.items()
def add(self, file):
self._check_file_exists(file)
self.__cached_docs[file] = None
def read(self, file):
self._check_file_exists(file)
# If there is no cached data...
if not self.contains(file) or self.__cached_docs[file] is None:
# Check if the file about to load is sane
if is_invalid_xml(file):
make_backup(file)
sanitize_xml(file)
# Parse the document
self.__cached_docs[file] = ET.parse(file)
return self.__cached_docs[file]
def write(self, file):
self._check_file_known(file)
# If there is a document in cache it may contain modifications
if self.__cached_docs[file] is not None:
make_backup(file)
self.__cached_docs[file].write(file)
def write_all(self):
for item in self.__cached_docs:
self.write(item)
def clear(self, file):
self._check_file_known(file)
self.__cached_docs[file] = None
def clear_all(self):
for item in self.__cached_docs:
self.clear(item)
def rollback(self, file):
self._check_file_known(file)
restore_backup(file)
self.clear(file)
def rollback_all(self):
for item in self.__cached_docs:
self.rollback(item)
setup_logging()
+210
View File
@@ -0,0 +1,210 @@
"""
Created on 09/08/2011
@author: mikel
"""
import os
import shutil
import xml.etree.ElementTree as ET
import xbmc
from . import SkinUtilsError, check_skin_writability, reload_skin, try_remove_file, case_file_exists, \
DocumentCache, get_logger
class FontXmlError(SkinUtilsError):
pass
class FontManager:
__installed_names = None
__installed_fonts = None
__doc_cache = None
def _list_skin_font_files(self):
font_xml_list = []
skin_path = xbmc.translatePath("special://skin/")
# Go into each dir. Could be 720, 1080...
for dir_item in os.listdir(skin_path):
dir_path = os.path.join(skin_path, dir_item)
if os.path.isdir(dir_path):
# Try with font.xml
file = os.path.join(dir_path, "font.xml")
if case_file_exists(file):
font_xml_list.append(file)
# Don't try the next step on windows, wasted time
file = os.path.join(dir_path, "Font.xml")
if case_file_exists(file):
font_xml_list.append(file)
return font_xml_list
def __init__(self):
self.__installed_names = []
self.__installed_fonts = []
self.__doc_cache = DocumentCache()
# Check if the environment is sane
check_skin_writability()
# Initialize the doc cache with the skin's files
for file in self._list_skin_font_files():
self.__doc_cache.add(file)
def is_name_installed(self, name):
return name in self.__installed_names
def is_font_installed(self, file):
return file in self.__installed_fonts
def _get_font_attr(self, node, name):
attrnode = node.find(name)
if attrnode is not None:
return attrnode.text
def _copy_font_file(self, file):
skin_font_path = xbmc.translatePath("special://skin/fonts/")
file_name = os.path.basename(file)
dest_file = os.path.join(skin_font_path, file_name)
# TODO: Unix systems could use symlinks
# Check if it's already there
if dest_file not in self.__installed_fonts:
self.__installed_fonts.append(dest_file)
# Overwrite if file exists
shutil.copyfile(file, dest_file)
def _add_font_attr(self, fontdef, name, value):
attr = ET.SubElement(fontdef, name)
attr.text = value
attr.tail = "\n\t\t\t"
return attr
def _install_font_def(self, skin_file, name, filename, size, style="", aspect="", linespacing=""):
# Add it to the registry
self.__installed_names.append(name)
# Get the parsed skin font file
font_doc = self.__doc_cache.read(skin_file)
# Iterate over all the fontsets on the file
for fontset in font_doc.getroot().findall("fontset"):
fontset.findall("font")[-1].tail = "\n\t\t"
fontdef = ET.SubElement(fontset, "font")
fontdef.text, fontdef.tail = "\n\t\t\t", "\n\t"
self._add_font_attr(fontdef, "name", name)
# We get the full file path to the font, so let's basename
self._add_font_attr(fontdef, "filename", os.path.basename(filename))
self._copy_font_file(filename)
last = self._add_font_attr(fontdef, "size", size)
if style:
if style in ["normal", "bold", "italics", "bolditalics", "lighten"]:
last = self._add_font_attr(fontdef, "style", style)
else:
raise FontXmlError(
"Font '%s' has an invalid style definition: %s"
% (name, style)
)
if aspect:
last = self._add_font_attr(fontdef, "aspect", aspect)
if linespacing:
last = self._add_font_attr(fontdef, "linespacing", linespacing)
last.tail = "\n\t\t"
def _install_file(self, doc_cache, user_file, skin_file, font_path):
user_doc = doc_cache.read(user_file)
# Handle only the first fontset
fontset = user_doc.getroot().find("fontset")
if len(fontset):
# Every font definition inside it
for item in fontset.findall("font"):
name = self._get_font_attr(item, "name")
# Basic check for malformed defs.
if name is None:
raise FontXmlError("Malformed XML: No name for font definition.")
# Omit already defined fonts
elif not self.is_name_installed(name):
font_file_path = os.path.join(
font_path, self._get_font_attr(item, "filename")
)
self._install_font_def(
skin_file,
name,
font_file_path,
self._get_font_attr(item, "size"),
self._get_font_attr(item, "style"),
self._get_font_attr(item, "aspect"),
self._get_font_attr(item, "linespacing")
)
def _get_res_folder(self, path):
return os.path.basename(os.path.dirname(path))
def _get_res_filename(self, res_folder, user_file):
path, ext = os.path.splitext(user_file)
return path + '-' + res_folder + ext
def install_file(self, user_file, font_path, commit=True, clear=True):
doc_cache = DocumentCache()
# If the file does not exist the following will fail
doc_cache.add(user_file)
# Install the file into every cached skin font file
for skin_file in self.__doc_cache.list_files():
res_folder = self._get_res_folder(skin_file)
res_file = self._get_res_filename(res_folder, user_file)
# If an specific res file exists...
if os.path.isfile(res_file):
self._install_file(doc_cache, res_file, skin_file, font_path)
# Otherwise use the dafault fallback
else:
self._install_file(doc_cache, user_file, skin_file, font_path)
# If save was requested
if commit:
self.__doc_cache.write_all()
# Clear cached docs after write (if requested)
if clear:
self.__doc_cache.clear_all()
def remove_font(self, name):
pass
def remove_installed_names(self):
self.__doc_cache.rollback_all()
def remove_installed_fonts(self):
for item in self.__installed_fonts:
if not try_remove_file(item):
get_logger().error(
'Failed removing font file "%s". XBMC may still be using it.' % item
)
def cleanup(self):
self.remove_installed_names()
# Reload skin so font files are no longer in use, and then delete them
reload_skin()
self.remove_installed_fonts()
def __del__(self):
self.cleanup()
+90
View File
@@ -0,0 +1,90 @@
"""
Created on 09/08/2011
@author: mikel
"""
import os
import xml.etree.ElementTree as ET
import xbmc
from . import SkinUtilsError, check_skin_writability, case_file_exists, DocumentCache, get_logger
class IncludeXmlError(SkinUtilsError):
pass
class IncludeManager:
__installed_names = None
__doc_cache = None
def _list_skin_include_files(self):
include_list = []
skin_path = xbmc.translatePath("special://skin/")
# Go into each dir. Could be 720, 1080...
for dir_item in os.listdir(skin_path):
dir_path = os.path.join(skin_path, dir_item)
if os.path.isdir(dir_path):
file = os.path.join(dir_path, "includes.xml")
if case_file_exists(file):
include_list.append(file)
file = os.path.join(dir_path, "Includes.xml")
if case_file_exists(file):
include_list.append(file)
return include_list
def __init__(self):
self.__installed_names = []
self.__doc_cache = DocumentCache()
# Check if the environment is sane
check_skin_writability()
# Initialize the doc cache with found files
for file in self._list_skin_include_files():
self.__doc_cache.add(file)
def is_name_installed(self, name):
return name in self.__installed_names
def add_include(self, name, node):
for file in self.__doc_cache.list_files():
doc = self.__doc_cache.read(file)
doc.getroot().append(node)
self.__installed_names.append(name)
def install_file(self, file, commit=True, clear=True):
get_logger().info('install include: %s' % file)
tree = ET.parse(file)
# Handle all includes
for item in tree.getroot().findall("include"):
name = item.get("name")
if name is None:
get_logger().warning('Only named includes are supported.')
elif self.is_name_installed(name):
get_logger().warning('Include name "%s" already installed' % name)
else:
self.add_include(name, item)
# If a save was requested
if commit:
self.__doc_cache.write_all()
# If we where requested to clear the cached docs
if clear:
self.__doc_cache.clear_all()
def remove_installed_names(self):
self.__doc_cache.rollback_all()
def cleanup(self):
self.remove_installed_names()
def __del__(self):
self.cleanup()