Salome HOME
Copyright update 2022
[modules/gui.git] / src / PVServerService / ENGINE / PVSERVER_impl.py
1 # Copyright (C) 2015-2022  CEA/DEN, EDF R&D, OPEN CASCADE
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 subprocess as subp
23 import socket
24 from time import sleep
25 import os
26 import sys
27 from SALOME import SALOME_Exception, ExceptionStruct, INTERNAL_ERROR
28 #from SALOME_utilities import MESSAGE
29
30 def MESSAGE(m):
31     """ Debug function """
32     pass
33     #os.system("echo \"%s\" >> /tmp/paravis_log.txt" % m)
34
35 class PVSERVER_impl(object):
36     """ The core implementation (non CORBA, or Study related).
37         See the IDL for the documentation.
38     """
39     MAX_PVSERVER_PORT_TRIES = 1000  # Maximum number of tries to get a free port for the PVServer
40     PVSERVER_DEFAULT_PORT = 11111   # First port being tried to launch the pvserver
41     
42     def __init__(self):
43         self.pvserverPort = -1
44         self.pvserverPop = None  # Popen object from subprocess module
45         self.isGUIConnected = False  # whether there is an active connection from the GUI.
46         try:
47             import paraview
48             tmp=paraview.__file__
49         except:
50             raise Exception("PVSERVER_Impl.__init__ : \"import paraview\" failed !")
51         # deduce dynamically PARAVIEW_ROOT_DIR from the paraview module location
52         self.PARAVIEW_ROOT_DIR = None
53         if sys.platform == "win32":
54           ZE_KEY_TO_FIND_PV_ROOT_DIR=["Lib"]
55           upCount =2
56         else:
57           ZE_KEY_TO_FIND_PV_ROOT_DIR=["lib", "lib64"]
58           upCount =1
59         tmp = os.path.sep.join(tmp.split('/'))
60         li=tmp.split(os.path.sep) ; li.reverse()
61         for key_to_find in ZE_KEY_TO_FIND_PV_ROOT_DIR:
62             if key_to_find in li:
63                 li=li[li.index(key_to_find)+upCount:] ; li.reverse()
64                 self.PARAVIEW_ROOT_DIR = os.path.sep.join(li)
65                 return                
66         raise SALOME_Exception(ExceptionStruct(INTERNAL_ERROR,
67                                                "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," or ".join(ZE_KEY_TO_FIND_PV_ROOT_DIR)),
68                                                "PVSERVER.py", 0))
69
70     """
71     Private. Identify a free port to launch the PVServer. 
72     This is done by trying to bind a socket on the port.
73     We are still subject to a race condition between this detection mechanism and the actual launch of the pvserver
74     itself ...
75     """
76     def __getFreePort(self, startPort):
77         cnt = 0
78         currPort = startPort
79         while cnt < self.MAX_PVSERVER_PORT_TRIES:
80             try:
81                 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
82                 s.bind(('', currPort))
83                 s.close()
84                 return currPort
85             except socket.error as e:
86                 cnt += 1
87                 currPort += 1
88                 pass
89         raise SALOME_Exception(ExceptionStruct(INTERNAL_ERROR,
90                             "[PVSERVER] maximum number of tries to retrieve a free port for the PVServer",
91                             "PVSERVER.py", 0))
92                                            
93     def FindOrStartPVServer( self, port ):
94         MESSAGE("[PVSERVER] FindOrStartPVServer ...")
95         host = "localhost"
96         alive = True
97         if self.pvserverPop is None:
98             alive = False
99         else:
100             # Poll active server to check if still alive
101             self.pvserverPop.poll()
102             if not self.pvserverPop.returncode is None:  # server terminated
103                 alive = False
104         
105         if alive:
106             return "cs://%s:%d" % (host, self.pvserverPort)  
107           
108         # (else) Server not alive, start it:
109         pvServerPath = os.path.join(self.PARAVIEW_ROOT_DIR, 'bin', 'pvserver')
110         opt = []
111         if port <= 0:
112             port = self.__getFreePort(self.PVSERVER_DEFAULT_PORT)
113         self.pvserverPop = subp.Popen([pvServerPath, "--multi-clients", "--server-port=%d" % port, "--force-offscreen-rendering"])
114         sleep(3)  # Give some time to the server to start up to avoid 
115                   # ugly messages on the client side saying that it cannot connect
116         # Is PID still alive? If yes, consider that the launch was successful
117         self.pvserverPop.poll()
118         if self.pvserverPop.returncode is None:
119             success = True
120             self.pvserverPort = port
121             MESSAGE("[PVSERVER] pvserver successfully launched on port %d" % port)
122         else:
123             raise SALOME_Exception(ExceptionStruct(INTERNAL_ERROR,
124                             "[PVSERVER] Unable to start PVServer on port %d!" % port,
125                             "PVSERVER.py", 0))
126         return "cs://%s:%d" % (host, self.pvserverPort)
127
128     def StopPVServer( self ):
129         MESSAGE("[PVSERVER] Trying to stop PVServer (sending KILL) ...")
130         if not self.pvserverPop is None:
131             self.pvserverPop.poll()
132             if self.pvserverPop.returncode is None:
133                 # Terminate if still running:
134                 self.pvserverPop.terminate()
135                 MESSAGE("[PVSERVER] KILL signal sent.")
136                 return True
137         MESSAGE("[PVSERVER] Nothing to kill.")
138         return False
139       
140     def SetGUIConnected( self, isConnected ):
141         self.isGUIConnected = isConnected
142         
143     def GetGUIConnected( self ):
144         return self.isGUIConnected
145
146