Salome HOME
Merge branch 'abn/cleanup'
[modules/paravis.git] / src / ENGINE / PVSERVER.py
1 # Copyright (C) 2007-2015  CEA/DEN, EDF R&D
2 #
3 # This library is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU Lesser General Public
5 # License as published by the Free Software Foundation; either
6 # version 2.1 of the License, or (at your option) any later version.
7 #
8 # This library is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 # Lesser General Public License for more details.
12 #
13 # You should have received a copy of the GNU Lesser General Public
14 # License along with this library; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
16 #
17 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 #
19 # Author : Adrien Bruneton (CEA)
20 #
21
22 import PVSERVER_ORB__POA
23 import SALOME_ComponentPy
24 import SALOME_DriverPy
25 import SALOMEDS
26 import SALOME
27 import PVSERVER_utils
28 import subprocess as subp
29 import socket
30 from time import sleep
31 import os
32 #from SALOME_utilities import MESSAGE
33
34 def MESSAGE(m):
35     """ Debug function """
36     pass
37     #os.system("echo \"%s\" >> /tmp/paravis_log.txt" % m)
38
39 class PVSERVER_Impl:
40     """ The core implementation (non CORBA, or Study related).
41         See the IDL for the documentation.
42     """
43     MAX_PVSERVER_PORT_TRIES = 1000  # Maximum number of tries to get a free port for the PVServer
44     PVSERVER_DEFAULT_PORT = 11111   # First port being tried to launch the pvserver
45     
46     def __init__(self):
47         self.pvserverPort = -1
48         self.pvserverPop = None  # Popen object from subprocess module
49         self.lastTrace = ""
50         self.isGUIConnected = False  # whether there is an active connection from the GUI.
51         try:
52             import paraview
53             tmp=paraview.__file__
54         except:
55             raise Exception("PVSERVER_Impl.__init__ : \"import paraview\" failed !")
56         # deduce dynamically PARAVIEW_ROOT_DIR from the paraview module location
57         self.PARAVIEW_ROOT_DIR = None
58         ZE_KEY_TO_FIND_PV_ROOT_DIR="lib"
59         li=tmp.split(os.path.sep) ; li.reverse()
60         if ZE_KEY_TO_FIND_PV_ROOT_DIR not in li:
61             raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.INTERNAL_ERROR,
62                       "PVSERVER_Impl.__init__ : error during dynamic deduction of PARAVIEW_ROOT_DIR : Loc of paraview module is \"%s\" ! \"%s\" is supposed to be the key to deduce it !"%(tmp,ZE_KEY_TO_FIND_PV_ROOT_DIR),
63                       "PVSERVER.py", 0))
64         li=li[li.index("lib")+1:] ; li.reverse()
65         self.PARAVIEW_ROOT_DIR = os.path.sep.join(li)
66
67     """
68     Private. Identify a free port to launch the PVServer. 
69     This is done by trying to bind a socket on the port.
70     We are still subject to a race condition between this detection mechanism and the actual launch of the pvserver
71     itself ...
72     """
73     def __getFreePort(self, startPort):
74         cnt = 0
75         currPort = startPort
76         while cnt < self.MAX_PVSERVER_PORT_TRIES:
77             try:
78                 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
79                 s.bind(('', currPort))
80                 s.close()
81                 return currPort
82             except socket.error as e:
83                 cnt += 1
84                 currPort += 1
85                 pass
86         raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.INTERNAL_ERROR,
87                             "[PVSERVER] maximum number of tries to retrieve a free port for the PVServer",
88                             "PVSERVER.py", 0))
89                                            
90     def FindOrStartPVServer( self, port ):
91         MESSAGE("[PVSERVER] FindOrStartPVServer ...")
92         host = "localhost"
93         alive = True
94         if self.pvserverPop is None:
95             alive = False
96         else:
97             # Poll active server to check if still alive
98             self.pvserverPop.poll()
99             if not self.pvserverPop.returncode is None:  # server terminated
100                 alive = False
101         
102         if alive:
103             return "cs://%s:%d" % (host, self.pvserverPort)  
104           
105         # (else) Server not alive, start it:
106         pvServerPath = os.path.join(self.PARAVIEW_ROOT_DIR, 'bin', 'pvserver')
107         opt = []
108         if port <= 0:
109             port = self.__getFreePort(self.PVSERVER_DEFAULT_PORT)
110         self.pvserverPop = subp.Popen([pvServerPath, "--multi-clients", "--server-port=%d" % port, "--use-offscreen-rendering"])
111         sleep(3)  # Give some time to the server to start up to avoid 
112                   # ugly messages on the client side saying that it cannot connect
113         # Is PID still alive? If yes, consider that the launch was successful
114         self.pvserverPop.poll()
115         if self.pvserverPop.returncode is None:
116             success = True
117             self.pvserverPort = port
118             MESSAGE("[PVSERVER] pvserver successfully launched on port %d" % port)
119         else:
120             raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.INTERNAL_ERROR,
121                             "[PVSERVER] Unable to start PVServer on port %d!" % port,
122                             "PVSERVER.py", 0))
123         return "cs://%s:%d" % (host, self.pvserverPort)
124
125     def StopPVServer( self ):
126         MESSAGE("[PVSERVER] Trying to stop PVServer (sending KILL) ...")
127         if not self.pvserverPop is None:
128             self.pvserverPop.poll()
129             if self.pvserverPop.returncode is None:
130                 # Terminate if still running:
131                 self.pvserverPop.terminate()
132                 MESSAGE("[PVSERVER] KILL signal sent.")
133                 return True
134         MESSAGE("[PVSERVER] Nothing to kill.")
135         return False
136     
137     def PutPythonTraceStringToEngine( self, t ):
138         self.lastTrace = t
139         
140     def GetPythonTraceString(self):
141         return self.lastTrace
142       
143     def SetGUIConnected( self, isConnected ):
144         self.isGUIConnected = isConnected
145         
146     def GetGUIConnected( self ):
147         return self.isGUIConnected
148     
149 class PVSERVER(PVSERVER_ORB__POA.PVSERVER_Gen,
150               SALOME_ComponentPy.SALOME_ComponentPy_i,
151               SALOME_DriverPy.SALOME_DriverPy_i,
152               PVSERVER_Impl):
153     """
154     Construct an instance of PVSERVER module engine.
155     The class PVSERVER implements CORBA interface PVSERVER_Gen (see PVSERVER_Gen.idl).
156     It is inherited from the classes SALOME_ComponentPy_i (implementation of
157     Engines::EngineComponent CORBA interface - SALOME component) and SALOME_DriverPy_i
158     (implementation of SALOMEDS::Driver CORBA interface - SALOME module's engine).
159     """
160     def __init__ ( self, orb, poa, contID, containerName, instanceName, 
161                    interfaceName ):
162         SALOME_ComponentPy.SALOME_ComponentPy_i.__init__(self, orb, poa,
163                     contID, containerName, instanceName, interfaceName, 0)
164         SALOME_DriverPy.SALOME_DriverPy_i.__init__(self, interfaceName)
165         PVSERVER_Impl.__init__(self)
166         #
167         self._naming_service = SALOME_ComponentPy.SALOME_NamingServicePy_i( self._orb )
168         #
169
170     """ Override base class destroy to make sure we try to kill the pvserver
171         before leaving.
172     """
173     def destroy(self):    
174         self.StopPVServer()
175         # Invokes super():
176         SALOME_ComponentPy.destroy(self)
177       
178     """
179     Get version information.
180     """
181     def getVersion( self ):
182         import salome_version
183         return salome_version.getVersion("PARAVIS", True)
184
185     def GetIOR(self):
186         return PVSERVER_utils.getEngineIOR()
187
188     """
189     Create object.
190     """
191     def createObject( self, study, name ):
192         MESSAGE("createObject()")
193         self._createdNew = True # used for getModifiedData method
194         builder = study.NewBuilder()
195         father  = findOrCreateComponent( study )
196         object  = builder.NewObject( father )
197         attr    = builder.FindOrCreateAttribute( object, "AttributeName" )
198         attr.SetValue( name )
199         attr    = builder.FindOrCreateAttribute( object, "AttributeLocalID" )
200         attr.SetValue( PVSERVER_utils.objectID() )
201         pass
202
203     """
204     Dump module data to the Python script.
205     """
206     def DumpPython( self, study, isPublished, isMultiFile ):
207         MESSAGE("dumpPython()") 
208         abuffer = self.GetPythonTraceString().split("\n")
209         if isMultiFile:
210             abuffer       = [ "  " + s for s in abuffer ]
211             abuffer[0:0]  = [ "def RebuildData( theStudy ):" ]
212             abuffer      += [ "  pass" ]
213         abuffer += [ "\0" ]
214         return ("\n".join( abuffer ), 1)
215   
216     """
217     Import file to restore module data
218     """
219     def importData(self, studyId, dataContainer, options):
220       MESSAGE("importData()")
221       # get study by Id
222       obj = self._naming_service.Resolve("myStudyManager")
223       myStudyManager = obj._narrow(SALOMEDS.StudyManager)
224       study = myStudyManager.GetStudyByID(studyId)
225       # create all objects from the imported stream
226       stream = dataContainer.get()
227       for objname in stream.split("\n"):
228         if len(objname) != 0:
229           self.createObject(study, objname)
230       self._createdNew = False # to store the modification of the study information later
231       return ["objects"] # identifier what is in this file
232  
233     def getModifiedData(self, studyId):
234       MESSAGE("getModifiedData()")
235       if self._createdNew:
236         # get study by Id
237         obj = self._naming_service.Resolve("myStudyManager")
238         myStudyManager = obj._narrow(SALOMEDS.StudyManager)
239         study = myStudyManager.GetStudyByID(studyId)
240         # iterate all objects to get their names and store this information in stream
241         stream=""
242         father = study.FindComponent( moduleName() )
243         if father:
244             iter = study.NewChildIterator( father )
245             while iter.More():
246                 name = iter.Value().GetName()
247                 stream += name + "\n"
248                 iter.Next()
249         # store stream to the temporary file to send it in DataContainer
250         dataContainer = SALOME_DataContainerPy_i(stream, "", "objects", False, True)
251         aVar = dataContainer._this()
252         return [aVar]
253       return []