@ -395,8 +395,7 @@ class NsmServerControl(object):
logger . info ( " nsmd is ready @ {} " . format ( self . nsmOSCUrl ) )
#Tell nsmd that we are a GUI and want to receive general messages async, not only after we request something
self . gui_announce ( ) #Triggers "hi" and session root
self . sessionRoot = self . _waitForSessionRootBlocking ( )
self . sessionRoot = self . _initial_announce ( ) #Triggers "hi" and session root
self . internalState [ " sessionRoot " ] = self . sessionRoot
atexit . register ( self . quit ) #mostly does stuff when we started nsmd ourself
@ -430,7 +429,6 @@ class NsmServerControl(object):
#print ("not executed")
return False
def processSingleInstance ( self ) :
""" Tests our unix socket for an incoming signal.
if received forward to the engine - > gui
@ -524,67 +522,72 @@ class NsmServerControl(object):
self . ourOwnServer = subprocess . Popen ( [ " nsmd " , " --osc-port " , str ( port ) ] )
def _blockingRequest ( self , path : str , arguments : list , answerPath : str , answerArguments : list , repeat = False ) - > list :
""" During start-up we need to wait for replies. Also some operations only make sense
if we got data back . This is an abstraction that deals with messages that may come
out - of - order and keeps them for later , but at least prevents our side from sending
messages out - of - order itself .
#Category: Better safe than sorry
def _waitForPingResponseBlocking ( self ) :
""" Only used to test if the nsm server is ready.
Uses timeout as waiting time .
Default is : send once , wait for answer . repeat = True sends multiple times until an answer arrives .
This cannot be used after our thread with run_receivingServer has started because
the ping reply will be received by this thread instead .
Returns list of arguments , can be empty .
"""
self . _setPause ( True )
self . sock . settimeout ( 0.01 )
logger . info ( " Sending /osc/ping " )
out_msg = _OutgoingMessage ( " /osc/ping " )
while True :
self . sock . sendto ( out_msg . build ( ) , self . nsmOSCUrl ) #we need to send multiple times. If the server is not ready it can't receive the ping :)
try :
data , addr = self . sock . recvfrom ( 1024 )
msg = _IncomingMessage ( data )
break
except BlockingIOError : #happens while no data is received. Has nothing to do with blocking or not.
continue
except socket . timeout :
continue
if msg . oscpath == " /reply " and msg . params [ 0 ] == " /osc/ping " :
logger . info ( " Got ping response " )
return True
else :
logger . error ( f " Waiting for ping, but got path: { msg . oscpath } with { msg . params } . Adding to queue for later. If the server got started anyway and is reacting to your commands, it is fine for now. " )
self . _queue . append ( msg )
assert not self . _queue , [ ( m . oscpath , m . params ) for m in self . _queue ]
logger . info ( f " [wait for answer]: Sending { path } : { arguments } " )
self . _setPause ( True )
out_msg = _OutgoingMessage ( path )
for arg in arguments :
out_msg . add_arg ( arg )
self . _setPause ( False )
if not repeat :
self . sock . sendto ( out_msg . build ( ) , self . nsmOSCUrl )
def _waitForSessionRootBlocking ( self ) :
""" Arrives after GUI Announce ' hi '
There is only one session root """
logger . info ( " Waiting for session root message in blocking mode " )
self . _setPause ( True )
#Wait for answer
ready = False
while not ready :
if repeat : #we need to send multiple times.
self . sock . sendto ( out_msg . build ( ) , self . nsmOSCUrl )
try :
data , addr = self . sock . recvfrom ( 1024 )
msg = _IncomingMessage ( data )
if msg . oscpath == " /nsm/gui/session/root " :
sessionRoot = msg . params [ 0 ]
if answerArguments and msg . oscpath == answerPath and msg . params == answerArguments :
result = msg . params
logger . info ( f " [wait from { path } ] Received { answerPath } : { result } " )
ready = True
elif msg . oscpath == answerPath :
result = msg . params
logger . info ( f " [wait from { path } ] Received { answerPath } : { result } " )
ready = True
else :
logger . error ( f " Waiting for session root from nsmd, but got: { msg . oscpath } with { msg . params } . Adding to queue for later. If the server got started anyway and is reacting to your commands, it is fine for now. " )
logger . warning ( f " Waiting for { answerPath } from nsmd, but got: { msg . oscpath } with { msg . params } . Adding to queue for later. " )
self . _queue . append ( msg )
except BlockingIOError : #happens while no data is received. Has nothing to do with blocking or not.
continue
except socket . timeout :
continue
logger . info ( f " Session root directory is { sessionRoot } " )
self . _setPause ( False )
return sessionRoot
return result
def _waitForPingResponseBlocking ( self ) :
self . _blockingRequest ( path = " /osc/ping " , arguments = [ ] , answerPath = " /reply " , answerArguments = [ " /osc/ping " , ] , repeat = True )
def _initial_announce ( self ) - > pathlib . Path :
""" nsm/gui/gui_announce triggers a multi-stage reply. First we get " hi " ,
then we get the session root . We wait for session root and then clean ' hi ' from the queue .
Returns session root as pathlib - path . """
resultArguments = self . _blockingRequest ( path = " /nsm/gui/gui_announce " , arguments = [ ] , answerPath = " /nsm/gui/session/root " , answerArguments = [ ] )
if len ( self . _queue ) == 1 and self . _queue [ 0 ] . oscpath == " /nsm/gui/gui_announce " and self . _queue [ 0 ] . params == [ " hi " ] :
self . _queue . clear ( )
else :
logging . error ( " For ValueError below: " , [ ( m . oscpath , m . params ) for m in self . _queue ] )
raise ValueError ( " We were expecting a clean _queue with only ' hi ' as leftover, but instead there were unhandled messages. see print above. Better abort than a wrong program state " )
#all ok
return pathlib . Path ( resultArguments [ 0 ] )
#General Commands
def send ( self , arg ) :
@ -599,6 +602,8 @@ class NsmServerControl(object):
self . sock . sendto ( msg . build ( ) , self . nsmOSCUrl )
def gui_announce ( self ) :
""" This is just the announce without any answer. This is a last-resort method if another GUI
" stole " our slot . For our own initial announce we use self . _initial_announce ( ) """
msg = _OutgoingMessage ( " /nsm/gui/gui_announce " )
self . sock . sendto ( msg . build ( ) , self . nsmOSCUrl )
@ -619,7 +624,7 @@ class NsmServerControl(object):
First is / nsm / gui / server / message [ ' Listing sessions ' ]
Then session names come one reply at a time such as / reply [ ' /nsm/server/list ' , ' test3 ' ]
Finally / nsm / server / list [ 0 , ' Done. ' ] , not a reply
Finally / nsm / server / list [ 0 , ' Done. ' ] , not a reply
"""
logger . info ( " Requesting project list from session server in blocking mode " )
self . _setPause ( True )
@ -643,6 +648,7 @@ class NsmServerControl(object):
logger . warning ( f " Expected project but got path { msg . oscpath } with { msg . params } . Adding to queue for later. " )
self . _queue . append ( msg )
continue
#This is what we want:
elif msg . oscpath == " /reply " and msg . params [ 0 ] == " /nsm/server/list " :
#/reply ['/nsm/server/list', 'test3']
self . internalState [ " sessions " ] . add ( msg . params [ 1 ] )
@ -683,7 +689,7 @@ class NsmServerControl(object):
self . sock . sendto ( message . build ( ) , self . nsmOSCUrl )
#Primarily Without Session
def open ( self , nsmSessionName : str ) :
def open ( self , nsmSessionName : str ) :
if nsmSessionName in self . internalState [ " sessions " ] :
msg = _OutgoingMessage ( " /nsm/server/open " )
msg . add_arg ( nsmSessionName ) #s:project_name
@ -1405,7 +1411,7 @@ class NsmServerControl(object):
entry [ " sessionFile " ] = sessionFile
entry [ " lockFile " ] = pathlib . Path ( basePath , " .lock " )
entry [ " fullPath " ] = str ( basePath )
entry [ " sizeInBytes " ] = sum ( f . stat ( ) . st_size for f in basePath . glob ( ' **/* ' ) if f . is_file ( ) )
entry [ " sizeInBytes " ] = sum ( f . stat ( ) . st_size for f in basePath . glob ( ' **/* ' ) if f . is_file ( ) )
entry [ " numberOfClients " ] = len ( open ( sessionFile ) . readlines ( ) )
entry [ " hasSymlinks " ] = self . _checkDirectoryForSymlinks ( basePath )
entry [ " parents " ] = basePath . relative_to ( self . sessionRoot ) . parts [ : - 1 ] #tuple of each dir between NSM root and nsmSessionName/session.nsm, exluding the actual project name. This is the tree