예제 #1
0
 def getDataMsgResponse(callId, timeout=None):
     Web.callbackLock.acquire()
     try:
         callbackStruct = Web.dataCallbacks.get(callId, None)
         if callbackStruct is None:
             raise StateError('sendDataMsgFromThread: no callbackStruct found for callId {}'.format(callId))
     finally:
         Web.callbackLock.release()
     # wait for semaphore signal indicating a callback for this callId has occured
     signaled = callbackStruct.semaphore.acquire(timeout=timeout)
     if signaled is False:
         raise TimeoutError("sendDataMessage: Data Request Timed Out({}) {}".
                            format(timeout, callbackStruct.msg))
     Web.callbackLock.acquire()
     try:
         # Remove from front of list not back to stay in order
         # Can test removing from back of list to make sure out-of-order works too
         response = callbackStruct.responses.pop(0)
         if 'data' in response:
             status = response.get('status', -1)
             numParts = response.get('numParts', 1)
             complete = (callbackStruct.numResponses == numParts and len(callbackStruct.responses) == 0)
             if complete or status != 200:
                 # End the multipart transfer
                 response['incomplete'] = False
                 Web.dataCallbacks.pop(callId, None)
             else:
                 response['incomplete'] = True
     except IndexError:
         raise StateError('sendDataMessage: callbackStruct.response is None for command {}'.
                          format(callbackStruct.msg))
     finally:
         Web.callbackLock.release()
     response['callId'] = callbackStruct.callId
     return response
예제 #2
0
 def dataCallback(client, message):
     response = json.loads(message)
     if 'cmd' not in response:
         raise StateError('dataCallback: cmd field missing from response: {}'.format(response))
     if 'status' not in response:
         raise StateError('dataCallback: status field missing from response: {}'.format(response))
     if 'callId' not in response:
         raise StateError('dataCallback: callId field missing from response: {}'.format(response))
     status = response.get('status', -1)
     callId = response.get('callId', -1)
     origCmd = response.get('cmd', 'NoCommand')
     logging.log(DebugLevels.L6, "callback {}: {} {}".format(callId, origCmd, status))
     # Thread Synchronized Section
     Web.callbackLock.acquire()
     try:
         callbackStruct = Web.dataCallbacks.get(callId, None)
         if callbackStruct is None:
             logging.error('ProjectInterface: dataCallback callId {} not found, current callId {}'
                           .format(callId, Web.dataSequenceNum))
             return
         if callbackStruct.callId != callId:
             # This should never happen
             raise StateError('callId mismtach {} {}'.format(callbackStruct.callId, callId))
         callbackStruct.responses.append(response)
         callbackStruct.numResponses += 1
         callbackStruct.semaphore.release()
     except Exception as err:
         logging.error('ProjectInterface: dataCallback error: {}'.format(err))
         raise err
     finally:
         Web.callbackLock.release()
     if time.time() > Web.cbPruneTime:
         Web.cbPruneTime = time.time() + 60
         Web.pruneCallbacks()
예제 #3
0
def uploadFiles(request):
    if 'cmd' not in request or request['cmd'] != "uploadFiles":
        raise StateError('uploadFiles: incorrect cmd request: {}'.format(request))
    if Web.wsDataConn is None:
        # A remote fileWatcher hasn't connected yet
        errStr = 'Waiting for fileWatcher to attach, please try again momentarily'
        Web.setUserError(errStr)
        return
    try:
        srcFile = request['srcFile']
        compress = request['compress']
    except KeyError as err:
        Web.setUserError("UploadFiles request missing a parameter: {}".format(err))
        return
    # get the list of file to upload
    cmd = listFilesReqStruct(srcFile)
    response = Web.sendDataMsgFromThread(cmd, timeout=10)
    if response.get('status') != 200:
        Web.setUserError("Error listing files {}: {}".
                         format(srcFile, response.get('error')))
        return
    fileList = response.get('fileList')
    if type(fileList) is not list:
        Web.setUserError("Invalid fileList reponse type {}: expecting list".
                         format(type(fileList)))
        return
    if len(fileList) == 0:
        response = {'cmd': 'uploadProgress', 'file': 'No Matching Files'}
        Web.sendUserMsgFromThread(json.dumps(response))
        return
    for file in fileList:
        try:
            cmd = getFileReqStruct(file, compress=compress)
            data = handleDataRequest(cmd)
            # write the returned data out to a file
            filename = response.get('filename')
            if filename is None:
                if 'data' in response: del response['data']
                raise StateError('sendDataRequestToFile: filename field not in response: {}'.format(response))
            # prepend with common output path and write out file
            # note: can't just use os.path.join() because if two or more elements
            #   have an aboslute path it discards the earlier elements
            global CommonOutputDir
            outputFilename = os.path.normpath(CommonOutputDir + filename)
            dirName = os.path.dirname(outputFilename)
            if not os.path.exists(dirName):
                os.makedirs(dirName)
            writeFile(outputFilename, data)
            response['filename'] = outputFilename
        except Exception as err:
            Web.setUserError(
                "Error uploading file {}: {}".format(file, str(err)))
            return
        response = {'cmd': 'uploadProgress', 'file': file}
        Web.sendUserMsgFromThread(json.dumps(response))
    response = {'cmd': 'uploadProgress', 'file': '------upload complete------'}
    Web.sendUserMsgFromThread(json.dumps(response))
예제 #4
0
 def callback(self, client, message):
     """Recieve a callback from the client and match it to the original request that was sent."""
     response = json.loads(message)
     if 'cmd' not in response:
         raise StateError(
             'dataCallback: cmd field missing from response: {}'.format(
                 response))
     if 'status' not in response:
         raise StateError(
             'dataCallback: status field missing from response: {}'.format(
                 response))
     if 'callId' not in response:
         raise StateError(
             'dataCallback: callId field missing from response: {}'.format(
                 response))
     status = response.get('status', -1)
     callId = response.get('callId', -1)
     origCmd = response.get('cmd', 'NoCommand')
     logging.log(DebugLevels.L6,
                 "callback {}: {} {}".format(callId, origCmd, status))
     # numParts = response.get('numParts')
     # partId = response.get('partId')
     # print(f'callback {callId}: {origCmd} {status} numParts {numParts} partId {partId}')
     # Thread Synchronized Section
     self.callbackLock.acquire()
     try:
         callbackStruct = self.dataCallbacks.get(callId, None)
         if callbackStruct is None:
             # print(f'webServer: dataCallback callId {callId} not found, current callId {self.dataSequenceNum}')
             logging.error(
                 'webServer: dataCallback callId {} not found, current callId {}'
                 .format(callId, self.dataSequenceNum))
             return
         if callbackStruct.callId != callId:
             # This should never happen
             raise StateError('callId mismtach {} {}'.format(
                 callbackStruct.callId, callId))
         callbackStruct.responses.append(response)
         callbackStruct.numResponses += 1
         callbackStruct.semaphore.release()
     except Exception as err:
         logging.error('webServer: dataCallback error: {}'.format(err))
         raise err
     finally:
         self.callbackLock.release()
     if time.time() > self.cbPruneTime:
         self.cbPruneTime = time.time() + 60
         self.pruneCallbacks()
예제 #5
0
 def sendDataMsgFromThreadAsync(msg):
     if Web.wsDataConn is None:
         raise StateError("ProjectInterface: FileServer not connected. Please run the fileServer.")
     callId = msg.get('callId')
     if not callId:
         callbackStruct = StructDict()
         callbackStruct.dataConn = Web.wsDataConn
         callbackStruct.numResponses = 0
         callbackStruct.responses = []
         callbackStruct.semaphore = threading.Semaphore(value=0)
         callbackStruct.timeStamp = time.time()
         callbackStruct.msg = msg.copy()
         if 'data' in callbackStruct.msg:
             del callbackStruct.msg['data']
         Web.callbackLock.acquire()
         try:
             Web.dataSequenceNum += 1
             callId = Web.dataSequenceNum
             callbackStruct.callId = callId
             msg['callId'] = callId
             Web.dataCallbacks[callId] = callbackStruct
         finally:
             Web.callbackLock.release()
         Web.ioLoopInst.add_callback(Web.sendDataMessage, msg)
     return callId
예제 #6
0
    def watchFile(self, filename: str, timeout: int = 5) -> bytes:
        """Watches for a specific file to be created and returns the file data.

        InitWatch() must be called first, before watching for specific files.
        If filename includes the full path, the path must match that used in initWatch().
        """
        data = None
        if not self.initWatchSet:
            raise StateError(
                "DataInterface: watchFile() called without an initWatch()")

        # check filename dir matches initWatch dir
        fileDir, fileCheck = os.path.split(filename)
        if fileDir not in ('', None):
            if fileDir != self.watchDir:
                raise RequestError(
                    "DataInterface: watchFile: filepath doesn't match "
                    f"watch directory: {fileDir}, {self.watchDir}")
            self._checkAllowedDirs(fileDir)
        self._checkAllowedFileTypes(fileCheck)

        self.fileWatchLock.acquire()
        try:
            foundFilename = self.fileWatcher.waitForFile(
                filename, timeout=timeout, timeCheckIncrement=0.2)
        finally:
            self.fileWatchLock.release()
        if foundFilename is None:
            raise TimeoutError("WatchFile: Timeout {}s: {}".format(
                timeout, filename))
        else:
            with open(foundFilename, 'rb') as fp:
                data = fp.read()
        return data
예제 #7
0
def parseDicomVolume(dicomImg, sliceDim):
    """
    The raw dicom file coming from the scanner will be a 2-dimensional picture
    made of up multiple image slices that are tiled together. This function
    separates the image slices to form a single volume.

    Used externally.
    """
    sliceWidth = sliceDim
    sliceHeight = sliceDim

    image = dicomImg.pixel_array

    dicomHeight, dicomWidth = image.shape
    numSlicesPerRow = dicomWidth // sliceWidth
    numSlicesPerCol = dicomHeight // sliceHeight

    max_slices = numSlicesPerRow * numSlicesPerCol
    volume = np.full((sliceWidth, sliceHeight, max_slices), np.nan)

    sliceNum = 0
    for row in range(numSlicesPerCol):
        for col in range(numSlicesPerRow):
            if sliceNum >= max_slices:
                raise StateError(
                    'parseDicomVolume: sliceNum {} exceeds max_slices {}'.
                    format(sliceNum, max_slices))
            rpos = row * sliceHeight
            cpos = col * sliceWidth
            slice = image[rpos:rpos + sliceHeight, cpos:cpos + sliceWidth]
            volume[:, :, sliceNum] = slice
            sliceNum += 1
    return volume
예제 #8
0
    def initFileNotifier(self,
                         dir: str,
                         filePattern: str,
                         minFileSize: int,
                         demoStep: int = 0) -> None:
        """
        Initialize the file watcher to watch for files in the specified directory.
        Note: inotify doesn't use filepatterns

        Args:
            dir (str): Directory to watch in
            filePattern (str): ignored by inotify implementation
            minFileSize (int): Minimum file size necessary to consider the file is wholely written.
                Below this size the filewatcher will assume file is paritally written and continue
                to wait.
            demoStep (int): If non-zero then it will space out file notifications by demoStep seconds.
                This is used when the image files are pre-existing but we want to simulate as if
                the arrive from the scanner every few seconds (demoStep seconds).
        """
        self.demoStep = demoStep
        self.minFileSize = minFileSize
        if dir is None:
            raise StateError('initFileNotifier: dir is None')
        if not os.path.exists(dir):
            raise NotADirectoryError("No such directory: %s" % (dir))
        if dir != self.watchDir:
            if self.watchDir is not None:
                self.notifier.remove_watch(self.watchDir)
            self.watchDir = dir
            self.notifier.add_watch(self.watchDir,
                                    mask=inotify.constants.IN_CLOSE_WRITE)
예제 #9
0
def parseDicomVolume(dicomImg, sliceDim):
    '''The raw dicom file will be a 2D picture with multiple slices tiled together.
       We need to separate the slices and form a volume from them.
    '''
    sliceWidth = sliceDim
    sliceHeight = sliceDim

    image = dicomImg.pixel_array

    dicomHeight, dicomWidth = image.shape
    numSlicesPerRow = dicomWidth // sliceWidth
    numSlicesPerCol = dicomHeight // sliceHeight

    max_slices = numSlicesPerRow * numSlicesPerCol
    volume = np.full((sliceWidth, sliceHeight, max_slices), np.nan)

    sliceNum = 0
    for row in range(numSlicesPerCol):
        for col in range(numSlicesPerRow):
            if sliceNum >= max_slices:
                raise StateError('parseDicomVolume: sliceNum {} exceeds max_slices {}'
                                 .format(sliceNum, max_slices))
            rpos = row * sliceHeight
            cpos = col * sliceWidth
            slice = image[rpos: rpos+sliceHeight, cpos: cpos+sliceWidth]
            volume[:, :, sliceNum] = slice
            sliceNum += 1
    return volume
예제 #10
0
    def _uploadFilesHandler(self, request):
        """Handle requests from the web interface to upload files to this computer."""
        global CommonOutputDir
        if 'cmd' not in request or request['cmd'] != "uploadFiles":
            raise StateError(
                'uploadFiles: incorrect cmd request: {}'.format(request))
        try:
            srcFile = request['srcFile']
            compress = request['compress']  # TODO use compress parameter
        except KeyError as err:
            self._setError(
                "UploadFiles request missing a parameter: {}".format(err))
            return

        # get handle to dataInterface
        dataInterface = ProjectRPCService.exposed_DataInterface

        # get the list of file to upload
        fileList = dataInterface.listFiles(srcFile)
        if len(fileList) == 0:
            # TODO - make sendUploadProgress() command
            self.webUI.sendUploadStatus('No Matching Files')
            return

        uploadFilesFromList(dataInterface, fileList, CommonOutputDir)
        # TODO - break long fileList into parts and provide periodic progress message
        # self.webUI.sendUploadStatus(fileName)
        self.webUI.sendUploadStatus('------upload complete------')
예제 #11
0
 def registerSubjectCommFunction(commFunction):
     """
     Register the function call to forward an RPC subject requests over websockets. This is the
     communication for the second hop (described above) to the remote service.
     """
     if ProjectRPCService.exposed_SubjectInterface is None:
         raise StateError("exposed_SubjectInterface not instatiated yet")
     ProjectRPCService.exposed_SubjectInterface.registerCommFunction(commFunction)
예제 #12
0
def clientSendCmd(commPipes, cmd):
    '''Send a request using named pipes to the projectInterface for handling.
    This allows a separate client process to make requests of the projectInterface process.
    It writes the request on fd_out and recieves the reply on fd_in.
    '''
    data = None
    savedError = None
    incomplete = True
    while incomplete:
        commPipes.fd_out.write(json.dumps(cmd) + os.linesep)
        msg = commPipes.fd_in.readline()
        if len(msg) == 0:
            # fifo closed
            raise StateError('commPipe closed')
        response = json.loads(msg)
        status = response.get('status', -1)
        if status != 200:
            raise RequestError(
                'clientSendCmd: Cmd: {} status {}: error {}'.format(
                    cmd.get('cmd'), status, response.get('error')))
        if 'data' in response:
            try:
                data = unpackDataMessage(response)
            except Exception as err:
                # The call may be incomplete, save the error and keep receiving as needed
                logging.error('clientSendCmd: {}'.format(err))
                if savedError is None:
                    savedError = err
            cmd['callId'] = response.get('callId', -1)
        # Check if need to continue to get more parts
        incomplete = response.get('incomplete', False)
    if savedError:
        raise RequestError('clientSendCmd: {}'.format(savedError))
    retVals = StructDict()
    retVals.statusCode = response.get('status', -1)
    if 'filename' in response:
        retVals.filename = response['filename']
    if 'fileList' in response:
        retVals.fileList = response['fileList']
    if 'fileTypes' in response:
        retVals.fileTypes = response['fileTypes']
    if data:
        retVals.data = data
        if retVals.filename is None:
            raise StateError('clientSendCmd: filename field is None')
    return retVals
예제 #13
0
def processPyScriptRequest(request):
    if 'cmd' not in request:
        raise StateError('handleFifoRequests: cmd field not in request: {}'.format(request))
    cmd = request['cmd']
    route = request.get('route')
    localtimeout = request.get('timeout', 10) + 5
    response = StructDict({'status': 200})
    if route == 'dataserver':
        try:
            response = Web.sendDataMsgFromThread(request, timeout=localtimeout)
            if response is None:
                raise StateError('handleFifoRequests: Response None from sendDataMessage')
            if 'status' not in response:
                raise StateError('handleFifoRequests: status field missing from response: {}'.format(response))
            if response['status'] not in (200, 408):
                if 'error' not in response:
                    raise StateError('handleFifoRequests: error field missing from response: {}'.format(response))
                Web.setUserError(response['error'])
                logging.error('handleFifo status {}: {}'.format(response['status'], response['error']))
        except Exception as err:
            errStr = 'SendDataMessage Exception type {}: error {}:'.format(type(err), str(err))
            response = {'status': 400, 'error': errStr}
            Web.setUserError(errStr)
            logging.error('handleFifo Excpetion: {}'.format(errStr))
            raise err
    else:
        if cmd == 'webCommonDir':
            response.filename = CommonOutputDir
        elif cmd == 'resultValue':
            try:
                # forward to bioFeedback Display
                Web.sendBiofeedbackMsgFromThread(json.dumps(request))
                # forward to main browser window
                Web.sendUserMsgFromThread(json.dumps(request))
                # Accumulate results locally to resend to browser as needed
                Web.addResultValue(request)
            except Exception as err:
                errStr = 'SendClassification Exception type {}: error {}:'.format(type(err), str(err))
                response = {'status': 400, 'error': errStr}
                Web.setUserError(errStr)
                logging.error('handleFifo Excpetion: {}'.format(errStr))
                raise err
        elif cmd == 'subjectDisplay':
            logging.info('subjectDisplay projectInterface Callback')
    return response
예제 #14
0
 def close_pending_requests(self, channelName):
     """Close out all pending RPC requests when a connection is disconnected"""
     handler = self.handlers.get(channelName)
     if handler is None:
         raise StateError(f'RPC Handler {channelName} not registered')
     try:
         handler.close_pending_requests()
     except Exception as err:
         self.setError('close_pending_requests: ' + format(err))
예제 #15
0
 def subjectWsCallback(self, client, message):
     """Callback for requests sent to remote service over the wsSubject channel"""
     handler = self.handlers.get('wsSubject')
     if handler is None:
         raise StateError(f'RPC Handler wsSubject not registered')
     try:
         handler.callback(client, message)
     except Exception as err:
         self.setError('subjectWsCallback: ' + format(err))
예제 #16
0
 def allowedFileTypes(self):
     if self.local:
         return ['*']
     else:
         cmd = projUtils.allowedFileTypesReqStruct()
         retVals = projUtils.clientSendCmd(self.commPipes, cmd)
         fileTypes = retVals.get('fileTypes')
         if type(fileTypes) is not list:
             errStr = "Invalid fileTypes reponse type {}: expecting list".format(type(fileTypes))
             raise StateError(errStr)
     return fileTypes
예제 #17
0
 def validateRequestedFile(dir, file, cmd):
     textFileTypeOnly = False
     wildcardAllowed = False
     if cmd in ('putTextFile', 'dataLog'):
         textFileTypeOnly = True
     if cmd in ('listFiles'):
         wildcardAllowed = True
     # Restrict requests to certain directories and file types
     WsFileWatcher.validationError = None
     if WsFileWatcher.allowedDirs is None or WsFileWatcher.allowedTypes is None:
         raise StateError(
             'FileServer: Allowed Directories or File Types is not set')
     if file is not None and file != '':
         fileDir, filename = os.path.split(file)
         fileExtension = Path(filename).suffix
         if textFileTypeOnly:
             if fileExtension != '.txt':
                 WsFileWatcher.validationError = \
                     'Only .txt files allowed with command putTextFile() or dataLog()'
                 return False
         if wildcardAllowed:
             pass  # wildcard searches will be filtered for filetype later
         elif fileExtension not in WsFileWatcher.allowedTypes:
             WsFileWatcher.validationError = \
                 "File type {} not in list of allowed file types {}. " \
                 "Specify allowed filetypes with FileServer -f parameter.". \
                 format(fileExtension, WsFileWatcher.allowedTypes)
             return False
         if fileDir is not None and fileDir != '':  # and os.path.isabs(fileDir):
             dirMatch = False
             for allowedDir in WsFileWatcher.allowedDirs:
                 if fileDir.startswith(allowedDir):
                     dirMatch = True
                     break
             if dirMatch is False:
                 WsFileWatcher.validationError = \
                     "Path {} not within list of allowed directories {}. " \
                     "Make sure you specified a full (absolute) path. " \
                     "Specify allowed directories with FileServer -d parameter.". \
                     format(fileDir, WsFileWatcher.allowedDirs)
                 return False
     if dir is not None and dir != '':
         for allowedDir in WsFileWatcher.allowedDirs:
             if dir.startswith(allowedDir):
                 return True
         WsFileWatcher.validationError = \
             "Path {} not within list of allowed directories {}. " \
             "Make sure you specified a full (absolute) path. " \
             "Specify allowed directories with FileServer -d parameter.". \
             format(dir, WsFileWatcher.allowedDirs)
         return False
     # default case
     return True
예제 #18
0
 def registerDataCommFunction(commFunction):
     """
     Register the function call to forward an RPC data requests over websockets. This is the
     communication for the second hop (described above) to the remote service.
     """
     if (ProjectRPCService.exposed_DataInterface is None
             and ProjectRPCService.exposed_BidsInterface is None):
         raise StateError("ServerRPC no dataInterface instatiated yet")
     if ProjectRPCService.exposed_DataInterface is not None:
         ProjectRPCService.exposed_DataInterface.registerCommFunction(
             commFunction)
     if ProjectRPCService.exposed_BidsInterface is not None:
         ProjectRPCService.exposed_BidsInterface.registerCommFunction(
             commFunction)
예제 #19
0
 def initFileNotifier(self, dir, filePattern, minFileSize, demoStep=0):
     # inotify doesn't use filepatterns
     self.demoStep = demoStep
     self.minFileSize = minFileSize
     if dir is None:
         raise StateError('initFileNotifier: dir is None')
     if not os.path.exists(dir):
         raise NotADirectoryError("No such directory: %s" % (dir))
     if dir != self.watchDir:
         if self.watchDir is not None:
             self.notifier.remove_watch(self.watchDir)
         self.watchDir = dir
         self.notifier.add_watch(self.watchDir,
                                 mask=inotify.constants.IN_CLOSE_WRITE)
예제 #20
0
 def watchFile(self, filename, timeout=5):
     data = None
     if not self.initWatchSet:
         raise StateError("FileInterface: watchFile() called without an initWatch()")
     if self.local:
         retVal = self.fileWatcher.waitForFile(filename, timeout=timeout)
         if retVal is None:
             raise FileNotFoundError("WatchFile: Timeout {}s: {}".format(timeout, filename))
         else:
             with open(filename, 'rb') as fp:
                 data = fp.read()
     else:
         watchCmd = projUtils.watchFileReqStruct(filename, timeout=timeout)
         retVals = projUtils.clientSendCmd(self.commPipes, watchCmd)
         data = retVals.data
     return data
예제 #21
0
 def listFiles(self, filePattern):
     if self.local:
         if not os.path.isabs(filePattern):
             errStr = "listFiles must have an absolute path: {}".format(filePattern)
             raise RequestError(errStr)
         fileList = []
         for filename in glob.iglob(filePattern, recursive=True):
             if os.path.isdir(filename):
                 continue
             fileList.append(filename)
     else:
         listCmd = projUtils.listFilesReqStruct(filePattern)
         retVals = projUtils.clientSendCmd(self.commPipes, listCmd)
         fileList = retVals.get('fileList')
         if type(fileList) is not list:
             errStr = "Invalid fileList reponse type {}: expecting list".format(type(fileList))
             raise StateError(errStr)
     return fileList
예제 #22
0
 def handleRPCRequest(self, channelName, cmd, timeout=60):
     """Process RPC requests using websocket RequestHandler to send the request"""
     """Caller will catch exceptions"""
     handler = self.handlers[channelName]
     if handler is None:
         raise StateError(f'RPC Handler {channelName} not registered')
     savedError = None
     incomplete = True
     # print(f'handle request {cmd}')
     if cmd.get('cmd') == 'rpc':
         # if cmd is rpc, check and encode any byte args as base64
         cmd = encodeByteTypeArgs(cmd)
         # Convert numpy arguments to native python types
         cmd['args'] = npToPy(cmd.get('args', ()))
         cmd['kwargs'] = npToPy(cmd.get('kwargs', {}))
     while incomplete:
         response = handler.doRequest(cmd, timeout)
         if response.get('status') != 200:
             errStr = 'handleDataRequest: status {}, err {}'.format(
                 response.get('status'), response.get('error'))
             self.setError(errStr)
             raise RequestError(errStr)
         try:
             data = unpackDataMessage(response)
         except Exception as err:
             errStr = 'handleDataRequest: unpackDataMessage: {}'.format(err)
             logging.error(errStr)
             if savedError is None:
                 savedError = errStr
         incomplete = response.get('incomplete', False)
         cmd['callId'] = response.get('callId', -1)
         cmd['incomplete'] = incomplete
     if savedError:
         self.setError(savedError)
         raise RequestError(savedError)
     serializationType = response.get('dataSerialization')
     if serializationType == 'json':
         if type(data) is bytes:
             data = data.decode()
         data = json.loads(data)
     elif serializationType == 'pickle':
         data = pickle.loads(data)
     return data
예제 #23
0
    def close():
        # Currently this should never be called
        raise StateError("Web close() called")

        Web.wsConnLock.acquire()
        try:
            if Web.wsDataConn is not None:
                Web.wsDataConn.close()
            Web.wsDataConn = None

            for client in Web.wsBrowserMainConns[:]:
                client.close()
            Web.wsBrowserMainConns = []

            for client in Web.wsBiofeedbackConns[:]:
                client.close()
            Web.wsBiofeedbackConns = []
        finally:
            Web.wsConnLock.release()
예제 #24
0
def sendWebSocketMessage(wsName, msg, conn=None):
    """Send messages from the web server to all clients connected on the specified wsName socket."""
    websocketState.wsConnLock.acquire()
    try:
        connList = websocketState.wsConnectionLists.get(wsName)
        if connList is not None:
            if conn is None:
                for client in connList:
                    client.write_message(msg)
            else:
                if conn not in connList:
                    raise StateError(
                        f'sendWebSocketMessage: {wsName} no matching connection {conn}'
                    )
                conn.write_message(msg)
        else:
            logging.log(
                DebugLevels.L6,
                f'sendWebSocketMessage: {wsName} has no connectionList')
    finally:
        websocketState.wsConnLock.release()
예제 #25
0
 def runRemoteCall(self, callDict):
     # print(f'remoteCall {callDict}')
     className = callDict.get('class')
     attributeName = callDict.get('attribute')
     if None in (className, attributeName):
         raise RequestError(f'Malformed remote call struct: missing one of '
                            f'class {className}, attribute {attributeName}')
     classInstance = self.classInstanceDict.get(className)
     if classInstance is None:
         raise StateError(
             f'RemoteHandler: class {className} not registered')
     attributeInstance = getattr(classInstance, attributeName)
     if not callable(attributeInstance):
         return attributeInstance
     args = callDict.get('args', ())
     if args is None:  # Can happen if key 'args' exists and is set to None
         args = ()
     kwargs = callDict.get('kwargs', {})
     if kwargs is None:
         kwargs = {}
     res = attributeInstance(*args, **kwargs)
     return res
예제 #26
0
 def prepare_request(self, msg):
     """Prepate a request to be sent, including creating a callback structure and unique ID."""
     # Get data server connection the request will be sent on
     websocketState.wsConnLock.acquire()
     try:
         wsConnections = websocketState.wsConnectionLists.get(self.name)
         if wsConnections is None or len(wsConnections) == 0:
             serviceName = 'DataService'
             if self.name == 'wsSubject':
                 serviceName = 'SubjectService'
             raise StateError(
                 f"RemoteService: {serviceName} not connected. Please start the remote service."
             )
         reqConn = wsConnections[-1]  # always use most recent connection
     finally:
         websocketState.wsConnLock.release()
     callId = msg.get('callId')
     if not callId:
         callbackStruct = StructDict()
         callbackStruct.dataConn = reqConn
         callbackStruct.numResponses = 0
         callbackStruct.responses = []
         callbackStruct.semaphore = threading.Semaphore(value=0)
         callbackStruct.timeStamp = time.time()
         callbackStruct.msg = msg.copy()
         if 'data' in callbackStruct.msg:
             del callbackStruct.msg['data']
         self.callbackLock.acquire()
         try:
             self.dataSequenceNum += 1
             callId = self.dataSequenceNum
             callbackStruct.callId = callId
             msg['callId'] = callId
             self.dataCallbacks[callId] = callbackStruct
         finally:
             self.callbackLock.release()
         # self.ioLoopInst.add_callback(Web.sendDataMessage, msg)
     return callId, reqConn
예제 #27
0
def signalFifoExit(fifoThread, commPipes):
    '''Under normal exit conditions the fifothread will exit when the fifo filehandles
    are closed. However if the fifo filehandles were never opened by both ends then
    the fifothread can be blocked waiting for them to open. To handle that case
    we open both filehandles with O_NONBLOCK flag so that if the fifo thread reader
    is listening it will be opened and closed, if not it will throw OSError exception
    in which case the fifothread has already exited and closed the fifo filehandles.
    '''
    if fifoThread is None:
        return
    try:
        pipeout = os.open(commPipes.name_out, os.O_RDONLY | os.O_NONBLOCK)
        os.close(pipeout)
        # trigger context swap to allow handleFifoRequests to open next pipe if needed
        time.sleep(0.1)
        pipein = os.open(commPipes.name_in, os.O_WRONLY | os.O_NONBLOCK)
        os.close(pipein)
    except OSError as err:
        # No reader/writer listening on file so fifoThread already exited
        # print('signalFifoExit: exception {}'.format(err))
        pass
    fifoThread.join(timeout=1)
    if fifoThread.is_alive() is not False:
        raise StateError('runSession: fifoThread not completed')
예제 #28
0
 def close():
     # Currently this should never be called
     raise StateError("Web close() called")
예제 #29
0
    def waitForFile(self, filename: str, timeout: int = 0) -> Optional[str]:
        """
        Wait for a specific filename to be created in the directory specified in initFileNotifier.
        Args:
            filename: Name of File to watch for creation of. If filename includes a path it must 
                match that specified in initFileNotifier.
            timeout: Max number of seconds to watch for the file creation. If timeout expires
                before the file is created then None will be returned
        Returns:
            The filename of the created file (same as input arg) or None if timeout expires
        """
        _filedir, _filename = os.path.split(filename)
        if _filedir in (None, ''):
            filename = os.path.join(self.watchDir, filename)
        elif _filedir != self.watchDir:
            raise StateError(
                f"FileWatcher: file path doesn't match watch directory: {_filedir}, {self.watchDir}"
            )

        fileExists = os.path.exists(filename)
        if not fileExists:
            if self.notify_thread is None:
                raise FileNotFoundError(
                    "No fileNotifier and dicom file not found %s" % (filename))
            else:
                logStr = "FileWatcher: Waiting for file {}, timeout {}s ".format(
                    filename, timeout)
                logging.log(DebugLevels.L6, logStr)
        eventLoopCount = 0
        exitWithFileEvent = False
        eventTimeStamp = 0
        startTime = time.time()
        timeToCheckForFile = time.time(
        ) + 1  # check if file exists at least every second
        while not fileExists:
            if timeout > 0 and time.time() > (startTime + timeout):
                return None
            # look for file creation event
            eventLoopCount += 1
            try:
                eventfile, ts = self.fileNotifyQ.get(block=True, timeout=1.0)
            except Empty:
                # The timeout occured on fileNotifyQ.get()
                fileExists = os.path.exists(filename)
                continue
            if eventfile is None:
                raise StateError('waitForFile: eventfile is None')
            # We may have a stale event from a previous file if multiple events
            #   are created per file or if the previous file eventloop
            #   timed out and then the event arrived later.
            if eventfile == filename:
                fileExists = True
                exitWithFileEvent = True
                eventTimeStamp = ts
                continue
            if time.time() > timeToCheckForFile:
                # periodically check if file exists, can occur if we get
                #   swamped with unrelated events
                fileExists = os.path.exists(filename)
                timeToCheckForFile = time.time() + 1
        if exitWithFileEvent is False:
            # We didn't get a file-close event because the file already existed.
            # Check the file size and sleep up to 300 ms waitig for full size
            waitIncrement = 0.1
            totalWriteWait = 0.0
            fileSize = os.path.getsize(filename)
            while fileSize < self.minFileSize and totalWriteWait < 0.3:
                time.sleep(waitIncrement)
                totalWriteWait += waitIncrement
                fileSize = os.path.getsize(filename)
        logging.log(
            DebugLevels.L6,
            "File avail: eventLoopCount %d, fileEventCaptured %s, "
            "fileName %s, eventTimeStamp %d", eventLoopCount,
            exitWithFileEvent, filename, eventTimeStamp)
        if self.demoStep is not None and self.demoStep > 0:
            self.prevEventTime = demoDelay(self.demoStep, self.prevEventTime)
        return filename
예제 #30
0
def unpackDataMessage(msg):
    global multiPartDataCache
    try:
        if msg.get('status') != 200:
            # On error delete any partial transfers
            fileHash = msg.get('fileHash')
            if fileHash is not None and fileHash in multiPartDataCache:
                del multiPartDataCache[fileHash]
            raise RequestError('unpackDataMessage: {} {}'.format(
                msg.get('status'), msg.get('error')))
        data = decodeMessageData(msg)
        multipart = msg.get('multipart', False)
        numParts = msg.get('numParts', 1)
        partId = msg.get('partId', 1)
        logging.debug('unpackDataMessage: callid {}, part {} of {}'.format(
            msg.get('callId'), partId, numParts))
        if multipart is False or numParts == 1:
            # All data sent in a single message
            return data
        else:
            assert numParts > 1
            assert multipart is True
            if partId > numParts:
                raise RequestError(
                    'unpackDataMessage: Inconsistent parts: partId {} exceeds numParts {}'
                    .format(partId, numParts))
            # get the data structure for this data
            fileHash = msg.get('fileHash')
            if partId > 1:
                partialDataStruct = multiPartDataCache.get(fileHash)
                if partialDataStruct is None:
                    raise RequestError(
                        'unpackDataMessage: partialDataStruct not found')
            else:
                partialDataStruct = StructDict({
                    'cachedDataParts': [None] * numParts,
                    'numCachedParts': 0
                })
                multiPartDataCache[fileHash] = partialDataStruct
            partialDataStruct.cachedDataParts[partId - 1] = data
            partialDataStruct.numCachedParts += 1
            if partialDataStruct.numCachedParts == numParts:
                # All parts of the multipart transfer have been received
                # Concatenate the data into one bytearray
                data = bytearray()
                for i in range(numParts):
                    dataPart = partialDataStruct.cachedDataParts[i]
                    if dataPart is None:
                        raise StateError(
                            'unpackDataMessage: missing dataPart {}'.format(i))
                    data.extend(dataPart)
                # Check fileHash and fileSize
                dataHash = hashlib.md5(data).hexdigest()
                dataSize = len(data)
                if dataHash != fileHash:
                    raise RequestError(
                        "unpackDataMessage: File checksum mismatch {} {}".
                        format(dataHash, fileHash))
                if dataSize != msg.get('fileSize', 0):
                    raise RequestError(
                        "unpackDataMessage: File size mismatch {} {}".format(
                            dataSize, msg.get('fileSize', 0)))
                # delete the multipart data cache for this item
                del multiPartDataCache[fileHash]
                return data
        # Multi-part transfer not complete, nothing to return
        return None
    except Exception as err:
        # removed any cached data
        fileHash = msg.get('fileHash')
        if fileHash and fileHash in multiPartDataCache:
            del multiPartDataCache[fileHash]
        raise err