From 16df4d6404e5bec8a9d9a682c673cae657d51b3d Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sat, 5 Nov 2016 13:36:46 -0500 Subject: [PATCH] New views.py Combines maintainsViews from librarysync and videonodes and playlist creation. Still need to move deletion from utils. --- resources/lib/kodidb_functions.py | 122 ------- resources/lib/librarysync.py | 224 +------------ resources/lib/player.py | 1 - resources/lib/read_embyserver.py | 16 +- resources/lib/service_entry.py | 4 +- resources/lib/videonodes.py | 392 ---------------------- resources/lib/views.py | 519 +++++++++++++++--------------- 7 files changed, 291 insertions(+), 987 deletions(-) delete mode 100644 resources/lib/kodidb_functions.py delete mode 100644 resources/lib/videonodes.py diff --git a/resources/lib/kodidb_functions.py b/resources/lib/kodidb_functions.py deleted file mode 100644 index e486250b..00000000 --- a/resources/lib/kodidb_functions.py +++ /dev/null @@ -1,122 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################## - -import logging - -import xbmc - -import api -import artwork - -################################################################################################# - -log = logging.getLogger("EMBY."+__name__) - -################################################################################################# - -class Kodidb_Functions(object): - - kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2]) - - - def __init__(self, cursor): - - self.cursor = cursor - self.artwork = artwork.Artwork() - - def createTag(self, name): - # This will create and return the tag_id - if self.kodiversion in (15, 16, 17): - # Kodi Isengard, Jarvis, Krypton - query = ' '.join(( - - "SELECT tag_id", - "FROM tag", - "WHERE name = ?", - "COLLATE NOCASE" - )) - self.cursor.execute(query, (name,)) - try: - tag_id = self.cursor.fetchone()[0] - - except TypeError: - self.cursor.execute("select coalesce(max(tag_id),0) from tag") - tag_id = self.cursor.fetchone()[0] + 1 - - query = "INSERT INTO tag(tag_id, name) values(?, ?)" - self.cursor.execute(query, (tag_id, name)) - log.debug("Create tag_id: %s name: %s", tag_id, name) - else: - # Kodi Helix - query = ' '.join(( - - "SELECT idTag", - "FROM tag", - "WHERE strTag = ?", - "COLLATE NOCASE" - )) - self.cursor.execute(query, (name,)) - try: - tag_id = self.cursor.fetchone()[0] - - except TypeError: - self.cursor.execute("select coalesce(max(idTag),0) from tag") - tag_id = self.cursor.fetchone()[0] + 1 - - query = "INSERT INTO tag(idTag, strTag) values(?, ?)" - self.cursor.execute(query, (tag_id, name)) - log.debug("Create idTag: %s name: %s", tag_id, name) - - return tag_id - - def updateTag(self, oldtag, newtag, kodiid, mediatype): - # TODO: Move to video nodes eventually - log.debug("Updating: %s with %s for %s: %s", oldtag, newtag, mediatype, kodiid) - - if self.kodiversion in (15, 16, 17): - # Kodi Isengard, Jarvis, Krypton - try: - query = ' '.join(( - - "UPDATE tag_link", - "SET tag_id = ?", - "WHERE media_id = ?", - "AND media_type = ?", - "AND tag_id = ?" - )) - self.cursor.execute(query, (newtag, kodiid, mediatype, oldtag,)) - except Exception: - # The new tag we are going to apply already exists for this item - # delete current tag instead - query = ' '.join(( - - "DELETE FROM tag_link", - "WHERE media_id = ?", - "AND media_type = ?", - "AND tag_id = ?" - )) - self.cursor.execute(query, (kodiid, mediatype, oldtag,)) - else: - # Kodi Helix - try: - query = ' '.join(( - - "UPDATE taglinks", - "SET idTag = ?", - "WHERE idMedia = ?", - "AND media_type = ?", - "AND idTag = ?" - )) - self.cursor.execute(query, (newtag, kodiid, mediatype, oldtag,)) - except Exception: - # The new tag we are going to apply already exists for this item - # delete current tag instead - query = ' '.join(( - - "DELETE FROM taglinks", - "WHERE idMedia = ?", - "AND media_type = ?", - "AND idTag = ?" - )) - self.cursor.execute(query, (kodiid, mediatype, oldtag,)) diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index 0dabebe9..8d302a97 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -17,10 +17,9 @@ import clientinfo import downloadutils import itemtypes import embydb_functions as embydb -import kodidb_functions as kodidb import read_embyserver as embyserver import userclient -import videonodes +import views from objects import Movies, MusicVideos, TVShows, Music from utils import window, settings, language as lang, should_stop from ga_client import GoogleAnalytics @@ -60,7 +59,6 @@ class LibrarySync(threading.Thread): self.doUtils = downloadutils.DownloadUtils().downloadUrl self.user = userclient.UserClient() self.emby = embyserver.Read_EmbyServer() - self.vnodes = videonodes.VideoNodes() self.kodi_version = int(xbmc.getInfoLabel('System.BuildVersion')[:2]) @@ -276,7 +274,9 @@ class LibrarySync(threading.Thread): starttotal = datetime.now() # Set views - self.maintainViews(cursor_emby, cursor_video) + views.Views(cursor_emby, cursor_video).maintain() + conn_emby.commit() + #self.maintainViews(cursor_emby, cursor_video) # Sync video library process = { @@ -362,222 +362,10 @@ class LibrarySync(threading.Thread): def refreshViews(self): - with DatabaseConn('emby') as conn_emby, DatabaseConn('video') as conn_video: + with DatabaseConn('emby') as conn_emby, DatabaseConn() as conn_video: with closing(conn_emby.cursor()) as cursor_emby, closing(conn_video.cursor()) as cursor_video: # Compare views, assign correct tags to items - self.maintainViews(cursor_emby, cursor_video) - - - def maintainViews(self, embycursor, kodicursor): - - # Compare the views to emby - emby = self.emby - emby_db = embydb.Embydb_Functions(embycursor) - kodi_db = kodidb.Kodidb_Functions(kodicursor) - - # Get views - result = self.doUtils("{server}/emby/Users/{UserId}/Views?format=json") - grouped_views = result['Items'] if 'Items' in result else [] - ordered_views = self.emby.getViews(sortedlist=True) - all_views = [] - sorted_views = [] - for view in ordered_views: - all_views.append(view['name']) - if view['type'] == "music": - continue - - if view['type'] == "mixed": - sorted_views.append(view['name']) - sorted_views.append(view['name']) - log.info("Sorted views: %s" % sorted_views) - - # total nodes for window properties - self.vnodes.clearProperties() - totalnodes = len(sorted_views) + 0 - - current_views = emby_db.getViews() - # Set views for supported media type - emby_mediatypes = { - - 'movies': "Movie", - 'tvshows': "Series", - 'musicvideos': "MusicVideo", - 'homevideos': "Video", - 'music': "Audio", - 'photos': "Photo" - } - for mediatype in ['movies', 'tvshows', 'musicvideos', 'homevideos', 'music', 'photos']: - - nodes = [] # Prevent duplicate for nodes of the same type - playlists = [] # Prevent duplicate for playlists of the same type - # Get media folders from server - folders = self.emby.getViews(mediatype, root=True) - for folder in folders: - - folderid = folder['id'] - foldername = folder['name'] - viewtype = folder['type'] - - if foldername not in all_views: - # Media folders are grouped into userview - params = { - 'ParentId': folderid, - 'Recursive': True, - 'Limit': 1, - 'IncludeItemTypes': emby_mediatypes[mediatype] - } # Get one item from server using the folderid - url = "{server}/emby/Users/{UserId}/Items?format=json" - result = self.doUtils(url, parameters=params) - try: - verifyitem = result['Items'][0]['Id'] - except (TypeError, IndexError): - # Something is wrong. Keep the same folder name. - # Could be the view is empty or the connection - pass - else: - for grouped_view in grouped_views: - # This is only reserved for the detection of grouped views - if (grouped_view['Type'] == "UserView" and - grouped_view.get('CollectionType') == mediatype): - # Take the userview, and validate the item belong to the view - if self.emby.verifyView(grouped_view['Id'], verifyitem): - # Take the name of the userview - log.info("Found corresponding view: %s %s" - % (grouped_view['Name'], grouped_view['Id'])) - foldername = grouped_view['Name'] - break - else: - # Unable to find a match, add the name to our sorted_view list - sorted_views.append(foldername) - log.info("Couldn't find corresponding grouped view: %s" % sorted_views) - - # Failsafe - try: - sorted_views.index(foldername) - except ValueError: - sorted_views.append(foldername) - - # Get current media folders from emby database - view = emby_db.getView_byId(folderid) - try: - current_viewname = view[0] - current_viewtype = view[1] - current_tagid = view[2] - - except TypeError: - log.info("Creating viewid: %s in Emby database." % folderid) - tagid = kodi_db.createTag(foldername) - # Create playlist for the video library - if (foldername not in playlists and - mediatype in ('movies', 'tvshows', 'musicvideos')): - utils.playlistXSP(mediatype, foldername, folderid, viewtype) - playlists.append(foldername) - # Create the video node - if foldername not in nodes and mediatype not in ("musicvideos", "music"): - self.vnodes.viewNode(sorted_views.index(foldername), foldername, mediatype, - viewtype, folderid) - if viewtype == "mixed": # Change the value - sorted_views[sorted_views.index(foldername)] = "%ss" % foldername - nodes.append(foldername) - totalnodes += 1 - # Add view to emby database - emby_db.addView(folderid, foldername, viewtype, tagid) - - else: - log.debug(' '.join(( - - "Found viewid: %s" % folderid, - "viewname: %s" % current_viewname, - "viewtype: %s" % current_viewtype, - "tagid: %s" % current_tagid))) - - # View is still valid - try: - current_views.remove(folderid) - except ValueError: - # View was just created, nothing to remove - pass - - # View was modified, update with latest info - if current_viewname != foldername: - log.info("viewid: %s new viewname: %s" % (folderid, foldername)) - tagid = kodi_db.createTag(foldername) - - # Update view with new info - emby_db.updateView(foldername, tagid, folderid) - - if mediatype != "music": - if emby_db.getView_byName(current_viewname) is None: - # The tag could be a combined view. Ensure there's no other tags - # with the same name before deleting playlist. - utils.playlistXSP( - mediatype, current_viewname, folderid, current_viewtype, True) - # Delete video node - if mediatype != "musicvideos": - self.vnodes.viewNode( - indexnumber=None, - tagname=current_viewname, - mediatype=mediatype, - viewtype=current_viewtype, - viewid=folderid, - delete=True) - # Added new playlist - if (foldername not in playlists and - mediatype in ('movies', 'tvshows', 'musicvideos')): - utils.playlistXSP(mediatype, foldername, folderid, viewtype) - playlists.append(foldername) - # Add new video node - if foldername not in nodes and mediatype != "musicvideos": - self.vnodes.viewNode(sorted_views.index(foldername), foldername, - mediatype, viewtype, folderid) - if viewtype == "mixed": # Change the value - sorted_views[sorted_views.index(foldername)] = "%ss" % foldername - nodes.append(foldername) - totalnodes += 1 - - # Update items with new tag - items = emby_db.getItem_byView(folderid) - for item in items: - # Remove the "s" from viewtype for tags - kodi_db.updateTag( - current_tagid, tagid, item[0], current_viewtype[:-1]) - else: - # Validate the playlist exists or recreate it - if mediatype != "music": - if (foldername not in playlists and - mediatype in ('movies', 'tvshows', 'musicvideos')): - utils.playlistXSP(mediatype, foldername, folderid, viewtype) - playlists.append(foldername) - # Create the video node if not already exists - if foldername not in nodes and mediatype != "musicvideos": - self.vnodes.viewNode(sorted_views.index(foldername), foldername, - mediatype, viewtype, folderid) - if viewtype == "mixed": # Change the value - sorted_views[sorted_views.index(foldername)] = "%ss" % foldername - nodes.append(foldername) - totalnodes += 1 - else: - # Add video nodes listings - self.vnodes.singleNode(totalnodes, "Favorite movies", "movies", "favourites") - totalnodes += 1 - self.vnodes.singleNode(totalnodes, "Favorite tvshows", "tvshows", "favourites") - totalnodes += 1 - self.vnodes.singleNode(totalnodes, "Favorite episodes", "episodes", "favourites") - totalnodes += 1 - self.vnodes.singleNode(totalnodes, "channels", "movies", "channels") - totalnodes += 1 - # Save total - window('Emby.nodes.total', str(totalnodes)) - - # Remove any old referenced views - log.info("Removing views: %s" % current_views) - for view in current_views: - emby_db.removeView(view) - # Remove any items that belongs to the old view - items = emby_db.get_item_by_view(view) - items = [i[0] for i in items] # Convert list of tuple to list - self.triage_items("remove", items) - + views.Views(cursor_emby, cursor_video).maintain() def movies(self, embycursor, kodicursor, pdialog): diff --git a/resources/lib/player.py b/resources/lib/player.py index 9505238d..9f196e66 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -11,7 +11,6 @@ import xbmcgui import clientinfo import downloadutils -import kodidb_functions as kodidb import websocket_client as wsc from utils import window, settings, language as lang from ga_client import GoogleAnalytics, log_error diff --git a/resources/lib/read_embyserver.py b/resources/lib/read_embyserver.py index b201e807..3c1718dd 100644 --- a/resources/lib/read_embyserver.py +++ b/resources/lib/read_embyserver.py @@ -31,6 +31,9 @@ class Read_EmbyServer(): self.userId = window('emby_currUser') self.server = window('emby_server%s' % self.userId) + def get_emby_url(self, handler): + return "{server}/emby/%s" % handler + def split_list(self, itemlist, size): # Split up list in pieces of size. Will generate a list of lists @@ -589,4 +592,15 @@ class Read_EmbyServer(): data = {'username': username, 'password': hashlib.sha1(password).hexdigest()} user = self.doUtils(url, postBody=data, action_type="POST", authenticate=False) - return user \ No newline at end of file + return user + + def get_single_item(self, media_type, parent_id): + + params = { + 'ParentId': parent_id, + 'Recursive': True, + 'Limit': 1, + 'IncludeItemTypes': media_type + } + url = self.get_emby_url('Users/{UserId}/Items?format=json') + return self.doUtils(url, parameters=params) \ No newline at end of file diff --git a/resources/lib/service_entry.py b/resources/lib/service_entry.py index f730d8fc..273dce45 100644 --- a/resources/lib/service_entry.py +++ b/resources/lib/service_entry.py @@ -16,8 +16,8 @@ import initialsetup import kodimonitor import librarysync import player -import videonodes import websocket_client as wsc +from views import VideoNodes from utils import window, settings, dialog, language as lang from ga_client import GoogleAnalytics import hashlib @@ -77,7 +77,7 @@ class Service(object): window(prop, clear=True) # Clear video nodes properties - videonodes.VideoNodes().clearProperties() + VideoNodes().clearProperties() # Set the minimum database version window('emby_minDBVersion', value="1.1.63") diff --git a/resources/lib/videonodes.py b/resources/lib/videonodes.py deleted file mode 100644 index 02f2f17e..00000000 --- a/resources/lib/videonodes.py +++ /dev/null @@ -1,392 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################# - -import logging -import shutil -import xml.etree.ElementTree as etree - -import xbmc -import xbmcaddon -import xbmcvfs - -import utils -from utils import window, language as lang - -################################################################################################# - -log = logging.getLogger("EMBY."+__name__) - -################################################################################################# - - -class VideoNodes(object): - - - def __init__(self): - - self.kodiversion = int(xbmc.getInfoLabel('System.BuildVersion')[:2]) - - - def commonRoot(self, order, label, tagname, roottype=1): - - if roottype == 0: - # Index - root = etree.Element('node', attrib={'order': "%s" % order}) - elif roottype == 1: - # Filter - root = etree.Element('node', attrib={'order': "%s" % order, 'type': "filter"}) - etree.SubElement(root, 'match').text = "all" - # Add tag rule - rule = etree.SubElement(root, 'rule', attrib={'field': "tag", 'operator': "is"}) - etree.SubElement(rule, 'value').text = tagname - else: - # Folder - root = etree.Element('node', attrib={'order': "%s" % order, 'type': "folder"}) - - etree.SubElement(root, 'label').text = label - etree.SubElement(root, 'icon').text = "special://home/addons/plugin.video.emby/icon.png" - - return root - - def viewNode(self, indexnumber, tagname, mediatype, viewtype, viewid, delete=False): - - if viewtype == "mixed": - dirname = "%s - %s" % (viewid, mediatype) - else: - dirname = viewid - - path = xbmc.translatePath("special://profile/library/video/").decode('utf-8') - nodepath = xbmc.translatePath( - "special://profile/library/video/emby/%s/" % dirname).decode('utf-8') - - # Verify the video directory - if not xbmcvfs.exists(path): - try: - shutil.copytree( - src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'), - dst=xbmc.translatePath("special://profile/library/video").decode('utf-8')) - except Exception as error: - log.error(error) - - xbmcvfs.exists(path) - - if delete: - dirs, files = xbmcvfs.listdir(nodepath) - for file in files: - xbmcvfs.delete(nodepath + file) - - log.info("Sucessfully removed videonode: %s." % tagname) - return - # Create the node directory - if not xbmcvfs.exists(nodepath) and not mediatype == "photos": - # We need to copy over the default items - xbmcvfs.mkdirs(nodepath) - - # Create index entry - nodeXML = "%sindex.xml" % nodepath - # Set windows property - path = "library://video/emby/%s/" % dirname - for i in range(1, indexnumber): - # Verify to make sure we don't create duplicates - if window('Emby.nodes.%s.index' % i) == path: - return - - if mediatype == "photos": - path = "plugin://plugin.video.emby/?id=%s&mode=getsubfolders" % indexnumber - - window('Emby.nodes.%s.index' % indexnumber, value=path) - - # Root - if not mediatype == "photos": - if viewtype == "mixed": - specialtag = "%s - %s" % (tagname, mediatype) - root = self.commonRoot(order=0, label=specialtag, tagname=tagname, roottype=0) - else: - root = self.commonRoot(order=0, label=tagname, tagname=tagname, roottype=0) - try: - utils.indent(root) - except: pass - etree.ElementTree(root).write(nodeXML) - - nodetypes = { - - '1': "all", - '2': "recent", - '3': "recentepisodes", - '4': "inprogress", - '5': "inprogressepisodes", - '6': "unwatched", - '7': "nextepisodes", - '8': "sets", - '9': "genres", - '10': "random", - '11': "recommended", - } - mediatypes = { - # label according to nodetype per mediatype - 'movies': - { - '1': tagname, - '2': 30174, - '4': 30177, - '6': 30189, - '8': 20434, - '9': 135, - '10': 30229, - '11': 30230 - }, - - 'tvshows': - { - '1': tagname, - '2': 30170, - '3': 30175, - '4': 30171, - '5': 30178, - '7': 30179, - '9': 135, - '10': 30229, - '11': 30230 - }, - - 'homevideos': - { - '1': tagname, - '2': 30251, - '11': 30253 - }, - - 'photos': - { - '1': tagname, - '2': 30252, - '8': 30255, - '11': 30254 - }, - - 'musicvideos': - { - '1': tagname, - '2': 30256, - '4': 30257, - '6': 30258 - } - } - - nodes = mediatypes[mediatype] - for node in nodes: - - nodetype = nodetypes[node] - nodeXML = "%s%s.xml" % (nodepath, nodetype) - # Get label - stringid = nodes[node] - if node != "1": - label = lang(stringid) - if not label: - label = xbmc.getLocalizedString(stringid) - else: - label = stringid - - # Set window properties - if (mediatype == "homevideos" or mediatype == "photos") and nodetype == "all": - # Custom query - path = ("plugin://plugin.video.emby/?id=%s&mode=browsecontent&type=%s" - % (tagname, mediatype)) - elif (mediatype == "homevideos" or mediatype == "photos"): - # Custom query - path = ("plugin://plugin.video.emby/?id=%s&mode=browsecontent&type=%s&folderid=%s" - % (tagname, mediatype, nodetype)) - elif nodetype == "nextepisodes": - # Custom query - path = "plugin://plugin.video.emby/?id=%s&mode=nextup&limit=25" % tagname - elif self.kodiversion == 14 and nodetype == "recentepisodes": - # Custom query - path = "plugin://plugin.video.emby/?id=%s&mode=recentepisodes&limit=25" % tagname - elif self.kodiversion == 14 and nodetype == "inprogressepisodes": - # Custom query - path = "plugin://plugin.video.emby/?id=%s&mode=inprogressepisodes&limit=25"% tagname - else: - path = "library://video/emby/%s/%s.xml" % (viewid, nodetype) - - if mediatype == "photos": - windowpath = "ActivateWindow(Pictures,%s,return)" % path - else: - windowpath = "ActivateWindow(Videos,%s,return)" % path - - if nodetype == "all": - - if viewtype == "mixed": - templabel = "%s - %s" % (tagname, mediatype) - else: - templabel = label - - embynode = "Emby.nodes.%s" % indexnumber - window('%s.title' % embynode, value=templabel) - window('%s.path' % embynode, value=windowpath) - window('%s.content' % embynode, value=path) - window('%s.type' % embynode, value=mediatype) - else: - embynode = "Emby.nodes.%s.%s" % (indexnumber, nodetype) - window('%s.title' % embynode, value=label) - window('%s.path' % embynode, value=windowpath) - window('%s.content' % embynode, value=path) - - if mediatype == "photos": - # For photos, we do not create a node in videos but we do want the window props - # to be created. - # To do: add our photos nodes to kodi picture sources somehow - continue - - if xbmcvfs.exists(nodeXML): - # Don't recreate xml if already exists - continue - - # Create the root - if (nodetype == "nextepisodes" or mediatype == "homevideos" or - (self.kodiversion == 14 and nodetype in ('recentepisodes', 'inprogressepisodes'))): - # Folder type with plugin path - root = self.commonRoot(order=node, label=label, tagname=tagname, roottype=2) - etree.SubElement(root, 'path').text = path - etree.SubElement(root, 'content').text = "episodes" - else: - root = self.commonRoot(order=node, label=label, tagname=tagname) - if nodetype in ('recentepisodes', 'inprogressepisodes'): - etree.SubElement(root, 'content').text = "episodes" - else: - etree.SubElement(root, 'content').text = mediatype - - limit = "25" - # Elements per nodetype - if nodetype == "all": - etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle" - - elif nodetype == "recent": - etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded" - etree.SubElement(root, 'limit').text = limit - rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"}) - etree.SubElement(rule, 'value').text = "0" - - elif nodetype == "inprogress": - etree.SubElement(root, 'rule', {'field': "inprogress", 'operator': "true"}) - etree.SubElement(root, 'limit').text = limit - - elif nodetype == "genres": - etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle" - etree.SubElement(root, 'group').text = "genres" - - elif nodetype == "unwatched": - etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle" - rule = etree.SubElement(root, "rule", {'field': "playcount", 'operator': "is"}) - etree.SubElement(rule, 'value').text = "0" - - elif nodetype == "sets": - etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle" - etree.SubElement(root, 'group').text = "sets" - - elif nodetype == "random": - etree.SubElement(root, 'order', {'direction': "ascending"}).text = "random" - etree.SubElement(root, 'limit').text = limit - - elif nodetype == "recommended": - etree.SubElement(root, 'order', {'direction': "descending"}).text = "rating" - etree.SubElement(root, 'limit').text = limit - rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"}) - etree.SubElement(rule, 'value').text = "0" - rule2 = etree.SubElement(root, 'rule', - attrib={'field': "rating", 'operator': "greaterthan"}) - etree.SubElement(rule2, 'value').text = "7" - - elif nodetype == "recentepisodes": - # Kodi Isengard, Jarvis - etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded" - etree.SubElement(root, 'limit').text = limit - rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"}) - etree.SubElement(rule, 'value').text = "0" - - elif nodetype == "inprogressepisodes": - # Kodi Isengard, Jarvis - etree.SubElement(root, 'limit').text = "25" - rule = etree.SubElement(root, 'rule', - attrib={'field': "inprogress", 'operator':"true"}) - - try: - utils.indent(root) - except: pass - etree.ElementTree(root).write(nodeXML) - - def singleNode(self, indexnumber, tagname, mediatype, itemtype): - - tagname = tagname.encode('utf-8') - cleantagname = utils.normalize_nodes(tagname) - nodepath = xbmc.translatePath("special://profile/library/video/").decode('utf-8') - nodeXML = "%semby_%s.xml" % (nodepath, cleantagname) - path = "library://video/emby_%s.xml" % cleantagname - windowpath = "ActivateWindow(Videos,%s,return)" % path - - # Create the video node directory - if not xbmcvfs.exists(nodepath): - # We need to copy over the default items - shutil.copytree( - src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'), - dst=xbmc.translatePath("special://profile/library/video").decode('utf-8')) - xbmcvfs.exists(path) - - labels = { - - 'Favorite movies': 30180, - 'Favorite tvshows': 30181, - 'Favorite episodes': 30182, - 'channels': 30173 - } - label = lang(labels[tagname]) - embynode = "Emby.nodes.%s" % indexnumber - window('%s.title' % embynode, value=label) - window('%s.path' % embynode, value=windowpath) - window('%s.content' % embynode, value=path) - window('%s.type' % embynode, value=itemtype) - - if xbmcvfs.exists(nodeXML): - # Don't recreate xml if already exists - return - - if itemtype == "channels": - root = self.commonRoot(order=1, label=label, tagname=tagname, roottype=2) - etree.SubElement(root, 'path').text = "plugin://plugin.video.emby/?id=0&mode=channels" - elif itemtype == "favourites" and mediatype == "episodes": - root = self.commonRoot(order=1, label=label, tagname=tagname, roottype=2) - etree.SubElement(root, 'path').text = "plugin://plugin.video.emby/?id=%s&mode=browsecontent&type=%s&folderid=favepisodes" %(tagname, mediatype) - else: - root = self.commonRoot(order=1, label=label, tagname=tagname) - etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle" - - etree.SubElement(root, 'content').text = mediatype - - try: - utils.indent(root) - except: pass - etree.ElementTree(root).write(nodeXML) - - def clearProperties(self): - - log.info("Clearing nodes properties.") - embyprops = window('Emby.nodes.total') - propnames = [ - - "index","path","title","content", - "inprogress.content","inprogress.title", - "inprogress.content","inprogress.path", - "nextepisodes.title","nextepisodes.content", - "nextepisodes.path","unwatched.title", - "unwatched.content","unwatched.path", - "recent.title","recent.content","recent.path", - "recentepisodes.title","recentepisodes.content", - "recentepisodes.path","inprogressepisodes.title", - "inprogressepisodes.content","inprogressepisodes.path" - ] - - if embyprops: - totalnodes = int(embyprops) - for i in range(totalnodes): - for prop in propnames: - window('Emby.nodes.%s.%s' % (str(i), prop), clear=True) \ No newline at end of file diff --git a/resources/lib/views.py b/resources/lib/views.py index d1c0556d..8a2384dc 100644 --- a/resources/lib/views.py +++ b/resources/lib/views.py @@ -13,7 +13,7 @@ import xbmcvfs import read_embyserver as embyserver import embydb_functions as embydb -from utils import window, language as lang +from utils import window, language as lang, normalize_nodes, indent as xml_indent ################################################################################################# @@ -30,20 +30,34 @@ class Views(object): playlists = list() views = list() sorted_views = list() + grouped_views = list() + media_types = { + + 'movies': "Movie", + 'tvshows': "Series", + 'musicvideos': "MusicVideo", + 'homevideos': "Video", + 'music': "Audio", + 'photos': "Photo" + } def __init__(self, emby_cursor, kodi_cursor): self.emby_cursor = emby_cursor self.kodi_cursor = kodi_cursor self.video_nodes = VideoNodes() + self.playlist = Playlist() self.emby = embyserver.Read_EmbyServer() - self.emby_db = embydb.Embydb_Functions(embycursor) + self.emby_db = embydb.Embydb_Functions(emby_cursor) def _populate_views(self): # Will get emby views and views in Kodi - self.views = self.emby.getViews(sortedlist=True) - for view in self.views: + grouped_views = self.emby.get_views() + self.grouped_views = grouped_views['Items'] if "Items" in grouped_views else [] + + for view in self.emby.getViews(sortedlist=True): + self.views.append(view['name']) if view['type'] == "music": continue @@ -57,178 +71,105 @@ class Views(object): def maintain(self): # Compare views to emby self._populate_views() - view_list = [view['name'] for view in self.all_views] - grouped_views = self.emby.get_views() - current_views = emby_db.getViews() + curr_views = self.emby_db.getViews() # total nodes for window properties - self.vnodes.clearProperties() + self.video_nodes.clearProperties() - # Set views for supported media type - emby_mediatypes = { - - 'movies': "Movie", - 'tvshows': "Series", - 'musicvideos': "MusicVideo", - 'homevideos': "Video", - 'music': "Audio", - 'photos': "Photo" - } - for mediatype in ['movies', 'tvshows', 'musicvideos', 'homevideos', 'music', 'photos']: + for media_type in ('movies', 'tvshows', 'musicvideos', 'homevideos', 'music', 'photos'): self.nodes = list() # Prevent duplicate for nodes of the same type self.playlists = list() # Prevent duplicate for playlists of the same type - # Get media folders from server - for folder in [view for view in ordered if view['type'] == mediatype]: + # Get media folders from the ordered + for folder in self.emby.getViews(media_type, root=True): - folderid = folder['id'] - foldername = folder['name'] - viewtype = folder['type'] + view_id = folder['id'] + view_name = folder['name'] + view_type = folder['type'] - if foldername not in view_list: + if view_name not in self.views: # Media folders are grouped into userview - params = { - 'ParentId': folderid, - 'Recursive': True, - 'Limit': 1, - 'IncludeItemTypes': emby_mediatypes[mediatype] - } # Get one item from server using the folderid - url = "{server}/emby/Users/{UserId}/Items?format=json" - result = self.doUtils(url, parameters=params) - try: - verifyitem = result['Items'][0]['Id'] - except (TypeError, IndexError): - # Something is wrong. Keep the same folder name. - # Could be the view is empty or the connection - pass - else: - for grouped_view in grouped_views: - # This is only reserved for the detection of grouped views - if (grouped_view['Type'] == "UserView" and - grouped_view.get('CollectionType') == mediatype): - # Take the userview, and validate the item belong to the view - if self.emby.verifyView(grouped_view['Id'], verifyitem): - # Take the name of the userview - log.info("Found corresponding view: %s %s" - % (grouped_view['Name'], grouped_view['Id'])) - foldername = grouped_view['Name'] - break - else: - # Unable to find a match, add the name to our sorted_view list - self.sorted_views.append(foldername) - log.info("Couldn't find corresponding grouped view: %s" % self.sorted_views) - - # Failsafe - try: - self.sorted_views.index(foldername) + view_name = self._get_grouped_view(media_type, view_id, view_name) + + try: # Make sure the view is in sorted views before proceeding + self.sorted_views.index(view_name) except ValueError: - self.sorted_views.append(foldername) + self.sorted_views.append(view_name) - # Get current media folders from emby database - view = emby_db.getView_byId(folderid) - try: - current_viewname = view[0] - current_viewtype = view[1] - current_tagid = view[2] + # Get current media folders from emby database and compare + if self.compare_view(media_type, view_id, view_name, view_type): + if view_id in curr_views: # View is still valid + curr_views.remove(view_id) - except TypeError: - self._add_view(mediatype, folderid, foldername, viewtype) - - else: - log.debug(' '.join(( - - "Found viewid: %s" % folderid, - "viewname: %s" % current_viewname, - "viewtype: %s" % current_viewtype, - "tagid: %s" % current_tagid))) - - # View is still valid - try: - current_views.remove(folderid) - except ValueError: - # View was just created, nothing to remove - pass - - # View was modified, update with latest info - if current_viewname != foldername: - log.info("viewid: %s new viewname: %s" % (folderid, foldername)) - tagid = self._get_tag(foldername) - - # Update view with new info - emby_db.updateView(foldername, tagid, folderid) - - if mediatype != "music": - if emby_db.getView_byName(current_viewname) is None: - # The tag could be a combined view. Ensure there's no other tags - # with the same name before deleting playlist. - self._playlist(mediatype, current_viewname, folderid, - current_viewtype, True) - # Delete video node - if mediatype != "musicvideos": - self.vnodes.viewNode( - indexnumber=None, - tagname=current_viewname, - mediatype=mediatype, - viewtype=current_viewtype, - viewid=folderid, - delete=True) - # Added new playlist - self.create_playlist(mediatype, foldername, folderid, viewtype) - # Add new video node - self.create_node(mediatype, foldername, folderid, viewtype) - - # Update items with new tag - items = emby_db.getItem_byView(folderid) - for item in items: - # Remove the "s" from viewtype for tags - self._update_tag( - current_tagid, tagid, item[0], current_viewtype[:-1]) - else: - # Validate the playlist exists or recreate it - if mediatype != "music": - self.create_playlist(mediatype, foldername, folderid, viewtype) - # Create the video node if not already exists - self.create_node(mediatype, foldername, folderid, viewtype) # Add video nodes listings - self.add_single_node(totalnodes, "Favorite movies", "movies", "favourites") - self.add_single_node(totalnodes, "Favorite tvshows", "tvshows", "favourites") - self.add_single_node(totalnodes, "Favorite episodes", "episodes", "favourites") - self.add_single_node(totalnodes, "channels", "movies", "channels") + self.add_single_nodes() # Save total window('Emby.nodes.total', str(self.total_nodes)) - # Remove any old referenced views - log.info("Removing views: %s", current_views) - for view in current_views: + log.info("Removing views: %s", curr_views) + for view in curr_views: self.remove_view(view) - def create_node(self, media_type, view_name, view_id, view_type): + def _get_grouped_view(self, media_type, view_id, view_name): + # Get single item from view to compare + result = self.emby.get_single_item(self.media_types[media_type], view_id) + try: + item = result['Items'][0]['Id'] + except (TypeError, IndexError): + # Something is wrong. Keep the same folder name. + # Could be the view is empty or the connection + pass + else: + for view in self.grouped_views: + if view['Type'] == "UserView" and view.get('CollectionType') == media_type: + # Take the userview, and validate the item belong to the view + if self.emby.verifyView(view['Id'], item): + log.info("found corresponding view: %s %s", view['Name'], view['Id']) + view_name = view['Name'] + break + else: # Unable to find a match, add the name to our sorted_view list + log.info("couldn't find corresponding grouped view: %s", self.sorted_views) - if view_name not in self.nodes and media_type not in ('musicvideos', 'music'): - index = self.sorted_views.index(view_name) - self.video_nodes.viewNode(index, view_name, media_type,view_type, view_id) - - if view_type == "mixed": # Change the value - self.sorted_views[index] = "%ss" % view_name - - self.nodes.append(view_name) - self.total_nodes += 1 - - def add_single_node(self, index, tag, media_type, item_type): - self.video_nodes.singleNode(index, tag, media_type, item_type) - self.total_nodes += 1 + return view_name def add_view(self, media_type, view_id, view_name, view_type): # Generate view, playlist and video node log.info("creating view %s: %s", view_name, view_id) - tag_id = self._get_tag(view_name) + tag_id = self.get_tag(view_name) - # Create playlist for the video library - self.create_playlist(media_type, view_name, view_id, view_type) - # Create the video node - self.create_node(media_type, view_name, view_id, view_type) + self.add_playlist_node(media_type, view_id, view_name, view_type) # Add view to emby database self.emby_db.addView(view_id, view_name, view_type, tag_id) + def compare_view(self, media_type, view_id, view_name, view_type): + + curr_view = self.emby_db.getView_byId(view_id) + try: + curr_view_name = curr_view[0] + curr_view_type = curr_view[1] + curr_tag_id = curr_view[2] + except TypeError: + self.add_view(media_type, view_id, view_name, view_type) + return False + + # View is still valid + log.debug("Found viewid: %s viewname: %s viewtype: %s tagid: %s", + view_id, curr_view_name, curr_view_type, curr_tag_id) + + if curr_view_name != view_name: + # View was modified, update with latest info + log.info("viewid: %s new viewname: %s", view_id, view_name) + tag_id = self.get_tag(view_name) + # Update view with new info + self.emby_db.updateView(view_name, tag_id, view_id) + # Delete old playlists and video nodes + self.delete_playlist_node(media_type, curr_view_name, view_id, curr_view_type) + # Update items with new tag + self._update_items_tag(curr_view_type[:-1], view_id, curr_tag_id, tag_id) + + # Verify existance of playlist and nodes + self.add_playlist_node(media_type, view_id, view_name, view_type) + return True + def remove_view(self, view): # Remove any items that belongs to the old view items = self.emby_db.get_item_by_view(view) @@ -236,79 +177,13 @@ class Views(object): # TODO: Triage not accessible from here yet #self.triage_items("remove", items) - def create_playlist(self, media_type, view_name, view_id, view_type): + def _update_items_tag(self, media_type, view_id, tag, new_tag): + items = self.emby_db.getItem_byView(view_id) + for item in items: + # Remove the "s" from viewtype for tags + self._update_tag(tag, new_tag, item[0], media_type) - if view_name not in self.playlists and media_type in ('movies', 'tvshows', 'musicvideos'): - - self._playlist(media_type, view_name, view_id, view_type) - self.playlists.append(view_name) - - @classmethod - def _add_playlist(cls, tag, playlist_name, path, media_type): - # Using write process since there's no guarantee the xml declaration works with etree - special_types = {'homevideos': "movies"} - log.info("writing playlist to: %s", path) - try: - f = xbmcvfs.File(path, 'w') - except: - log.info("failed to create playlist: %s", path) - return False - else: - f.write( - '\n' - '\n\t' - 'Emby %s\n\t' - 'all\n\t' - '\n\t\t' - '%s\n\t' - '' - '' - % (special_types.get(media_type, media_type), playlist_name, tag)) - f.close() - log.info("successfully added playlist: %s" % tag) - return True - - @classmethod - def _delete_playlist(cls, path): - xbmcvfs.delete(path) - log.info("successfully removed playlist: %s", path) - - def _playlist(self, media_type, tag, view_id, view_type="", delete=False): - # Tagname is in unicode - actions: add or delete - tag = tag.encode('utf-8') - path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8') - - if view_type == "mixed": - playlist_name = "%s - %s" % (tag, media_type) - xsp_path = os.path.join(path, "Emby %s - %s.xsp" % (view_id, media_type)) - else: - playlist_name = tag - xsp_path = os.path.join(path, "Emby %s" % view_id) - - # Only add the playlist if it doesn't exist - if xbmcvfs.exists(xsp_path): - if delete: - self._delete_playlist(xsp_path) - return True - - elif not xbmcvfs.exists(path): - log.info("creating directory: %s", path) - xbmcvfs.mkdirs(path) - - return self._add_playlist(tag, playlist_name, xsp_path, media_type) - - def _add_tag(self, tag): - - self.kodi_cursor.execute("select coalesce(max(tag_id),0) from tag") - tag_id = self.kodi_cursor.fetchone()[0] + 1 - - query = "INSERT INTO tag(tag_id, name) values(?, ?)" - self.kodi_cursor.execute(query, (tag_id, tag)) - log.debug("Create tag_id: %s name: %s", tag_id, tag) - - return tag_id - - def _get_tag(self, tag): + def get_tag(self, tag): # This will create and return the tag_id if KODI in (15, 16, 17): # Kodi Isengard, Jarvis, Krypton @@ -345,6 +220,17 @@ class Views(object): return tag_id + def _add_tag(self, tag): + + self.kodi_cursor.execute("select coalesce(max(tag_id),0) from tag") + tag_id = self.kodi_cursor.fetchone()[0] + 1 + + query = "INSERT INTO tag(tag_id, name) values(?, ?)" + self.kodi_cursor.execute(query, (tag_id, tag)) + log.debug("Create tag_id: %s name: %s", tag_id, tag) + + return tag_id + def _update_tag(self, tag, new_tag, kodi_id, media_type): log.debug("Updating: %s with %s for %s: %s", tag, new_tag, media_type, kodi_id) @@ -395,6 +281,110 @@ class Views(object): )) self.kodi_cursor.execute(query, (kodi_id, media_type, tag,)) + def add_playlist_node(self, media_type, view_id, view_name, view_type): + # Create playlist for the video library + if view_name not in self.playlists and media_type in ('movies', 'tvshows', 'musicvideos'): + self.playlist.process_playlist(media_type, view_id, view_name, view_type) + self.playlists.append(view_name) + # Create the video node + self._create_node(media_type, view_id, view_name, view_type) + + def delete_playlist_node(self, media_type, view_id, view_name, view_type): + + if media_type == "music": + return + + if self.emby_db.getView_byName(view_name) is None: + # The tag could be a combined view. Ensure there's no other tags + # with the same name before deleting playlist. + self.playlist.process_playlist(media_type, view_id, view_name, view_type, True) + # Delete video node + if media_type != "musicvideos": + self.video_nodes.viewNode(None, view_name, media_type, view_type, view_id, True) + + def _create_node(self, media_type, view_id, view_name, view_type): + + if view_name not in self.nodes and media_type not in ('musicvideos', 'music'): + index = self.sorted_views.index(view_name) + self.video_nodes.viewNode(index, view_name, media_type,view_type, view_id) + + if view_type == "mixed": # Change the value + self.sorted_views[index] = "%ss" % view_name + + self.nodes.append(view_name) + self.total_nodes += 1 + + def add_single_nodes(self): + singles = [ + ("Favorite movies", "movies", "favourites"), + ("Favorite tvshows", "tvshows", "favourites"), + ("Favorite episodes", "episodes", "favourites"), + ("channels", "movies", "channels") + ] + for args in singles: + self._single_node(self.total_nodes, *args) + + def _single_node(self, index, tag, media_type, view_type): + self.video_nodes.singleNode(index, tag, media_type, view_type) + self.total_nodes += 1 + + +class Playlist(object): + + def __init__(self): + pass + + def process_playlist(self, media_type, view_id, view_name, view_type, delete=False): + # Tagname is in unicode - actions: add or delete + tag = view_name.encode('utf-8') + path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8') + + if view_type == "mixed": + playlist_name = "%s - %s" % (tag, media_type) + xsp_path = os.path.join(path, "Emby %s - %s.xsp" % (view_id, media_type)) + else: + playlist_name = tag + xsp_path = os.path.join(path, "Emby %s" % view_id) + + # Only add the playlist if it doesn't exist + if xbmcvfs.exists(xsp_path): + if delete: + self._delete_playlist(xsp_path) + return + + elif not xbmcvfs.exists(path): + log.info("creating directory: %s", path) + xbmcvfs.mkdirs(path) + + self._add_playlist(tag, playlist_name, xsp_path, media_type) + + def _add_playlist(self, tag, name, path, media_type): + # Using write process since there's no guarantee the xml declaration works with etree + special_types = {'homevideos': "movies"} + log.info("writing playlist to: %s", path) + try: + f = xbmcvfs.File(path, 'w') + except: + log.info("failed to create playlist: %s", path) + else: + f.write( + '\n' + '\n\t' + 'Emby %s\n\t' + 'all\n\t' + '\n\t\t' + '%s\n\t' + '' + '' + % (special_types.get(media_type, media_type), name, tag)) + f.close() + log.info("successfully added playlist: %s" % tag) + + @classmethod + def _delete_playlist(cls, path): + xbmcvfs.delete(path) + log.info("successfully removed playlist: %s", path) + class VideoNodes(object): @@ -402,7 +392,7 @@ class VideoNodes(object): def __init__(self): pass - def commonRoot(self, order, label, tagname, roottype=1): + def commonRoot(self, order, label, tagname="", roottype=1): if roottype == 0: # Index @@ -430,29 +420,10 @@ class VideoNodes(object): else: dirname = viewid - path = xbmc.translatePath("special://profile/library/video/").decode('utf-8') + nodepath = xbmc.translatePath( "special://profile/library/video/emby/%s/" % dirname).decode('utf-8') - # Verify the video directory - if not xbmcvfs.exists(path): - try: - shutil.copytree( - src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'), - dst=xbmc.translatePath("special://profile/library/video").decode('utf-8')) - except Exception as error: - log.error(error) - - xbmcvfs.exists(path) - - emby_path = xbmc.translatePath("special://profile/library/video/emby/index.xml").decode('utf-8') - if not emby_path: - root = self.commonRoot(order=0, label="Emby", roottype=0) - try: - utils.indent(root) - except: pass - etree.ElementTree(root).write(emby_path) - if delete: dirs, files = xbmcvfs.listdir(nodepath) for file in files: @@ -460,10 +431,32 @@ class VideoNodes(object): log.info("Sucessfully removed videonode: %s." % tagname) return + + # Verify the video directory + path = xbmc.translatePath("special://profile/library/video/").decode('utf-8') + if not xbmcvfs.exists(path): + try: + shutil.copytree( + src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'), + dst=xbmc.translatePath("special://profile/library/video").decode('utf-8')) + except Exception as error: + log.error(error) + + xbmcvfs.mkdir(path) + + embypath = xbmc.translatePath("special://profile/library/video/emby/").decode('utf-8') + if not xbmcvfs.exists(embypath): + xbmcvfs.mkdir(embypath) + root = self.commonRoot(order=0, label="Emby", roottype=0) + try: + xml_indent(root) + except: pass + etree.ElementTree(root).write(os.path.join(embypath, "index.xml")) + # Create the node directory if not xbmcvfs.exists(nodepath) and not mediatype == "photos": # We need to copy over the default items - xbmcvfs.mkdirs(nodepath) + xbmcvfs.mkdir(nodepath) # Create index entry nodeXML = "%sindex.xml" % nodepath @@ -487,7 +480,7 @@ class VideoNodes(object): else: root = self.commonRoot(order=0, label=tagname, tagname=tagname, roottype=0) try: - utils.indent(root) + xml_indent(root) except: pass etree.ElementTree(root).write(nodeXML) @@ -582,10 +575,10 @@ class VideoNodes(object): elif nodetype == "nextepisodes": # Custom query path = "plugin://plugin.video.emby/?id=%s&mode=nextup&limit=25" % tagname - elif self.kodiversion == 14 and nodetype == "recentepisodes": + elif KODI == 14 and nodetype == "recentepisodes": # Custom query path = "plugin://plugin.video.emby/?id=%s&mode=recentepisodes&limit=25" % tagname - elif self.kodiversion == 14 and nodetype == "inprogressepisodes": + elif KODI == 14 and nodetype == "inprogressepisodes": # Custom query path = "plugin://plugin.video.emby/?id=%s&mode=inprogressepisodes&limit=25"% tagname else: @@ -626,7 +619,7 @@ class VideoNodes(object): # Create the root if (nodetype == "nextepisodes" or mediatype == "homevideos" or - (self.kodiversion == 14 and nodetype in ('recentepisodes', 'inprogressepisodes'))): + (KODI == 14 and nodetype in ('recentepisodes', 'inprogressepisodes'))): # Folder type with plugin path root = self.commonRoot(order=node, label=label, tagname=tagname, roottype=2) etree.SubElement(root, 'path').text = path @@ -693,14 +686,14 @@ class VideoNodes(object): attrib={'field': "inprogress", 'operator':"true"}) try: - utils.indent(root) + xml_indent(root) except: pass etree.ElementTree(root).write(nodeXML) def singleNode(self, indexnumber, tagname, mediatype, itemtype): tagname = tagname.encode('utf-8') - cleantagname = utils.normalize_nodes(tagname) + cleantagname = normalize_nodes(tagname) nodepath = xbmc.translatePath("special://profile/library/video/").decode('utf-8') nodeXML = "%semby_%s.xml" % (nodepath, cleantagname) path = "library://video/emby_%s.xml" % cleantagname @@ -745,6 +738,30 @@ class VideoNodes(object): etree.SubElement(root, 'content').text = mediatype try: - utils.indent(root) + xml_indent(root) except: pass etree.ElementTree(root).write(nodeXML) + + def clearProperties(self): + + log.info("Clearing nodes properties.") + embyprops = window('Emby.nodes.total') + propnames = [ + + "index","path","title","content", + "inprogress.content","inprogress.title", + "inprogress.content","inprogress.path", + "nextepisodes.title","nextepisodes.content", + "nextepisodes.path","unwatched.title", + "unwatched.content","unwatched.path", + "recent.title","recent.content","recent.path", + "recentepisodes.title","recentepisodes.content", + "recentepisodes.path","inprogressepisodes.title", + "inprogressepisodes.content","inprogressepisodes.path" + ] + + if embyprops: + totalnodes = int(embyprops) + for i in range(totalnodes): + for prop in propnames: + window('Emby.nodes.%s.%s' % (str(i), prop), clear=True)