Files
DevOps/Kodi/Lenovo/addons/plugin.library.node.editor/resources/lib/rules.py

1129 lines
54 KiB
Python

# coding=utf-8
import os, sys, shutil
import xbmc, xbmcaddon, xbmcplugin, xbmcgui, xbmcvfs
import xml.etree.ElementTree as xmltree
import json
from traceback import print_exc
from urllib.parse import quote, unquote
from resources.lib.common import *
class RuleFunctions():
def __init__(self, ltype):
self.nodeRules = None
self.ltype = ltype
def _load_rules( self ):
if self.ltype.startswith('video'):
overridepath = os.path.join( DEFAULTPATH , "videorules.xml" )
else:
overridepath = os.path.join( DEFAULTPATH , "musicrules.xml" )
try:
tree = xmltree.parse( overridepath )
return tree
except:
return None
def translateRule( self, rule ):
# Load the rules
tree = self._load_rules()
hasValue = True
elems = tree.getroot().find( "matches" ).findall( "match" )
for elem in elems:
if elem.attrib.get( "name" ) == rule[ 0 ]:
match = xbmc.getLocalizedString( int( elem.find( "label" ).text ) )
group = elem.find( "operator" ).text
elems = tree.getroot().find( "operators" ).findall( "group" )
operator = None
defaultOperator = None
defaultOperatorValue = None
for elem in elems:
if elem.attrib.get( "name" ) == group:
for operators in elem.findall( "operator" ):
if operators.text == rule[ 1 ]:
operator = xbmc.getLocalizedString( int( operators.attrib.get( "label" ) ) )
if defaultOperator is None:
defaultOperator = xbmc.getLocalizedString( int( operators.attrib.get( "label" ) ) )
defaultOperatorValue = operators.text
if "option" in elem.attrib:
hasValue = False
# If we didn't match an operator, set it to the default
if operator is None:
operator = defaultOperator
rule[ 1 ] = defaultOperatorValue
if hasValue == False:
return [ [ match, rule[ 0 ] ], [ operator, group, rule[ 1 ] ], [ "|NONE|", "<No value>" ] ]
if len( rule ) == 2 or rule[ 2 ] == "" or rule[ 2 ] is None:
return [ [ match, rule[ 0 ] ], [ operator, group, rule[ 1 ] ], [ "", "<No value>" ] ]
return [ [ match, rule[ 0 ] ], [ operator, group, rule[ 1 ] ], [ rule[ 2 ], rule[ 2 ] ] ]
def displayRule( self, actionPath, path, ruleNum ):
if actionPath.endswith( "index.xml" ):
# If this is a parent node, call alternative function
self.displayNodeRule( actionPath, ruleNum )
return
try:
# Load the xml file
tree = xmltree.parse( unquote(actionPath) )
root = tree.getroot()
actionPath = actionPath
# Get the content type
content = root.find( "content" )
if content is not None:
content = content.text
else:
content = "NONE"
# Get all the rules
ruleCount = 0
rules = root.findall( "rule" )
if len( rules ) == int( ruleNum ):
# This rule doesn't exist - create it
self.newRule( tree, unquote( actionPath ) )
rules = root.findall( "rule" )
if rules is not None:
for rule in rules:
if str( ruleCount ) == ruleNum:
value = rule.find( "value" )
if value is None:
value = ""
else:
value = value.text
translated = self.translateRule( [ rule.attrib.get( "field" ), rule.attrib.get( "operator" ), value ] )
# Rule to change match
listitem = xbmcgui.ListItem( label="%s" % ( translated[ 0 ][ 0 ] ) )
action = "plugin://plugin.library.node.editor?ltype=%s&type=editMatch&actionPath=" % self.ltype + actionPath + "&content=" + content + "&default=" + translated[ 0 ][ 1 ] + "&rule=" + str( ruleCount )
xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
listitem = xbmcgui.ListItem( label="%s" % ( translated[ 1 ][ 0 ] ) )
action = "plugin://plugin.library.node.editor?ltype=%s&type=editOperator&actionPath=" % self.ltype + actionPath + "&group=" + translated[ 1 ][ 1 ] + "&default=" + translated[ 1 ][ 2 ] + "&rule=" + str( ruleCount )
xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
if not ( translated[ 2 ][ 0 ] ) == "|NONE|":
listitem = xbmcgui.ListItem( label="%s" % ( translated[ 2 ][ 1 ] ) )
action = "plugin://plugin.library.node.editor?ltype=%s&type=editValue&actionPath=" % self.ltype + actionPath + "&rule=" + str( ruleCount )
xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
# Check if this match type can be browsed
if self.canBrowse( translated[ 0 ][ 1 ], content ):
#listitem.addContextMenuItems( [(LANGUAGE(30107), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=browseValue&actionPath=" % ltype + actionPath + "&rule=" + str( ruleCount ) + "&match=" + translated[ 0 ][ 1 ] + "&content=" + content + ")" )], replaceItems = True )
listitem = xbmcgui.ListItem( label=LANGUAGE(30107) )
action = "plugin://plugin.library.node.editor?ltype=%s&type=browseValue&actionPath=" % self.ltype + actionPath + "&rule=" + str( ruleCount ) + "&match=" + translated[ 0 ][ 1 ] + "&content=" + content
xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
#self.browse( translated[ 0 ][ 1 ], content )
xbmcplugin.setContent(int(sys.argv[1]), 'files')
xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
return
ruleCount = ruleCount + 1
except:
print_exc()
def editMatch( self, actionPath, ruleNum, content, default ):
# Load all operator groups
tree = self._load_rules().getroot()
elems = tree.find( "matches" ).findall( "match" )
selectName = []
selectValue = []
# Find the matches for the content we've been passed
for elem in elems:
if content != "NONE":
contentMatch = elem.find( content )
if contentMatch is not None:
selectName.append( xbmc.getLocalizedString( int( elem.find( "label" ).text ) ) )
selectValue.append( elem.attrib.get( "name" ) )
else:
pass
else:
selectName.append( xbmc.getLocalizedString( int( elem.find( "label" ).text ) ) )
selectValue.append( elem.attrib.get( "name" ) )
# Let the user select an operator
selectedOperator = xbmcgui.Dialog().select( LANGUAGE( 30305 ), selectName )
# If the user selected no operator...
if selectedOperator == -1:
return
self.writeUpdatedRule( actionPath, ruleNum, match = selectValue[ selectedOperator ] )
def editOperator( self, actionPath, ruleNum, group, default ):
# Load all operator groups
tree = self._load_rules().getroot()
elems = tree.find( "operators" ).findall( "group" )
selectName = []
selectValue = []
# Find the group we've been passed and load its operators
for elem in elems:
if elem.attrib.get( "name" ) == group:
for operators in elem.findall( "operator" ):
selectName.append( xbmc.getLocalizedString( int( operators.attrib.get( "label" ) ) ) )
selectValue.append( operators.text )
# Let the user select an operator
selectedOperator = xbmcgui.Dialog().select( LANGUAGE( 30306 ), selectName )
# If the user selected no operator...
if selectedOperator == -1:
return
self.writeUpdatedRule( actionPath, ruleNum, operator = selectValue[ selectedOperator ] )
def editValue( self, actionPath, ruleNum ):
# This function is the entry point for editing the value of a rule
# Because we can't always pass the current value through the uri, we first need
# to retrieve it, and the operator data type
actionPath = unquote(actionPath)
try:
if actionPath.endswith( "index.xml" ):
( filePath, fileName ) = os.path.split(unquote(actionPath))
# Load the rules file
if self.ltype.startswith('video'):
tree = xmltree.parse( os.path.join( DATAPATH, "videorules.xml" ) )
else:
tree = xmltree.parse( os.path.join( DATAPATH, "musicrules.xml" ) )
root = tree.getroot()
nodes = root.findall( "node" )
for node in nodes:
if node.attrib.get( "name" ) == filePath:
rules = node.findall( "rule" )
ruleCount = 0
for rule in rules:
if ruleCount == int( ruleNum ):
# This is the rule we'll be updating
# Get the current value
curValue = rule.find( "value" )
if curValue is None:
curValue = ""
else:
curValue = curValue.text
match = rule.attrib.get( "field" )
operator = rule.attrib.get( "operator" )
ruleCount += 1
else:
# Load the xml file
tree = xmltree.parse( unquote(actionPath) )
root = tree.getroot()
# Get all the rules
ruleCount = 0
rules = root.findall( "rule" )
if rules is not None:
for rule in rules:
if str( ruleCount ) == ruleNum:
# This is the rule we'll be updating
# Get the current value
curValue = rule.find( "value" )
if curValue is None:
curValue = ""
else:
curValue = curValue.text
match = rule.attrib.get( "field" )
operator = rule.attrib.get( "operator" )
ruleCount = ruleCount + 1
# Now, use the match value to get the group of operators this
# comes from (this will tell us the data type in all types
# but "date")
tree = self._load_rules().getroot()
elems = tree.find( "matches" ).findall( "match" )
for elem in elems:
if elem.attrib.get( "name" ) == match:
group = elem.find( "operator" ).text
if group == "date":
# We probably should go through the tree again, but we'll just check
# for string ending in "inthelast", and switch the type to numeric
if operator.endswith( "inthelast" ):
group = "numeric"
# Set the type of text entry dialog to be used
if group == "string":
type = xbmcgui.INPUT_ALPHANUM
if group == "numeric":
type = xbmcgui.INPUT_NUMERIC
if group == "time":
type = xbmcgui.INPUT_TIME
if group == "date":
type = xbmcgui.INPUT_DATE
if group == "isornot":
type = xbmcgui.INPUT_ALPHANUM
if group == "seconds":
type = xbmcgui.INPUT_TIME
# album duration is in HH:MM:SS but we can't call that input dialog from python so
# find and strip any seconds that may be in an existing node. Use the HH:MM dialog
# and add the seconds field back on before we write the node
secPos = -1
if curValue is not None:
secPos = curValue.rfind(":00")
if secPos == -1:
secPos = len( curValue)
if ((curValue is not None) and (secPos >= 4)):
curValue = curValue[0:secPos]
if len( curValue ) < 5:
curValue = " " + curValue
returnVal = xbmcgui.Dialog().input( LANGUAGE( 30307 ), curValue, type=type )
if ( group == "seconds" and returnVal !="" ):
returnVal += ":00"# Add the seconds if we previously removed them
if returnVal != "":
self.writeUpdatedRule( unquote(actionPath), ruleNum, value=returnVal )
except:
print_exc()
def writeUpdatedRule( self, actionPath, ruleNum, match = None, operator = None, value = None ):
# This function writes an updated match, operator or value to a rule
if actionPath.endswith( "index.xml" ):
# This is a parent node rule, so call the relevant function
#( filePath, fileName ) = os.path.split( actionPath )
#self.editNodeRule( filePath, originalRule, translated )
self.editNodeRule( unquote(actionPath), ruleNum, match, operator, value )
return
try:
# Load the xml file
tree = xmltree.parse( unquote(actionPath) )
root = tree.getroot()
# Get all the rules
ruleCount = 0
rules = root.findall( "rule" )
if rules is not None:
for rule in rules:
if str( ruleCount ) == ruleNum:
# This is the rule we're updating
valueElem = rule.find( "value" )
if match is None:
match = rule.attrib.get( "field" )
if operator is None:
operator = rule.attrib.get( "operator" )
if value is None:
if valueElem is None:
value = ""
else:
value = valueElem.text
if value is None:
value = ""
translated = self.translateRule( [ match, operator, value ] )
# Update the rule
rule.set( "field", translated[ 0 ][ 1 ] )
rule.set( "operator", translated[ 1 ][ 2 ] )
if len( translated ) == 3:
if rule.find( "value" ) == None:
# Create a new rule node
xmltree.SubElement( rule, "value" ).text = translated[ 2 ][ 0 ]
else:
rule.find( "value" ).text = translated[ 2 ][ 0 ]
ruleCount = ruleCount + 1
# Save the file
self.indent( root )
tree.write( unquote(actionPath), encoding="UTF-8" )
except:
print_exc()
def newRule( self, tree, actionPath ):
# This function adds a new rule, with default match and operator, no value
try:
# Load the xml file
# tree = xmltree.parse( actionPath )
root = tree.getroot()
# Get the content type
content = root.find( "content" )
# Find the default match for this content type
ruleTree = self._load_rules().getroot()
elems = ruleTree.find( "matches" ).findall( "match" )
match = "title"
for elem in elems:
if content is not None:
contentCheck = elem.find( content.text )
if contentCheck is not None:
# We've found the first match for this type
match = elem.attrib.get( "name" )
operator = elem.find( "operator" ).text
break
else:
# We've found the first match for this type
match = elem.attrib.get( "name" )
operator = elem.find( "operator" ).text
break
# Find the default operator for this match
elems = ruleTree.find( "operators" ).findall( "group" )
for elem in elems:
if elem.attrib.get( "name" ) == operator:
operator = elem.find( "operator" ).text
break
# Write the new rule
newRule = xmltree.SubElement( root, "rule" )
newRule.set( "field", match )
newRule.set( "operator", operator )
xmltree.SubElement( newRule, "value" )
# Save the file
self.indent( root )
tree.write( unquote(actionPath), encoding="UTF-8" )
if actionPath.endswith( "index.xml" ):
( filePath, fileName ) = os.path.split( actionPath )
newRule = self.translateRule( [ match, operator, "" ] )
self.addNodeRule( filePath, newRule )
except:
print_exc()
def deleteRule( self, actionPath, ruleNum ):
# This function deletes a rule
result = xbmcgui.Dialog().yesno(ADDONNAME, LANGUAGE( 30405 ) )
if not result:
return
if actionPath.endswith( "index.xml" ):
# This is a parent node rule, so call the relevant function
#( filePath, fileName ) = os.path.split( actionPath )
#self.deleteNodeRule( filePath, originalRule )
self.deleteNodeRule( unquote(actionPath), ruleNum )
try:
# Load the xml file
tree = xmltree.parse( unquote(actionPath) )
root = tree.getroot()
# Get all the rules
ruleCount = 0
rules = root.findall( "rule" )
if rules is not None:
for rule in rules:
if str( ruleCount ) == ruleNum:
# This is the rule we want to delete
if actionPath.endswith( "index.xml" ):
# Translate the rule, so we can delete it from the views
valueElem = rule.find( "value" )
origMatch = rule.attrib.get( "field" )
origOperator = rule.attrib.get( "operator" )
if valueElem is None:
origValue = ""
else:
origValue = valueElem.text
originalRule = self.translateRule( [ origMatch, origOperator, origValue ] )
# Delete the rule
root.remove( rule )
break
ruleCount = ruleCount + 1
# Save the file
self.indent( root )
tree.write( unquote(actionPath), encoding="UTF-8" )
except:
print_exc()
# Functions for managing rules in all views
def displayNodeRule( self, actionPath, ruleNum ):
# This function will load and display a parent node rule
# (and create one, if the ruleNum specified doesn't exist)
# Split the actionPath, to make things easier
( filePath, fileName ) = os.path.split( unquote(actionPath) )
try:
# Load the rules file
if self.ltype.startswith('video'):
tree = xmltree.parse( os.path.join( DATAPATH, "videorules.xml" ) )
else:
tree = xmltree.parse( os.path.join( DATAPATH, "musicrules.xml" ) )
root = tree.getroot()
# Find the relevant node
nodes = root.findall( "node" )
if nodes is None:
self.newNodeRule( actionPath, ruleNum )
return
ruleNode = None
for node in nodes:
if node.attrib.get( "name" ) == filePath:
ruleNode = node
break
if ruleNode == None:
self.newNodeRule( actionPath, ruleNum )
return
# Find the relevant rule
rules = node.findall( "rule" )
if rules is None or len( rules ) == int( ruleNum ):
self.newNodeRule( actionPath, ruleNum )
return
ruleCount = 0
for rule in rules:
if ruleCount == int( ruleNum ):
value = rule.find( "value" )
if value is None:
value = ""
else:
value = value.text
translated = self.translateRule( [ rule.attrib.get( "field" ), rule.attrib.get( "operator" ), value ] )
actionPath = quote( actionPath )
# Rule to change match
listitem = xbmcgui.ListItem( label="%s" % ( translated[ 0 ][ 0 ] ) )
action = "plugin://plugin.library.node.editor?ltype=%s&type=editMatch&actionPath=" % self.ltype + actionPath + "&default=" + translated[ 0 ][ 1 ] + "&rule=" + str( ruleCount ) + "&content=NONE"
xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
listitem = xbmcgui.ListItem( label="%s" % ( translated[ 1 ][ 0 ] ) )
action = "plugin://plugin.library.node.editor?ltype=%s&type=editOperator&actionPath=" % self.ltype + actionPath + "&group=" + translated[ 1 ][ 1 ] + "&default=" + translated[ 1 ][ 2 ] + "&rule=" + str( ruleCount )
xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
if not ( translated[ 2 ][ 0 ] ) == "|NONE|":
listitem = xbmcgui.ListItem( label="%s" % ( translated[ 2 ][ 1 ] ) )
action = "plugin://plugin.library.node.editor?ltype=%s&type=editValue&actionPath=" % self.ltype + actionPath + "&rule=" + str( ruleCount )
xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
# Check if this match type can be browsed
if self.canBrowse( translated[ 0 ][ 1 ] ):
#listitem.addContextMenuItems( [(LANGUAGE(30107), "RunPlugin(plugin://plugin.library.node.editor?ltype=%s&type=browseValue&actionPath=" % ltype + actionPath + "&rule=" + str( ruleCount ) + "&match=" + translated[ 0 ][ 1 ] + "&content=" + content + ")" )], replaceItems = True )
listitem = xbmcgui.ListItem( label=LANGUAGE(30107) )
action = "plugin://plugin.library.node.editor?ltype=%s&type=browseValue&actionPath=" % self.ltype + actionPath + "&rule=" + str( ruleCount ) + "&match=" + translated[ 0 ][ 1 ] + "&content=NONE"
xbmcplugin.addDirectoryItem( int(sys.argv[ 1 ]), action, listitem, isFolder=False )
#self.browse( translated[ 0 ][ 1 ], content )
xbmcplugin.setContent(int(sys.argv[1]), 'files')
xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
return
ruleCount += 1
except:
print_exc()
self.newNodeRule( actionPath, ruleNum )
return
def newNodeRule( self, actionPath, ruleNum ):
# This function creates a new node rule, then re-calls the displayNodeRule function
# Split the actionPath, to make things easier
( filePath, fileName ) = os.path.split( unquote(actionPath) )
# Open the rules file if it exists, else create it
if self.ltype.startswith('video'):
rulesfile = 'videorules.xml'
else:
rulesfile = 'musicrules.xml'
if os.path.exists( os.path.join( DATAPATH, rulesfile ) ):
tree = xmltree.parse( os.path.join( DATAPATH, rulesfile ) )
root = tree.getroot()
else:
tree = xmltree.ElementTree( xmltree.Element( "rules" ) )
root = tree.getroot()
# See if we already have a element for the node we're parsing
nodes = root.findall( "node" )
ruleNode = None
if nodes is not None:
for node in nodes:
if node.attrib.get( "name" ) == filePath:
ruleNode = node
break
if ruleNode is None:
# We couldn't find an existing element for the node we're parsing - so create one
ruleNode = xmltree.SubElement( root, "node" )
ruleNode.set( "name", filePath )
# Create a new rule
ruleTree = self._load_rules().getroot()
elems = ruleTree.find( "matches" ).findall( "match" )
match = "title"
for elem in elems:
# We've found the first match for this type
match = elem.attrib.get( "name" )
operator = elem.find( "operator" ).text
break
# Find the default operator for this match
elems = ruleTree.find( "operators" ).findall( "group" )
for elem in elems:
if elem.attrib.get( "name" ) == operator:
operator = elem.find( "operator" ).text
break
# Write the new rule
newRule = xmltree.SubElement( ruleNode, "rule" )
newRule.set( "field", match )
newRule.set( "operator", operator )
xmltree.SubElement( newRule, "value" )
# Save the file
self.indent( root )
tree.write( os.path.join( DATAPATH, rulesfile ), encoding="UTF-8" )
# Now add the rule to all views within the node
dirs, files = xbmcvfs.listdir( filePath )
for file in files:
if file == "index.xml":
continue
elif file.endswith( ".xml" ):
filename = os.path.join( filePath, file )
try:
# Load the xml file
tree = xmltree.parse( filename )
root = tree.getroot()
rule = xmltree.SubElement( root, "rule" )
rule.set( "field", match )
rule.set( "operator", operator )
xmltree.SubElement( rule, "value" )
# Save the file
self.indent( root )
tree.write( filename, encoding="UTF-8" )
except:
print_exc()
# Re-call the displayNodeRule function
self.displayNodeRule( actionPath, ruleNum )
#def editNodeRule( self, actionPath, originalRule, newRule ):
def editNodeRule( self, actionPath, ruleNum, match, operator, value ):
( filePath, fileName ) = os.path.split( unquote(actionPath) )
# Update the rule in the rules file
if self.ltype.startswith('video'):
rulesfile = 'videorules.xml'
else:
rulesfile = 'musicrules.xml'
try:
tree = xmltree.parse( os.path.join( DATAPATH, rulesfile ) )
root = tree.getroot()
nodes = root.findall( "node" )
for node in nodes:
if node.attrib.get( "name" ) == filePath:
ruleCount = 0
rules = node.findall( "rule" )
for rule in rules:
if ruleCount == int( ruleNum ):
# This is the rule we want to update
valueElem = rule.find( "value" )
if match is None:
match = rule.attrib.get( "field" )
if operator is None:
operator = rule.attrib.get( "operator" )
if value is None:
if valueElem is None:
value = ""
else:
value = valueElem.text
if value is None:
value = ""
newRule = self.translateRule( [ match, operator, value ] )
# Get the original rule
origMatch = rule.attrib.get( "field" )
origOperator = rule.attrib.get( "operator" )
if valueElem is None:
origValue = ""
else:
origValue = valueElem.text
originalRule = self.translateRule( [ origMatch, origOperator, origValue ] )
# Update the rule
rule.set( "field", newRule[ 0 ][ 1 ] )
rule.set( "operator", newRule[ 1 ][ 2 ] )
if len( newRule ) == 3:
if rule.find( "value" ) == None:
# Create a new rule node
xmltree.SubElement( rule, "value" ).text = newRule[ 2 ][ 0 ]
else:
rule.find( "value" ).text = newRule[ 2 ][ 0 ]
ruleCount += 1
# Save the file
self.indent( root )
tree.write( os.path.join( DATAPATH, rulesfile ), encoding="UTF-8" )
except:
print_exc()
return
# Now update the views with the new rule
dirs, files = xbmcvfs.listdir( filePath )
for file in files:
if file == "index.xml":
continue
elif file.endswith( ".xml" ):
filename = os.path.join( filePath, file )
# List the rules
try:
# Load the xml file
tree = xmltree.parse( filename )
root = tree.getroot()
# Look for any rules
rules = root.findall( "rule" )
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.translateRule( [ rule.attrib.get( "field" ), rule.attrib.get( "operator" ), value.text ] )
else:
translated = self.translateRule( [ rule.attrib.get( "field" ), rule.attrib.get( "operator" ), "" ] )
if originalRule[ 0 ][ 1 ] == translated[ 0 ][ 1 ] and originalRule[ 1 ][ 2 ] == translated[ 1 ][ 2 ] and originalRule[ 2 ][ 0 ] == translated[ 2 ][ 0 ]:
# This is the right rule, update it
rule.set( "field", newRule[ 0 ][ 1 ] )
rule.set( "operator", newRule[ 1 ][ 2 ] )
if value is not None:
value.text = newRule[ 2 ][ 0 ]
else:
xmltree.SubElement( rule, "value" ).text = newRule[ 2 ][ 0 ]
break
# Save the file
self.indent( root )
tree.write( filename, encoding="UTF-8" )
except:
print_exc()
#def deleteNodeRule( self, actionPath, originalRule ):
def deleteNodeRule( self, actionPath, ruleNum ):
( filePath, fileName ) = os.path.split( actionPath )
# Delete the rule from the rules file
if self.ltype.startswith('video'):
rulesfile = 'videorules.xml'
else:
rulesfile = 'musicrules.xml'
try:
tree = xmltree.parse( os.path.join( DATAPATH, rulesfile ) )
root = tree.getroot()
nodes = root.findall( "node" )
for node in nodes:
if node.attrib.get( "name" ) == filePath:
ruleCount = 0
rules = node.findall( "rule" )
for rule in rules:
if ruleCount == int( ruleNum ):
# This is the rule we want to delete
# Translate the rule, so we can delete it from the views
valueElem = rule.find( "value" )
origMatch = rule.attrib.get( "field" )
origOperator = rule.attrib.get( "operator" )
if valueElem is None:
origValue = ""
else:
origValue = valueElem.text
originalRule = self.translateRule( [ origMatch, origOperator, origValue ] )
node.remove( rule )
ruleCount += 1
# Save the file
self.indent( root )
tree.write( os.path.join( DATAPATH, rulesfile ), encoding="UTF-8" )
except:
print_exc()
return
# Now delete the rule from all the views
dirs, files = xbmcvfs.listdir( filePath )
for file in files:
if file == "index.xml":
continue
elif file.endswith( ".xml" ):
filename = os.path.join( filePath, file )
# List the rules
try:
# Load the xml file
tree = xmltree.parse( filename )
root = tree.getroot()
# Look for any rules
rules = root.findall( "rule" )
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.translateRule( [ rule.attrib.get( "field" ), rule.attrib.get( "operator" ), value.text ] )
else:
translated = self.translateRule( [ rule.attrib.get( "field" ), rule.attrib.get( "operator" ), "" ] )
if originalRule[ 0 ][ 1 ] == translated[ 0 ][ 1 ] and originalRule[ 1 ][ 2 ] == translated[ 1 ][ 2 ] and originalRule[ 2 ][ 0 ] == translated[ 2 ][ 0 ]:
# This is the right rule, delete it
root.remove( rule )
break
# Save the file
self.indent( root )
tree.write( filename, encoding="UTF-8" )
except:
print_exc()
def deleteAllNodeRules( self, actionPath ):
if self.ltype.startswith('video'):
rulesfile = 'videorules.xml'
else:
rulesfile = 'musicrules.xml'
try:
# Remove all rules for this parent node from the rules file
tree = xmltree.parse( os.path.join( DATAPATH, rulesfile ) )
root = tree.getroot()
nodes = root.findall( "node" )
for node in nodes:
if node.attrib.get( "name" ) == actionPath:
root.remove( node )
# Write the updated file
self.indent( root )
tree.write( os.path.join( DATAPATH, rulesfile ), encoding="UTF-8" )
except:
print_exc()
def isNodeRule( self, viewRule, actionPath ):
if actionPath.endswith( "index.xml" ):
return False
if self.nodeRules is None:
self.nodeRules = []
# Load all the node rules for current directory
( filePath, fileName ) = os.path.split( actionPath )
self.loadNodeRules( filePath )
# If there are no node rules, return False
if len( self.nodeRules ) == 0:
return False
# Compare the passed in rule with those in self.nodeRules
count = 0
for nodeRule in self.nodeRules:
if nodeRule[0] == viewRule[0][1] and nodeRule[1] == viewRule[1][2] and nodeRule[2] == viewRule[2][0]:
# Rule matches
self.nodeRules.pop( count )
return True
count += 1
return False
def addAllNodeRules( self, actionPath, root ):
if self.nodeRules is None:
self.loadNodeRules( actionPath )
if len( self.nodeRules ) == 0:
return
for nodeRule in self.nodeRules:
rule = xmltree.SubElement( root, "rule" )
rule.set( "field", nodeRule[ 0 ] )
rule.set( "operator", nodeRule[ 1 ] )
xmltree.SubElement( rule, "value" ).text = nodeRule[ 2 ]
def getNodeRules( self, actionPath ):
( filePath, fileName ) = os.path.split( actionPath )
if self.nodeRules is None:
self.loadNodeRules( filePath )
if len( self.nodeRules ) == 0:
return None
else:
return self.nodeRules
def loadNodeRules( self, actionPath ):
self.nodeRules = []
# Load all the node rules for current directory
#actionPath = os.path.join( actionPath, "index.xml" )
if self.ltype.startswith('video'):
filename = os.path.join( DATAPATH, "videorules.xml" )
else:
filename = os.path.join( DATAPATH, "musicrules.xml" )
if os.path.exists( filename ):
try:
# Load the xml file
tree = xmltree.parse( filename )
root = tree.getroot()
# Find the node element for this path
nodes = root.findall( "node" )
if nodes is None:
return
ruleNode = None
for node in nodes:
if node.attrib.get( "name" ) == actionPath:
ruleNode = node
break
if ruleNode is None:
# There don't appear to be any rules
return
# Look for any rules
rules = ruleNode.findall( "rule" )
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.translateRule( [ rule.attrib.get( "field" ), rule.attrib.get( "operator" ), value.text ] )
else:
translated = self.translateRule( [ rule.attrib.get( "field" ), rule.attrib.get( "operator" ), "" ] )
# Save the rule
self.nodeRules.append( [ translated[0][1], translated[1][2], translated[2][0] ] )
except:
print_exc()
def moveNodeRuleToAppdata( self, path, actionPath ):
#BETA2 ONLY CODE
# This function will move any parent node rules out of the index.xml, and into the rules file in the plugins appdata folder
# Open the rules file if it exists, else create it
if self.ltype.startswith('video'):
rulesfile = 'videorules.xml'
else:
rulesfile = 'musicrules.xml'
if os.path.exists( os.path.join( DATAPATH, rulesfile ) ):
ruleTree = xmltree.parse( os.path.join( DATAPATH, rulesfile ) )
ruleRoot = ruleTree.getroot()
else:
ruleTree = xmltree.ElementTree( xmltree.Element( "rules" ) )
ruleRoot = ruleTree.getroot()
# See if we already have a element for the node we're parsing
nodes = ruleRoot.findall( "node" )
ruleNode = None
if nodes is not None:
for node in nodes:
if node.attrib.get( "name" ) == path:
ruleNode = node
break
if ruleNode is None:
# We couldn't find an existing element for the node we're parsing - so create one
ruleNode = xmltree.SubElement( ruleRoot, "node" )
ruleNode.set( "name", path )
try:
tree = xmltree.parse( unquote(actionPath) )
root = tree.getroot()
rules = root.findall( "rule" )
if rules is not None:
for rule in rules:
# Create a new rule in the ruleTree
newRule = xmltree.SubElement( ruleNode, "rule" )
newRule.set( "field", rule.attrib.get( "field" ) )
newRule.set( "operator", rule.attrib.get( "operator" ) )
value = rule.find( "value" )
if value is not None:
xmltree.SubElement( newRule, "value" ).text = value.text
# Delete the rule from the tree
root.remove( rule )
# Write both files
self.indent( root )
tree.write( unquote(actionPath), encoding="UTF-8" )
self.indent( ruleRoot )
ruleTree.write( os.path.join( DATAPATH, rulesfile ), encoding="UTF-8" )
except:
print_exc()
#/BETA2 ONLY CODE
# Functions for browsing for value
def canBrowse( self, match, content = None ):
# Check whether the match type allows browsing
if content == "NONE":
content = None
# Load the rules
tree = self._load_rules()
elems = tree.getroot().find( "matches" ).findall( "match" )
for elem in elems:
if elem.attrib.get( "name" ) == match:
canBrowse = elem.find( "browse" )
if canBrowse is None:
# This match type is marked as non-browsable
return False
if content is None:
# If we haven't been passed a content type, allow to browse all
return True
canBrowse = elem.find( content )
if canBrowse is None:
# We can't browse for this content type
return False
# We can browse this content type
return True
return False
def browse( self, actionPath, ruleNum, match, content = None ):
# This function launches the browser for the given match and content type
if content is None or content == "" or content == "NONE":
if match != "path" and match != "playlist":
# No content parameter passed, so check what contents are valid for
# this type
tree = self._load_rules()
elems = tree.getroot().find( "matches" ).findall( "match" )
matches = {}
for elem in elems:
if elem.attrib.get( "name" ) == match:
if self.ltype.startswith('video'):
matches["movies"] = elem.find( "movies" )
matches["tvshows"] = elem.find( "tvshows" )
matches["episodes"] = elem.find( "episodes" )
matches["musicvideos"] = elem.find( "musicvideos" )
else:
matches["artists"] = elem.find( "artists" )
matches["albums"] = elem.find( "albums" )
matches["songs"] = elem.find( "songs" )
break
matchesList = []
matchesValue = []
# Generate a list of the available content types
elems = tree.getroot().find( "content" ).findall( "type" )
for elem in elems:
if matches[ elem.text ] is not None:
matchesList.append( xbmc.getLocalizedString( int( elem.attrib.get( "label" ) ) ) )
matchesValue.append( elem.text )
if len( matchesList ) == 0:
return
if len( matchesList ) == 1:
# Only one returned, no point offering a choice of content type
content = matchesValue[ 0 ]
else:
# Display a select dialog so user can choose their content
selectedContent = xbmcgui.Dialog().select( LANGUAGE( 30308 ), matchesList )
# If the user selected nothing...
if selectedContent == -1:
return
content = matchesValue[ selectedContent ]
if match == "title":
self.createBrowseNode( content, None )
returnVal = self.browser( self.niceMatchName( match ) )
elif match == "tvshow":
if content == "episodes":
content = "tvshows"
self.createBrowseNode( content, None )
returnVal = self.browser( self.niceMatchName( match ) )
elif match == "genre":
self.createBrowseNode( content, "genres" )
returnVal = self.browser( self.niceMatchName( match ) )
elif match == "album":
self.createBrowseNode( content, "none" )
returnVal = self.browser( self.niceMatchName( match ) )
elif match == "country":
self.createBrowseNode( content, "countries" )
returnVal = self.browser( self.niceMatchName( match ) )
elif match == "year":
if content == "episodes":
content = "tvshows"
self.createBrowseNode( content, "years" )
returnVal = self.browser( self.niceMatchName( match ) )
elif match == "artist":
self.createBrowseNode( content, "artists" )
returnVal = self.browser( self.niceMatchName( match ) )
elif match == "director":
self.createBrowseNode( content, "directors" )
returnVal = self.browser( self.niceMatchName( match ) )
elif match == "actor":
if content == "episodes":
content = "tvshows"
self.createBrowseNode( content, "actors" )
returnVal = self.browser( self.niceMatchName( match ) )
elif match == "studio":
self.createBrowseNode( content, "studios" )
returnVal = self.browser( self.niceMatchName( match ) )
elif match == "path":
returnVal = xbmcgui.Dialog().browse(0, self.niceMatchName( match ), self.ltype )
elif match == "set":
self.createBrowseNode( content, "sets" )
returnVal = self.browser( self.niceMatchName( match ) )
elif match == "tag":
self.createBrowseNode( content, "tags" )
returnVal = self.browser( self.niceMatchName( match ) )
elif match == "playlist":
returnVal = self.browserPlaylist( self.niceMatchName( match ) )
elif match == "virtualfolder":
returnVal = self.browserPlaylist( self.niceMatchName( match ) )
elif match == "albumartist":
self.createBrowseNode( content, "artists" )
returnVal = self.browser( self.niceMatchName( match ) )
try:
# Delete any fake node
xbmcvfs.delete( os.path.join( xbmcvfs.translatePath( "special://profile" ), "library", self.ltype, "plugin.library.node.editor", "temp.xml" ) )
except:
print_exc()
self.writeUpdatedRule( actionPath, ruleNum, value = returnVal )
def niceMatchName( self, match ):
# This function retrieves the translated label for a given match
tree = self._load_rules()
elems = tree.getroot().find( "matches" ).findall( "match" )
matches = {}
for elem in elems:
if elem.attrib.get( "name" ) == match:
return xbmc.getLocalizedString( int( elem.find( "label" ).text ) )
def createBrowseNode( self, content, grouping = None ):
# This function creates a fake node which we'll use for browsing
targetDir = os.path.join( xbmcvfs.translatePath( "special://profile" ), "library", self.ltype, "plugin.library.node.editor" )
if not os.path.exists( targetDir ):
xbmcvfs.mkdirs( targetDir )
# Create a new etree
tree = xmltree.ElementTree(xmltree.Element( "node" ) )
root = tree.getroot()
root.set( "type", "filter" )
xmltree.SubElement( root, "label" ).text = "Fake node used for browsing"
xmltree.SubElement( root, "content" ).text = content
if grouping is not None:
xmltree.SubElement( root, "group" ).text = grouping
else:
order = xmltree.SubElement( root, "order" )
order.text = "sorttitle"
order.set( "direction", "ascending" )
self.indent( root )
tree.write( os.path.join( targetDir, "temp.xml" ), encoding="UTF-8" )
def browser( self, title ):
# Browser instance used by majority of browses
json_query = xbmc.executeJSONRPC('{ "jsonrpc": "2.0", "id": 0, "method": "Files.GetDirectory", "params": { "properties": ["title", "file", "genre", "studio", "director", "thumbnail"], "directory": "library://%s/plugin.library.node.editor/temp.xml", "media": "files" } }' % self.ltype)
json_response = json.loads(json_query)
listings = []
values = []
# Add all directories returned by the json query
if json_response.get('result') and json_response['result'].get('files') and json_response['result']['files'] is not None:
for item in json_response['result']['files']:
if item[ "label" ] == "..":
continue
thumb = None
if item[ "thumbnail" ] is not "":
thumb = item[ "thumbnail" ]
if "videodb://" not in item["file"]:
if title.lower() == "genre":
for genre in item["genre"]:
label = genre
elif title.lower() == "studios":
for studio in item["studio"]:
label = studio
elif title.lower() == "director":
for director in item["director"]:
label = director
else:
label = item["label"]
else:
label = item["label"]
if label not in values:
listitem = xbmcgui.ListItem(label=label)
if thumb:
listitem.setArt({'icon': thumb, 'thumb': thumb})
listitem.setProperty( "thumbnail", thumb )
listings.append( listitem )
values.append( label )
# Show dialog
w = ShowDialog( "DialogSelect.xml", CWD, listing=listings, windowtitle=title )
w.doModal()
selectedItem = w.result
del w
if selectedItem == "" or selectedItem == -1:
return None
return values[ selectedItem ]
def browserPlaylist( self, title ):
# Browser instance used by playlists
json_query = xbmc.executeJSONRPC('{ "jsonrpc": "2.0", "id": 0, "method": "Files.GetDirectory", "params": { "properties": ["title", "file", "thumbnail"], "directory": "special://%splaylists/", "media": "files" } }' % self.ltype)
json_response = json.loads(json_query)
listings = []
values = []
# Add all directories returned by the json query
if json_response.get('result') and json_response['result'].get('files') and json_response['result']['files'] is not None:
for item in json_response['result']['files']:
if item[ "label" ] == "..":
continue
thumb = None
if item[ "thumbnail" ] is not "":
thumb = item[ "thumbnail" ]
listitem = xbmcgui.ListItem(label=item[ "label" ])
listitem.setArt({"icon": thumb, "thumbnail": thumb})
listitem.setProperty( "thumbnail", thumb )
listings.append( listitem )
values.append( item[ "label" ] )
# Show dialog
w = ShowDialog( "DialogSelect.xml", CWD, listing=listings, windowtitle=title )
w.doModal()
selectedItem = w.result
del w
if selectedItem == "" or selectedItem == -1:
return None
return values[ selectedItem ]
# 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
# ============================
# === PRETTY SELECT DIALOG ===
# ============================
class ShowDialog( xbmcgui.WindowXMLDialog ):
def __init__( self, *args, **kwargs ):
xbmcgui.WindowXMLDialog.__init__( self )
self.listing = kwargs.get( "listing" )
self.windowtitle = kwargs.get( "windowtitle" )
self.result = -1
def onInit(self):
try:
self.fav_list = self.getControl(6)
self.getControl(3).setVisible(False)
except:
print_exc()
self.fav_list = self.getControl(3)
self.getControl(5).setVisible(False)
self.getControl(1).setLabel(self.windowtitle)
for item in self.listing :
listitem = xbmcgui.ListItem(label=item.getLabel(), label2=item.getLabel2())
listitem.setArt({"icon": item.getProperty( "icon" ), "thumbnail": item.getProperty( "thumbnail" )})
listitem.setProperty( "Addon.Summary", item.getLabel2() )
self.fav_list.addItem( listitem )
self.setFocus(self.fav_list)
def onAction(self, action):
if action.getId() in ( 9, 10, 92, 216, 247, 257, 275, 61467, 61448, ):
self.result = -1
self.close()
def onClick(self, controlID):
if controlID == 6 or controlID == 3:
num = self.fav_list.getSelectedPosition()
self.result = num
else:
self.result = -1
self.close()
def onFocus(self, controlID):
pass