jf-roku/components/TrackerTask.xml
2019-10-03 22:47:34 -04:00

2621 lines
86 KiB
XML

<?rokuml version="1.0" encoding="utf-8" ?>
<!--********** Copyright 2019 Roku Corp. All Rights Reserved. **********-->
<component name="TrackerTask" extends="Task">
<interface>
<field id="event" type="assocarray"/>
<field id="isExternallyExposed" type="boolean" value="true"/>
<function name="UIThread_init"/>
<function name="UIThread_showSelectorView"/>
<function name="UIThread_hideSelectorView"/>
<function name="UIThread_selectNode"/>
<function name="UIThread_updateNode"/>
<function name="UIThread_getNodeData"/>
<function name="UIThread_getItemList"/>
<function name="UIThread_getNodeTree"/>
<function name="UIThread_getNodeById"/>
<function name="UIThread_getNodeByName"/>
<function name="UIThread_addChild"/>
<function name="UIThread_removeChild"/>
<function name="UIThread_moveChild"/>
<function name="UIThread_setField"/>
<function name="UIThread_removeField"/>
<function name="UIThread_setFocus"/>
<function name="UIThread_selectFocusedNode"/>
<function name="UIThread_setBoundingRect"/>
<function name="UIThread_getRegistrySections"/>
<function name="UIThread_clearRegistry"/>
<function name="UIThread_addRegistrySection"/>
<function name="UIThread_removeRegistrySection"/>
<function name="UIThread_addRegistryField"/>
<function name="UIThread_removeRegistryField"/>
<function name="UIThread_editRegistryField"/>
<function name="UIThread_log"/>
<function name="UIThread_setLogVerbosity"/>
<function name="UIThread_setLogFormat"/>
</interface>
<script type = "text/brightscript" >
<![CDATA[
' RALE_Config - RALE config singleton class
' @return {object} - RALE config properties
Function RALE_Config() as Object
if m.appConfig = Invalid then m.appConfig = RALE_InitConfig()
return m.appConfig
End Function
' RALE_InitConfig - RALE config init function. Defines all config properties
' @return {object} - RALE config properties
Function RALE_InitConfig() as Object
this = {
bufferSize: 1024,
minSocketPort: 49152,
maxSocketPort: 65535,
screensaverResetTimeout: 30000,
commandTimeout: 10000,
defaultLogLevel: 3,
logFormat: "[RALE][%level%] - %message%"
}
return this
End Function
' RALE_Logger - Constructor of Logger class
' @param {integer} level - log verbosity level (0-4)
' @param {string} format - log message format ("[MY_CUSTOM_PREFIX]%message%")
' @return {object} - logger instance
function RALE_Logger(level = -1 as Integer, format = "%message%" as String) as Object
this = {}
this.verbosityList = ["OFF", "ERROR", "WARN", "INFO", "DEBUG"]
this.verbosityMap = {
off : 0
error : 1
warning : 2
info : 3
debug : 4
}
this.formatMethods = {
date: "getDate",
time: "getTime",
level: "getLevel",
message: "getMessage"
}
this.dt = createObject("roDateTime")
this.setVerbosity = RALE_Logger__SetVerbosityLevel
this.setFormat = RALE_Logger__SetFormat
this.getDate = RALE_Logger__GetDate
this.getTime = RALE_Logger__GetTime
this.getLevel = RALE_Logger__GetLevel
this.getMessage = RALE_Logger__GetMessage
this.debug = RALE_Logger__DebugLog
this.info = RALE_Logger__InfoLog
this.warning = RALE_Logger__WarningLog
this.error = RALE_Logger__ErrorLog
this.logMessage = RALE_Logger__LogMessage
this.setVerbosity(level)
this.setFormat(format)
return this
end function
' RALE_Logger__SetVerbosityLevel - change logger verbosity level
' @param {integer} level - log verbosity level (0-4)
sub RALE_Logger__SetVerbosityLevel(level = 4 as Integer)
if level > m.verbosityMap.debug then
m.verbosityLevel = m.verbosityMap.debug
else if level < m.verbosityMap.off then
m.verbosityLevel = m.verbosityMap.off
else
m.verbosityLevel = level
end if
end sub
' RALE_Logger__SetFormat - Change logger message format
' @param {string} format - log message format ("[MY_CUSTOM_PREFIX]%message%")
' Available format variables:
' %message% - message with which the log function is called (at the end by default)
' %date% - current date in format "[year]-[month]-[day]"
' %time% - current time in format "[hours]:[minutes]:[seconds].[miliseconds]"
' %level% - verbosity level ("ERROR", "WARN", "INFO" or "DEBUG")
sub RALE_Logger__SetFormat(format = "%message%" as String)
if Instr(1, format, "%message%") = 0 then
format = format + "%message%"
end if
m.formatList = format.split("%")
end sub
'==========================================
' Wrappers over various levels of logging
'==========================================
' RALE_Logger__DebugLog - Log message into console with DEBUG verbosity level
' @param {string} message - log message
sub RALE_Logger__DebugLog(message = "")
m.logMessage(message, m.verbosityMap.debug)
end sub
' RALE_Logger__InfoLog - Log message into console with INFO verbosity level
' @param {string} message - log message
sub RALE_Logger__InfoLog(message = "")
m.logMessage(message, m.verbosityMap.info)
end sub
' RALE_Logger__WarningLog - Log message into console with WARNING verbosity level
' @param {string} message - log message
sub RALE_Logger__WarningLog(message = "")
m.logMessage(message, m.verbosityMap.warning)
end sub
' RALE_Logger__ErrorLog - Log message into console with ERROR verbosity level
' @param {string} message - log message
sub RALE_Logger__ErrorLog(message)
m.logMessage(message, m.verbosityMap.error)
end sub
'==========================================
' Main log function
'==========================================
' RALE_Logger__LogMessage - Log message into console by message format if logLevel is more than current verbosityLevel
' @param {string} message - log message
' @param {integer} logLevel - log verbosity level (0-4)
sub RALE_Logger__LogMessage(message as String, logLevel as Integer)
if logLevel > 0 and m.verbosityLevel >= logLevel then
logMessage = ""
for each item in m.formatList
formatMethod = m.formatMethods[item]
if formatMethod = Invalid then
logMessage = logMessage + item
else
logMessage = logMessage + m[formatMethod](message, logLevel)
end If
end for
print logMessage
end if
end sub
'==========================================
' Utitlity functions
'==========================================
' RALE_Logger__GetTime - Returns current time
' @param {string} message - log message
' @return {string} - current time in format: "[hours]:[minutes]:[seconds].[miliseconds]"
function RALE_Logger__GetTime(message as String, level as Integer)
m.dt.Mark()
h = RALE_Logger__getZeroNumber(m.dt.getHours())
n = RALE_Logger__getZeroNumber(m.dt.getMinutes())
s = RALE_Logger__getZeroNumber(m.dt.getSeconds())
ms = str(m.dt.getMilliseconds()).trim()
if len(ms) = 1
ms = ms + "0"
else
ms = left(ms, 2)
end if
return h + ":" + n + ":" + s + "." + ms
end function
' RALE_Logger__GetDate - Returns current date
' @param {string} message - log message
' @return {string} - current date in format: "[year]-[month]-[day]"
function RALE_Logger__GetDate(message as String, level as Integer)
m.dt.Mark()
y = str(m.dt.getYear()).trim()
mm = RALE_Logger__getZeroNumber(m.dt.getMonth())
d = RALE_Logger__getZeroNumber(m.dt.getDayOfMonth())
return y + "-" + mm + "-" + d
end function
' RALE_Logger__GetLevel - Returns current verbosity level
' @param {string} message - log message
' @return {integer} - verbosity level
function RALE_Logger__GetLevel(message as String, level as Integer)
return m.verbosityList[level]
end function
' RALE_Logger__GetMessage - Returns log message (needed for formating log message)
' @param {string} message - log message
' @return {string} - log message
function RALE_Logger__GetMessage(message as String, level as Integer)
return message
end function
' RALE_Logger__getZeroNumber - Convert number to format "0[number]" if it has less then 2 digits
' @param {number} n - number for converting
' @param {number} to_length - the nessesary count of digits
' @return {string} - converted numer format
function RALE_Logger__getZeroNumber(n, to_length=2)
n = str(n).trim()
if len(n) < to_length
n = String(to_length - 1, "0") + n
end if
return n
end function
' CreateSelectorView - Selector (live) view class constructor
' @return {object} - selector (live) view
Function CreateSelectorView() as Object
view = CreateObject("roSGNode", "Group")
m.resolution = m.top.getScene().getField("currentDesignResolution")
m.resWidth = m.resolution.width
m.resHeight = m.resolution.height
children = [
{subtype : "Rectangle", fields : {id : "l"}}
{subtype : "Rectangle", fields : {id : "r"}}
{subtype : "Rectangle", fields : {id : "b"}}
{subtype : "Rectangle", fields : {id : "t"}}
{subtype : "Rectangle", fields : {id : "horiz"}}
{subtype : "Rectangle", fields : {id : "vert"}}
{subtype : "Rectangle", fields : {id : "leftLine"}}
{subtype : "Rectangle", fields : {id : "topLine"}}
{subtype : "Rectangle", fields : {id : "rightLine"}}
{subtype : "Rectangle", fields : {id : "bottomLine"}}
{subtype : "Label", fields : {id : "leftCords"}}
{subtype : "Label", fields : {id : "topCords"}}
{subtype : "Label", fields : {id : "rightCords"}}
{subtype : "Label", fields : {id : "bottomCords"}}
{subtype : "Rectangle", fields : {id : "fill", color : "#ffffff44"}}
{subtype : "Timer", fields : {id : "timer", duration : 1 / 60, repeat : true}}
]
viewObject = NodeUtils_AddChildrenToNode(view, children)
childrenMap = {}
for each child in viewObject.children
node = child.node
childrenMap[node.id] = node
end for
interface = [
{id:"width", type:"float", value : "1280", onChange : "SelectorView_UpdateView"}
{id:"height", type:"float", value : "40", onChange : "SelectorView_UpdateView"}
{id:"border", type:"float", value : "2", onChange : "SelectorView_UpdateView"}
{id:"oldBoundingRect", type:"assocarray" }
{id:"boundingRect", type:"assocarray", onChange : "SelectorView_UpdateViewFromBRect"}
{id:"color", type:"color", value : "#FFFFFF", onChange : "SelectorView_UpdateColors"}
{id:"attachedView", type:"node", onChange : "SelectorView_AttachToView"}
{id:"isExternallyExposed", type:"boolean", value : "true"}
{id:"childrenMap", type:"assocarray", value : childrenMap}
]
NodeUtils_AddInterfaceToNode(view, interface)
timer = view.childrenMap.timer
timer.observeField("fire","SelectorView_UpdateViewPosition")
timer.control = "start"
return view
End Function
' NodeUtils_AddInterfaceToNode - Adds interface to node
' @param {object} node - node
' @param {object} interface - interface
' @return {boolean} - is interface was added
Function NodeUtils_AddInterfaceToNode(node, interface as Object) as Boolean
if node = invalid OR interface = invalid then return false
for each field in interface
NodeUtils_AddNodeInterfaceField(node, field)
end for
return true
End Function
' NodeUtils_AddNodeInterfaceField - Adds interface field to node
' @param {object} node - node
' @param {object} fieldConfig - field config
' @return {boolean} - is interface was added
Function NodeUtils_AddNodeInterfaceField(node, fieldConfig as Object) as Boolean
if node = invalid OR fieldConfig = invalid then return false
if fieldConfig.id = invalid then return false
if fieldConfig.type = invalid then return false
if fieldConfig.alwaysNotify = invalid then fieldConfig.alwaysNotify = true
if node.hasField(fieldConfig.id) then return false
node.addField(fieldConfig.id, fieldConfig.type, fieldConfig.alwaysNotify)
if not node.hasField(fieldConfig.id) then return false
if fieldConfig.value <> invalid then
node[fieldConfig.id] = fieldConfig.value
end if
if fieldConfig.onChange <> invalid then
node.observeField(fieldConfig.id, fieldConfig.onChange)
end if
return true
End Function
' NodeUtils_AddChildrenToNode - Adds children to node
' @param {object} node - node
' @param {object} childrenConfig - children config
' @return {object} - view object
Function NodeUtils_AddChildrenToNode(node, childrenConfig as Object) as Object
if node = invalid then return invalid
viewObject = {node : node, children : []}
if childrenConfig <> invalid then
for each childConfig in childrenConfig
childObject = NodeUtils_AddChildToNode(node, childConfig)
viewObject.children.push(childObject)
end for
end if
return viewObject
End Function
' NodeUtils_AddChildToNode - Adds child to node
' @param {object} node - node
' @param {object} childConfig - child config
' @return {object} - view object
Function NodeUtils_AddChildToNode(node, childConfig) as Object
if node = invalid or childConfig = invalid then return invalid
if childConfig.subtype = invalid then return invalid
view = node.CreateChild(childConfig.subtype)
if view = invalid then return invalid
if childConfig.fields <> invalid then view.setFields(childConfig.fields)
return NodeUtils_AddChildrenToNode(view, childConfig.children)
End Function
' NumberToFixed - formats a number using fixed-point notation
' @param {float, double} number - formated number
' @param {integer} digits - the number of digits to appear after the decimal point
' @return {string} - a string representing the given number using fixed-point notation
Function NumberToFixed(number, digits) as String
str = number.toStr()
arr = str.split(".")
if arr.count() > 1 and digits > 0 then
return arr[0] + "." + Left(arr[1], digits)
end if
return arr[0]
End Function
' SelectorView_UpdateView - width, height and border change event handler. Updates Selector (live) view
' @param {object} event - event data
Sub SelectorView_UpdateView(event)
node = event.getRoSGNode()
border = node.border
width = node.width
height = node.height
x = node.boundingRect.x
y = node.boundingRect.y
rightPos = x + width + border*2
bottomPos = y + height + border*2
l = node.childrenMap.l
l.width = border
l.height = height + border*2
l.translation = [-border, -border]
r = node.childrenMap.r
r.width = border
r.height = height + border*2
r.translation = [width, -border]
t = node.childrenMap.t
t.width = width + border*2
t.height = border
t.translation = [-border, -border]
b = node.childrenMap.b
b.width = width + border*2
b.height = border
b.translation = [-border, height]
horiz = node.childrenMap.horiz
horiz.width = width
horiz.height = border
horiz.translation = [0, (height - border) / 2]
vert = node.childrenMap.vert
vert.width = border
vert.height = height
vert.translation = [(width - border) / 2, 0]
leftLine = node.childrenMap.leftLine
leftLine.width = x
leftLine.height = border
leftLine.translation = [-x, (height - border) / 2]
topLine = node.childrenMap.topLine
topLine.width = border
topLine.height = y
topLine.translation = [(width - border) / 2, -y]
rightLine = node.childrenMap.rightLine
rightLine.width = m.resWidth - x + 10
rightLine.height = border
rightLine.translation = [width + border, (height - border) / 2]
bottomLine = node.childrenMap.bottomLine
bottomLine.width = border
bottomLine.height = m.resHeight - y + 10
bottomLine.translation = [(width - border) / 2, height + border]
leftCords = node.childrenMap.leftCords
leftCords.font.size = 20
leftCords.color = "#FF0000"
leftCords.text = NumberToFixed(x, 0)
leftCords.translation = [-x/2 - 10, (height - border) / 2 - 20]
topCords = node.childrenMap.topCords
topCords.font.size = 20
topCords.color = "#FF0000"
topCords.text = NumberToFixed(y, 0)
topCords.translation = [(width - border) / 2 + 10, -y/2 - 10]
rightCords = node.childrenMap.rightCords
rightCords.font.size = 20
rightCords.color = "#FF0000"
rightCords.text = NumberToFixed(m.resWidth - x - width, 0)
rightCords.translation = [width + (m.resWidth - rightPos)/2 - 5, (height - border) / 2 - 20]
bottomCords = node.childrenMap.bottomCords
bottomCords.font.size = 20
bottomCords.color = "#FF0000"
bottomCords.text = NumberToFixed(m.resHeight - y - height, 0)
bottomCords.translation = [(width - border) / 2 + 10, height + (m.resHeight - bottomPos)/2 - 5]
fill = node.childrenMap.fill
fill.width = width
fill.height = height
End Sub
' SelectorView_UpdateColors - color change event handler. Updates Selector (live) view color
' @param {object} event - event data
Sub SelectorView_UpdateColors(event)
node = event.getRoSGNode()
for each view in node.getchildren(-1,0)
if view.isSubtype("Rectangle") and view.id <> "fill" then view.color = node.color
end for
End Sub
' SelectorView_UpdateViewFromBRect - boundingRect change event handler.
' Updates Selector (live) view translation, width and height
' @param {object} event - event data
Sub SelectorView_UpdateViewFromBRect(event)
node = event.getRoSGNode()
boundingRect = node.boundingRect
if boundingRect <> Invalid then
node.setFields({
translation : [boundingRect.x, boundingRect.y]
width : boundingRect.width
height : boundingRect.height
})
end if
End Sub
' isBoundingRectChanged - checks is bounding rect was changed
' @param {object} br - node bounding rect
' @param {object} node - node
' @return {boolean} - true if node bounding rect was changed
Sub isBoundingRectChanged(br, node) as Boolean
o = node.oldBoundingRect
if o = Invalid then
node.oldBoundingRect = br
return true
end if
if o.x = br.x and o.y = br.y and o.width = br.width and o.height = br.height then
return false
else
node.oldBoundingRect = br
return true
end if
End Sub
' SelectorView_UpdateViewPosition - interval event handler. Updates selector (live) view position
' @param {object} event - node bounding rect
Sub SelectorView_UpdateViewPosition(event)
timer = event.getRoSGNode()
node = timer.getparent()
if node.attachedView <> Invalid then
boundingRect = node.attachedView.sceneboundingRect()
else
boundingRect = {x: -100, y: -100, width: 0, height: 0}
end if
if isBoundingRectChanged(boundingRect, node) then
node.boundingRect = boundingRect
end if
End Sub
' getCommandMap - Defines map that contains all RALE commands handlers
' @return {object} - handlers map
Sub getCommandMap() as Object
return {
"init" : "UIThread_init",
' Config commands
"showSelectorView" : "UIThread_showSelectorView",
"hideSelectorView" : "UIThread_hideSelectorView",
' Node commands
"selectNode" : "UIThread_selectNode",
"updateNode" : "UIThread_updateNode",
"getNodeData" : "UIThread_getNodeData",
' Tree View commands
"getItemList" : "UIThread_getItemList",
"getNodeTree" : "UIThread_getNodeTree",
"getNodeById" : "UIThread_getNodeById",
"getNodeByName" : "UIThread_getNodeByName",
' Children commands
"addChild" : "UIThread_addChild",
"removeChild" : "UIThread_removeChild",
"moveChild" : "UIThread_moveChild",
' Field commands
"setField" : "UIThread_setField",
"removeField" : "UIThread_removeField",
' Focus commands
"setFocus" : "UIThread_setFocus",
"selectFocusedNode" : "UIThread_selectFocusedNode",
' Bounding Rect commands
"setBoundingRect" : "UIThread_setBoundingRect",
' Registry commands
"getRegistrySections" : "UIThread_getRegistrySections",
"clearRegistry" : "UIThread_clearRegistry",
"addRegistrySection" : "UIThread_addRegistrySection",
"removeRegistrySection" : "UIThread_removeRegistrySection",
"addRegistryField" : "UIThread_addRegistryField",
"removeRegistryField" : "UIThread_removeRegistryField",
"editRegistryField" : "UIThread_editRegistryField",
' Logger commands
"setLogVerbosity" : "UIThread_setLogVerbosity",
"setLogFormat" : "UIThread_setLogFormat",
"log" : "UIThread_log"
}
End Sub
' callCommand - Call RALE command functuin from UI thread
' @param {string} command - command name
' @param {object} args - command arguments
' @return {object} - response of RALE command
Sub callCommand(command, args) as Object
return m.top.callFunc(command, args)
End Sub
' UIThread_init - RALE "init" command
' @param {object} args - command arguments
' @return {object} - response of command. Contains RALE version
Sub UIThread_init(args) as Object
root = m.top.GetScene()
m.currentNode = root
m.currentPath = []
if m.selectorView = Invalid then
m.selectorView = CreateSelectorView()
m.selectorView.color = "#ff0000"
m.selectorView.opacity = 0.6
m.showSelectorView = true
root.appendChild(m.selectorView)
end if
if args.logVerbosity >= 0 then
UIThread_setLogVerbosity({ level: args.logVerbosity })
end if
if FW_IsString(args.logFormat) then
UIThread_setLogFormat({ format: args.logFormat })
end if
m.logger.info("RALE Initialized")
return { raleVersion: m.raleVersion }
End Sub
'=======================
' Config commands
'=======================
' UIThread_showSelectorView - RALE "showSelectorView" command handler. Shows Selector (Live) View
' @param {object} args - command arguments
Sub UIThread_showSelectorView(args) as Object
m.showSelectorView = true
setSelectorView(m.currentNode, type(m.currentNode), m.currentPath)
m.logger.debug("Selector View was disabled")
End Sub
' UIThread_hideSelectorView - RALE "hideSelectorView" command handler. Hides Selector (Live) View
' @param {object} args - command arguments
Sub UIThread_hideSelectorView(args) as Object
m.showSelectorView = false
setSelectorView(m.currentNode, type(m.currentNode), m.currentPath)
m.logger.debug("Selector View was enabled")
End Sub
'=======================
' Node commands
'=======================
' UIThread_selectNode - RALE "selectNode" command handler. Selects node by path
' @param {object} args - command arguments
' @return {object} - response of command. Contains node path and node data (id, type, fields, layout etc.)
Sub UIThread_selectNode(args) as Object
path = args["path"]
root = m.top.getScene()
node = RALE_getNodeByPath(root, path)
if node = Invalid then return getError("Invalid Path")
nodeType = type(node)
if nodeType <> "roSGNode" and nodeType = "roArray" and nodeType = "roAssociativeArray" then
return getError("Invalid Node Type")
end if
if nodeType = "roSGNode" and node.isExternallyExposed <> Invalid then
return getError("Node Is Externally Exposed")
end if
if nodeType <> "roSGNode" or not node.IsSameNode(m.currentNode) then
m.currentPath = path
m.currentNode = node
end if
setSelectorView(node, nodeType, path)
m.logger.debug("Node selected: " + FW_AsString(m.currentNode))
return {
path: m.currentPath,
node: getNodeData(m.currentNode, m.currentPath, root)
}
End Sub
' UIThread_updateNode - RALE "updateNode" command handler. Updates selected node
' @param {object} args - command arguments
' @return {object} - response of command. Contains node path and data (id, type, fields, layout etc.)
Sub UIThread_updateNode(args) as Object
return {
path: m.currentPath,
node: getNodeData(m.currentNode, m.currentPath, Invalid)
}
End Sub
' UIThread_getNodeData - RALE "getNodeData" command handler. Returns selected node data
' @param {object} args - command arguments
' @return {object} - response of command. Node data (id, type, fields, layout etc.)
Sub UIThread_getNodeData(args) as Object
path = args["path"]
if path = Invalid then
path = m.currentPath
node = m.currentNode
else
root = m.top.getScene()
node = RALE_getNodeByPath(root, path)
if node = Invalid then return getError("Invalid Path")
end if
return getNodeData(node, path, Invalid)
End Sub
'=======================
' Tree View commands
'=======================
' UIThread_getItemList - RALE "getItemList" command handler. Returns node children and fields, by node path
' @param {object} args - command arguments
' @return {object} - response of command. List of node children and fields
Sub UIThread_getItemList(args) as Object
path = args["path"]
root = m.top.getScene()
node = RALE_getNodeByPath(root, path)
if node = Invalid then return getError("Invalid Path")
return getItemList(node, path)
End Sub
' UIThread_getNodeTree - RALE "getNodeTree" command handler. Returns children hierarchy by node path
' @param {object} args - command arguments
' @return {object} - response of command. Node children hierarchy
Sub UIThread_getNodeTree(args) as Object
path = args["path"]
maxLevel = args["maxLevel"]
root = m.top.getScene()
node = RALE_getNodeByPath(root, path)
index = RALE_getNodeIndexByPath(path)
if node = Invalid then return getError("Invalid Path")
return getNodeTree(node, index, maxLevel)
End Sub
' UIThread_getNodeById - RALE "getNodeById" command handler. Returns node with requested id
' @param {object} args - command arguments
' @return {object} - response of command. Found node data
Sub UIThread_getNodeById(args) as Object
path = args["path"]
id = args["id"]
if id = Invalid then return getError("id is required")
root = m.top.getScene()
rootNode = RALE_getNodeByPath(root, path)
if rootNode = Invalid then return getError("Invalid Path")
foundNode = getNodeById(rootNode, id)
foundPath = RALE_getNodePath(foundNode, root)
return getNodeData(foundNode, foundPath, root)
End Sub
' UIThread_getNodeByName - RALE "getNodeByName" command handler. Returns node with requested name
' @param {object} args - command arguments
' @return {object} - response of command. Found node path
Sub UIThread_getNodeByName(args) as Object
path = args["path"]
name = args["name"]
if name = Invalid then return getError("name is required")
root = m.top.getScene()
rootNode = RALE_getNodeByPath(root, path)
if rootNode = Invalid then return getError("Invalid Path")
foundNode = getNodeByName(rootNode, name)
foundPath = RALE_getNodePath(foundNode, root)
return getNodeData(foundNode, foundPath, root)
End Sub
'=======================
' Children commands
'=======================
' UIThread_addChild - RALE "addChild" command handler. Adds new child to node by path (if no path adds to selected node)
' @param {object} args - command arguments
' @return {object} - response of command. Contains node children hierarchy and index
Sub UIThread_addChild(args) as Object
childType = args["type"]
path = args["path"]
if not FW_IsString(childType) then return getError("childType is required")
index = FW_AsInteger(args["index"])
if index = 0 AND FW_AsString(args["index"]) <> "0" then index = Invalid
if path <> Invalid then
root = m.top.getScene()
node = RALE_getNodeByPath(root, path)
if node = Invalid then return getError("Invalid Path")
else
node = m.currentNode
path = m.currentPath
end if
result = addChild(node, childType, index)
if result.success = true then
index = RALE_getNodeIndexByPath(path)
return {
tree: getNodeTree(result.child, index, 50),
childindex: result.index
}
else
return result
end if
End Sub
' UIThread_removeChild - RALE "removeChild" command handler. Removes child from node by path (if no path removes from selected node)
' @param {object} args - command arguments
' @return {object} - response of command
Sub UIThread_removeChild(args) as Object
index = FW_AsInteger(args["index"])
path = args["path"]
if index = 0 AND FW_AsString(args["index"]) <> "0" then return getError("index is required")
if path <> Invalid then
root = m.top.getScene()
node = RALE_getNodeByPath(root, path)
if node = Invalid then return getError("Invalid Path")
else
node = m.currentNode
path = m.currentPath
end if
return removeChild(node, index)
End Sub
' UIThread_moveChild - RALE "moveChild" command handler. Moves selected node child
' @param {object} args - command arguments
' @return {object} - response of command
Sub UIThread_moveChild(args) as Object
fromIndex = FW_AsString(args["fromIndex"])
fromIndexInt = FW_AsInteger(fromIndex)
if fromIndexInt = 0 AND fromIndex <> "0" then return getError("fromIndex is required")
toIndex = FW_AsString(args["toIndex"])
toIndexInt = FW_AsInteger(toIndex)
if toIndexInt = 0 AND toIndex <> "0" then return getError("toIndex is required")
return moveChild(m.currentNode, fromIndexInt, toIndexInt)
End Sub
'=======================
' Fields commands
'=======================
' UIThread_setField - RALE "setField" command handler. Sets new value into node field (if the field doesn't exist creates it)
' @param {object} args - command arguments
' @return {array} - response of command. Node field list
Sub UIThread_setField(args) as Object
field = FW_AsString(args["field"])
fieldType = args["type"]
fieldValue = args["value"]
if field = "" then return getError("Field ID is required")
if RALE_convertToType(fieldValue, FW_AsString(fieldType)) = Invalid then return getError("Invalid Field Type or Value")
path = m.currentPath
nodeType = type(m.currentNode)
if nodeType = "roSGNode" then
setNodeField(m.currentNode, field, fieldValue, fieldType)
else
m.currentNode = setObjField(path, field, fieldValue, fieldType)
end if
m.logger.debug("Field '" + field + "' of selected node was set to " + FW_AsString(fieldValue))
return getFieldList(m.currentNode)
End Sub
' UIThread_removeField - RALE "removeField" command handler. Remove field from node by id
' @param {object} args - command arguments
' @return {array} - response of command. Node field list
Sub UIThread_removeField(args) as Object
field = args["field"]
if field = Invalid then return getError("Field ID is required")
node = m.currentNode
nodeType = type(node)
if nodeType = "roSGNode" then
node.removeField(field)
if node.hasField(field) then return getError("Field cannot be removed")
else if nodeType = "roAssociativeArray" or nodeType = "roArray" then
if nodeType = "roArray" then field = FW_AsInteger(field)
node.Delete(field)
inOwnerField = RALE_getNodeIndexByPath(m.currentPath)
if inOwnerField = Invalid then return getError("Invalid Path")
ownerPath = []
ownerPath.append(m.currentPath)
ownerPath.pop()
owner = setInParentNode(ownerPath, inOwnerField, node)
end if
m.logger.debug("Field '" + FW_AsString(field) + "' of selected node was removed")
return getFieldList(node)
End Sub
'=======================
' Focus commands
'=======================
' UIThread_setFocus - RALE "setFocus" command handler. Sets focus in node by path
' @param {object} args - command arguments
' @return {object} - response of command. Status
Sub UIThread_setFocus(args) as Object
path = args["path"]
node = m.top.getScene()
node = RALE_getNodeByPath(node, path)
if node = Invalid then return getError("Invalid Path")
m.logger.debug(FW_AsString(node) + " was focused")
node.setFocus(true)
return { success: true }
End Sub
' UIThread_selectFocusedNode - RALE "selectFocusedNode" command handler. Select focused node
' @param {object} args - command arguments
' @return {object} - response of command. Contains node path and data (id, type, fields, layout etc.)
Sub UIThread_selectFocusedNode(args) as Object
root = m.top.getScene()
node = getFocusedNodeObj(root)
if m.currentNode.IsSameNode(node) then
return {
path: m.currentPath,
node: getNodeData(node, m.currentPath, root)
}
else
path = RALE_getNodePath(node, root)
m.currentNode = node
m.currentPath = path
setSelectorView(node, type(node), path)
m.logger.debug("Node selected: " + FW_AsString(node))
return {
path: path,
node: getNodeData(node, path, root)
}
end if
End Sub
'=======================
' Bounding Rect commands
'=======================
' UIThread_setBoundingRect - RALE "setBoundingRect" command handler. Sets properties into selected node layout
' @param {object} args - command arguments
' @return {object} - response of command. Node sceneBoundingRect
Sub UIThread_setBoundingRect(args) as Object
boundingRect = args["boundingrect"]
if boundingRect = Invalid then return getError("boundingrect is required")
node = m.currentNode
if type(node) <> "roSGNode" then return getError("Invalid Node Type")
currentBoundingRect = node.sceneBoundingRect()
if currentBoundingRect <> Invalid then
deltaX = 0
deltaY = 0
deltaWidth = 0
deltaHeight = 0
if boundingRect.x <> Invalid and boundingRect.y <> Invalid then
deltaX = currentBoundingRect.x - boundingRect.x
deltaY = currentBoundingRect.y - boundingRect.y
end if
if boundingRect.width <> Invalid and boundingRect.height <> Invalid then
deltaWidth = currentBoundingRect.width - boundingRect.width
deltaHeight = currentBoundingRect.height - boundingRect.height
end if
fields = {}
if node.hasField("translation") AND (deltaY <> 0 or deltaX <> 0) then
fields.translation = [node.translation[0] - deltaX, node.translation[1] - deltaY]
m.logger.debug("Selected node translation was set to " + FW_AsString(fields.translation))
end if
if node.hasField("width") AND deltaWidth <> 0 then
if node.width = 0 AND deltaWidth > 0 then
fields.width = boundingRect.width
else
fields.width = node.width - deltaWidth
end if
m.logger.debug("Selected node width was set to " + FW_AsString(fields.width))
end if
if node.hasField("height") AND deltaHeight <> 0 then
if node.height = 0 AND deltaHeight > 0 then
fields.height = boundingRect.height
else
fields.height = node.height - deltaHeight
end if
m.logger.debug("Selected node height was set to " + FW_AsString(fields.height))
end if
node.setFields(fields)
end if
return node.sceneBoundingRect()
End Sub
'=======================
' Registry commands
'=======================
' UIThread_getRegistrySections - TBD
Sub UIThread_getRegistrySections(args) as Object
return getRegistrySections()
End Sub
' UIThread_addRegistrySection - TBD
Sub UIThread_clearRegistry(args) as Object
return clearRegistry()
End Sub
' UIThread_addRegistrySection - TBD
Sub UIThread_addRegistrySection(args) as Object
name = args.name
section = args.section
if not FW_IsString(name) or name = "" then return getError("Invalid name")
if not FW_IsAssociativeArray(section) then return getError("Invalid section. Expected an object")
return addRegistrySection(name, section)
End Sub
' UIThread_removeRegistrySection - TBD
Sub UIThread_removeRegistrySection(args) as Object
name = args.name
if not FW_IsString(name) or name = "" then return getError("Invalid name")
return removeRegistrySection(name)
End Sub
' UIThread_addRegistryField - TBD
Sub UIThread_addRegistryField(args) as Object
sectionName = args.sectionName
key = args.key
value = args.value
if not FW_IsString(sectionName) or sectionName = "" then return getError("Invalid section name")
if not FW_IsString(key) or key = "" then return getError("Invalid key")
if not FW_IsString(value) then return getError("Invalid value")
return addRegistryField(sectionName, key, value)
End Sub
' UIThread_removeRegistryField - TBD
Sub UIThread_removeRegistryField(args) as Object
sectionName = args.sectionName
key = args.key
if not FW_IsString(sectionName) or sectionName = "" then return getError("Invalid section name")
if not FW_IsString(key) or key = "" then return getError("Invalid key")
return removeRegistryField(sectionName, key)
End Sub
' UIThread_editRegistryField - TBD
Sub UIThread_editRegistryField(args) as Object
sectionName = args.sectionName
key = args.key
newKey = args.newKey
newValue = args.newValue
if not FW_IsString(sectionName) or sectionName = "" then return getError("Invalid section name")
if not FW_IsString(key) or key = "" then return getError("Invalid key")
if not FW_IsString(newKey) or newKey = "" then return getError("Invalid newKey")
return editRegistryField(sectionName, key, newKey, newValue)
End Sub
' UIThread_log - TBD
Sub UIThread_log(args) as Object
m.logger.logMessage(args.message, m.logger.verbosityMap[args.status])
End Sub
' UIThread_setLogVerbosity - TBD
Sub UIThread_setLogVerbosity(args) as Object
level = args.level
if not FW_IsInteger(level) then return getError("Invalid level")
m.logger.setVerbosity(args.level)
return { success: true }
End Sub
' UIThread_setLogFormat - TBD
Sub UIThread_setLogFormat(args) as Object
format = args.format
if not FW_IsString(format) then return getError("Invalid format")
m.logger.setFormat(args.format)
return { success: true }
End Sub
'=======================
' Helpers functions
'=======================
' getLayout - Returns node layout fields (boundingRect, resolution, isMovable, isHeightResizable, isWidthResizable)
' @param {object} node - node
' @param {object} root - root of nodes (main scene)
' @return {object} - node layout
Sub getLayout(node, root) as Object
if not FW_IsSGNode(node) then return getError("Invalid Node Type")
if root = Invalid then root = m.top.getScene()
parent = node.getParent()
isWidthResizable = node.hasField("width")
isHeightResizable = node.hasField("height")
isMovable = false
if parent <> Invalid and not m.sceneBG.isSameNode(node) then
isMovable = node.hasField("translation") and parent.ParentSubtype("LayoutGroup") <> ""
end if
return {
isMovable : isMovable,
isHeightResizable : isHeightResizable,
isWidthResizable : isWidthResizable,
boundingRect : node.sceneBoundingRect(),
resolution : root.getField("currentDesignResolution")
}
End Sub
' getItemObj - Returns node object
' @param {object} item - node or filed
' @param {string} index - field id or child index
' @return {object} - node data object
Sub getItemObj(item, index) as Dynamic
itemType = type(item)
if itemType = "roSGNode" then
return {
index: index,
id: item.id,
childrenCount: item.getChildCount(),
subtype: item.subtype(),
type: itemType,
value: "{object}"
isExposed: FW_AsBoolean(item.isExternallyExposed)
}
else if itemType = "roArray" or itemType = "roAssociativeArray" then
return {
id: index,
type: itemType,
value: "{object}"
}
else
return {
id: index,
type: itemType,
value: FW_AsString(item)
}
end if
End Sub
' getFieldList - Returns node field list
' @param {object} node - node
' @return {array} - list of fields
Sub getFieldList(node) as Object
nodeType = type(node)
if nodeType = "roSGNode" then
fields = node.getFields()
else if nodeType = "roArray" or nodeType = "roAssociativeArray" then
fields = node
else
return getError("Invalid Node Type")
end if
fieldList = {}
result = 0
if type(fields) = "roArray" then
count = fields.count() - 1
for i = 0 to count
id = i.ToStr()
fieldList[id] = { item: getItemObj(fields[i], i) }
fieldList[id].item.id = i
end for
else
keys = fields.keys()
for each fieldId in keys
fieldList[fieldId] = { item: getItemObj(fields[fieldId], fieldId) }
if nodeType = "roSGNode" then fieldList[fieldId].item.fieldType = node.getFieldType(fieldId)
end for
end if
return fieldList
End Sub
' getChildList - Returns node children list
' @param {object} node - node
' @return {array} - list of children
Sub getChildList(node) as Object
nodeType = type(node)
if nodeType = "roSGNode" then
childList = []
children = node.getChildren(-1, 0)
count = children.count()
if count > 0 then
count--
for i = 0 to count
if children[i] <> Invalid then
childList[i] = { item: getItemObj(children[i], i) }
end if
end for
end if
return childList
else
return getError("Invalid Node Type")
end if
End Sub
' getItemList - Returns node children list and node data
' @param {object} node - node
' @param {object} index - node index
' @return {object} - node data object and list of node children
Sub getItemList(node, index) as Object
item = { item: getItemObj(node, index) }
nodeType = type(node)
if nodeType = "roSGNode" then
item.childList = getChildList(node)
else
return getError("Invalid Node Type")
end if
return item
End Sub
' getNodeTree - Goes through node children and returns node children hierarchy
' @param {object} node - node or filed
' @param {string} id - node index or filed id
' @return {array} - node children hierarchy
Sub getNodeTree(node, id, maxLevel) as Object
item = {
item: getItemObj(node, id),
childList: []
}
result = RALE_forEachChild(node, { maxLevel: maxLevel }, getNodeTreeCallback, { item: item })
if not result then
return getError("Invalid Node Type")
end if
return item
End Sub
Sub getNodeTreeCallback(node, index, parentObj, childObj, storage) as Boolean
if parentObj.item = Invalid then
parentObj.item = storage.item
end if
childItem = {
item : getItemObj(node, index),
childList : []
}
parentObj.item.childlist[index] = childItem
if childObj <> Invalid then
childObj.item = childItem
end if
return true
End Sub
' getNodeById - Search for node by id
' @param {object} node - root node
' @param {string} id - node id
' @return {object} - found node
Sub getNodeById(rootNode, id) as Object
if rootNode.id = id then
return rootNode
end if
storage = {
node: Invalid,
id: id
}
result = RALE_forEachChild(rootNode, {}, getNodeByIdCallback, storage)
if not result then
return getError("Invalid Node Type")
end if
return storage.node
End Sub
Sub getNodeByIdCallback(node, index, parentObj, childObj, storage) as Boolean
if node.id = storage.id then
storage.node = node
return false
end if
return true
End Sub
' getNodeByName - Search for node by name (subtype)
' @param {object} node - root node
' @param {string} name - node name (subtype)
' @return {object} - found node
Sub getNodeByName(rootNode, name) as Object
if rootNode.subtype() = name then
return rootNode
end if
storage = {
node: Invalid,
name: name
}
result = RALE_forEachChild(rootNode, {}, getNodeByNameCallback, storage)
if not result then
return getError("Invalid Node Type")
end if
return storage.node
End Sub
Sub getNodeByNameCallback(node, index, parentObj, childObj, storage) as Boolean
if node.subtype() = storage.name then
storage.node = node
return false
end if
return true
End Sub
' getNodeData - Returns node children hierarchy
' @param {object} node - node or filed
' @param {array} path - path to node
' @param {object} root - root of nodes (main scene)
' @return {object} - node data (item, fieldlist, layout)
Sub getNodeData(node, path, root) as Object
index = RALE_getNodeIndexByPath(path)
if index = Invalid then return getError("Invalid Path")
result = {
item: getItemObj(node, index),
fieldlist: getFieldList(node),
layout: getLayout(node, root)
}
if type(node) = "roSGNode" then result.childlist = getChildList(node)
return result
End Sub
' addChild - Adds child to node by type and index
' @param {object} node - parent node
' @param {string} childType - path to node
' @param {string} index - child index
' @return {object} - status
Sub addChild(node, childType, index) as Object
if not FW_IsSGNode(node) then return getError("Cannot Add Child. Invalid Node Type")
child = CreateObject("roSGNode", childType)
if not FW_IsSGNode(child) then return getError("Cannot Add Child. Invalid Component Type")
childCount = node.getChildCount()
if FW_IsInteger(index) and index < childCount then
node.insertChild(child, index)
else
index = childCount
node.appendChild(child)
end if
addedChild = node.getChild(index)
if type(addedChild) = "roSGNode" and addedChild.isSameNode(child) then
m.logger.debug("Child " + childType + " was added to " + FW_AsString(node) + " by index " + FW_AsString(index))
return { success: true, child: addedChild, index: index }
else
return getError("Cannot Add Child")
end if
End Sub
' removeChild - Removes child from node by index
' @param {object} node - parent node
' @param {string} index - child index
' @return {object} - status
Sub removeChild(node, index as Integer) as Object
if not FW_IsSGNode(node) then return getError("Invalid Node Type")
count = node.getChildCount()
if FW_IsSGNode(node.getChild(index)) then
node.removeChildIndex(index)
if node.getChildCount() < count then
m.logger.debug("Child with index " + FW_AsString(index) + " was removed from " + FW_AsString(node))
return { success: true }
end if
return getError("Node cannot be removed")
else
return getError("No child with this index")
end if
End Sub
' moveChild - Moves node child by index to another position
' @param {object} node - parent node
' @param {string} fromIndex - current child index
' @param {string} toIndex - index where you want to move
' @return {object} - status
Sub moveChild(node, fromIndex, toIndex) as Object
if not FW_IsSGNode(node) then return getError("Invalid Node Type")
if fromIndex >= 0 AND toIndex >= 0
child = node.getChild(fromIndex)
if child <> Invalid then
node.removeChildIndex(fromIndex)
if toIndex > fromIndex then toIndex--
node.insertChild(child, toIndex)
m.logger.debug("Child of " + FW_AsString(node) + " was moved from index " + FW_AsString(fromIndex) + " to index " + FW_AsString(toIndex))
return { success: true }
else
return getError("No child with this index")
end if
else
return getError("fromIndex and toIndex must be positive integer")
end if
End Sub
' setSelectorView - Shows selector (live) view for node
' @param {object} node - node
' @param {string} nodeType - type of node
' @param {array} nodePath - path to node
' @return {object} - status
Function setSelectorView(node, nodeType, nodePath) as Object
if m.selectorView <> Invalid then
if m.showSelectorView and nodeType = "roSGNode" and nodePath.count() > 0 then
m.selectorView.attachedView = node
else
m.selectorView.attachedView = Invalid
end if
end if
End Function
' setNodeField - Sets field into node
' @param {object} node - node
' @param {string} field - field id
' @param {dynamic} value - field value
' @param {string} fieldType - field type
Sub setNodeField(node, field, value, fieldType) as Object
fields = {}
if fieldType = Invalid then
fields[field] = value
if node.hasField(field) then
node.setFields(fields)
else
node.addFields(fields)
end if
else
fields[field] = RALE_convertToType(value, fieldType)
if node.hasField(field) and node.getFieldType(field) <> fieldType then
node.removeField(field)
end if
if not node.hasField(field) then
node.addField(field, RALE_parseType(fieldType), false)
end if
node.setFields(fields)
end if
End Sub
' setObjField - Sets field into AssociativeArray
' @param {array} parentPath - node
' @param {string} field - field id
' @param {dynamic} value - field value
' @param {string} fieldType - field type
' @return {object} - status
Sub setObjField(parentPath, field, value, fieldType) as Object
if fieldType <> Invalid then
value = RALE_convertToType(value, fieldType)
end if
return setInParentNode(parentPath, field, value)
End Sub
' setInParentNode - Sets field into parent node
' @param {array} path - path to field
' @param {string} field - field id
' @param {dynamic} value - field value
' @return {object} - parent node
Function setInParentNode(path, field, value)
root = m.top.getScene()
parentPath = []
parentPath.append(path)
nodeList = RALE_getNodeListByPath(root, path)
if nodeList = Invalid then return getError("Invalid Path")
index = nodeList.count() - 1
parent = nodeList[index]
if type(parent) = "roSGNode" then
setNodeField(parent, field, value, Invalid)
return parent
end if
while type(parent) <> "roSGNode"
if type(parent) = "roArray" then
field = FW_AsInteger(field)
if field > parent.count() then
field = parent.count()
end if
parent[field] = value
else
parent[field] = value
end if
value = parent
index--
field = path[index].field
parent = nodeList[index]
parentPath.pop()
end while
parent = RALE_getNodeByPath(root, parentPath)
setNodeField(parent, field, value, Invalid)
return RALE_getNodeByPath(root, path)
End Function
' setFocus - Focus node
' @param {object} node - node
Sub setFocus(node) as Object
if not FW_IsSGNode(node) then return getError("Invalid Node Type")
node.setFocus(true)
End Sub
' getFocusedNodeObj - Returns focused node
' @param {object} root - root
' @param {object} maxDeep - search depth
' @return {object} - focused node
Function getFocusedNodeObj(root, maxDeep = 25) as Object
node = root
focusedChild = node.focusedChild
while maxDeep > 0 AND focusedChild <> Invalid AND not node.isSameNode(focusedChild)
node = focusedChild
focusedChild = node.focusedChild
maxDeep--
end while
return node
End Function
' getRegistrySections - Returns map of roRegistry
' @return {object} - sections
Function getRegistrySections() as Object
sections = {}
RALE_forEachRegistryField(getRegistrySectionsCallback, sections)
return sections
End Function
Sub getRegistrySectionsCallback(value, key, section, sections) as Boolean
if not FW_IsAssociativeArray(sections[section]) then
sections[section] = {}
end If
sections[section][key] = value
return true
End Sub
' clearRegistry - Clear roRegistry
Function clearRegistry() as Object
RALE_forEachRegistrySection(clearRegistryCallback, {})
return { success: true }
End Function
Sub clearRegistryCallback(section, sectionName, storage) as Object
return removeRegistrySection(sectionName)
End Sub
' addRegistrySection - TBD
' @return {object} - sections
Function addRegistrySection(sectionName, section) as Object
RegistrySection = CreateObject("roRegistrySection", sectionName)
keys = section.keys()
for each key in keys
RegistrySection.write(key, section[key])
end for
RegistrySection.flush()
m.logger.debug("Registry section " + sectionName + " was added to roRegistry")
return { success: true }
End Function
' removeRegistrySection - TBD
' @return {object} - sections
Function removeRegistrySection(sectionName) as Object
Registry = CreateObject("roRegistry")
success = Registry.delete(sectionName)
Registry.flush()
m.logger.debug("Registry section " + sectionName + " was removed from roRegistry")
return { success: success }
End Function
' addRegistryField - TBD
' @return {object} - sections
Function addRegistryField(sectionName, key, value) as Object
RegistrySection = CreateObject("roRegistrySection", sectionName)
success = RegistrySection.write(key, value)
m.logger.debug("Field " + key + " with value " + value + " was added to " + sectionName + " registry section")
return { success: success }
End Function
' removeRegistryField - TBD
' @return {object} - sections
Function removeRegistryField(sectionName, key) as Object
RegistrySection = CreateObject("roRegistrySection", sectionName)
success = RegistrySection.delete(key)
m.logger.debug("Field " + key + " was removed from " + sectionName + " registry section")
return { success: success }
End Function
' getRegistrySections - TBD
' @return {object} - sections
Function editRegistryField(sectionName, key, newKey, newValue) as Object
result = removeRegistryField(sectionName, key)
if result.success then
result = addRegistryField(sectionName, newKey, newValue)
end if
return result
End Function
' getError - Returns error object
' @param {string} message - error message
Function getError(message as String) as Object
m.logger.error(message)
return { error: { message: message } }
End Function
' @desc TrackerTask init function
Sub init()
m.top.functionName = "tracker"
m.top.control = "RUN"
m.config = RALE_Config()
m.logger = RALE_Logger(m.config.defaultLogLevel, m.config.logFormat)
m.sceneBG = m.top.GetScene().getChild(0)
m.raleVersion = "2.0.9"
m.logger.info("Roku Advanced Layout Editor v." + m.raleVersion)
End Sub
' tracker - TrackerTask run function
Sub tracker()
appInfo = CreateObject("roAppInfo")
handlersMap = getCommandMap()
RALE_InfoLog("TrackerTask run")
buffer = CreateObject("roByteArray")
buffer[m.config.bufferSize] = 0
messagePort = CreateObject("roMessagePort")
addr = CreateObject("roSocketAddress")
inputPort = CreateObject("roMessagePort")
inputObj = CreateObject("roInput")
inputObj.SetMessagePort(inputPort)
while True
raleEnabled = true
port = 0
RALE_InfoLog("Waiting on ECP input request")
msg = wait(0, inputPort)
if type(msg) = "roInputEvent"
if msg.IsInput()
info = msg.GetInfo()
if type(info) = "roAssociativeArray" and info.rale <> Invalid then
port = FW_AsInteger(info.port)
end if
nonDev = FW_AsBoolean(info.nonDev)
if not nonDev and not appInfo.IsDev() then
RALE_InfoLog("Disable RALE for non-dev channels")
raleEnabled = false
end if
end if
end if
if raleEnabled and port > m.config.minSocketPort and port < m.config.maxSocketPort then
socketConnection(addr, messagePort, port, buffer, handlersMap, m.config)
end if
end while
End Sub
' socketConnection - Starts socket connection. Keep alive while connection won't be closed
' @param {object} addr - roSocketAddress
' @param {object} messagePort - roMessagePort
' @param {number} port - socket connection port
' @param {array} buffer - buffer for socket packets
' @param {object} handlersMap - map that contains all RALE commands handlers
' @param {object} config - configuration constants
' @return {string} - connection status
Sub socketConnection(addr, messagePort, port, buffer, handlersMap, config) as String
connections = {}
appManager = createObject("roAppManager")
screensaverTimer = CreateObject("roTimespan")
screensaverTimer.Mark()
addr.setPort(port)
tcpListen = CreateObject("roStreamSocket")
tcpListen.setMessagePort(messagePort)
tcpListen.setAddress(addr)
tcpListen.notifyReadable(true)
tcpListen.listen(4)
wasConnected = false
RALE_InfoLog("Start socket server on port " + FW_AsString(port))
while True
if not wasConnected then
event = wait(3000, messagePort)
if event = Invalid then
tcpListen.close()
RALE_WarningLog("No 'init' command received. Socket connection closed")
return "connection timeout"
end if
else
event = wait(config.commandTimeout, messagePort)
end if
' Resets the screensaver timer
if screensaverTimer.TotalMilliseconds() > config.screensaverResetTimeout then
screensaverTimer.Mark()
appManager.UpdateLastKeyPressTime()
end if
' ? type(event)
if type(event) = "roSocketEvent"
wasConnected = true
changedID = event.getSocketID()
if changedID = tcpListen.getID() and tcpListen.isReadable()
' New
newConnection = tcpListen.accept()
if newConnection = Invalid
RALE_WarningLog("Connection accept failed")
else
RALE_InfoLog("Accepted new connection " + FW_AsString(newConnection.getID()))
newConnection.notifyReadable(true)
newConnection.setMessagePort(messagePort)
connections[Stri(newConnection.getID())] = newConnection
end if
else
' Activity on an open connection
connection = connections[Stri(changedID)]
closed = False
if connection.isReadable()
received = connection.receive(buffer, 0, 1024)
if received > 0
timer = CreateObject("roTimespan")
timer.Mark()
str = buffer.ToAsciiString().Left(received)
request = ParseJson(str)
buffer = CreateObject("roByteArray")
buffer[1024] = 0
if request <> Invalid then
response = {}
if request["command"] <> Invalid then
handler = handlersMap[request["command"]]
if handler <> Invalid then
RALE_DebugLog("Handling command: " + request["command"])
RALE_DebugLog("Received arguments: " + FW_AsString(request["args"]))
response = callCommand(handler, request["args"])
else
RALE_ErrorLog("No such command: " + request["command"])
response = getError("No such command")
end if
end if
id = request.uuid
idLen = FW_AsString(Len(id))
json = FormatJson(response)
res = "[start][uuid:" + idLen + "]" + id + json + "[end]"
resList = RALE_splitStringByLength(res, 3000)
count = resList.count() - 1
for i = 0 to count
connection.SendStr(resList[i])
sleep(20)
end for
RALE_DebugLog("Command handled in: " + FW_AsString(timer.TotalMilliseconds()) + "ms")
end if
else if received=0 ' client closed
closed = True
end if
end if
if closed or not connection.eOK()
connection.close()
connections.delete(Stri(changedID))
exit while
end if
end if
end if
end while
tcpListen.close()
for each id in connections
connections[id].close()
end for
RALE_InfoLog("Socket connection closed")
return "connection closed"
End Sub
'IMPORTS=
'=====================
' Types
'=====================
'*************************************************
' FW_IsXmlElement - check if value contains XMLElement interface
' @param value As Dynamic
' @return As Boolean - true if value contains XMLElement interface, else return false
'*************************************************
Function FW_IsXmlElement(value As Dynamic) As Boolean
Return FW_IsValid(value) And GetInterface(value, "ifXMLElement") <> invalid
End Function
'*************************************************
' FW_IsFunction - check if value contains Function interface
' @param value As Dynamic
' @return As Boolean - true if value contains Function interface, else return false
'*************************************************
Function FW_IsFunction(value As Dynamic) As Boolean
Return FW_IsValid(value) And GetInterface(value, "ifFunction") <> invalid
End Function
'*************************************************
' FW_IsBoolean - check if value contains Boolean interface
' @param value As Dynamic
' @return As Boolean - true if value contains Boolean interface, else return false
'*************************************************
Function FW_IsBoolean(value As Dynamic) As Boolean
Return FW_IsValid(value) And GetInterface(value, "ifBoolean") <> invalid
End Function
'*************************************************
' FW_IsInteger - check if value type equals Integer
' @param value As Dynamic
' @return As Boolean - true if value type equals Integer, else return false
'*************************************************
Function FW_IsInteger(value As Dynamic) As Boolean
Return FW_IsValid(value) And GetInterface(value, "ifInt") <> invalid And (Type(value) = "roInt" Or Type(value) = "roInteger" Or Type(value) = "Integer")
End Function
'*************************************************
' FW_IsFloat - check if value contains Float interface
' @param value As Dynamic
' @return As Boolean - true if value contains Float interface, else return false
'*************************************************
Function FW_IsFloat(value As Dynamic) As Boolean
Return FW_IsValid(value) And GetInterface(value, "ifFloat") <> invalid
End Function
'*************************************************
' FW_IsDouble - check if value contains Double interface
' @param value As Dynamic
' @return As Boolean - true if value contains Double interface, else return false
'*************************************************
Function FW_IsDouble(value As Dynamic) As Boolean
Return FW_IsValid(value) And GetInterface(value, "ifDouble") <> invalid
End Function
'*************************************************
' FW_IsLongInteger - check if value contains LongInteger interface
' @param value As Dynamic
' @return As Boolean - true if value contains LongInteger interface, else return false
'*************************************************
Function FW_IsLongInteger(value As Dynamic) As Boolean
Return FW_IsValid(value) And GetInterface(value, "ifLongInt") <> invalid
End Function
'*************************************************
' FW_IsNumber - check if value contains LongInteger or Integer or Double or Float interface
' @param value As Dynamic
' @return As Boolean - true if value is number, else return false
'*************************************************
Function FW_IsNumber(value As Dynamic) As Boolean
Return FW_IsLongInteger(value) or FW_IsDouble(value) or FW_IsInteger(value) or FW_IsFloat(value)
End Function
'*************************************************
' FW_IsList - check if value contains List interface
' @param value As Dynamic
' @return As Boolean - true if value contains List interface, else return false
'*************************************************
Function FW_IsList(value As Dynamic) As Boolean
Return FW_IsValid(value) And GetInterface(value, "ifList") <> invalid
End Function
'*************************************************
' FW_IsArray - check if value contains Array interface
' @param value As Dynamic
' @return As Boolean - true if value contains Array interface, else return false
'*************************************************
Function FW_IsArray(value As Dynamic) As Boolean
Return FW_IsValid(value) And GetInterface(value, "ifArray") <> invalid
End Function
'*************************************************
' FW_IsAssociativeArray - check if value contains AssociativeArray interface
' @param value As Dynamic
' @return As Boolean - true if value contains AssociativeArray interface, else return false
'*************************************************
Function FW_IsAssociativeArray(value As Dynamic) As Boolean
Return FW_IsValid(value) And GetInterface(value, "ifAssociativeArray") <> invalid
End Function
'*************************************************
' FW_IsSGNode - check if value contains SGNodeChildren interface
' @param value As Dynamic
' @return As Boolean - true if value contains SGNodeChildren interface, else return false
'*************************************************
Function FW_IsSGNode(value As Dynamic) As Boolean
Return FW_IsValid(value) And GetInterface(value, "ifSGNodeChildren") <> invalid
End Function
'*************************************************
' FW_IsString - check if value contains String interface
' @param value As Dynamic
' @return As Boolean - true if value contains String interface, else return false
'*************************************************
Function FW_IsString(value As Dynamic) As Boolean
Return FW_IsValid(value) And GetInterface(value, "ifString") <> invalid
End Function
'*************************************************
' FW_IsNotEmptyString - check if value contains String interface and length more 0
' @param value As Dynamic
' @return As Boolean - true if value contains String interface and length more 0, else return false
'*************************************************
Function FW_IsNotEmptyString(value As Dynamic) As Boolean
Return FW_IsString(value) and len(value) > 0
End Function
'*************************************************
' FW_IsDateTime - check if value contains DateTime interface
' @param value As Dynamic
' @return As Boolean - true if value contains DateTime interface, else return false
'*************************************************
Function FW_IsDateTime(value As Dynamic) As Boolean
Return FW_IsValid(value) And (GetInterface(value, "ifDateTime") <> invalid Or Type(value) = "roDateTime")
End Function
'*************************************************
' FW_IsValid - check if value initialized and not equal invalid
' @param value As Dynamic
' @return As Boolean - true if value initialized and not equal invalid, else return false
'*************************************************
Function FW_IsValid(value As Dynamic) As Boolean
Return Type(value) <> "<uninitialized>" And value <> invalid
End Function
'*************************************************
' FW_ValidStr - return value if his contains String interface else return empty string
' @param value As Object
' @return As String - value if his contains String interface else return empty string
'*************************************************
Function FW_ValidStr(obj As Object) As String
if obj <> invalid and GetInterface(obj, "ifString") <> invalid
return obj
else
return ""
endif
End Function
'*************************************************
' FW_AsString - convert input to String if this possible, else return empty string
' @param input As Dynamic
' @return As String - return converted string
'*************************************************
Function FW_AsString(input As Dynamic) As String
If FW_IsValid(input) = False Then
Return ""
Else If FW_IsString(input) Then
Return input
Else If FW_IsInteger(input) or FW_IsLongInteger(input) or FW_IsBoolean(input)Then
Return input.ToStr()
Else If FW_IsFloat(input) or FW_IsDouble(input) Then
Return Str(input).Trim()
Else If FW_IsBoolean(input) Then
If input Then
Return "true"
Else
Return "false"
End If
Else If FW_IsSGNode(input) Then
return input.subtype()
Else If FW_IsArray(input) Then
result = ""
For each item in input
result = result + FW_AsString(item) + ", "
End For
Return "[" + Left(result, Len(result) - 2) + "]"
Else If FW_IsAssociativeArray(input) Then
result = ""
For each key in input
result = result + key + " : " + FW_AsString(input[key]) + ", "
End For
Return "{ " + Left(result, Len(result) - 2) + " }"
End If
End Function
'*************************************************
' FW_AsInteger - convert input to Integer if this possible, else return 0
' @param input As Dynamic
' @return As Integer - return converted Integer
'*************************************************
Function FW_AsInteger(input As Dynamic) As Integer
If FW_IsValid(input) = False Then
Return 0
Else If FW_IsString(input) Then
Return input.ToInt()
Else If FW_IsInteger(input) Then
Return input
Else If FW_IsFloat(input) or FW_IsDouble(input) or FW_IsLongInteger(input) Then
Return Int(input)
Else
Return 0
End If
End Function
'*************************************************
' FW_AsLongInteger - convert input to LongInteger if this possible, else return 0
' @param input As Dynamic
' @return As Integer - return converted LongInteger
'*************************************************
Function FW_AsLongInteger(input As Dynamic) As LongInteger
If FW_IsValid(input) = False Then
Return 0
Else If FW_IsString(input) Then
Return FW_AsInteger(input)
Else If FW_IsLongInteger(input) or FW_IsFloat(input) or FW_IsDouble(input) or FW_IsInteger(input) Then
Return input
Else
Return 0
End If
End Function
'*************************************************
' FW_AsFloat - convert input to Float if this possible, else return 0.0
' @param input As Dynamic
' @return As Float - return converted Float
'*************************************************
Function FW_AsFloat(input As Dynamic) As Float
If FW_IsValid(input) = False Then
Return 0.0
Else If FW_IsString(input) Then
Return input.ToFloat()
Else If FW_IsInteger(input) Then
Return (input / 1)
Else If FW_IsFloat(input) or FW_IsDouble(input) or FW_IsLongInteger(input) Then
Return input
Else
Return 0.0
End If
End Function
'*************************************************
' FW_AsDouble - convert input to Double if this possible, else return 0.0
' @param input As Dynamic
' @return As Float - return converted Double
'*************************************************
Function FW_AsDouble(input As Dynamic) As Double
If FW_IsValid(input) = False Then
Return 0.0
Else If FW_IsString(input) Then
Return FW_AsFloat(input)
Else If FW_IsInteger(input) or FW_IsLongInteger(input) or FW_IsFloat(input) or FW_IsDouble(input) Then
Return input
Else
Return 0.0
End If
End Function
'*************************************************
' FW_AsBoolean - convert input to Boolean if this possible, else return False
' @param input As Dynamic
' @return As Boolean
'*************************************************
Function FW_AsBoolean(input As Dynamic) As Boolean
If FW_IsValid(input) = False Then
Return False
Else If FW_IsString(input) Then
Return LCase(input) = "true"
Else If FW_IsInteger(input) Or FW_IsFloat(input) Then
Return input <> 0
Else If FW_IsBoolean(input) Then
Return input
Else
Return False
End If
End Function
'*************************************************
' FW_AsArray - if type of value equals array return value, else return array with one element [value]
' @param value As Object
' @return As Object - roArray
'*************************************************
Function FW_AsArray(value As Object) As Object
If FW_IsValid(value)
If Not FW_IsArray(value) Then
Return [value]
Else
Return value
End If
End If
Return []
End Function
'*************************************************
' FW_ValidAA - check if obj contains all keys
' @param obj As Object - roAssociativeArray
' @param keys as Object - roArray of keys(string) to check
' @param delim as String - key delimiter (default = ".")
' @return As Boolean - return true if type of obj equals AssociativeArray and obj contains all keys, else return false
'*************************************************
Function FW_ValidAA(obj As Object, keys as Object, delim = "." as String) as Boolean
'All keys on level 0
if not FW_IsAssociativeArray(obj) or not FW_IsNotEmptyString(delim) or not FW_isArray(keys) then
return false
end if
for each key in Keys
subKeys = FW_Split(key, delim)
aa_check = obj
'go down the hierarchy key.subkey.subsubkey...
for each subkey in subKeys
if FW_IsAssociativeArray(aa_check) and aa_check[subkey] <> invalid then
aa_check = aa_check[subkey]
else
print "Key::"; key
print "subkey::"; subkey
printAA(obj)
return false
end if
end for
end for
return true
end Function
'*************************************************
' FW_GetSubElement - check if obj contains subElementTree element and return him
' @param element as Dynamic - roAssociativeArray
' @param subElementTree as Dynamic - String keys tree
' @param delim as String - key delimiter (default = ".")
' @return As Dynamic - if exist obj[subElementTree] value else invalid
'*************************************************
function FW_GetSubElement(element as Dynamic, subElementTree as Dynamic, delim = "." as String) as Dynamic
if FW_IsValid(element) = false or FW_IsValid(subElementTree) = false then
return invalid
end if
subElementTreeArray = []
result = element
if FW_IsNotEmptyString(subElementTree) and FW_IsNotEmptyString(delim) then
subElementTreeArray = FW_Split(subElementTree, delim)
else if FW_IsArray(subElementTree) then
subElementTreeArray = subElementTree
else
result = invalid
end if
for each field in subElementTreeArray
if FW_IsSGNode(result) then
key = FW_AsString(field)
if result.HasField(key) then
result = result.GetField(key)
else
index = FW_AsInteger(field)
if (index = 0 and key <> "0") or index < 0 or index >= result.GetChildCount() then
result = invalid
exit for
end if
result = result.GetChild(index)
end if
else if FW_IsAssociativeArray(result) then
result = result.LookupCI(FW_AsString(field)) 'use case-insensitive lookup for AAs
else if FW_IsArray(result) then
index = FW_AsInteger(field)
if (index = 0 and FW_AsString(field) <> "0") or index < 0 or index >= result.Count() then 'index is not an integer or out of range?
result = invalid
exit for
end if
result = result[index]
else
result = invalid
exit for
end if
end for
return result
end function
' RALE_parseType - Parse type name to brs type
' @param {string} _type - type
' @return {string} - brs type
Function RALE_parseType(_type as String) as String
_type = LCase(_type)
convertMap = {
"rointeger" : "integer",
"roint" : "integer",
"int" : "integer",
"num" : "integer",
"number" : "integer",
"rofloat" : "float",
"rostring" : "string",
"text" : "string",
"str" : "string",
"roboolean" : "bool",
"boolean" : "bool",
"rosgnode" : "node",
"roarray" : "array",
"array" : "array",
"arr" : "array",
"object" : "assocarray",
"associativearray" : "assocarray",
"roassociativearray" : "assocarray"
}
if convertMap[_type] <> Invalid then return convertMap[_type]
return _type
End function
' RALE_convertToType - Convert variable to type
' @param {dynamic} value - variable value
' @param {string} _type - type to which you want to convert
' @return {dynamic} - new variable value
Function RALE_convertToType(value as Dynamic, _type as String) as Dynamic
_type = RALE_parseType(_type)
convertMap = {
"integer" : FW_AsInteger,
"float" : FW_AsFloat,
"bool" : FW_AsBoolean,
"string" : FW_AsString,
"array" : FW_AsArray,
}
if _type = "assocarray" and type(value) <> "roAssociativeArray" then
value = CreateObject("roAssociativeArray")
end if
if _type = "array" and type(value) <> "roArray" then
value = FW_AsString(value).split(",")
end if
if _type = "node" then
value = CreateObject("roSGNode", FW_AsString(value))
end if
if convertMap[_type] <> Invalid then
return convertMap[_type](value)
end if
return value
End function
' RALE_getNodeByItem - Returns node field or child by item data
' @param {dynamic} item - item data
' @param {dynamic} node - type to which you want to convert
' @return {dynamic} - node field or child
Function RALE_getNodeByItem(item as Dynamic, node as Dynamic) as Dynamic
itemType = type(item)
if itemType <> "roAssociativeArray" then return Invalid
nodeType = type(node)
if nodeType <> "roSGNode" and nodeType <> "roArray" and nodeType <> "roAssociativeArray" then
return Invalid
end if
if nodeType = "roSGNode" and item.child <> Invalid then
index = FW_AsInteger(item.child)
if index = 0 and FW_AsString(item.child) <> "0" then return Invalid
return node.getChild(index)
else if item.field <> Invalid then
key = item.field
if nodeType = "roSGNode" then
if node.hasField(key) then
return node.getField(key)
else
return Invalid
end if
else
key = item.field
if nodeType = "roArray" then
key = FW_AsInteger(key)
if key = 0 and FW_AsString(item.field) <> "0" then return Invalid
end if
return node[key]
end if
else
return Invalid
end if
End function
' RALE_getNodeByPath - Returns node by path
' @param {object} root - root of nodes (main scene)
' @param {array} path - path to node (for example [{ child: 1 }, { field: "fieldId" }])
' @return {dynamic} - node or Invalid (if no node by this path)
Function RALE_getNodeByPath(root as Dynamic, path as Object) as Dynamic
node = root
if type(path) <> "roArray" then return Invalid
for each item in path
node = RALE_getNodeByItem(item, node)
if node = Invalid then return Invalid
end for
return node
End function
' RALE_getNodeIndex - Returns node index
' @param {object} node - node
' @param {object} parent - node parent
' @return {integer} - node index
Function RALE_getNodeIndex(node, parent) as Integer
for i = 0 to parent.getchildCount() - 1
if parent.getChild(i).isSameNode(node) then return i
end for
return -1
End Function
' RALE_getNodePath - Returns node path
' @param {object} node - node
' @param {object} root - root
' @return {array} - node path
Function RALE_getNodePath(node, root) as Object
path = []
if node <> Invalid and root <> Invalid then
parent = node.getParent()
while parent <> Invalid
index = RALE_getNodeIndex(node, parent)
if index = -1 then exit while
node = parent
parent = node.getParent()
path.unshift({ child: index })
if root.isSameNode(node) then exit while
end while
end if
return path
End Function
' RALE_getNodeListByPath - Returns node by path
' @param {object} root - root of nodes (main scene)
' @param {array} path - path to node (for example [{ child: 1 }, { field: "fieldId" }])
' @return {dynamic} - list of nodes (parent nodes and selected one) or Invalid (if no node by this path)
Function RALE_getNodeListByPath(root as Dynamic, path as Object) as Dynamic
node = root
nodeList = [node]
if type(path) <> "roArray" then return Invalid
for each item in path
node = RALE_getNodeByItem(item, node)
if node = Invalid then return Invalid
nodeList.push(node)
end for
return nodeList
End function
' RALE_getNodeIndexByPath - Returns node index or id by path
' @param {array} path - path to node (for example [{ child: 1 }, { field: "fieldId" }])
' @return {dynamic} - node index or id or Invalid (if no node by this path)
Function RALE_getNodeIndexByPath(path) as Dynamic
if type(path) <> "roArray" then return Invalid
length = path.count()
if length = 0 then return ""
index = path[length - 1]
if type(index) <> "roAssociativeArray" then return Invalid
if index.child <> Invalid then
index = index.child
else if index.field <> Invalid then
index = index.field
else
return Invalid
end if
return index
End function
' RALE_splitStringByLength - Splits string on parts and store in array
' @param {string} str - string which will be splitted
' @param {integer} length - max part langth
' @return {array} - splitted string
Function RALE_splitStringByLength(str, length) as Dynamic
list = []
strLength = str.len()
currentPosition = 0
while currentPosition < strLength
list.push(str.mid(currentPosition, length))
currentPosition = currentPosition + length
end while
return list
End function
' RALE_forEachNode - Goes through all the children and call the callback function
' @param {object} node - root node
' @param {object} options - options
' @param {function} callback - callback function that will be called for each node
Function RALE_forEachChild(node, options, callback, storage) as Dynamic
nodeType = type(node)
maxLevel = 50
if FW_IsInteger(options.maxLevel) then
maxLevel = options.maxLevel
end if
if nodeType = "roSGNode" then
level = 0
stateStack = []
stateObj = {
index : 0
node : node
}
childCount = node.getchildcount()
while true
if childCount > stateObj.index then
childIndex = stateObj.index
childNode = node.getchild(childIndex)
stateObj.index++
if childNode <> invalid then
childCountTmp = childNode.getchildcount()
parentObj = stateObj
childObj = Invalid
if childCountTmp > 0 and level < maxLevel then
childCount = childCountTmp
stateStack.push(stateObj)
level++
stateObj = {
index : 0
node : childNode
}
childObj = stateObj
node = childNode
end if
result = callback(childNode, childIndex, parentObj, childObj, storage)
if not result then
exit while
end if
end if
else if level > 0 then
stateObj = stateStack.pop()
level--
node = stateObj.node
childCount = stateObj.node.getchildcount()
else
exit while
end if
end while
else
return false
end if
return true
End function
' RALE_forEachRegistryField - Goes through all the roRegistry and call the callback function for each field
' @param {function} callback - callback function that will be called for each field
Function RALE_forEachRegistryField(callback, storage) as Dynamic
Registry = CreateObject("roRegistry")
for each section in Registry.GetSectionList()
RegistrySection = CreateObject("roRegistrySection", section)
for each key in RegistrySection.GetKeyList()
callback(RegistrySection.Read(key), key, section, storage)
end for
end for
End function
' RALE_forEachRegistrySection - Goes through all the roRegistry and call the callback function for each section
' @param {function} callback - callback function that will be called for each section
Function RALE_forEachRegistrySection(callback, storage) as Dynamic
Registry = CreateObject("roRegistry")
for each section in Registry.GetSectionList()
callback(CreateObject("roRegistrySection", section), section, storage)
end for
End function
Function RALE_DebugLog(message as String)
callCommand("UIThread_log", {
message: message,
status: "debug"
})
End Function
Function RALE_InfoLog(message as String)
callCommand("UIThread_log", {
message: message,
status: "info"
})
End Function
Function RALE_WarningLog(message as String)
callCommand("UIThread_log", {
message: message,
status: "warning"
})
End Function
Function RALE_ErrorLog(message as String)
callCommand("UIThread_log", {
message: message,
status: "error"
})
End Function
]]>
</script>
</component>