Skip to content
Snippets Groups Projects
Select Git revision
  • 77305f09753a0414ae0090ed1ad966b7414b92bb
  • master default
  • 1.1
  • 1.0
4 results

normalize.cpp

Blame
  • VirtualAcousticsStarterServer.py 17.37 KiB
    import sys, socket, subprocess, time, os, json
    from enum import Enum
    from configparser import ConfigParser
    
    
    class ErrorCodes(Enum):
      NO_ERROR = 0
      ERROR_NO_CONFIG_FOUND = 1
      ERROR_INCOMPLETE_CONFIG = 2
      ERROR_BINDING_SOCKET = 3
      ERROR_CONNECTING_SOCKET = 4
      ERROR_MISSING_VA_INI = 5
      ERROR_INVALID_REPRODUCTION_ID = 6
      ERROR_INCOMPLETE_VA_INI = 7
      ERROR_UNDEFINED_LAUNCHER_STATE = 8
    
    class ReproductionInput(Enum):
      NOT_SPECIFIED = 0
      BINAURAL = 1
      AMBISONICS = 2
      CUSTOM = 3
    
    # Class representing the VA-Launcher config (.json) file
    class LauncherConfig:
      def __init__(conf, sConfigFile):
        try:
          with open( sConfigFile ) as json_file:
            json_config = json.load(json_file)
        except Exception as e:
          print( "ERROR reading the json config:" + os.linesep + str(e) )
          sys.exit( ErrorCodes.ERROR_INCOMPLETE_CONFIG )
    
        try:
          conf.dVirtualAcousticDirectories = json_config["dVAServerDirectories"]
          conf.sLocalIP = json_config["sLocalIP"]
          conf.nLauncherPort = json_config["nLauncherPort"]
          conf.nVAServerPort = json_config["nVAServerPort"]
          conf.nWaitForVAServerStart = json_config["nWaitForVAServerStart"]
        except KeyError as e:
          print( "ERROR reading the json config. Missing " + str(e.args[0]) )
          sys.exit( ErrorCodes.ERROR_INCOMPLETE_CONFIG )
        
        try:
          conf.sVASetupIni = json_config["sVASetupIni"]
        except KeyError:
          conf.sVASetupIni = None
    
        try:
          conf.lsBinauralReproductionModules = json_config["lsBinauralReproductionModules"]
        except KeyError:
          conf.lsBinauralReproductionModules = None
        try:
          conf.lsAmbisonicsReproductionModules = json_config["lsAmbisonicsReproductionModules"]
        except KeyError:
          conf.lsAmbisonicsReproductionModules = None
        try:
          conf.lsCustomReproductionModules = json_config["lsCustomReproductionModules"]
        except KeyError:
          conf.lsCustomReproductionModules = None
    
    
    class VAComposedIniParser:
      def __init__(self, oLauncherConf : LauncherConfig):
        self.sVASetupIni = oLauncherConf.sVASetupIni
        self.lsBinauralReproductionModules = oLauncherConf.lsBinauralReproductionModules
        self.lsAmbisonicsReproductionModules = oLauncherConf.lsAmbisonicsReproductionModules
        self.lsCustomReproductionModules = oLauncherConf.lsCustomReproductionModules
        self.eReproductionInput = ReproductionInput.NOT_SPECIFIED
        self.sRendererIniPath = None
        self.sConfFolder = "../conf/"
    
      def _create_parser(self):
        parser = ConfigParser()
        parser.optionxform = str
        return parser
    
      def get_main_inifile(self):
        return self.sConfFolder + "VACore.Main.ini"
    
      def prepare_inis(self):
        print("Parsing VA ini-files")
        self.link_setup_and_renderer_ini()
        return self.prepare_reproduction_ini()
    
      def link_setup_and_renderer_ini(self):
        if not self.sRendererIniPath:
          print("No renderer ini-file sent by client, using default one: 'VARenderer.Default.ini'")
          self.sRendererIniPath = "VARenderer.Default.ini"
        if not self.sVASetupIni:
          print("No setup ini-file specified, using default one: 'VASetup.Launcher.ini'")
          self.sVASetupIni = "VASetup.Launcher.ini"
        
        sMainInifile = self.get_main_inifile()
        if not os.path.isfile(sMainInifile):
          print("ERROR: Could not find 'VACore.Main.ini' in 'conf' folder. Did you delete it?")
          sys.exit( ErrorCodes.ERROR_MISSING_VA_INI )
    
        mainIni = self._create_parser()
        try:
          mainIni.read( sMainInifile )
          mainIni["Files"]["VARendererIni"] = self.sRendererIniPath
          mainIni["Files"]["VASetupIni"] = self.sVASetupIni
        except Exception as e:
          print( "ERROR: " + str(e) )
          sys.exit( ErrorCodes.ERROR_INCOMPLETE_VA_INI )
        
        with open( sMainInifile, 'w' ) as inifile:
          mainIni.write(inifile)
      
      def prepare_reproduction_ini(self):
        sFileToRead = self.sConfFolder + "VAReproduction.Prototype.ini"
        sFileToWrite = self.sConfFolder + "VAReproduction.ini"
        if not os.path.isfile(sFileToRead):
          print("ERROR: Could not find 'VAReproduction.Prototype.ini' in 'conf' folder. Did you delete it?")
          sys.exit( ErrorCodes.ERROR_MISSING_VA_INI )
    
        reproductionIni = self._create_parser()
        try:
          reproductionIni.read( sFileToRead )
        except Exception as e:
          print( "ERROR: " + str(e) )
          sys.exit( ErrorCodes.ERROR_INCOMPLETE_VA_INI )
        
        if self.eReproductionInput == ReproductionInput.BINAURAL:
          if self.lsBinauralReproductionModules:
            lsReproductionModuleIDs = self.lsBinauralReproductionModules
          else:
            print("ERROR: Requested BINAURAL reproduction modules are not specified")
            return False
        elif self.eReproductionInput == ReproductionInput.AMBISONICS:
          if self.lsAmbisonicsReproductionModules:
            lsReproductionModuleIDs = self.lsAmbisonicsReproductionModules
          else:
            print("ERROR: Requested AMBISONICS reproduction modules are not specified")
            return False
        elif self.eReproductionInput == ReproductionInput.CUSTOM:
          if self.lsCustomReproductionModules:
            lsReproductionModuleIDs = self.lsCustomReproductionModules
          else:
            print("ERROR: Requested CUSTOM reproduction modules are not specified")
            return False
    
        for sReproductionID in lsReproductionModuleIDs:
          sSection = "Reproduction:" + sReproductionID
          if not reproductionIni.has_section(sSection):
            print( "ERROR: Reproduction module with ID: '" + sReproductionID  + "' not available in respective ini file '" + sFileToRead + "'")
            #sys.exit( ErrorCodes.ERROR_INVALID_REPRODUCTION_ID )
            return False
          reproductionIni[sSection]["Enabled"] = "True"
        
        with open( sFileToWrite, 'w' ) as inifile:
          reproductionIni.write(inifile)
        
        return True
    
    
    #Main class representing the VA Launcher app
    class VirtualAcousticsLauncher:
      def __init__(self):
        print("init")
        self.oConfig = None
        self.vaIniParser = None
        self.oVAProcess = None 
        self.oLauncherServerSocket = None 
        self.oLauncherConnection = None 
        self.sCurrentScriptsDirectory = os.path.dirname( os.path.realpath( sys.argv[0] ) )
        self.sVAServerID = None
        self.sVAServerDir = None
    
        self.start()
    
      #Start the launcher
      def start(self):
        print( "VirtualAcoustics Starter script - press ctrl+c to quit" )
        self.read_config()
        self.open_server_socket()
        self.main_loop()
    
    
      #Reads the launcher config and initializes all respective class parameters
      def read_config(self):
        sHostConfigurationFile = "VirtualAcousticStarterConfig." + socket.gethostname() + ".json"
        sGeneralConfigurationFile = "VirtualAcousticStarterConfig.json"
    
        #check which config file exists and load it
        sUsedConfigFile = ""
        if os.path.isfile( sHostConfigurationFile ):
          sUsedConfigFile = sHostConfigurationFile
        elif os.path.isfile( self.sCurrentScriptsDirectory + "/" + sHostConfigurationFile ):
          sUsedConfigFile = self.sCurrentScriptsDirectory + "/" + sHostConfigurationFile
        elif os.path.isfile( sGeneralConfigurationFile ):
          sUsedConfigFile = sGeneralConfigurationFile
        elif os.path.isfile( self.sCurrentScriptsDirectory + "/" + sGeneralConfigurationFile ):
          sUsedConfigFile = self.sCurrentScriptsDirectory + "/" + sGeneralConfigurationFile
        else:
          print( "ERROR: No configuration file found - please create " + sHostConfigurationFile + " or " + sGeneralConfigurationFile )
          sys.exit( ErrorCodes.ERROR_NO_CONFIG_FOUND )
    
        print("Using config: " + sUsedConfigFile)
        self.oConfig = LauncherConfig( sUsedConfigFile )
    
        self.vaIniParser = VAComposedIniParser(self.oConfig)
    
      #Open network socket used for the communication
      def open_server_socket(self):
        print( "Creating server socket at " + self.oConfig.sLocalIP + ":" + str( self.oConfig.nLauncherPort ) )
        
        self.oLauncherServerSocket = socket.socket()
        if self.oConfig.sLocalIP == "":
          self.oConfig.sLocalIP = socket.gethostname()
        try:
          self.oLauncherServerSocket.bind( ( self.oConfig.sLocalIP, self.oConfig.nLauncherPort ) )
          self.oLauncherServerSocket.listen( 3 ) 
        except socket.error:
          print( "Error on binding socket" )
          sys.exit( ErrorCodes.ERROR_BINDING_SOCKET )
        self.oLauncherServerSocket.settimeout( 1.0 )
    
      def _reset_connection(self):
        if self.oLauncherConnection:
          self.oLauncherConnection.close()
          self.oLauncherConnection = None
        self.sVAServerDir = None
        self.sVAServerID = None
        self.vaIniParser.sRendererIniPath = None
    
      def _close_va_and_reset_connection(self):
        if self.oVAProcess:
          print( "Closing VA instance" )
          self.oVAProcess.terminate()
          time.sleep( 1 )
          self.oVAProcess.kill()
          self.oVAProcess = None
        self._reset_connection()
    
      def main_loop(self):
        try:
          while True:
            if not self.oLauncherConnection:
              self.wait_for_connection()
                
            elif self.sVAServerDir and not self.oVAProcess:
              self.start_va_server()
    
            elif self.oVAProcess:
              self.listen_for_requests()
    
            else:
              print("ERROR: Undefined state launcher state leading to infinite loop")
              sys.exit( ErrorCodes.ERROR_UNDEFINED_LAUNCHER_STATE )
    
        except KeyboardInterrupt:
          print( "Caught keyboard interrupt, quitting" )
    
        self._reset_connection()
    
      def wait_for_connection(self):
        print( "Waiting for launcher connection..." )
        while not self.oLauncherConnection:
          try:
            self.oLauncherConnection, sAddress = self.oLauncherServerSocket.accept()
          except socket.timeout:
            self.oLauncherConnection = None
          except socket.error:
            print( "Error while listening for launcher connection" )
            sys.exit( ErrorCodes.ERROR_CONNECTING_SOCKET )
          except (KeyboardInterrupt, SystemExit):
            raise #re-raising the received exception
    
        print( "Connection received from " + sAddress[0] )
        
        if self.oVAProcess:
          print( "Closing current VA instance" )
          self.oVAProcess.kill()
          self.oVAProcess = None
    
        self.receive_va_start_info()
        if not self.vaIniParser.prepare_inis():
          print( "Resetting launcher connection" )
          self._reset_connection()
    
    
      #Checks for a message containing the ID of the VAServer instance to be started and returns the respective VAServer directory
      def receive_va_start_info(self):
        try:
          sMessage = self.oLauncherConnection.recv( 512 )
          if type( sMessage ) is bytes:
            sMessage = sMessage.decode( 'utf-8' )
          
          if ":" not in sMessage: #VAServer ID, should be received last
            self.sVAServerID = sMessage
          elif sMessage.startswith("reproduction_input_type:"): #ReproductionInput type (Binaural / Ambisonics), optional
            self.receive_reproduction_input(sMessage)
            return self.receive_va_start_info()
          elif sMessage.startswith("file:"): #VARenderer.ini file, optional
            self.vaIniParser.sRendererIniPath = self.receive_file(sMessage)
            return self.receive_va_start_info()
          else:
            lMessageParts = sMessage.split(":")
            print("ERROR: Invalid message keyword '" + lMessageParts[0] + "' while receiving VA start info")
            return False
    
          print( "Received launch request for VAServer ID: " + self.sVAServerID )
        except socket.error:
          print( "ERROR: Socket error while reading VAServer ID" )
          self._reset_connection()
          return False
        else:
          try:
            self.sVAServerDir = self.oConfig.dVirtualAcousticDirectories[self.sVAServerID]
          except KeyError:
            self.sVAServerDir = None
    
          if not self.sVAServerDir:
            print( 'Requested VA Instance "' + self.sVAServerID + '" not available' )
            self.oLauncherConnection.send( b'f' ) #answer 'requested version not available
            self._reset_connection()
            return False
    
          return True
    
      def receive_reproduction_input(self, sMessage):
        try:
          lsMessageParts = sMessage.split(":")
          sReproductionInput = lsMessageParts[1].upper()
          self.vaIniParser.eReproductionInput = ReproductionInput[sReproductionInput]
        except IndexError:
          print("ERROR: Message for receiving reproduction input type was empty")
          self.oLauncherConnection.send( b'fail' )
        except ValueError:
          print("ERROR: Invalid ID (case-insensitive) for reproduction input: '" + sReproductionInput + "'")
          self.oLauncherConnection.send( b'fail' )
        else: #send acceptance
          self.oLauncherConnection.send( b'ack' )
    
    
      #Starts the VAServer from given directory
      def start_va_server(self):
    
        # Check for VAServer.exe
        sVAExecutableFile = "bin/VAServer.exe"
        try:
          if not os.path.isfile( sVAExecutableFile ):
            if self.sVAServerDir and os.path.isfile( self.sVAServerDir + "/" + sVAExecutableFile ):
              sVAExecutableFile = self.sVAServerDir + "/" + sVAExecutableFile
            else:
              print( "ERROR: Invalid config for " + self.sVAServerID + " -- file " + sVAExecutableFile + " does not exist" )
              self.oLauncherConnection.send( b'n' ) #answer 'binary file cannot be found or invalid'
              return
        except KeyError:
          sVAExecutableFile = None
          print( "ERROR: config for " + self.sVAServerID + " has no valid \"file\" entry" )
          self.oLauncherConnection.send( b'i' ) #answer 'invalid file entry in the config'
          self._reset_connection()
          return
    
        if not sVAExecutableFile:
          return
    
        # Create start command
        sConnectionParam = self.oConfig.sLocalIP + ":" + str( self.oConfig.nVAServerPort )
        sVACoreIniParam = self.vaIniParser.get_main_inifile()
        sParams = sConnectionParam + " " + sVACoreIniParam
        sCommand = sVAExecutableFile + " " + sParams
        
        # start instance
        print( 'executing "' + sCommand + '"' )
              
        self.oVAProcess = subprocess.Popen( sCommand, cwd = self.sVAServerDir, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP )
    
        # wait for requested duration before sending the go signal
        time.sleep( self.oConfig.nWaitForVAServerStart )
    
        if self.oVAProcess.poll() != None:
          print( "VA Process died - sending abort token" )
          self.oLauncherConnection.send( b'a' ) #answer 'VAServer was aborted
          self._reset_connection()
          return
        else:        
          print( "sending go token" )
          self.oLauncherConnection.send( b'g' )#answer 'go, VAServer is correctly started'
    
      #Listens for requests while the VAServer is running
      def listen_for_requests(self):
        while True:
          try:
            sResult = self.oLauncherConnection.recv( 512 )
    
            if sResult != '':
              #check whether we are about to reveive a file
              sMessage = sResult.decode("utf-8")
              if sMessage.startswith("file"):
                self.receive_file(sMessage)
              else: #NOT sMessage.startswith("file")      
                print( "Received quit event: " + sMessage )
                self._close_va_and_reset_connection()
                break
    
          except socket.timeout:
            # timeouts are okay
            print( "Launcher Socket timeout, keep running anyways" )
          except socket.error:
            print( "Launcher Connection terminated unexpectedly" )
            break
          except (KeyboardInterrupt, SystemExit):
            raise #re-raise for higher instance to catch
      
    
      # Receives a file from a client, copies it to a tmp folder and returns the respective fullpath
      #   Input is a string message starting with "file:"
      #   it has the content file:[RelativePathToFile]:[FileLengthInBytes]:[ModificationTimeInSecondsSinceEPOCH]
      def receive_file(self, sMessage):
        aMessageParts = sMessage.split(":")
        iBytesToReceive = int(aMessageParts[2])
        iLastModificationTime = int(aMessageParts[3])
        Path, Filename = os.path.split(aMessageParts[1])
        ProjectName = aMessageParts[3]
        Fullpath = os.path.join(self.sCurrentScriptsDirectory, "..", "tmp", ProjectName, Path, "")
        print("Should receive file: "+Filename+" in path "+Fullpath+ " with "+str(iBytesToReceive)+" bytes")
    
        iLocalFileLastModification = os.path.getmtime(Fullpath+Filename)
    
        #check whether the file with this exact size (which is not older than the file to send) already exists
        if os.path.isfile(Fullpath+Filename) and os.stat(Fullpath+Filename).st_size==iBytesToReceive and iLocalFileLastModification<iLastModificationTime:
          self.oLauncherConnection.send( b'exists' )
          print("File already exists with this size, so no need for resending")
    
        else: #file need to be received
          #create dir if it does not exist
          if not os.path.exists(Fullpath):
            os.makedirs(Fullpath)
    
          #send acceptance
          self.oLauncherConnection.send( b'ack' )
    
          #receive file
          iBytesReceived = 0
          with open(Fullpath+Filename, "wb") as f:
            bReceivingFile = True
            while bReceivingFile:
              # read 1024 bytes from the socket (receive)
              bytes_read = self.oLauncherConnection.recv(1024)
              if not bytes_read:    
                # nothing is received
                # file transmitting is done
                bReceivingFile = False
              else:
                # write to the file the bytes we just received
                f.write(bytes_read)
                iBytesReceived += len(bytes_read)
              if iBytesReceived == iBytesToReceive:
                bReceivingFile = False
            f.close()
    
          #check whether received file seems ok  
          if iBytesReceived == iBytesToReceive:
            self.oLauncherConnection.send( b'ack' ) #send acceptance
            print("File received successfully")
          else:
            self.oLauncherConnection.send( b'fail' ) #send failure
            print("File receive failed")
    
        return Fullpath+Filename
    
    
    
    #create an instance of the class
    oLauncher = VirtualAcousticsLauncher()