# coding=utf-8 import os, sys, shutil, unicodedata, re, types from resources.lib.common import * from html.entities import name2codepoint from urllib.parse import parse_qs from urllib.parse import quote, unquote import xbmc, xbmcplugin, xbmcgui, xbmcvfs import xml.etree.ElementTree as xmltree import urllib from unidecode import unidecode from traceback import print_exc import json from resources.lib import rules, pathrules, viewattrib, orderby, moveNodes # character entity reference CHAR_ENTITY_REXP = re.compile('&(%s);' % '|'.join(name2codepoint)) # decimal character reference DECIMAL_REXP = re.compile('&#(\d+);') # hexadecimal character reference HEX_REXP = re.compile('&#x([\da-fA-F]+);') REPLACE1_REXP = re.compile(r'[\']+') REPLACE2_REXP = re.compile(r'[^-a-z0-9]+') REMOVE_REXP = re.compile('-{2,}') class Main: # MAIN ENTRY POINT def __init__(self, params, ltype, rule, attrib, pathrule, orderby): self._parse_argv() self.ltype = ltype self.PARAMS = params self.RULE = rule self.ATTRIB = attrib self.PATHRULE = pathrule self.ORDERBY = orderby # If there are no custom library nodes in the profile directory, copy them from the Kodi install targetDir = os.path.join( xbmcvfs.translatePath( "special://profile" ), "library", ltype ) if True: if not os.path.exists( targetDir ): xbmcvfs.mkdirs( targetDir ) originDir = os.path.join( xbmcvfs.translatePath( "special://xbmc" ), "system", "library", ltype ) dirs, files = xbmcvfs.listdir( originDir ) self.copyNode( dirs, files, targetDir, originDir ) else: xbmcgui.Dialog().ok(ADDONNAME, LANGUAGE( 30400 ) ) print_exc xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) return # Create data if not exists if not os.path.exists(DATAPATH): xbmcvfs.mkdir(DATAPATH) if "type" in self.PARAMS: # We're performing a specific action if self.PARAMS[ "type" ] == "delete": message = LANGUAGE( 30401 ) actionpath = unquote(self.PARAMS["actionPath"]) if self.PARAMS[ "actionPath" ] == targetDir: # Ask the user is they want to reset all nodes message = LANGUAGE( 30402 ) result = xbmcgui.Dialog().yesno(ADDONNAME, message ) if result: if actionpath.endswith( ".xml" ): # Delete single file xbmcvfs.delete(actionpath) else: # Delete folder self.RULE.deleteAllNodeRules(actionpath) shutil.rmtree(actionpath) else: return elif self.PARAMS[ "type" ] == "deletenode": result = xbmcgui.Dialog().yesno(ADDONNAME, LANGUAGE( 30403 ) ) if result: self.changeViewElement( self.PARAMS[ "actionPath" ], self.PARAMS[ "node" ], "" ) elif self.PARAMS[ "type" ] == "editlabel": if self.PARAMS[ "label" ].isdigit(): label = xbmc.getLocalizedString( int( self.PARAMS[ "label" ] ) ) else: label = self.PARAMS[ "label" ] # Get new label from keyboard dialog keyboard = xbmc.Keyboard( label, LANGUAGE( 30300 ), False ) keyboard.doModal() if ( keyboard.isConfirmed() ): newlabel = keyboard.getText() if newlabel != "" and newlabel != label: # We've got a new label, update the xml file self.changeViewElement( self.PARAMS[ "actionPath" ], "label", newlabel ) else: return elif self.PARAMS[ "type" ] == "editvisibility": currentVisibility = self.getRootAttrib( self.PARAMS[ "actionPath" ], "visible" ) # Get new visibility from keyboard dialog keyboard = xbmc.Keyboard( currentVisibility, LANGUAGE( 30301 ), False ) keyboard.doModal() if ( keyboard.isConfirmed() ): newVisibility = keyboard.getText() if newVisibility != currentVisibility: # We've got a new label, update the xml file self.changeRootAttrib( self.PARAMS[ "actionPath" ], "visible", newVisibility ) else: return elif self.PARAMS[ "type" ] == "moveNode": self.indexCounter = -1 # Get existing nodes nodes = {} self.listNodes( self.PARAMS[ "actionPath" ], nodes ) # Get updated order newOrder = moveNodes.getNewOrder( nodes, int( self.PARAMS[ "actionItem" ] ) ) if newOrder is not None: # Update the orders for i, node in enumerate( newOrder, 1 ): path = unquote( node[ 2 ] ) if node[ 3 ] == "folder": path = os.path.join( unquote( node[ 2 ] ), "index.xml" ) self.changeRootAttrib( path, "order", str( i * 10 ) ) elif self.PARAMS[ "type" ] == "newView": # Get new view name from keyboard dialog keyboard = xbmc.Keyboard( "", LANGUAGE( 30316 ), False ) keyboard.doModal() if ( keyboard.isConfirmed() ): newView = keyboard.getText() if newView != "": # Ensure filename is unique filename = self.slugify( newView.lower().replace( " ", "" ) ) if os.path.exists( os.path.join( self.PARAMS[ "actionPath" ], filename + ".xml" ) ): count = 0 while os.path.exists( os.path.join( self.PARAMS[ "actionPath" ], filename + "-" + str( count ) + ".xml" ) ): count += 1 filename = filename + "-" + str( count ) # Create a new xml file tree = xmltree.ElementTree( xmltree.Element( "node" ) ) root = tree.getroot() subtree = xmltree.SubElement( root, "label" ).text = newView # Add any node rules self.RULE.addAllNodeRules( self.PARAMS[ "actionPath" ], root ) # Write the xml file self.indent( root ) xmlfile = unquote(os.path.join( self.PARAMS[ "actionPath" ], filename + ".xml" )) if not os.path.exists(xmlfile): with open(xmlfile, 'a'): os.utime(xmlfile, None) tree.write( xmlfile, encoding="UTF-8" ) else: return elif self.PARAMS[ "type" ] == "newNode": # Get new node name from the keyboard dialog keyboard = xbmc.Keyboard( "", LANGUAGE( 30303 ), False ) keyboard.doModal() if ( keyboard.isConfirmed() ): newNode = keyboard.getText() if newNode == "": return # Ensure foldername is unique foldername = self.slugify( newNode.lower().replace( " ", "" ) ) if os.path.exists( os.path.join( self.PARAMS[ "actionPath" ], foldername + os.pathsep ) ): count = 0 while os.path.exists( os.path.join( self.PARAMS[ "actionPath" ], foldername + "-" + str( count ) + os.pathsep ) ): count += 1 foldername = foldername + "-" + str( count ) foldername = unquote(os.path.join( self.PARAMS[ "actionPath" ], foldername )) # Create new node folder xbmcvfs.mkdir( foldername ) # Create a new xml file tree = xmltree.ElementTree( xmltree.Element( "node" ) ) root = tree.getroot() subtree = xmltree.SubElement( root, "label" ).text = newNode # Ask user if they want to import defaults if self.ltype.startswith( "video" ): defaultNames = [ xbmc.getLocalizedString( 231 ), xbmc.getLocalizedString( 342 ), xbmc.getLocalizedString( 20343 ), xbmc.getLocalizedString( 20389 ) ] defaultValues = [ "", "movies", "tvshows", "musicvideos" ] selected = xbmcgui.Dialog().select( LANGUAGE( 30304 ), defaultNames ) else: selected = 0 # If the user selected some defaults... if selected != -1 and selected != 0: try: # Copy those defaults across originDir = os.path.join( xbmcvfs.translatePath( "special://xbmc" ), "system", "library", self.ltype, defaultValues[ selected ] ) dirs, files = xbmcvfs.listdir( originDir ) for file in files: if file != "index.xml": xbmcvfs.copy( os.path.join( originDir, file), os.path.join( foldername, file ) ) # Open index.xml and copy values across index = xmltree.parse( os.path.join( originDir, "index.xml" ) ).getroot() if "visible" in index.attrib: root.set( "visible", index.attrib.get( "visible" ) ) icon = index.find( "icon" ) if icon is not None: xmltree.SubElement( root, "icon" ).text = icon.text except: print_exc() # Write the xml file self.indent( root ) tree.write( unquote(os.path.join( foldername, "index.xml" )), encoding="UTF-8" ) else: return elif self.PARAMS[ "type" ] == "rule": # Display list of all elements of a rule self.RULE.displayRule( self.PARAMS[ "actionPath" ], self.PATH, self.PARAMS[ "rule" ] ) return elif self.PARAMS[ "type" ] == "editMatch": # Editing the field the rule is matched against self.RULE.editMatch( self.PARAMS[ "actionPath" ], self.PARAMS[ "rule" ], self.PARAMS[ "content"], self.PARAMS[ "default" ] ) elif self.PARAMS[ "type" ] == "editOperator": # Editing the operator of a rule self.RULE.editOperator( self.PARAMS[ "actionPath" ], self.PARAMS[ "rule" ], self.PARAMS[ "group" ], self.PARAMS[ "default" ] ) elif self.PARAMS[ "type" ] == "editValue": # Editing the value of a rule self.RULE.editValue(self.PARAMS["actionPath"], self.PARAMS[ "rule" ] ) elif self.PARAMS[ "type" ] == "browseValue": # Browse for the new value of a rule self.RULE.browse( self.PARAMS[ "actionPath" ], self.PARAMS[ "rule" ], self.PARAMS[ "match" ], self.PARAMS[ "content" ] ) elif self.PARAMS[ "type" ] == "deleteRule": # Delete a rule self.RULE.deleteRule( self.PARAMS[ "actionPath" ], self.PARAMS[ "rule" ] ) elif self.PARAMS[ "type" ] == "editRulesMatch": # Editing whether any or all rules must match self.ATTRIB.editMatch( self.PARAMS[ "actionPath" ] ) # --- Edit order-by --- elif self.PARAMS[ "type" ] == "orderby": # Display all elements of order by self.ORDERBY.displayOrderBy( self.PARAMS[ "actionPath" ]) return elif self.PARAMS[ "type" ] == "editOrderBy": self.ORDERBY.editOrderBy( self.PARAMS[ "actionPath" ], self.PARAMS[ "content" ], self.PARAMS[ "default" ] ) elif self.PARAMS[ "type" ] == "editOrderByDirection": self.ORDERBY.editDirection( self.PARAMS[ "actionPath" ], self.PARAMS[ "default" ] ) # --- Edit paths --- elif self.PARAMS[ "type" ] == "addPath": self.ATTRIB.addPath( self.PARAMS[ "actionPath" ] ) elif self.PARAMS[ "type" ] == "editPath": self.ATTRIB.editPath( self.PARAMS[ "actionPath" ], self.PARAMS[ "value" ] ) elif self.PARAMS[ "type" ] == "pathRule": self.PATHRULE.displayRule( self.PARAMS[ "actionPath" ], int( self.PARAMS[ "rule" ] ) ) return elif self.PARAMS[ "type" ] == "deletePathRule": self.ATTRIB.deletePathRule( self.PARAMS[ "actionPath" ], int( self.PARAMS[ "rule" ] ) ) elif self.PARAMS[ "type" ] == "editPathMatch": # Editing the field the rule is matched against self.PATHRULE.editMatch( self.PARAMS[ "actionPath" ], int( self.PARAMS[ "rule" ] ) ) elif self.PARAMS[ "type" ] == "editPathValue": # Editing the value of a rule self.PATHRULE.editValue( self.PARAMS[ "actionPath" ], int( self.PARAMS[ "rule" ] ) ) elif self.PARAMS[ "type" ] == "browsePathValue": # Browse for the new value of a rule self.PATHRULE.browse( self.PARAMS[ "actionPath" ], int( self.PARAMS[ "rule" ] ) ) # --- Edit other attribute of view --- # > Content elif self.PARAMS[ "type" ] == "editContent": self.ATTRIB.editContent( self.PARAMS[ "actionPath" ], "" ) # No default to pass, yet! # > Grouping elif self.PARAMS[ "type" ] == "editGroup": self.ATTRIB.editGroup( self.PARAMS[ "actionPath" ], self.PARAMS[ "content" ], "" ) # > Limit elif self.PARAMS[ "type" ] == "editLimit": self.ATTRIB.editLimit( self.PARAMS[ "actionPath" ], self.PARAMS[ "value" ] ) # > Icon (also for node) elif self.PARAMS[ "type" ] == "editIcon": self.ATTRIB.editIcon( self.PARAMS[ "actionPath" ], self.PARAMS[ "value" ] ) elif self.PARAMS[ "type" ] == "browseIcon": self.ATTRIB.browseIcon( self.PARAMS[ "actionPath" ] ) # Refresh the listings and exit xbmc.executebuiltin("Container.Refresh") return if self.PATH.endswith( ".xml" ): self.RulesList() else: self.NodesList(targetDir) def NodesList( self, targetDir ): # List nodes and views nodes = {} self.indexCounter = -1 if self.PATH != "": self.listNodes( self.PATH, nodes ) else: self.listNodes( targetDir, nodes ) self.PATH = quote( self.PATH ) for i, key in enumerate( sorted( nodes ) ): # 0 = Label # 1 = Icon # 2 = Path # 3 = Type # 4 = Order # Localize the label if nodes[ key ][ 0 ].isdigit(): label = xbmc.getLocalizedString( int( nodes[ key ][ 0 ] ) ) else: label = nodes[ key ][ 0 ] # Create the listitem if nodes[ key ][ 3 ] == "folder": listitem = xbmcgui.ListItem( label="%s >" % ( label ), label2=nodes[ key ][ 4 ] ) listitem.setArt({"icon": nodes[ key ][ 1 ]}) else: listitem = xbmcgui.ListItem( label=label, label2=nodes[ key ][ 4 ] ) listitem.setArt({"icon": nodes[ key ][ 1 ]}) # Add context menu items commands = [] commandsNode = [] commandsView = [] commandsNode.append( ( LANGUAGE(30101), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=editlabel&actionPath=" % self.ltype + os.path.join( nodes[ key ][ 2 ], "index.xml" ) + "&label=" + nodes[ key ][ 0 ] + ")" ) ) commandsNode.append( ( LANGUAGE(30102), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=editIcon&actionPath=" % self.ltype + os.path.join( nodes[ key ][ 2 ], "index.xml" ) + "&value=" + nodes[ key ][ 1 ] + ")" ) ) commandsNode.append( ( LANGUAGE(30103), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=browseIcon&actionPath=" % self.ltype + os.path.join( nodes [ key ][ 2 ], "index.xml" ) + ")" ) ) if self.PATH == "": commandsNode.append( ( LANGUAGE(30104), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=moveNode&actionPath=" % self.ltype + targetDir + "&actionItem=" + str( i ) + ")" ) ) else: commandsNode.append( ( LANGUAGE(30104), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=moveNode&actionPath=" % self.ltype + self.PATH + "&actionItem=" + str( i ) + ")" ) ) commandsNode.append( ( LANGUAGE(30105), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=editvisibility&actionPath=" % self.ltype + os.path.join( nodes[ key ][ 2 ], "index.xml" ) + ")" ) ) commandsNode.append( ( LANGUAGE(30100), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=delete&actionPath=" % self.ltype + nodes[ key ][ 2 ] + ")" ) ) commandsView.append( ( LANGUAGE(30101), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=editlabel&actionPath=" % self.ltype + nodes[ key ][ 2 ] + "&label=" + nodes[ key ][ 0 ] + ")" ) ) commandsView.append( ( LANGUAGE(30102), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=editIcon&actionPath=" % self.ltype + nodes[ key ][ 2 ] + "&value=" + nodes[ key ][ 1 ] + ")" ) ) commandsView.append( ( LANGUAGE(30103), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=browseIcon&actionPath=" % self.ltype + nodes[ key ][ 2 ] + ")" ) ) if self.PATH == "": commandsView.append( ( LANGUAGE(30104), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=moveNode&actionPath=" % self.ltype + targetDir + "&actionItem=" + str( i ) + ")" ) ) else: commandsView.append( ( LANGUAGE(30104), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=moveNode&actionPath=" % self.ltype + self.PATH + "&actionItem=" + str( i ) + ")" ) ) commandsView.append( ( LANGUAGE(30105), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=editvisibility&actionPath=" % self.ltype + nodes[ key ][ 2 ] + ")" ) ) commandsView.append( ( LANGUAGE(30100), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=delete&actionPath=" % self.ltype + nodes[ key ][ 2 ] + ")" ) ) if nodes[ key ][ 3 ] == "folder": listitem.addContextMenuItems( commandsNode ) xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), "plugin://plugin.library.node.editor?ltype=%s&path=" % self.ltype + nodes[ key ][ 2 ], listitem, isFolder=True ) else: listitem.addContextMenuItems( commandsView ) xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), "plugin://plugin.library.node.editor?ltype=%s&path=" % self.ltype + nodes[ key ][ 2 ], listitem, isFolder=True ) if self.PATH != "": # Get any rules from the index.xml rules, nextRule = self.getRules( os.path.join( unquote( self.PATH ), "index.xml" ), True ) rulecount = 0 if rules is not None: for rule in rules: commands = [] if rule[ 0 ] == "rule": # 1 = field # 2 = operator # 3 = value (optional) if len(rule) == 3: translated = self.RULE.translateRule( [ rule[ 1 ], rule[ 2 ] ] ) else: translated = self.RULE.translateRule( [ rule[ 1 ], rule[ 2 ], rule[ 3 ] ] ) if len(translated) == 2: listitem = xbmcgui.ListItem( label="%s: %s %s" % ( LANGUAGE(30205), translated[ 0 ][ 0 ], translated[ 1 ][ 0 ] ) ) else: listitem = xbmcgui.ListItem( label="%s: %s %s %s" % ( LANGUAGE(30205), translated[ 0 ][ 0 ], translated[ 1 ][ 0 ], translated[ 2 ][ 1 ] ) ) commands.append( ( LANGUAGE( 30100 ), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=deleteRule&actionPath=" % self.ltype + os.path.join( self.PATH, "index.xml" ) + "&rule=" + str( rulecount ) + ")" ) ) action = "plugin://plugin.library.node.editor?ltype=%s&type=rule&actionPath=" % self.ltype + os.path.join( self.PATH, "index.xml" ) + "&rule=" + str( rulecount ) rulecount += 1 listitem.addContextMenuItems( commands, replaceItems = True ) if rule[ 0 ] == "rule" or rule[ 0 ] == "order": xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=True ) else: xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False ) # New rule xbmcplugin.addDirectoryItem( int( sys.argv[ 1 ] ), "plugin://plugin.library.node.editor?ltype=%s&type=rule&actionPath=" % self.ltype + os.path.join( self.PATH, "index.xml" ) + "&rule=" + str( nextRule), xbmcgui.ListItem( label="* %s" %( LANGUAGE(30005) ) ), isFolder=True ) showReset = False if self.PATH == "": self.PATH = quote( targetDir ) showReset = True # New view and node xbmcplugin.addDirectoryItem( int( sys.argv[ 1 ] ), "plugin://plugin.library.node.editor?ltype=%s&type=newView&actionPath=" % self.ltype + self.PATH, xbmcgui.ListItem( label="* %s" %( LANGUAGE(30006) ) ), isFolder=False ) xbmcplugin.addDirectoryItem( int( sys.argv[ 1 ] ), "plugin://plugin.library.node.editor?ltype=%s&type=newNode&actionPath=" % self.ltype + self.PATH, xbmcgui.ListItem( label="* %s" %( LANGUAGE(30007) ) ), isFolder=False ) if showReset: xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), "plugin://plugin.library.node.editor?ltype=%s&type=delete&actionPath=" % self.ltype + targetDir, xbmcgui.ListItem( label="* %s" %( LANGUAGE(30008) ) ), isFolder=False ) xbmcplugin.setContent(int(sys.argv[1]), 'files') xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) def RulesList( self ): # List rules for specific view rules, nextRule = self.getRules( self.PATH ) hasContent = False content = "" hasOrder = False hasGroup = False hasLimit = False hasPath = False splitPath = None rulecount = 0 if rules is not None: for rule in rules: commands = [] if rule[ 0 ] == "content": # 1 = Content listitem = xbmcgui.ListItem( label="%s: %s" % ( LANGUAGE(30200), self.ATTRIB.translateContent( rule[ 1 ] ) ) ) commands.append( ( LANGUAGE(30100), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=deletenode&actionPath=" % self.ltype + self.PATH + "&node=content)" ) ) action = "plugin://plugin.library.node.editor?ltype=%s&type=editContent&actionPath=" % self.ltype + self.PATH hasContent = True content = rule[ 1 ] elif rule[ 0 ] == "order": # 1 = orderby # 2 = direction (optional?) if len( rule ) == 3: translate = self.ORDERBY.translateOrderBy( [ rule[ 1 ], rule[ 2 ] ] ) listitem = xbmcgui.ListItem( label="%s: %s (%s)" % ( LANGUAGE(30201), translate[ 0 ][ 0 ], translate[ 1 ][ 0 ] ) ) else: translate = self.ORDERBY.translateOrderBy( [ rule[ 1 ], "" ] ) listitem = xbmcgui.ListItem( label="%s: %s" % ( LANGUAGE(30201), translate[ 0 ][ 0 ] ) ) commands.append( ( LANGUAGE(30100), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=deletenode&actionPath=" % self.ltype + self.PATH + "&node=order)" ) ) action = "plugin://plugin.library.node.editor?ltype=%s&type=orderby&actionPath=" % self.ltype + self.PATH hasOrder = True elif rule[ 0 ] == "group": # 1 = group listitem = xbmcgui.ListItem( label="%s: %s" % ( LANGUAGE(30202), self.ATTRIB.translateGroup( rule[ 1 ] ) ) ) commands.append( ( LANGUAGE(30100), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=deletenode&actionPath=" % self.ltype + self.PATH + "&node=group)" ) ) action = "plugin://plugin.library.node.editor?ltype=%s&type=editGroup&actionPath=" % self.ltype + self.PATH + "&value=" + rule[ 1 ] + "&content=" + content hasGroup = True elif rule[ 0 ] == "limit": # 1 = limit listitem = xbmcgui.ListItem( label="%s: %s" % ( LANGUAGE(30203), rule[ 1 ] ) ) commands.append( ( LANGUAGE(30100), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=deletenode&actionPath=" % self.ltype + self.PATH + "&node=limit)" ) ) action = "plugin://plugin.library.node.editor?ltype=%s&type=editLimit&actionPath=" % self.ltype + self.PATH + "&value=" + rule[ 1 ] hasLimit = True elif rule[ 0 ] == "path": # 1 = path # Split the path into components splitPath = self.ATTRIB.splitPath( rule[ 1 ] ) # Add each element of the path to the list for x, component in enumerate( splitPath ): if x == 0: # library://path/ listitem = xbmcgui.ListItem( label="%s: %s" % ( LANGUAGE(30204), self.ATTRIB.translatePath( component ) ) ) commands.append( ( LANGUAGE(30100), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=deletenode&actionPath=" % self.ltype + self.PATH + "&node=path)" ) ) action = "plugin://plugin.library.node.editor?ltype=%s&type=addPath&actionPath=" % self.ltype + self.PATH # Get the rules rules = self.PATHRULE.getRulesForPath( splitPath[ 0 ] ) if x != 0: # Specific component # Add the listitem generated from the last component we processed listitem.addContextMenuItems( commands, replaceItems = True ) xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=True ) commands = [] # Get the rule for this component componentRule = self.PATHRULE.getMatchingRule( component, rules ) translatedComponent = self.PATHRULE.translateComponent( componentRule, splitPath[ x ] ) translatedValue = self.PATHRULE.translateValue( componentRule, splitPath, x ) listitem = xbmcgui.ListItem( label="%s: %s" % ( translatedComponent, translatedValue ) ) commands.append( ( LANGUAGE(30100), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=deletePathRule&actionPath=%s&rule=%d)" %( self.ltype, self.PATH, x ) ) ) action = "plugin://plugin.library.node.editor?ltype=%s&type=pathRule&actionPath=%s&rule=%d" % ( self.ltype, self.PATH, x ) hasPath = True elif rule[ 0 ] == "rule": # 1 = field # 2 = operator # 3 = value (optional) # 4 = ruleNum if len(rule) == 3: translated = self.RULE.translateRule( [ rule[ 1 ], rule[ 2 ] ] ) else: translated = self.RULE.translateRule( [ rule[ 1 ], rule[ 2 ], rule[ 3 ] ] ) if translated[ 2 ][ 0 ] == "|NONE|": listitem = xbmcgui.ListItem( label="%s: %s %s" % ( LANGUAGE(30205), translated[ 0 ][ 0 ], translated[ 1 ][ 0 ] ) ) else: listitem = xbmcgui.ListItem( label="%s: %s %s %s" % ( LANGUAGE(30205), translated[ 0 ][ 0 ], translated[ 1 ][ 0 ], translated[ 2 ][ 1 ] ) ) commands.append( ( LANGUAGE(30100), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=deleteRule&actionPath=" % self.ltype + self.PATH + "&rule=" + str( rule[ 4 ] ) + ")" ) ) action = "plugin://plugin.library.node.editor?ltype=%s&type=rule&actionPath=" % self.ltype + self.PATH + "&rule=" + str( rule[ 4 ] ) rulecount += 1 elif rule[ 0 ] == "match": # 1 = value listitem = xbmcgui.ListItem( label="%s: %s" % ( LANGUAGE(30206), self.ATTRIB.translateMatch( rule[ 1 ] ) ) ) action = "plugin://plugin.library.node.editor?ltype=%s&type=editRulesMatch&actionPath=%s" %( self.ltype, self.PATH ) hasGroup = True listitem.addContextMenuItems( commands, replaceItems = True ) if rule[ 0 ] == "rule" or rule[ 0 ] == "order" or rule[ 0 ] == "path": xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=True ) else: xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False ) if not hasContent and not hasPath: # Add content xbmcplugin.addDirectoryItem( int( sys.argv[ 1 ] ), "plugin://plugin.library.node.editor?ltype=%s&type=editContent&actionPath=" % self.ltype + self.PATH, xbmcgui.ListItem( label="* %s" %( LANGUAGE(30000) ) ) ) if not hasOrder and hasContent: # Add order xbmcplugin.addDirectoryItem( int( sys.argv[ 1 ] ), "plugin://plugin.library.node.editor?ltype=%s&type=orderby&actionPath=" % self.ltype + self.PATH, xbmcgui.ListItem( label="* %s" %( LANGUAGE(30002) ) ), isFolder=True ) if not hasGroup and hasContent: # Add group xbmcplugin.addDirectoryItem( int( sys.argv[ 1 ] ), "plugin://plugin.library.node.editor?ltype=%s&type=editGroup&actionPath=" % self.ltype + self.PATH + "&content=" + content, xbmcgui.ListItem( label="* %s" %( LANGUAGE(30004) ) ) ) if not hasLimit and hasContent: # Add limit xbmcplugin.addDirectoryItem( int( sys.argv[ 1 ] ), "plugin://plugin.library.node.editor?ltype=%s&type=editLimit&actionPath=" % self.ltype + self.PATH + "&value=25", xbmcgui.ListItem( label="* %s" %( LANGUAGE(30003) ) ) ) if not hasPath and not hasContent: # Add path xbmcplugin.addDirectoryItem( int( sys.argv[ 1 ] ), "plugin://plugin.library.node.editor?ltype=%s&type=addPath&actionPath=" % self.ltype + self.PATH, xbmcgui.ListItem( label="* %s" %( LANGUAGE(30001) ) ) ) if hasContent: # Add rule xbmcplugin.addDirectoryItem( int( sys.argv[ 1 ] ), "plugin://plugin.library.node.editor?ltype=%s&type=rule&actionPath=" % self.ltype + self.PATH + "&rule=" + str( nextRule ), xbmcgui.ListItem( label="* %s" %( LANGUAGE(30005) ) ), isFolder = True ) if hasPath: if "plugin://" not in splitPath[0][0]: # Add component xbmcplugin.addDirectoryItem( int( sys.argv[ 1 ] ), "plugin://plugin.library.node.editor?ltype=%s&type=pathRule&actionPath=%s&rule=%d" % ( self.ltype, self.PATH, x + 1 ), xbmcgui.ListItem( label="* %s" %( LANGUAGE(30009) ) ), isFolder = True ) # Manually edit path xbmcplugin.addDirectoryItem( int( sys.argv[ 1 ] ), "plugin://plugin.library.node.editor?ltype=%s&type=editPath&actionPath=" % self.ltype + self.PATH + "&value=" + quote( rule[ 1 ] ), xbmcgui.ListItem( label="* %s" %( LANGUAGE(30010) ) ), isFolder = True ) xbmcplugin.setContent(int(sys.argv[1]), 'files') xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) def _parse_argv( self ): try: p = parse_qs(sys.argv[2][1:]) for i in p.keys(): p[i] = p[i][0] self.PARAMS = p except: p = parse_qs(sys.argv[1]) for i in p.keys(): p[i] = p[i][0] self.PARAMS = p if "path" in self.PARAMS: self.PATH = self.PARAMS[ "path" ] else: self.PATH = "" def getRules( self, actionPath, justRules = False ): returnVal = [] try: # Load the xml file tree = xmltree.parse( actionPath ) root = tree.getroot() if justRules == False: # Look for a 'content' content = root.find( "content" ) if content is not None: returnVal.append( ( "content", content.text ) ) # Look for an 'order' order = root.find( "order" ) if order is not None: if "direction" in order.attrib: returnVal.append( ( "order", order.text, order.attrib.get( "direction" ) ) ) else: returnVal.append( ( "order", order.text ) ) # Look for a 'group' group = root.find( "group" ) if group is not None: returnVal.append( ( "group", group.text ) ) # Look for a 'limit' limit = root.find( "limit" ) if limit is not None: returnVal.append( ( "limit", limit.text ) ) # Look for a 'path' path = root.find( "path" ) if path is not None: returnVal.append( ( "path", path.text ) ) # Save the current length of the returnVal - we'll insert Match here if there are two or more rules currentLen = len( returnVal ) # Look for any rules ruleNum = 0 if actionPath.endswith( "index.xml" ): # Load the rules from RULE module rules = self.RULE.getNodeRules( actionPath ) if rules is not None: for rule in rules: returnVal.append( ( "rule", rule[ 0 ], rule[ 1 ], rule[ 2 ], ruleNum ) ) ruleNum += 1 return returnVal, len( rules ) else: return returnVal, 0 else: rules = root.findall( "rule" ) # Process the rules if rules is not None: for rule in rules: value = rule.find( "value" ) if value is not None and value.text is not None: translated = self.RULE.translateRule( [ rule.attrib.get( "field" ), rule.attrib.get( "operator" ), value.text ] ) if not self.RULE.isNodeRule( translated, actionPath ): returnVal.append( ( "rule", rule.attrib.get( "field" ), rule.attrib.get( "operator" ), value.text, ruleNum ) ) else: translated = self.RULE.translateRule( [ rule.attrib.get( "field" ), rule.attrib.get( "operator" ), "" ] ) if not self.RULE.isNodeRule( translated, actionPath ): returnVal.append( ( "rule", rule.attrib.get( "field" ), rule.attrib.get( "operator" ), "", ruleNum ) ) ruleNum += 1 # Get any current match value if there are more than two rules # (if there's only one, the match value doesn't matter) if ruleNum >= 2: matchRules = "all" match = root.find( "match" ) if match is not None: matchRules = match.text returnVal.insert( currentLen, ( "match", matchRules ) ) return returnVal, len( rules ) return returnVal, 0 except: print_exc() def listNodes( self, targetDir, nodes ): dirs, files = xbmcvfs.listdir( targetDir ) for dir in dirs: self.parseNode( os.path.join( targetDir, dir ), nodes ) for file in files: self.parseItem( os.path.join( targetDir, file ), nodes ) def parseNode( self, node, nodes ): # If the folder we've been passed contains an index.xml, send that file to be processed if os.path.exists( os.path.join( node, "index.xml" ) ): # BETA2 ONLY CODE self.RULE.moveNodeRuleToAppdata( node, os.path.join( node, "index.xml" ) ) # /BETA2 ONLY CODE self.parseItem( os.path.join( node, "index.xml" ), nodes, True, node ) def parseItem( self, file, nodes, isFolder = False, origFolder = None ): if not isFolder and file.endswith( "index.xml" ): return try: # Load the xml file tree = xmltree.parse( file ) root = tree.getroot() # Get the item index if "order" in tree.getroot().attrib: index = tree.getroot().attrib.get( "order" ) origIndex = index while int( index ) in nodes: index = int( index ) index += 1 index = str( index ) else: self.indexCounter -= 1 index = str( self.indexCounter ) origIndex = "-" # Get label and icon label = root.find( "label" ).text icon = root.find( "icon" ) if icon is not None: icon = icon.text else: icon = "" # Add it to our list of nodes if isFolder: nodes[ int( index ) ] = [ label, icon, quote( origFolder ), "folder", origIndex ] else: nodes[ int( index ) ] = [ label, icon, file, "item", origIndex ] except: print_exc() def getViewElement( self, file, element, newvalue ): try: # Load the file tree = xmltree.parse( file ) root = tree.getroot() # Change the element node = root.find( element ) if node is not None: return node.text else: return "" except: print_exc() def changeViewElement( self, file, element, newvalue ): try: # Load the file tree = xmltree.parse( file ) root = tree.getroot() # If the element is content, we can only delete this if there are no # rules, limits, orders if element == "content": rule = root.find( "rule" ) order = root.find( "order" ) limit = root.find( "limit" ) if rule is not None or order is not None or limit is not None: xbmcgui.Dialog().ok( ADDONNAME, LANGUAGE( 30404 ) ) return # Find the element node = root.find( element ) if node is not None: # If we've been passed an empty value, delete the node if newvalue == "": root.remove( node ) else: node.text = newvalue else: # Add a new node if newvalue != "": xmltree.SubElement( root, element ).text = newvalue # Pretty print and save self.indent( root ) tree.write( file, encoding="UTF-8" ) except: print_exc() def getRootAttrib( self, file, attrib ): try: # Load the file tree = xmltree.parse( file ) root = tree.getroot() # Find the element if attrib in root.attrib: return root.attrib.get( attrib ) else: return "" except: print_exc() def changeRootAttrib( self, file, attrib, newvalue ): try: # Load the file tree = xmltree.parse( file ) root = tree.getroot() # If empty newvalue, delete the attribute if newvalue == "": if attrib in root.attrib: root.attrib.pop( attrib ) else: # Change or add the attribute root.set( attrib, newvalue ) # Pretty print and save self.indent( root ) tree.write( file, encoding="UTF-8" ) except: print_exc() def copyNode(self, dirs, files, target, origin): for file in files: success = xbmcvfs.copy( os.path.join( origin, file ), os.path.join( target, file ) ) for dir in dirs: nextDirs, nextFiles = xbmcvfs.listdir( os.path.join( origin, dir ) ) self.copyNode( nextDirs, nextFiles, os.path.join( target, dir ), os.path.join( origin, dir ) ) # in-place prettyprint formatter def indent( self, elem, level=0 ): i = "\n" + level*"\t" if len(elem): if not elem.text or not elem.text.strip(): elem.text = i + "\t" if not elem.tail or not elem.tail.strip(): elem.tail = i for elem in elem: self.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 # Slugify functions def smart_truncate(string, max_length=0, word_boundaries=False, separator=' '): string = string.strip(separator) if not max_length: return string if len(string) < max_length: return string if not word_boundaries: return string[:max_length].strip(separator) if separator not in string: return string[:max_length] truncated = '' for word in string.split(separator): if word: next_len = len(truncated) + len(word) + len(separator) if next_len <= max_length: truncated += '{0}{1}'.format(word, separator) if not truncated: truncated = string[:max_length] return truncated.strip(separator) def slugify(self, text, entities=True, decimal=True, hexadecimal=True, max_length=0, word_boundary=False, separator='-', convertInteger=False): # Handle integers if convertInteger and text.isdigit(): text = "NUM-" + text # decode unicode ( ??? = Ying Shi Ma) text = unidecode(text) # character entity reference if entities: text = CHAR_ENTITY_REXP.sub(lambda m: unichr(name2codepoint[m.group(1)]), text) # decimal character reference if decimal: try: text = DECIMAL_REXP.sub(lambda m: unichr(int(m.group(1))), text) except: pass # hexadecimal character reference if hexadecimal: try: text = HEX_REXP.sub(lambda m: unichr(int(m.group(1), 16)), text) except: pass # translate text = unicodedata.normalize('NFKD', text) if sys.version_info < (3,): text = text.encode('ascii', 'ignore') # replace unwanted characters text = REPLACE1_REXP.sub('', text.lower()) # replace ' with nothing instead with - text = REPLACE2_REXP.sub('-', text.lower()) # remove redundant - text = REMOVE_REXP.sub('-', text).strip('-') # smart truncate if requested if max_length > 0: text = smart_truncate(text, max_length, word_boundary, '-') if separator != '-': text = text.replace('-', separator) return text def getVideoLibraryLType(): json_query = xbmc.executeJSONRPC('{ "jsonrpc": "2.0", "id": 0, "method": "Settings.GetSettingValue", "params": {"setting": "myvideos.flatten"}}') json_response = json.loads(json_query) if json_response.get('result') and json_response['result'].get('value'): if json_response['result']['value']: return "video_flat" return "video" def run(args): log('script version %s started' % ADDONVERSION) ltype = '' if args[2] == '': videoltype = getVideoLibraryLType() xbmcplugin.addDirectoryItem( int( args[ 1 ] ), "plugin://plugin.library.node.editor?ltype=%s" %( videoltype ), xbmcgui.ListItem( label=LANGUAGE(30091) ), isFolder=True ) xbmcplugin.addDirectoryItem( int( args[ 1 ] ), "plugin://plugin.library.node.editor?ltype=music", xbmcgui.ListItem( label=LANGUAGE(30092) ), isFolder=True ) xbmcplugin.setContent(int(args[1]), 'files') xbmcplugin.endOfDirectory(handle=int(args[1])) else: params = dict( arg.split( "=" ) for arg in args[ 2 ][1:].split( "&" ) ) ltype = params['ltype'] if ltype != '': RULE = rules.RuleFunctions(ltype) ATTRIB = viewattrib.ViewAttribFunctions(ltype) PATHRULE = pathrules.PathRuleFunctions(ltype) PATHRULE.ATTRIB = ATTRIB ORDERBY = orderby.OrderByFunctions(ltype) Main(params, ltype, RULE, ATTRIB, PATHRULE, ORDERBY) log('script stopped')