Пример #1
0
class _WaitMS(_WaitBase):
    def __init__(self, scriptRunner, msec):
        self._waitTimer = Timer()
        _WaitBase.__init__(self, scriptRunner)
        self._waitTimer.start(msec / 1000.0, self._continue)

    def cancelWait(self):
        self._waitTimer.cancel()
Пример #2
0
class _WaitMS(_WaitBase):
    def __init__(self, scriptRunner, msec):
        self._waitTimer = Timer()
        _WaitBase.__init__(self, scriptRunner)
        self._waitTimer.start(msec / 1000.0, self._continue)

    def cancelWait(self):
        self._waitTimer.cancel()
Пример #3
0
class _WaitThread(_WaitBase):
    def __init__(self, scriptRunner, func, *args, **kargs):
        #       print "_WaitThread.__init__(%r, *%r, **%r)" % (func, args, kargs)
        self._pollTimer = Timer()
        _WaitBase.__init__(self, scriptRunner)

        if not callable(func):
            raise ValueError("%r is not callable" % func)

        self.queue = Queue.Queue()
        self.func = func

        self.threadObj = threading.Thread(target=self.threadFunc,
                                          args=args,
                                          kwargs=kargs)
        self.threadObj.setDaemon(True)
        self.threadObj.start()
        self._pollTimer.start(_PollDelaySec, self.checkEnd)
#       print "_WaitThread__init__(%r) done" % self.func

    def checkEnd(self):
        if self.threadObj.isAlive():
            self._pollTimer.start(_PollDelaySec, self.checkEnd)
            return


#       print "_WaitThread(%r).checkEnd: thread done" % self.func

        retVal = self.queue.get()
        #       print "_WaitThread(%r).checkEnd; retVal=%r" % (self.func, retVal)
        self._continue(val=retVal)

    def cleanup(self):
        #       print "_WaitThread(%r).cleanup" % self.func
        self._pollTimer.cancel()
        self.threadObj = None

    def threadFunc(self, *args, **kargs):
        retVal = self.func(*args, **kargs)
        self.queue.put(retVal)
Пример #4
0
class _WaitThread(_WaitBase):
    def __init__(self, scriptRunner, func, *args, **kargs):
        #       print "_WaitThread.__init__(%r, *%r, **%r)" % (func, args, kargs)
        self._pollTimer = Timer()
        _WaitBase.__init__(self, scriptRunner)

        if not callable(func):
            raise ValueError("%r is not callable" % func)

        self.queue = Queue.Queue()
        self.func = func

        self.threadObj = threading.Thread(target=self.threadFunc, args=args, kwargs=kargs)
        self.threadObj.setDaemon(True)
        self.threadObj.start()
        self._pollTimer.start(_PollDelaySec, self.checkEnd)

    #       print "_WaitThread__init__(%r) done" % self.func

    def checkEnd(self):
        if self.threadObj.isAlive():
            self._pollTimer.start(_PollDelaySec, self.checkEnd)
            return
        #       print "_WaitThread(%r).checkEnd: thread done" % self.func

        retVal = self.queue.get()
        #       print "_WaitThread(%r).checkEnd; retVal=%r" % (self.func, retVal)
        self._continue(val=retVal)

    def cleanup(self):
        #       print "_WaitThread(%r).cleanup" % self.func
        self._pollTimer.cancel()
        self.threadObj = None

    def threadFunc(self, *args, **kargs):
        retVal = self.func(*args, **kargs)
        self.queue.put(retVal)
Пример #5
0
class KeyDispatcher(object):
    """
    A keyword dispatcher sets keyword variables based on keyword/value data.
    
    Inputs:
    - name: used as the actor when the dispatcher reports errors
    - connection: an RO.Conn.HubConnection object or similar;
      if omitted, an RO.Conn.HubConnection.NullConnection is used,
      which is useful for testing.
    - logFunc: a function that logs a message. Argument list must be:
        (msgStr, severity, actor, cmdr)
        where the first argument is positional and the others are by name

    Fields:
    - readUnixTime: unix time at which last message received from connection; 0 if no message ever received.
    """
    def __init__(self,
        name = "KeyDispatcher",
        connection = None,
        logFunc = None,
    ):
        self.name = name
        self.readUnixTime = 0

        self._isConnected = False

        # keyVarListDict keys are (actor, keyword) tuples and values are lists of KeyVariables
        self.keyVarListDict = {}
        
        # cmdDict keys are command ID and values are KeyCommands
        self.cmdDict = {}

        # refreshCmdDict contains information about keyVar refresh commands:
        # key is: actor, refresh command, e.g. as returned by keyVar.getRefreshInfo()
        # refresh command: set of keyVars that use this command
        self.refreshCmdDict = {}
        
        self._checkCmdTimer = Timer()
        self._checkRemCmdTimer = Timer()
        self._refreshAllTimer = Timer()
        self._refreshNextTimer = Timer()
        
        if connection:
            self.connection = connection
            self.connection.addReadCallback(self.doRead)
            self.connection.addStateCallback(self._connStateCallback)
        else:
            self.connection = RO.Comm.HubConnection.NullConnection()
        self._isConnected = self.connection.isConnected
        self.userCmdIDGen = RO.Alg.IDGen(1, _CmdNumWrap)
        self.refreshCmdIDGen = RO.Alg.IDGen(_CmdNumWrap + 1, 2 * _CmdNumWrap)
        
        self.setLogFunc(logFunc)        
        
        self.refreshAllVar()
        self.checkCmdTimeouts()
        
    def abortCmdByID(self, cmdID):
        """Abort the command with the specified ID.
        
        Issue the command specified by cmdVar.abortCmdStr, if present.
        Report the command as failed.
        
        Has no effect if the command was never dispatched (cmdID == None)
        or has already finished.
        """
        if cmdID == None:
            return

        cmdVar = self.cmdDict.get(cmdID)
        if not cmdVar:
            return

        # check isDone
        if cmdVar.isDone():
            return
        
        # if relevant, issue abort command, with no callbacks
        if cmdVar.abortCmdStr and self._isConnected:
            abortCmd = RO.KeyVariable.CmdVar(
                cmdStr = cmdVar.abortCmdStr,
                actor = cmdVar.actor,
            )
            self.executeCmd(abortCmd)
            
        # report command as aborted
        errMsgDict = self.makeMsgDict (
            cmdID = cmdVar.cmdID,
            dataStr = "Aborted; Actor=%r; Cmd=%r" % (
                cmdVar.actor, cmdVar.cmdStr),
        )
        self._replyCmdVar(cmdVar, errMsgDict)

    def addKeyVar(self, keyVar):
        """
        Adds a keyword variable to the list.

        Inputs:
        - keyVar: the keyword variable; typically of class RO.KeyVariable
          but can be any object that:
          - has property: keyword (a string)
          - has method "set" with arguments:
            - valueTuple (positional): a tuple of one or more values for the keyword
              the values may be strings, even if another type is expected
            - keyword (by name): the keyword
            - msgDict (by name): the full message dictionary
        """
        dictKey = (keyVar.actor, keyVar.keyword.lower())
        # get list of keyVars, adding it if not already present
        keyList = self.keyVarListDict.setdefault(dictKey, [])
        # append new keyVar to the list
        keyList.append(keyVar)

        if keyVar.hasRefreshCmd():
            refreshInfo = keyVar.getRefreshInfo()
            keyVarSet = self.refreshCmdDict.get(refreshInfo)
            if keyVarSet:
                keyVarSet.add(keyVar)
            else:
                self.refreshCmdDict[refreshInfo] = set((keyVar,))
            if self._isConnected:
                self._refreshAllTimer.start(_ShortInterval, self.refreshAllVar, False)

    def checkCmdTimeouts(self):
        """Check all pending commands for timeouts"""
#       print "RO.KeyDispatcher.checkCmdTimeouts()"
        
        # cancel pending update, if any
        self._checkCmdTimer.cancel()
        self._checkRemCmdTimer.cancel()
        
        # iterate over a copy of the values
        # so we can modify the dictionary while checking command timeouts
        cmdVarIter = iter(list(self.cmdDict.values()))
        self._checkRemCmdTimeouts(cmdVarIter)
        
    def dispatch(self, msgDict):
        """
        Updates the appropriate entries based on the supplied message data.

        Inputs:
        - msgDict: message dictionary. Required fields:
          - cmdr: name of commander that triggered the message (string)
          - cmdID: command ID that triggered the message (int)
          - actor: the actor that generated the message (string)
          - msgType: message type (character)
          - data: dict of keyword: data_tuple entries;
            data_tuple is always a tuple, even if it contains one or zero values
        """
#       print "dispatching", msgDict
        
        # extract user number, command number and data dictionary; die if absent
        cmdr  = msgDict["cmdr"]
        cmdID   = msgDict["cmdID"]
        actor = msgDict["actor"]
        msgType  = msgDict["msgType"]
        dataDict = msgDict["data"]

        # handle keywords
        # note: keywords from actor keys.<actor>
        # should be handled as if from <actor>
        if actor.startswith("keys."):
            keyActor = actor[5:]
        else:
            keyActor = actor
        for keywd, valueTuple in dataDict.items():
            dictKey = (keyActor, keywd.lower())
            keyVarList = self.keyVarListDict.get(dictKey, [])
            for keyVar in keyVarList:
                try:
                    keyVar.set(valueTuple, msgDict = msgDict)
                except:
                    traceback.print_exc(file=sys.stderr)

        # if you are the commander for this message,
        # execute the command callback (if any)
        if cmdr == self.connection.cmdr:
            # get the command for this command id, if any
            cmdVar = self.cmdDict.get(cmdID, None)
            if cmdVar != None:
                # send reply but don't log (that's already been done)
                self._replyCmdVar(cmdVar, msgDict, doLog=False)
                    
    def doRead(self, sock, msgStr):
        """Reads, parses and dispatches a message from the hub
        
        Sets self.readUnixTime to time.time()
        """
        # parse message; if it fails, log it as an error
        self.readUnixTime = time.time()
        try:
            msgDict = RO.ParseMsg.parseHubMsg(msgStr)
        except Exception as e:
            self.logMsg(
                msgStr = "CouldNotParse; Msg=%r; Text=%r" % (msgStr, RO.StringUtil.strFromException(e)),
                severity = RO.Constants.sevError,
            )
            return
        
        # log message
        self.logMsgDict(msgDict)
        
        # dispatch message
        try:
            self.dispatch(msgDict)
        except Exception as e:
            sys.stderr.write("Could not dispatch: %r\n" % (msgDict,))
            traceback.print_exc(file=sys.stderr)
                
    def executeCmd(self, cmdVar):
        """Executes the command (of type RO.KeyVariable.CmdVar) by performing the following tasks:
        - Sets the command number
        - Sets the start time
        - Puts the command on the keyword dispatcher queue
        - Issues the command to the server

        Inputs:
        - cmdVar: the command, of class RO.KeyVariable.CmdVar
            
        Note:
        - we always increment cmdID since every command must have a unique command ID
          (even commands that go to different actors); this simplifies the
          dispatcher code and also makes the hub's life easier
          (since it can report certain kinds of failures using actor=hub).
        """
        if not self._isConnected:
            errMsgDict = self.makeMsgDict(
                dataStr = "Failed; Actor=%r; Cmd=%r; Text=\"not connected\"" % (
                    cmdVar.actor, cmdVar.cmdStr),
            )
            self._replyCmdVar(cmdVar, errMsgDict)
            return
        
        while True:
            if cmdVar.isRefresh:
                cmdID = next(self.refreshCmdIDGen)
            else:
                cmdID = next(self.userCmdIDGen)
            if cmdID not in self.cmdDict:
                break
        self.cmdDict[cmdID] = cmdVar
        cmdVar._setStartInfo(self, cmdID)
    
        try:
            fullCmd = "%d %s %s" % (cmdVar.cmdID, cmdVar.actor, cmdVar.cmdStr)
            self.connection.writeLine (fullCmd)
#             self.logMsg (
#                 msgStr = fullCmd,
#                 actor = cmdVar.actor,
#                 cmdID = cmdVar.cmdID,
#             )
#           print "executing:", fullCmd
        except Exception as e:
            errMsgDict = self.makeMsgDict(
                cmdID = cmdVar.cmdID,
                dataStr = "WriteFailed; Actor=%r; Cmd=%r; Text=%r" % (
                    cmdVar.actor, cmdVar.cmdStr, RO.StringUtil.strFromException(e)),
            )
            self._replyCmdVar(cmdVar, errMsgDict)

    @staticmethod
    def getMaxUserCmdID():
        """Return the maximum user command ID number.
        
        User command ID numbers range from 1 through getMaxUserCmdID()
        Refresh command ID numbers range from getMaxUserCmdID() + 1 through 2 * getMaxUserCmdID()
        """
        return _CmdNumWrap
        
    def logMsg(self,
        msgStr,
        severity = RO.Constants.sevNormal,
        actor = "TUI",
        cmdr = None,
        cmdID = 0,
    ):
        """Writes a message to the log.
        
        If no logFunc was supplied then the message is printed to stderr.
        On error, prints a message to stderr and returns normally.
        
        Inputs:
        - msgStr: message to display; a final \n is appended
        - severity: message severity (an RO.Constants.sevX constant)
        - actor: name of actor
        - cmdr: commander; defaults to self
        """
        if not self.logFunc:
            sys.stderr.write(msgStr + "\n")
            return

        try:
            self.logFunc(
                msgStr,
                severity = severity,
                actor = actor,
                cmdr = cmdr,
                cmdID = cmdID,
            )
        except Exception as e:
            sys.stderr.write("Could not log: %r; severity=%r; actor=%r; cmdr=%r\n" % \
                (msgStr, severity, actor, cmdr))
            traceback.print_exc(file=sys.stderr)
    
    def logMsgDict(self, msgDict):
        try:
            msgType = msgDict["msgType"].lower()
            severity = RO.KeyVariable.TypeDict[msgType][1]
            self.logMsg(
                msgStr = msgDict["msgStr"],
                severity = severity,
                actor = msgDict["actor"],
                cmdr = msgDict["cmdr"],
                cmdID = msgDict["cmdID"],
            )
        except Exception as e:
            sys.stderr.write("Could not log message dict:\n%r\n" % (msgDict,))
            traceback.print_exc(file=sys.stderr)
        
    def makeMsgDict(self,
        cmdr = None,
        cmdID = 0,
        actor = None,
        msgType = "f",
        dataStr = "",
    ):
        """Generate a hub message based on the supplied data.
        Useful for reporting internal errors.
        """
        if cmdr == None:
            cmdr = self.connection.cmdr
        if actor == None:
            actor = self.name

        headerStr = "%s %d %s %s" % (
            cmdr,
            cmdID,
            actor,
            msgType,
        )
        msgStr = " ".join((headerStr, dataStr))
        try:
            return RO.ParseMsg.parseHubMsg(msgStr)
        except Exception as e:
            sys.stderr.write("Could not make message dict from %r; error: %s" % (msgStr, e))
            traceback.print_exc(file=sys.stderr)
            msgDict = RO.ParseMsg.parseHubMsg(headerStr)
            msgDict["msgStr"] = msgStr
            msgDict["data"] = {}
            return msgDict
    
    def refreshAllVar(self, resetAll=True):
        """Examines all keywords, looking for ones that need updating
        and issues the appropriate refresh commands.
        
        Inputs:
        - resetAll: reset all keyword variables to notCurrent
        """
#         print "refreshAllVar()"

        # cancel pending update, if any
        self._refreshAllTimer.cancel()
        self._refreshNextTimer.cancel()
    
        if resetAll:
            # clear the refresh command dict
            # and invalidate all keyVars
            # (leave pending refresh commands alone; they will time out)
            for keyVarList in list(self.keyVarListDict.values()):
                for keyVar in keyVarList:
                    keyVar.setNotCurrent()
        
        self._sendNextRefreshCmd()

    def removeKeyVar(self, keyVar):
        """
        Removes the specified keyword variable,
        returning whatever was removed, or None if keyVar not found.
        See also "add".

        Inputs:
        - keyVar: the keyword variable to remove

        Returns:
        - keyVar, if present, None otherwise.
        """
        dictKey = (keyVar.actor, keyVar.keywd.lower())
        keyVarList = self.keyVarListDict.get(dictKey, [])
        if keyVar not in keyVarList:
            return None
        keyVarList.remove(keyVar)
        
        # remove refresh command, if present
        keyVarSet = self.refreshCmdDict.get(keyVar.getRefreshInfo())
        if keyVarSet and keyVar in keyVarSet:
            keyVarSet.remove(keyVar)
            if not keyVarSet:
                # that was the only keyVar using this refresh command
                del(self.refreshCmdDict[keyVar.getRefreshInfo()])
        return keyVar

    def setLogFunc(self, logFunc=None):
        """Sets the log output device, or clears it if none specified.
        
        The function must take the following arguments: (msgStr, severity, actor, cmdr)
        where the first argument is positional and the others are by name
        """
        self.logFunc = logFunc
    
    def _updateRefreshCmds(self):
        """Update the cache of refresh commands by scanning the keyVars.
        """
        self.refreshCmdDict = {}
        for keyVarList in self.keyVarListDict.values():
            for keyVar in keyVarList:
                if keyVar.hasRefreshCmd():
                    refreshInfo = keyVar.getRefreshInfo()
                    keyVarSet = self.refreshCmdDict.get(refreshInfo)
                    if keyVarSet:
                        keyVarSet.add(keyVar)
                    else:
                        self.refreshCmdDict[refreshInfo] = set((keyVar,))

    def _checkRemCmdTimeouts(self, cmdVarIter):
        """Helper function for checkCmdTimeouts.
        Check the remaining command variables in cmdVarIter.
        If a timeout is found, time out that one command
        and schedule myself to run again shortly
        (thereby giving other events a chance to run).

        Once the iterator is exhausted, schedule
        my parent function checkCmdTimeouts to run
        at the usual interval later.
        """
#       print "RO.KeyDispatcher._checkRemCmdTimeouts(%s)" % cmdVarIter
        try:
            errMsgDict = None
            currTime = time.time()
            for cmdVar in cmdVarIter:
                # if cmd still exits (i.e. has not been deleted for other reasons)
                # check if it has a time limit and has timed out
                if cmdVar.cmdID not in self.cmdDict:
                    continue
                if not self._isConnected:
                    errMsgDict = self.makeMsgDict (
                        cmdID = cmdVar.cmdID,
                        dataStr = "Aborted; Actor=%r; Cmd=%r; Text=\"disconnected\"" % (
                            cmdVar.actor, cmdVar.cmdStr),
                    )
                    # no connection, so cannot send abort command
                    cmdVar.abortCmdStr = ""
                    break
                elif cmdVar.maxEndTime and (cmdVar.maxEndTime < currTime):
                    # time out this command
                    errMsgDict = self.makeMsgDict (
                        cmdID = cmdVar.cmdID,
                        dataStr = "Timeout; Actor=%r; Cmd=%r" % (
                            cmdVar.actor, cmdVar.cmdStr),
                    )
                    break
            if errMsgDict:
                self._replyCmdVar(cmdVar, errMsgDict)
    
                # schedule myself to run again shortly
                # (thereby giving other time to other events)
                # continuing where I left off
                self._checkRemCmdTimer.start(_ShortInterval, self._checkRemCmdTimeouts, cmdVarIter)
        except:
            sys.stderr.write ("RO.KeyDispatcher._checkRemCmdTimeouts failed\n")
            traceback.print_exc(file=sys.stderr)

        # finished checking all commands in the current cmdVarIter;
        # schedule a new checkCmdTimeouts at the usual interval
        self._checkCmdTimer.start(_TimeoutInterval, self.checkCmdTimeouts)

    def _connStateCallback(self, conn):
        """If connection state changes, update refresh variables.
        """
        wasConnected = self._isConnected
        self._isConnected = conn.isConnected

        if wasConnected != self._isConnected:
            self._refreshAllTimer.start(_ShortInterval, self.refreshAllVar)

    def _refreshCmdCallback(self, msgType, msgDict, cmdVar):
        """Refresh command callback; complain if command failed or some keyVars not updated
        """
        if not cmdVar.isDone:
            return
        refreshInfo = (cmdVar.actor, cmdVar.cmdStr)
        keyVarSet = self.refreshCmdDict.get(refreshInfo, set())
        if cmdVar.didFail():
            keyVarNamesStr = ", ".join(sorted([kv.keyword for kv in keyVarSet]))
            errMsg = "Refresh command %s %s failed; keyVars not refreshed: %s" % \
                (cmdVar.actor, cmdVar.cmdStr, keyVarNamesStr)
            self.logMsg(
                msgStr = errMsg,
                severity = RO.Constants.sevWarning,
                cmdID = cmdVar.cmdID,
            )
        elif keyVarSet:
            aKeyVar = next(iter(keyVarSet))
            actor = aKeyVar.actor
            missingKeyVarNamesStr = ", ".join(sorted([kv.keyword for kv in keyVarSet if not kv.isCurrent()]))
            if missingKeyVarNamesStr:
                errMsg = "No refresh data for %s keyVars: %s" % (actor, missingKeyVarNamesStr)
                self.logMsg(errMsg, severity=RO.Constants.sevWarning)
        else:
            # all of the keyVars were removed or there is a bug
            errMsg = "Warning: refresh command %s %s finished but no keyVars found\n" % refreshInfo
            self.logMsg(errMsg, severity=RO.Constants.sevWarning)
    
    def _replyCmdVar(self, cmdVar, msgDict, doLog=True):
        """Send a message to a command variable and optionally log it.

        If the command is done, delete it from the command dict.
        If the command is a refresh command and is done,
        update the refresh command dict accordingly.
        
        Inputs:
        - cmdVar    command variable (RO.KeyVariable.CmdVar)
        - msgDict   message to send
        """
        if doLog:
            self.logMsgDict(msgDict)
        cmdVar.reply(msgDict)
        if cmdVar.isDone() and cmdVar.cmdID != None:
            try:
                del (self.cmdDict[cmdVar.cmdID])
            except KeyError:
                sys.stderr.write("KeyDispatcher bug: tried to delete cmd %s=%s but it was missing\n" % \
                    (cmdVar.cmdID, cmdVar))

    def _sendNextRefreshCmd(self, refreshCmdItemIter=None):
        """Helper function for refreshAllVar.
        
        Plow through a keyVarList iterator until a refresh command is found that is wanted, issue it,
        then schedule a call for myself for ASAP (giving other events a chance to execute first).
        
        Inputs:
        - refreshCmdItemIter: iterator over items in refreshCmdDict;
          if None then set to self.refreshCmdDict.iteritems()
        """
#         print "_sendNextRefreshCmd(%s)" % (refreshCmdItemIter,)
        if not self._isConnected:
            return

        if refreshCmdItemIter == None:
            self._updateRefreshCmds()
            refreshCmdItemIter = iter(self.refreshCmdDict.items())

        try:
            refreshCmdInfo, keyVarSet = next(refreshCmdItemIter)
        except StopIteration:
            return
        actor, cmdStr = refreshCmdInfo
        try:
            cmdVar = RO.KeyVariable.CmdVar (
                actor = actor,
                cmdStr = cmdStr,
                timeLim = _RefreshTimeLim,
                callFunc = self._refreshCmdCallback,
                isRefresh = True,
            )
            self.executeCmd(cmdVar)
        except:
            sys.stderr.write("%s._sendNextRefreshCmd: refresh command %s failed:\n" % (self.__class__.__name__, cmdVar,))
            traceback.print_exc(file=sys.stderr)
        self._refreshNextTimer.start(_ShortInterval, self._sendNextRefreshCmd, refreshCmdItemIter)
Пример #6
0
class KeyDispatcher(object):
    """
    A keyword dispatcher sets keyword variables based on keyword/value data.
    
    Inputs:
    - name: used as the actor when the dispatcher reports errors
    - connection: an RO.Conn.HubConnection object or similar;
      if omitted, an RO.Conn.HubConnection.NullConnection is used,
      which is useful for testing.
    - logFunc: a function that logs a message. Argument list must be:
        (msgStr, severity, actor, cmdr)
        where the first argument is positional and the others are by name

    Fields:
    - readUnixTime: unix time at which last message received from connection; 0 if no message ever received.
    """
    def __init__(
        self,
        name="KeyDispatcher",
        connection=None,
        logFunc=None,
    ):
        self.name = name
        self.readUnixTime = 0

        self._isConnected = False

        # keyVarListDict keys are (actor, keyword) tuples and values are lists of KeyVariables
        self.keyVarListDict = {}

        # cmdDict keys are command ID and values are KeyCommands
        self.cmdDict = {}

        # refreshCmdDict contains information about keyVar refresh commands:
        # key is: actor, refresh command, e.g. as returned by keyVar.getRefreshInfo()
        # refresh command: set of keyVars that use this command
        self.refreshCmdDict = {}

        self._checkCmdTimer = Timer()
        self._checkRemCmdTimer = Timer()
        self._refreshAllTimer = Timer()
        self._refreshNextTimer = Timer()

        if connection:
            self.connection = connection
            self.connection.addReadCallback(self.doRead)
            self.connection.addStateCallback(self._connStateCallback)
        else:
            self.connection = RO.Comm.HubConnection.NullConnection()
        self._isConnected = self.connection.isConnected
        self.userCmdIDGen = RO.Alg.IDGen(1, _CmdNumWrap)
        self.refreshCmdIDGen = RO.Alg.IDGen(_CmdNumWrap + 1, 2 * _CmdNumWrap)

        self.setLogFunc(logFunc)

        self.refreshAllVar()
        self.checkCmdTimeouts()

    def abortCmdByID(self, cmdID):
        """Abort the command with the specified ID.
        
        Issue the command specified by cmdVar.abortCmdStr, if present.
        Report the command as failed.
        
        Has no effect if the command was never dispatched (cmdID is None)
        or has already finished.
        """
        if cmdID is None:
            return

        cmdVar = self.cmdDict.get(cmdID)
        if not cmdVar:
            return

        # check isDone
        if cmdVar.isDone():
            return

        # if relevant, issue abort command, with no callbacks
        if cmdVar.abortCmdStr and self._isConnected:
            abortCmd = RO.KeyVariable.CmdVar(
                cmdStr=cmdVar.abortCmdStr,
                actor=cmdVar.actor,
            )
            self.executeCmd(abortCmd)

        # report command as aborted
        errMsgDict = self.makeMsgDict(
            cmdID=cmdVar.cmdID,
            dataStr="Aborted; Actor=%r; Cmd=%r" %
            (cmdVar.actor, cmdVar.cmdStr),
        )
        self._replyCmdVar(cmdVar, errMsgDict)

    def addKeyVar(self, keyVar):
        """
        Adds a keyword variable to the list.

        Inputs:
        - keyVar: the keyword variable; typically of class RO.KeyVariable
          but can be any object that:
          - has property: keyword (a string)
          - has method "set" with arguments:
            - valueTuple (positional): a tuple of one or more values for the keyword
              the values may be strings, even if another type is expected
            - keyword (by name): the keyword
            - msgDict (by name): the full message dictionary
        """
        dictKey = (keyVar.actor, keyVar.keyword.lower())
        # get list of keyVars, adding it if not already present
        keyList = self.keyVarListDict.setdefault(dictKey, [])
        # append new keyVar to the list
        keyList.append(keyVar)

        if keyVar.hasRefreshCmd():
            refreshInfo = keyVar.getRefreshInfo()
            keyVarSet = self.refreshCmdDict.get(refreshInfo)
            if keyVarSet:
                keyVarSet.add(keyVar)
            else:
                self.refreshCmdDict[refreshInfo] = set((keyVar, ))
            if self._isConnected:
                self._refreshAllTimer.start(_ShortInterval, self.refreshAllVar,
                                            False)

    def checkCmdTimeouts(self):
        """Check all pending commands for timeouts"""
        #       print "RO.KeyDispatcher.checkCmdTimeouts()"

        # cancel pending update, if any
        self._checkCmdTimer.cancel()
        self._checkRemCmdTimer.cancel()

        # iterate over a copy of the values
        # so we can modify the dictionary while checking command timeouts
        cmdVarIter = iter(self.cmdDict.values())
        self._checkRemCmdTimeouts(cmdVarIter)

    def dispatch(self, msgDict):
        """
        Updates the appropriate entries based on the supplied message data.

        Inputs:
        - msgDict: message dictionary. Required fields:
          - cmdr: name of commander that triggered the message (string)
          - cmdID: command ID that triggered the message (int)
          - actor: the actor that generated the message (string)
          - msgType: message type (character)
          - data: dict of keyword: data_tuple entries;
            data_tuple is always a tuple, even if it contains one or zero values
        """
        #       print "dispatching", msgDict

        # extract user number, command number and data dictionary; die if absent
        cmdr = msgDict["cmdr"]
        cmdID = msgDict["cmdID"]
        actor = msgDict["actor"]
        dataDict = msgDict["data"]

        # handle keywords
        # note: keywords from actor keys.<actor>
        # should be handled as if from <actor>
        if actor.startswith("keys."):
            keyActor = actor[5:]
        else:
            keyActor = actor
        for keywd, valueTuple in dataDict.iteritems():
            dictKey = (keyActor, keywd.lower())
            keyVarList = self.keyVarListDict.get(dictKey, [])
            for keyVar in keyVarList:
                try:
                    keyVar.set(valueTuple, msgDict=msgDict)
                except Exception:
                    traceback.print_exc(file=sys.stderr)

        # if you are the commander for this message,
        # execute the command callback (if any)
        if cmdr == self.connection.cmdr:
            # get the command for this command id, if any
            cmdVar = self.cmdDict.get(cmdID, None)
            if cmdVar is not None:
                # send reply but don't log (that's already been done)
                self._replyCmdVar(cmdVar, msgDict, doLog=False)

    def doRead(self, sock, msgStr):
        """Reads, parses and dispatches a message from the hub
        
        Sets self.readUnixTime to time.time()
        """
        # parse message; if it fails, log it as an error
        self.readUnixTime = time.time()
        try:
            msgDict = RO.ParseMsg.parseHubMsg(msgStr)
        except Exception as e:
            self.logMsg(
                msgStr="CouldNotParse; Msg=%r; Text=%r" %
                (msgStr, RO.StringUtil.strFromException(e)),
                severity=RO.Constants.sevError,
            )
            return

        # log message
        self.logMsgDict(msgDict)

        # dispatch message
        try:
            self.dispatch(msgDict)
        except Exception as e:
            sys.stderr.write("Could not dispatch: %r\n" % (msgDict, ))
            traceback.print_exc(file=sys.stderr)

    def executeCmd(self, cmdVar):
        """Executes the command (of type RO.KeyVariable.CmdVar) by performing the following tasks:
        - Sets the command number
        - Sets the start time
        - Puts the command on the keyword dispatcher queue
        - Issues the command to the server

        Inputs:
        - cmdVar: the command, of class RO.KeyVariable.CmdVar
            
        Note:
        - we always increment cmdID since every command must have a unique command ID
          (even commands that go to different actors); this simplifies the
          dispatcher code and also makes the hub's life easier
          (since it can report certain kinds of failures using actor=hub).
        """
        if not self._isConnected:
            errMsgDict = self.makeMsgDict(
                dataStr="Failed; Actor=%r; Cmd=%r; Text=\"not connected\"" %
                (cmdVar.actor, cmdVar.cmdStr), )
            self._replyCmdVar(cmdVar, errMsgDict)
            return

        while True:
            if cmdVar.isRefresh:
                cmdID = next(self.refreshCmdIDGen)
            else:
                cmdID = next(self.userCmdIDGen)
            if cmdID not in self.cmdDict:
                break
        self.cmdDict[cmdID] = cmdVar
        cmdVar._setStartInfo(self, cmdID)

        try:
            fullCmd = "%d %s %s" % (cmdVar.cmdID, cmdVar.actor, cmdVar.cmdStr)
            self.connection.writeLine(fullCmd)


#             self.logMsg (
#                 msgStr = fullCmd,
#                 actor = cmdVar.actor,
#                 cmdID = cmdVar.cmdID,
#             )
#           print "executing:", fullCmd
        except Exception as e:
            errMsgDict = self.makeMsgDict(
                cmdID=cmdVar.cmdID,
                dataStr="WriteFailed; Actor=%r; Cmd=%r; Text=%r" %
                (cmdVar.actor, cmdVar.cmdStr,
                 RO.StringUtil.strFromException(e)),
            )
            self._replyCmdVar(cmdVar, errMsgDict)

    @staticmethod
    def getMaxUserCmdID():
        """Return the maximum user command ID number.
        
        User command ID numbers range from 1 through getMaxUserCmdID()
        Refresh command ID numbers range from getMaxUserCmdID() + 1 through 2 * getMaxUserCmdID()
        """
        return _CmdNumWrap

    def logMsg(
        self,
        msgStr,
        severity=RO.Constants.sevNormal,
        actor="TUI",
        cmdr=None,
        cmdID=0,
    ):
        """Writes a message to the log.
        
        If no logFunc was supplied then the message is printed to stderr.
        On error, prints a message to stderr and returns normally.
        
        Inputs:
        - msgStr: message to display; a final \n is appended
        - severity: message severity (an RO.Constants.sevX constant)
        - actor: name of actor
        - cmdr: commander; defaults to self
        """
        if not self.logFunc:
            sys.stderr.write(msgStr + "\n")
            return

        try:
            self.logFunc(
                msgStr,
                severity=severity,
                actor=actor,
                cmdr=cmdr,
                cmdID=cmdID,
            )
        except Exception:
            sys.stderr.write("Could not log: %r; severity=%r; actor=%r; cmdr=%r\n" % \
                (msgStr, severity, actor, cmdr))
            traceback.print_exc(file=sys.stderr)

    def logMsgDict(self, msgDict):
        try:
            msgType = msgDict["msgType"].lower()
            severity = RO.KeyVariable.TypeDict[msgType][1]
            self.logMsg(
                msgStr=msgDict["msgStr"],
                severity=severity,
                actor=msgDict["actor"],
                cmdr=msgDict["cmdr"],
                cmdID=msgDict["cmdID"],
            )
        except Exception:
            sys.stderr.write("Could not log message dict:\n%r\n" % (msgDict, ))
            traceback.print_exc(file=sys.stderr)

    def makeMsgDict(
        self,
        cmdr=None,
        cmdID=0,
        actor=None,
        msgType="f",
        dataStr="",
    ):
        """Generate a hub message based on the supplied data.
        Useful for reporting internal errors.
        """
        if cmdr is None:
            cmdr = self.connection.cmdr
        if actor is None:
            actor = self.name

        headerStr = "%s %d %s %s" % (
            cmdr,
            cmdID,
            actor,
            msgType,
        )
        msgStr = " ".join((headerStr, dataStr))
        try:
            return RO.ParseMsg.parseHubMsg(msgStr)
        except Exception as e:
            sys.stderr.write("Could not make message dict from %r; error: %s" %
                             (msgStr, e))
            traceback.print_exc(file=sys.stderr)
            msgDict = RO.ParseMsg.parseHubMsg(headerStr)
            msgDict["msgStr"] = msgStr
            msgDict["data"] = {}
            return msgDict

    def refreshAllVar(self, resetAll=True):
        """Examines all keywords, looking for ones that need updating
        and issues the appropriate refresh commands.
        
        Inputs:
        - resetAll: reset all keyword variables to notCurrent
        """
        #         print "refreshAllVar()"

        # cancel pending update, if any
        self._refreshAllTimer.cancel()
        self._refreshNextTimer.cancel()

        if resetAll:
            # clear the refresh command dict
            # and invalidate all keyVars
            # (leave pending refresh commands alone; they will time out)
            for keyVarList in self.keyVarListDict.values():
                for keyVar in keyVarList:
                    keyVar.setNotCurrent()

        self._sendNextRefreshCmd()

    def removeKeyVar(self, keyVar):
        """
        Removes the specified keyword variable,
        returning whatever was removed, or None if keyVar not found.
        See also "add".

        Inputs:
        - keyVar: the keyword variable to remove

        Returns:
        - keyVar, if present, None otherwise.
        """
        dictKey = (keyVar.actor, keyVar.keywd.lower())
        keyVarList = self.keyVarListDict.get(dictKey, [])
        if keyVar not in keyVarList:
            return None
        keyVarList.remove(keyVar)

        # remove refresh command, if present
        keyVarSet = self.refreshCmdDict.get(keyVar.getRefreshInfo())
        if keyVarSet and keyVar in keyVarSet:
            keyVarSet.remove(keyVar)
            if not keyVarSet:
                # that was the only keyVar using this refresh command
                del (self.refreshCmdDict[keyVar.getRefreshInfo()])
        return keyVar

    def setLogFunc(self, logFunc=None):
        """Sets the log output device, or clears it if none specified.
        
        The function must take the following arguments: (msgStr, severity, actor, cmdr)
        where the first argument is positional and the others are by name
        """
        self.logFunc = logFunc

    def _updateRefreshCmds(self):
        """Update the cache of refresh commands by scanning the keyVars.
        """
        self.refreshCmdDict = {}
        for keyVarList in self.keyVarListDict.itervalues():
            for keyVar in keyVarList:
                if keyVar.hasRefreshCmd():
                    refreshInfo = keyVar.getRefreshInfo()
                    keyVarSet = self.refreshCmdDict.get(refreshInfo)
                    if keyVarSet:
                        keyVarSet.add(keyVar)
                    else:
                        self.refreshCmdDict[refreshInfo] = set((keyVar, ))

    def _checkRemCmdTimeouts(self, cmdVarIter):
        """Helper function for checkCmdTimeouts.
        Check the remaining command variables in cmdVarIter.
        If a timeout is found, time out that one command
        and schedule myself to run again shortly
        (thereby giving other events a chance to run).

        Once the iterator is exhausted, schedule
        my parent function checkCmdTimeouts to run
        at the usual interval later.
        """
        #       print "RO.KeyDispatcher._checkRemCmdTimeouts(%s)" % cmdVarIter
        try:
            errMsgDict = None
            currTime = time.time()
            for cmdVar in cmdVarIter:
                # if cmd still exits (i.e. has not been deleted for other reasons)
                # check if it has a time limit and has timed out
                if cmdVar.cmdID not in self.cmdDict:
                    continue
                if not self._isConnected:
                    errMsgDict = self.makeMsgDict(
                        cmdID=cmdVar.cmdID,
                        dataStr=
                        "Aborted; Actor=%r; Cmd=%r; Text=\"disconnected\"" %
                        (cmdVar.actor, cmdVar.cmdStr),
                    )
                    # no connection, so cannot send abort command
                    cmdVar.abortCmdStr = ""
                    break
                elif cmdVar.maxEndTime and (cmdVar.maxEndTime < currTime):
                    # time out this command
                    errMsgDict = self.makeMsgDict(
                        cmdID=cmdVar.cmdID,
                        dataStr="Timeout; Actor=%r; Cmd=%r" %
                        (cmdVar.actor, cmdVar.cmdStr),
                    )
                    break
            if errMsgDict:
                self._replyCmdVar(cmdVar, errMsgDict)

                # schedule myself to run again shortly
                # (thereby giving other time to other events)
                # continuing where I left off
                self._checkRemCmdTimer.start(_ShortInterval,
                                             self._checkRemCmdTimeouts,
                                             cmdVarIter)
        except Exception:
            sys.stderr.write("RO.KeyDispatcher._checkRemCmdTimeouts failed\n")
            traceback.print_exc(file=sys.stderr)

        # finished checking all commands in the current cmdVarIter;
        # schedule a new checkCmdTimeouts at the usual interval
        self._checkCmdTimer.start(_TimeoutInterval, self.checkCmdTimeouts)

    def _connStateCallback(self, conn):
        """If connection state changes, update refresh variables.
        """
        wasConnected = self._isConnected
        self._isConnected = conn.isConnected

        if wasConnected != self._isConnected:
            self._refreshAllTimer.start(_ShortInterval, self.refreshAllVar)

    def _refreshCmdCallback(self, msgType, msgDict, cmdVar):
        """Refresh command callback; complain if command failed or some keyVars not updated
        """
        if not cmdVar.isDone:
            return
        refreshInfo = (cmdVar.actor, cmdVar.cmdStr)
        keyVarSet = self.refreshCmdDict.get(refreshInfo, set())
        if cmdVar.didFail():
            keyVarNamesStr = ", ".join(sorted([kv.keyword
                                               for kv in keyVarSet]))
            errMsg = "Refresh command %s %s failed; keyVars not refreshed: %s" % \
                (cmdVar.actor, cmdVar.cmdStr, keyVarNamesStr)
            self.logMsg(
                msgStr=errMsg,
                severity=RO.Constants.sevWarning,
                cmdID=cmdVar.cmdID,
            )
        elif keyVarSet:
            aKeyVar = next(iter(keyVarSet))
            actor = aKeyVar.actor
            missingKeyVarNamesStr = ", ".join(
                sorted([kv.keyword for kv in keyVarSet if not kv.isCurrent()]))
            if missingKeyVarNamesStr:
                errMsg = "No refresh data for %s keyVars: %s" % (
                    actor, missingKeyVarNamesStr)
                self.logMsg(errMsg, severity=RO.Constants.sevWarning)
        else:
            # all of the keyVars were removed or there is a bug
            errMsg = "Warning: refresh command %s %s finished but no keyVars found\n" % refreshInfo
            self.logMsg(errMsg, severity=RO.Constants.sevWarning)

    def _replyCmdVar(self, cmdVar, msgDict, doLog=True):
        """Send a message to a command variable and optionally log it.

        If the command is done, delete it from the command dict.
        If the command is a refresh command and is done,
        update the refresh command dict accordingly.
        
        Inputs:
        - cmdVar    command variable (RO.KeyVariable.CmdVar)
        - msgDict   message to send
        """
        if doLog:
            self.logMsgDict(msgDict)
        cmdVar.reply(msgDict)
        if cmdVar.isDone() and cmdVar.cmdID is not None:
            try:
                del (self.cmdDict[cmdVar.cmdID])
            except KeyError:
                sys.stderr.write("KeyDispatcher bug: tried to delete cmd %s=%s but it was missing\n" % \
                    (cmdVar.cmdID, cmdVar))

    def _sendNextRefreshCmd(self, refreshCmdItemIter=None):
        """Helper function for refreshAllVar.
        
        Plow through a keyVarList iterator until a refresh command is found that is wanted, issue it,
        then schedule a call for myself for ASAP (giving other events a chance to execute first).
        
        Inputs:
        - refreshCmdItemIter: iterator over items in refreshCmdDict;
          if None then set to self.refreshCmdDict.iteritems()
        """
        #         print "_sendNextRefreshCmd(%s)" % (refreshCmdItemIter,)
        if not self._isConnected:
            return

        if refreshCmdItemIter is None:
            self._updateRefreshCmds()
            refreshCmdItemIter = self.refreshCmdDict.iteritems()

        try:
            refreshCmdInfo, keyVarSet = next(refreshCmdItemIter)
        except StopIteration:
            return
        actor, cmdStr = refreshCmdInfo
        try:
            cmdVar = RO.KeyVariable.CmdVar(
                actor=actor,
                cmdStr=cmdStr,
                timeLim=_RefreshTimeLim,
                callFunc=self._refreshCmdCallback,
                isRefresh=True,
            )
            self.executeCmd(cmdVar)
        except Exception:
            sys.stderr.write(
                "%s._sendNextRefreshCmd: refresh command %s failed:\n" % (
                    self.__class__.__name__,
                    cmdVar,
                ))
            traceback.print_exc(file=sys.stderr)
        self._refreshNextTimer.start(_ShortInterval, self._sendNextRefreshCmd,
                                     refreshCmdItemIter)
Пример #7
0
class PermsTableWdg(Tkinter.Frame):
    """Inputs:
    - master        master widget
    - statusBar     status bar to handle commands.
    - readOnlyCallback  a function that is called when the readOnly state changes;
        the function receives one argument: isReadOnly: True for read only, False otherwise.
        Note that isReadOnly always starts out True.
    """
    def __init__(self,
        master,
        statusBar,
        readOnlyCallback = None,
    ):
        Tkinter.Frame.__init__(self, master)
        self._statusBar = statusBar
        self._tuiModel = TUI.TUIModel.getModel()
        self._readOnlyCallback = readOnlyCallback

        self._actorList = ActorList(startCol=1)
        self._progDict = {} # prog name: prog perms

        self._titleWdgSet = []

        self._titleBorder = Tkinter.Frame(self, borderwidth=2, relief="sunken")
        self._titleBorder.grid(row=0, column=0, sticky="ew")
        self._titleBorder.grid_columnconfigure(1, weight=1)
        
        self._titleFrame = Tkinter.Frame(self._titleBorder, borderwidth=0)
        self._titleFrame.grid(row=0, column=0, sticky="w")
        
        self._scrollWdg = RO.Wdg.ScrolledWdg(
            master = self,
            hscroll = False,
            vscroll = True,
            borderwidth = 2,
            relief = "sunken",
        )
        self._scrollWdg.grid(row=1, column=0, sticky="nsew")
        self._tableFrame = Tkinter.Frame(self._scrollWdg.getWdgParent(), borderwidth=0)
        self._vertMeasWdg = Tkinter.Frame(self._tableFrame)
        self._vertMeasWdg.grid(row=0, column=0, sticky="wns")
        self._scrollWdg.setWdg(
            wdg = self._tableFrame,
            vincr = self._vertMeasWdg,
        )
        self.grid_rowconfigure(1, weight=1)
        
        self._nextRow = 0
        self._readOnly = True
        self._updActorTimer = Timer()
        
        self.permsModel = TUI.Models.PermsModel.getModel()
        
        self.permsModel.actors.addCallback(self._updActors)
        self.permsModel.authList.addCallback(self._updAuthList)
        self.permsModel.lockedActors.addCallback(self._updLockedActors)
        self.permsModel.programs.addCallback(self._updPrograms)
        
        self._lockoutRow = 3
        self._lockoutWdg = _LockoutPerms(
            master = self._titleFrame,
            actorList = self._actorList,
            readOnly = self._readOnly,
            row = self._lockoutRow,
            statusBar = self._statusBar,
        )

        statusBar.dispatcher.connection.addStateCallback(self.__connStateCallback)
    
    def purge(self):
        """Remove unregistered programs.
        """
        knownProgs = self.permsModel.programs.get()[0]

        # use items instead of iteritems so we can modify as we go
        for prog, progPerms in self._progDict.items():
            if progPerms.isRegistered() or prog in knownProgs:
                continue
            progPerms.delete()
            del(self._progDict[prog])
    
    def sort(self):
        """Sort existing programs and redisplay all data.
        """
        self._actorList.clearAllTitleWdg()
        for wdg in self._titleWdgSet:
            wdg.destroy()
        for col, actor in self._actorList.getColActorList():
            if not actor:
                # insert dividor
                self._addTitle("  ", col)
            else:
                titleLabel = self._addTitle(actor, col)
                self._actorList.setTitleWdg(actor, titleLabel)
        
        self._lockoutWdg.display(row=self._lockoutRow)
        
        progNames = self._progDict.keys()
        progNames.sort()
        self._nextRow = 0
        for prog in progNames:
            progPerms = self._progDict[prog]
            progPerms.display(row=self._nextRow)
            self._nextRow += 1

    def _addProg(self, prog):
        """Create and display a new program.
        
        Called when the hub informs this widget of a new program
        (to add a program send the suitable command to the hub,
        don't just call this method).
        """
        prog = prog.upper()
        newProg = _ProgPerms(
            master = self._tableFrame,
            prog = prog,
            actorList = self._actorList,
            readOnly = self._readOnly,
            row = self._nextRow,
            statusBar = self._statusBar,
        )
        self._nextRow += 1
        self._progDict[prog] = newProg
    
    def _addTitle(self, text, col):
        """Create and grid a title label and two associated
        width measuring frames (one in the title frame, one in the main frame).
        
        Inputs:
        - text  text for title
        - col   column for title
        
        Returns the title label
        """
#         print "_addTitle(%r, %r)" % (text, col)
        strWdg = RO.Wdg.StrLabel(
            master = self._titleFrame,
            text = text,
        )
        strWdg.grid(row=0, column=col)
        titleSpacer = Tkinter.Frame(self._titleFrame)
        titleSpacer.grid(row=1, column=col, sticky="new")
        mainSpacer = Tkinter.Frame(self._tableFrame)
        mainSpacer.grid(row=0, column=col, sticky="new")
        self._titleWdgSet += [strWdg, titleSpacer, mainSpacer]
        
        def dotitle(evt):
#           print "dotitle: titlewidth = %r, mainwidth = %r" % (
#               titleSpacer.winfo_width(), mainSpacer.winfo_width(),
#           )
            if titleSpacer.winfo_width() > mainSpacer.winfo_width():
                mainSpacer["width"] = titleSpacer.winfo_width()  
        titleSpacer.bind("<Configure>", dotitle)
        
        def domain(evt):
#           print "domain: titlewidth = %r, mainwidth = %r" % (
#               titleSpacer.winfo_width(), mainSpacer.winfo_width(),
#           )
            if mainSpacer.winfo_width() > titleSpacer.winfo_width():
                titleSpacer["width"] = mainSpacer.winfo_width()     
        mainSpacer.bind("<Configure>", domain)
        return strWdg
    
    def __connStateCallback(self, conn):
        """If the connection closes, clear all programs from the list.
        """
        if self._progDict and not conn.isConnected:
            for prog, progPerms in self._progDict.items():
                progPerms.delete()
                del(self._progDict[prog])

    def _updActors(self, actors, isCurrent=True, **kargs):
        """Perms list of actors updated.
        """
#         print "%s._updActors(%r)" % (self.__class__, actors,)
        if not isCurrent:
            return
        
        if self._actorList.isSameActors(actors):
            return
        
        if not self._readOnly and self._actorList:
            self._statusBar.setMsg("Updating actors", severity=RO.Constants.sevWarning, isTemp = True, duration=_NewActorDelay * 1000.0)
            self._setReadOnly(True)
            self._updActorTimer.start(_NewActorDelay, self._setReadOnly, False)

        self._actorList.setActors(actors)

        # Update lockout and each program
        self._lockoutWdg.updateActorList()
        for progPerms in self._progDict.itervalues():
            progPerms.updateActorList()

        # display new header and everything
        self.sort()

    def _updPrograms(self, programs, isCurrent=True, **kargs):
        """Hub's list of registered programs updated.
        
        Delete old programs based on this info, but don't add new ones
        (instead, look for an authList entry for the new program,
        so we get auth info at the same time).
        """
        if not isCurrent:
            return
#       print "_updPrograms(%r)" % (programs,)

        # raise program names to uppercase
        programs = [prog.upper() for prog in programs]

        if self._tuiModel.getProgID().upper() not in programs:
#           print "my prog=%s is not in programs=%s; currReadOnly=%s" % (prog, programs, self._readOnly)
            self._setReadOnly(True)

        # mark unregistered programs
        anyUnreg = False
        for prog, progPerms in self._progDict.iteritems():
            if prog not in programs:
                # mark progPerms as unregistered
                anyUnreg = True
                progPerms.setRegistered(False)
        
        # if read only, then automatically purge (if necessary) and sort
        if self._readOnly:
            if anyUnreg:
                self.purge()
            self.sort()
        
    def _setReadOnly(self, readOnly):
        """Set read only state.
        """
        readOnly = bool(readOnly)
        if self._readOnly != readOnly:
            self._readOnly = readOnly
#           print "toggling readOnly to", self._readOnly
            self._lockoutWdg.setReadOnly(self._readOnly)
            for progPerms in self._progDict.itervalues():
                progPerms.setReadOnly(self._readOnly)
            if self._readOnlyCallback:
                self._readOnlyCallback(self._readOnly)
    
    def _updAuthList(self, progAuthList, isCurrent=True, **kargs):
        """New authList received.
        
        progAuthList is:
        - program name
        - 0 or more actorList
        """
        if not isCurrent:
            return
#         print "_updAuthList(%r)" % (progAuthList,)
        
        prog = progAuthList[0].upper()
        authActors = progAuthList[1:]
    
        if prog == self._tuiModel.getProgID().upper():
            # this is info about me (my program); check if I can set permissions
            readOnly = "perms" not in authActors
#             print "prog=%s is me; readOnly=%s, currReadOnly=%s, actorList=%s" % (prog, readOnly, self._readOnly, authActors)
            self._setReadOnly(readOnly)

        isNew = prog not in self._progDict
        if isNew:
#             print "program %s is not in program dict; adding" % (prog,)
            self._addProg(prog)

        progPerms = self._progDict[prog]
        progPerms.setRegistered(True)
        progPerms.setCurrActors(authActors)
    
    def _updLockedActors(self, lockedActors, isCurrent=True, **kargs):
        """Hub's locked actor list updated.
        """
        if not isCurrent:
            return
        
        self._lockoutWdg.setCurrActors(lockedActors)
Пример #8
0
class PermsTableWdg(Tkinter.Frame):
    """Inputs:
    - master        master widget
    - statusBar     status bar to handle commands.
    - readOnlyCallback  a function that is called when the readOnly state changes;
        the function receives one argument: isReadOnly: True for read only, False otherwise.
        Note that isReadOnly always starts out True.
    """
    def __init__(self,
        master,
        statusBar,
        readOnlyCallback = None,
    ):
        Tkinter.Frame.__init__(self, master)
        self._statusBar = statusBar
        self._tuiModel = TUI.TUIModel.getModel()
        self._readOnlyCallback = readOnlyCallback

        self._actorList = ActorList(startCol=1)
        self._progDict = {} # prog name: prog perms

        self._titleWdgSet = []

        self._titleBorder = Tkinter.Frame(self, borderwidth=2, relief="sunken")
        self._titleBorder.grid(row=0, column=0, sticky="ew")
        self._titleBorder.grid_columnconfigure(1, weight=1)
        
        self._titleFrame = Tkinter.Frame(self._titleBorder, borderwidth=0)
        self._titleFrame.grid(row=0, column=0, sticky="w")
        
        self._scrollWdg = RO.Wdg.ScrolledWdg(
            master = self,
            hscroll = False,
            vscroll = True,
            borderwidth = 2,
            relief = "sunken",
        )
        self._scrollWdg.grid(row=1, column=0, sticky="nsew")
        self._tableFrame = Tkinter.Frame(self._scrollWdg.getWdgParent(), borderwidth=0)
        self._vertMeasWdg = Tkinter.Frame(self._tableFrame)
        self._vertMeasWdg.grid(row=0, column=0, sticky="wns")
        self._scrollWdg.setWdg(
            wdg = self._tableFrame,
            vincr = self._vertMeasWdg,
        )
        self.grid_rowconfigure(1, weight=1)
        
        self._nextRow = 0
        self._readOnly = True
        self._updActorTimer = Timer()
        
        self.permsModel = TUI.Models.PermsModel.getModel()
        
        self.permsModel.actors.addCallback(self._updActors)
        self.permsModel.authList.addCallback(self._updAuthList)
        self.permsModel.lockedActors.addCallback(self._updLockedActors)
        self.permsModel.programs.addCallback(self._updPrograms)
        
        self._lockoutRow = 3
        self._lockoutWdg = _LockoutPerms(
            master = self._titleFrame,
            actorList = self._actorList,
            readOnly = self._readOnly,
            row = self._lockoutRow,
            statusBar = self._statusBar,
        )

        statusBar.dispatcher.connection.addStateCallback(self.__connStateCallback)
    
    def purge(self):
        """Remove unregistered programs.
        """
        knownProgs = self.permsModel.programs.get()[0]

        # use items instead of iteritems so we can modify as we go
        for prog, progPerms in self._progDict.items():
            if progPerms.isRegistered() or prog in knownProgs:
                continue
            progPerms.delete()
            del(self._progDict[prog])
    
    def sort(self):
        """Sort existing programs and redisplay all data.
        """
        self._actorList.clearAllTitleWdg()
        for wdg in self._titleWdgSet:
            wdg.destroy()
        for col, actor in self._actorList.getColActorList():
            if not actor:
                # insert dividor
                self._addTitle("  ", col)
            else:
                titleLabel = self._addTitle(actor, col)
                self._actorList.setTitleWdg(actor, titleLabel)
        
        self._lockoutWdg.display(row=self._lockoutRow)
        
        progNames = self._progDict.keys()
        progNames.sort()
        self._nextRow = 0
        for prog in progNames:
            progPerms = self._progDict[prog]
            progPerms.display(row=self._nextRow)
            self._nextRow += 1

    def _addProg(self, prog):
        """Create and display a new program.
        
        Called when the hub informs this widget of a new program
        (to add a program send the suitable command to the hub,
        don't just call this method).
        """
        prog = prog.upper()
        newProg = _ProgPerms(
            master = self._tableFrame,
            prog = prog,
            actorList = self._actorList,
            readOnly = self._readOnly,
            row = self._nextRow,
            statusBar = self._statusBar,
        )
        self._nextRow += 1
        self._progDict[prog] = newProg
    
    def _addTitle(self, text, col):
        """Create and grid a title label and two associated
        width measuring frames (one in the title frame, one in the main frame).
        
        Inputs:
        - text  text for title
        - col   column for title
        
        Returns the title label
        """
#         print "_addTitle(%r, %r)" % (text, col)
        strWdg = RO.Wdg.StrLabel(
            master = self._titleFrame,
            text = text,
        )
        strWdg.grid(row=0, column=col)
        titleSpacer = Tkinter.Frame(self._titleFrame)
        titleSpacer.grid(row=1, column=col, sticky="new")
        mainSpacer = Tkinter.Frame(self._tableFrame)
        mainSpacer.grid(row=0, column=col, sticky="new")
        self._titleWdgSet += [strWdg, titleSpacer, mainSpacer]
        
        def dotitle(evt):
#           print "dotitle: titlewidth = %r, mainwidth = %r" % (
#               titleSpacer.winfo_width(), mainSpacer.winfo_width(),
#           )
            if titleSpacer.winfo_width() > mainSpacer.winfo_width():
                mainSpacer["width"] = titleSpacer.winfo_width()  
        titleSpacer.bind("<Configure>", dotitle)
        
        def domain(evt):
#           print "domain: titlewidth = %r, mainwidth = %r" % (
#               titleSpacer.winfo_width(), mainSpacer.winfo_width(),
#           )
            if mainSpacer.winfo_width() > titleSpacer.winfo_width():
                titleSpacer["width"] = mainSpacer.winfo_width()     
        mainSpacer.bind("<Configure>", domain)
        return strWdg
    
    def __connStateCallback(self, conn):
        """If the connection closes, clear all programs from the list.
        """
        if self._progDict and not conn.isConnected:
            for prog, progPerms in self._progDict.items():
                progPerms.delete()
                del(self._progDict[prog])

    def _updActors(self, actors, isCurrent=True, **kargs):
        """Perms list of actors updated.
        """
#         print "%s._updActors(%r)" % (self.__class__, actors,)
        if not isCurrent:
            return
        
        if self._actorList.isSameActors(actors):
            return
        
        if not self._readOnly and self._actorList:
            self._statusBar.setMsg("Updating actors", severity=RO.Constants.sevWarning, isTemp = True, duration=_NewActorDelay * 1000.0)
            self._setReadOnly(True)
            self._updActorTimer.start(_NewActorDelay, self._setReadOnly, False)

        self._actorList.setActors(actors)

        # Update lockout and each program
        self._lockoutWdg.updateActorList()
        for progPerms in self._progDict.itervalues():
            progPerms.updateActorList()

        # display new header and everything
        self.sort()

    def _updPrograms(self, programs, isCurrent=True, **kargs):
        """Hub's list of registered programs updated.
        
        Delete old programs based on this info, but don't add new ones
        (instead, look for an authList entry for the new program,
        so we get auth info at the same time).
        """
        if not isCurrent:
            return
#       print "_updPrograms(%r)" % (programs,)

        # raise program names to uppercase
        programs = [prog.upper() for prog in programs]

        if self._tuiModel.getProgID().upper() not in programs:
#           print "my prog=%s is not in programs=%s; currReadOnly=%s" % (prog, programs, self._readOnly)
            self._setReadOnly(True)

        # mark unregistered programs
        anyUnreg = False
        for prog, progPerms in self._progDict.iteritems():
            if prog not in programs:
                # mark progPerms as unregistered
                anyUnreg = True
                progPerms.setRegistered(False)
        
        # if read only, then automatically purge (if necessary) and sort
        if self._readOnly:
            if anyUnreg:
                self.purge()
            self.sort()
        
    def _setReadOnly(self, readOnly):
        """Set read only state.
        """
        readOnly = bool(readOnly)
        if self._readOnly != readOnly:
            self._readOnly = readOnly
#           print "toggling readOnly to", self._readOnly
            self._lockoutWdg.setReadOnly(self._readOnly)
            for progPerms in self._progDict.itervalues():
                progPerms.setReadOnly(self._readOnly)
            if self._readOnlyCallback:
                self._readOnlyCallback(self._readOnly)
    
    def _updAuthList(self, progAuthList, isCurrent=True, **kargs):
        """New authList received.
        
        progAuthList is:
        - program name
        - 0 or more actorList
        """
        if not isCurrent:
            return
#         print "_updAuthList(%r)" % (progAuthList,)
        
        prog = progAuthList[0].upper()
        authActors = progAuthList[1:]
    
        if prog == self._tuiModel.getProgID().upper():
            # this is info about me (my program); check if I can set permissions
            readOnly = "perms" not in authActors
#             print "prog=%s is me; readOnly=%s, currReadOnly=%s, actorList=%s" % (prog, readOnly, self._readOnly, authActors)
            self._setReadOnly(readOnly)

        isNew = prog not in self._progDict
        if isNew:
#             print "program %s is not in program dict; adding" % (prog,)
            self._addProg(prog)

        progPerms = self._progDict[prog]
        progPerms.setRegistered(True)
        progPerms.setCurrActors(authActors)
    
    def _updLockedActors(self, lockedActors, isCurrent=True, **kargs):
        """Hub's locked actor list updated.
        """
        if not isCurrent:
            return
        
        self._lockoutWdg.setCurrActors(lockedActors)
Пример #9
0
class BackgroundKwds(object):
    """Processes various keywords that are handled in the background.
    
    Also verify that we're getting data from the hub (also detects computer sleep)
    and try to refresh variables if there is a problem.
    """
    def __init__(self,
        maxTimeErr = 10.0,
        checkConnInterval = 5.0,
        maxEntryAge = 60.0,
    ):
        """Create BackgroundKwds
        
        Inputs:
        - maxTimeErr: maximum clock error (sec) before a warning is printed
        - checkConnInterval: interval (sec) at which to check connection
        - maxEntryAge: maximum age of log entry (sec)
        """
        self.maxTimeErr = float(maxTimeErr)
        self.checkConnInterval = float(checkConnInterval)
        self.maxEntryAge = float(maxEntryAge)

        self.tuiModel = TUI.TUIModel.getModel()
        self.tccModel = TUI.TCC.TCCModel.getModel()
        self.connection = self.tuiModel.getConnection()
        self.dispatcher = self.tuiModel.dispatcher
        self.didSetUTCMinusTAI = False
        self.checkConnTimer = Timer()
        self.clockType = None # set to "UTC" or "TAI" if keeping that time system

        self.tccModel.utcMinusTAI.addIndexedCallback(self.setUTCMinusTAI, ind=0, callNow=False)
    
        self.connection.addStateCallback(self.connCallback, callNow=True)

    def connCallback(self, conn):
        """Called when connection changes state

        When connected check the connection regularly,
        when not, don't
        """
        if conn.isConnected:
            self.checkConnTimer.start(self.checkConnInterval, self.checkConnection)
            self.checkClock()
        else:
            self.checkConnTimer.cancel()
    
    def checkConnection(self):
        """Check for aliveness of connection by looking at the time of the last hub message
        """
        doQueue = True
        try:
            entryAge = time.time() - self.dispatcher.readUnixTime
            if entryAge > self.maxEntryAge:
                self.tuiModel.logMsg(
                    "No data seen in %s seconds; testing the connection" % (self.maxEntryAge,),
                    severity = RO.Constants.sevWarning)
                cmdVar = RO.KeyVariable.CmdVar(
                    actor = "hub",
                    cmdStr = "version",
                    timeLim = 5.0,
                    dispatcher = self.dispatcher,
                    callFunc=self.checkCmdCallback,
                )
                doQueue = False
        finally:
            if doQueue:
                self.checkConnTimer.start(self.checkConnInterval, self.checkConnection)
    
    def checkClock(self):
        """Check computer clock by asking the TCC for time
        """
        cmdVar = RO.KeyVariable.CmdVar(
            actor = "tcc",
            cmdStr = "show time",
            timeLim = 2.0,
            dispatcher = self.dispatcher,
            callFunc = self.checkClockCallback,
            keyVars = (self.tccModel.tai,),
        )

    def checkCmdCallback(self, msgType, msgDict, cmdVar):
        if not cmdVar.isDone():
            return
        doQueue = True
        try:
            if cmdVar.didFail():
                self.connection.disconnect(isOK = False, reason="Connection is dead")
                doQueue = False
                TUI.PlaySound.cmdFailed()
            else:
                self.dispatcher.refreshAllVar()
        finally:
            if doQueue:
                self.checkConnTimer.start(self.checkConnInterval, self.checkConnection)
    
    def checkClockCallback(self, msgType, msgDict, cmdVar):
        """Callback from TCC "show time" command
        
        Determine if clock is keeping UTC, TAI or something else, and act accordingly.
        """
        if not cmdVar.isDone():
            return
        if cmdVar.didFail():
            self.tuiModel.logMsg(
                "clock check failed: tcc show time failed; assuming UTC",
                severity = RO.Constants.sevError,
            )
            return
        
        currTAI = cmdVar.getLastKeyVarData(self.tccModel.tai, ind=0)
        if currTAI is None:
            self.tuiModel.logMsg(
                "clock check failed: current TAI unknown; assuming UTC",
                severity = RO.Constants.sevError,
            )
            return
        if not self.didSetUTCMinusTAI:
            self.tuiModel.logMsg(
                "clock check failed: UTC-TAI unknown; assuming UTC",
                severity = RO.Constants.sevError,
            )
            return
        utcMinusTAI = RO.Astro.Tm.getUTCMinusTAI()
        currUTC = utcMinusTAI + currTAI

        RO.Astro.Tm.setClockError(0)
        clockUTC = RO.Astro.Tm.utcFromPySec() * RO.PhysConst.SecPerDay
        
        if abs(clockUTC - currUTC) < 3.0:
            # clock keeps accurate UTC (as well as we can figure); set time error to 0
            self.clockType = "UTC"
            self.tuiModel.logMsg("Your computer clock is keeping UTC")
        elif abs(clockUTC - currTAI) < 3.0:
            # clock keeps accurate TAI (as well as we can figure); set time error to UTC-TAI
            self.clockType = "TAI"
            RO.Astro.Tm.setClockError(-utcMinusTAI)
            self.tuiModel.logMsg("Your computer clock is keeping TAI")
        else:
            # clock system unknown or not keeping accurate time; adjust based on current UTC
            self.clockType = None
            timeError = clockUTC - currUTC
            RO.Astro.Tm.setClockError(timeError)
            self.tuiModel.logMsg(
                "Your computer clock is off by = %f.1 seconds" % (timeError,),
                severity = RO.Constants.sevWarning,
            )
        
    def setUTCMinusTAI(self, utcMinusTAI, isCurrent=1, keyVar=None):
        """Updates UTC-TAI in RO.Astro.Tm
        """
        if isCurrent and utcMinusTAI != None:
            RO.Astro.Tm.setUTCMinusTAI(utcMinusTAI)
            self.didSetUTCMinusTAI = True
            if self.clockType == "TAI":
                RO.Astro.Tm.setClockError(-utcMinusTAI)