<reponame>DaVukovic/script.artwork.beef<gh_stars>10-100
import xbmc
import xbmcgui
import xbmcvfs
import xml.etree.ElementTree as ET
from contextlib import closing

from lib.libs.pykodi import log, localize as L

FILENAME = 'special://userdata/advancedsettings.xml'
FILENAME_BAK = FILENAME + '.beef.bak'

ROOT_TAG = 'advancedsettings'
VIDEO_TAG = 'videolibrary'
MUSIC_TAG = 'musiclibrary'
ARTTYPE_TAG = 'arttype'

# 0: mediatype tag name, 1: library tag name, 2: Kodi's hardcoded art types to exclude from AS.xml
mediatype_map = {'tvshow': ('tvshowextraart', VIDEO_TAG, ('poster', 'banner', 'fanart')),
    'season': ('tvshowseasonextraart', VIDEO_TAG, ('poster', 'banner', 'fanart')),
    'episode': ('episodeextraart', VIDEO_TAG, ('thumb',)),
    'movie': ('movieextraart', VIDEO_TAG, ('poster', 'fanart')),
    'set': ('moviesetextraart', VIDEO_TAG, ('poster', 'fanart')),
    'musicvideo': ('musicvideoextraart', VIDEO_TAG, ('poster', 'fanart')),
    'artist': ('artistextraart', MUSIC_TAG, ('thumb', 'fanart')),
    'album': ('albumextraart', MUSIC_TAG, ('thumb',))}
unsupported_types = ('song',)

MALFORMED = 32654
BACKUP_SUCCESSFUL = 32655
BACKUP_UNSUCCESSFUL = 32656
RESTORE_SUCCESSFUL = 32657
RESTORE_UNSUCCESSFUL = 32658
RESTART_KODI = 32659

def save_arttypes(arttype_map):
    root = read_xml()
    if root is None:
        xbmcgui.Dialog().notification("Artwork Beef", L(MALFORMED), xbmcgui.NOTIFICATION_WARNING)
        return False

    set_arttypes(root, arttype_map)

    if save_backup():
        save_xml(root)
        xbmcgui.Dialog().ok("Artwork Beef", L(RESTART_KODI))

def set_arttypes(root, arttype_map):
    for key, artlist in arttype_map.items():
        if key not in mediatype_map:
            if key not in unsupported_types:
                log("Can't set arttypes for '{0}' in advancedsettings.xml".format(key), xbmc.LOGNOTICE)
            continue
        typemap = mediatype_map[key]
        library_elem = root.find(typemap[1])
        if library_elem is None:
            library_elem = ET.SubElement(root, typemap[1])
        mediatype_elem = library_elem.find(typemap[0])
        if artlist:
            if mediatype_elem is None:
                mediatype_elem = ET.SubElement(library_elem, typemap[0])
            else:
                mediatype_elem.clear()
            for arttype in artlist:
                if arttype in typemap[2]:
                    continue
                arttype_elem = ET.SubElement(mediatype_elem, ARTTYPE_TAG)
                arttype_elem.text = arttype
        elif mediatype_elem is not None:
            library_elem.remove(mediatype_elem)

def read_xml():
    if not xbmcvfs.exists(FILENAME):
        return ET.Element(ROOT_TAG)

    parser = ET.XMLParser(target=CommentedTreeBuilder())
    with closing(xbmcvfs.File(FILENAME)) as as_xml:
        try:
            return ET.parse(as_xml, parser).getroot()
        except ET.ParseError:
            log("Can't parse advancedsettings.xml", xbmc.LOGWARNING)

def save_xml(advancedsettings):
    indent(advancedsettings)
    with closing(xbmcvfs.File(FILENAME, 'w')) as as_xml:
        ET.ElementTree(advancedsettings).write(as_xml, 'utf-8', True)

def save_backup():
    if xbmcvfs.exists(FILENAME):
        xbmcvfs.copy(FILENAME, FILENAME_BAK)
        result = xbmcvfs.exists(FILENAME_BAK)
        if result:
            xbmcgui.Dialog().notification("Artwork Beef", L(BACKUP_SUCCESSFUL))
        else:
            xbmcgui.Dialog().notification("Artwork Beef", L(BACKUP_UNSUCCESSFUL), xbmc.LOGWARNING)
        return result
    log("advancedsettings.xml doesn't exist, can't save backup", xbmc.LOGNOTICE)
    return True

def restore_backup():
    if xbmcvfs.exists(FILENAME_BAK):
        xbmcvfs.copy(FILENAME_BAK, FILENAME)
        xbmcvfs.delete(FILENAME_BAK)
        result = xbmcvfs.exists(FILENAME)
        if result:
            xbmcgui.Dialog().notification("Artwork Beef", L(RESTORE_SUCCESSFUL))
        else:
            xbmcgui.Dialog().notification("Artwork Beef", L(RESTORE_UNSUCCESSFUL), xbmc.LOGWARNING)
        return result
    log("advancedsettings.xml.beef.bak doesn't exist, can't restore backup", xbmc.LOGWARNING)
    return False

def has_backup():
    return xbmcvfs.exists(FILENAME_BAK)

def indent(element, level=0):
    i = "\n" + level*"\t"
    if len(element):
        if not element.text or not element.text.strip():
            element.text = i + "\t"
        if not element.tail or not element.tail.strip():
            element.tail = i
        for element in element:
            indent(element, level + 1)
        if not element.tail or not element.tail.strip():
            element.tail = i
    else:
        if level and (not element.tail or not element.tail.strip()):
            element.tail = i

class CommentedTreeBuilder(ET.TreeBuilder):
    def comment(self, data):
        self.start(ET.Comment, {})
        self.data(data)
        self.end(ET.Comment)

import xbmc
import xbmcgui
import xbmcvfs
import xml.etree.ElementTree as ET
from contextlib import closing

from lib.libs.pykodi import log, localize as L

FILENAME = 'special://userdata/advancedsettings.xml'
FILENAME_BAK = FILENAME + '.beef.bak'

ROOT_TAG = 'advancedsettings'
VIDEO_TAG = 'videolibrary'
MUSIC_TAG = 'musiclibrary'
ARTTYPE_TAG = 'arttype'

# 0: mediatype tag name, 1: library tag name, 2: Kodi's hardcoded art types to exclude from AS.xml
mediatype_map = {'tvshow': ('tvshowextraart', VIDEO_TAG, ('poster', 'banner', 'fanart')),
    'season': ('tvshowseasonextraart', VIDEO_TAG, ('poster', 'banner', 'fanart')),
    'episode': ('episodeextraart', VIDEO_TAG, ('thumb',)),
    'movie': ('movieextraart', VIDEO_TAG, ('poster', 'fanart')),
    'set': ('moviesetextraart', VIDEO_TAG, ('poster', 'fanart')),
    'musicvideo': ('musicvideoextraart', VIDEO_TAG, ('poster', 'fanart')),
    'artist': ('artistextraart', MUSIC_TAG, ('thumb', 'fanart')),
    'album': ('albumextraart', MUSIC_TAG, ('thumb',))}
unsupported_types = ('song',)

MALFORMED = 32654
BACKUP_SUCCESSFUL = 32655
BACKUP_UNSUCCESSFUL = 32656
RESTORE_SUCCESSFUL = 32657
RESTORE_UNSUCCESSFUL = 32658
RESTART_KODI = 32659

def save_arttypes(arttype_map):
    root = read_xml()
    if root is None:
        xbmcgui.Dialog().notification("Artwork Beef", L(MALFORMED), xbmcgui.NOTIFICATION_WARNING)
        return False

    set_arttypes(root, arttype_map)

    if save_backup():
        save_xml(root)
        xbmcgui.Dialog().ok("Artwork Beef", L(RESTART_KODI))

def set_arttypes(root, arttype_map):
    for key, artlist in arttype_map.items():
        if key not in mediatype_map:
            if key not in unsupported_types:
                log("Can't set arttypes for '{0}' in advancedsettings.xml".format(key), xbmc.LOGNOTICE)
            continue
        typemap = mediatype_map[key]
        library_elem = root.find(typemap[1])
        if library_elem is None:
            library_elem = ET.SubElement(root, typemap[1])
        mediatype_elem = library_elem.find(typemap[0])
        if artlist:
            if mediatype_elem is None:
                mediatype_elem = ET.SubElement(library_elem, typemap[0])
            else:
                mediatype_elem.clear()
            for arttype in artlist:
                if arttype in typemap[2]:
                    continue
                arttype_elem = ET.SubElement(mediatype_elem, ARTTYPE_TAG)
                arttype_elem.text = arttype
        elif mediatype_elem is not None:
            library_elem.remove(mediatype_elem)

def read_xml():
    if not xbmcvfs.exists(FILENAME):
        return ET.Element(ROOT_TAG)

    parser = ET.XMLParser(target=CommentedTreeBuilder())
    with closing(xbmcvfs.File(FILENAME)) as as_xml:
        try:
            return ET.parse(as_xml, parser).getroot()
        except ET.ParseError:
            log("Can't parse advancedsettings.xml", xbmc.LOGWARNING)

def save_xml(advancedsettings):
    indent(advancedsettings)
    with closing(xbmcvfs.File(FILENAME, 'w')) as as_xml:
        ET.ElementTree(advancedsettings).write(as_xml, 'utf-8', True)

def save_backup():
    if xbmcvfs.exists(FILENAME):
        xbmcvfs.copy(FILENAME, FILENAME_BAK)
        result = xbmcvfs.exists(FILENAME_BAK)
        if result:
            xbmcgui.Dialog().notification("Artwork Beef", L(BACKUP_SUCCESSFUL))
        else:
            xbmcgui.Dialog().notification("Artwork Beef", L(BACKUP_UNSUCCESSFUL), xbmc.LOGWARNING)
        return result
    log("advancedsettings.xml doesn't exist, can't save backup", xbmc.LOGNOTICE)
    return True

def restore_backup():
    if xbmcvfs.exists(FILENAME_BAK):
        xbmcvfs.copy(FILENAME_BAK, FILENAME)
        xbmcvfs.delete(FILENAME_BAK)
        result = xbmcvfs.exists(FILENAME)
        if result:
            xbmcgui.Dialog().notification("Artwork Beef", L(RESTORE_SUCCESSFUL))
        else:
            xbmcgui.Dialog().notification("Artwork Beef", L(RESTORE_UNSUCCESSFUL), xbmc.LOGWARNING)
        return result
    log("advancedsettings.xml.beef.bak doesn't exist, can't restore backup", xbmc.LOGWARNING)
    return False

def has_backup():
    return xbmcvfs.exists(FILENAME_BAK)

def indent(element, level=0):
    i = "\n" + level*"\t"
    if len(element):
        if not element.text or not element.text.strip():
            element.text = i + "\t"
        if not element.tail or not element.tail.strip():
            element.tail = i
        for element in element:
            indent(element, level + 1)
        if not element.tail or not element.tail.strip():
            element.tail = i
    else:
        if level and (not element.tail or not element.tail.strip()):
            element.tail = i

class CommentedTreeBuilder(ET.TreeBuilder):
    def comment(self, data):
        self.start(ET.Comment, {})
        self.data(data)
        self.end(ET.Comment)
# 0: mediatype tag name, 1: library tag name, 2: Kodi's hardcoded art types to exclude from AS.xml