""" 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()