p3lib 1.1.108__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- p3lib/__init__.py +0 -0
- p3lib/ate.py +108 -0
- p3lib/bokeh_auth.py +363 -0
- p3lib/bokeh_gui.py +845 -0
- p3lib/boot_manager.py +420 -0
- p3lib/conduit.py +145 -0
- p3lib/database_if.py +289 -0
- p3lib/file_io.py +154 -0
- p3lib/gnome_desktop_app.py +146 -0
- p3lib/helper.py +420 -0
- p3lib/json_networking.py +239 -0
- p3lib/login.html +98 -0
- p3lib/mqtt_rpc.py +240 -0
- p3lib/netif.py +226 -0
- p3lib/netplotly.py +223 -0
- p3lib/ngt.py +841 -0
- p3lib/pconfig.py +874 -0
- p3lib/ssh.py +935 -0
- p3lib/table_plot.py +675 -0
- p3lib/uio.py +574 -0
- p3lib-1.1.108.dist-info/LICENSE +21 -0
- p3lib-1.1.108.dist-info/METADATA +34 -0
- p3lib-1.1.108.dist-info/RECORD +24 -0
- p3lib-1.1.108.dist-info/WHEEL +4 -0
p3lib/boot_manager.py
ADDED
@@ -0,0 +1,420 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
|
3
|
+
import os
|
4
|
+
import sys
|
5
|
+
import platform
|
6
|
+
import getpass
|
7
|
+
|
8
|
+
from subprocess import check_call, DEVNULL, STDOUT, Popen, PIPE
|
9
|
+
from datetime import datetime
|
10
|
+
|
11
|
+
class BootManager(object):
|
12
|
+
"""Responsible for adding and removing startup processes (python programs) when the computer boots.
|
13
|
+
Currently supports the following platforms
|
14
|
+
Linux"""
|
15
|
+
|
16
|
+
LINUX_OS_NAME = "Linux"
|
17
|
+
ENABLE_CMD_OPT = "--enable_auto_start"
|
18
|
+
DISABLE_CMD_OPT = "--disable_auto_start"
|
19
|
+
CHECK_CMD_OPT = "--check_auto_start"
|
20
|
+
|
21
|
+
@staticmethod
|
22
|
+
def AddCmdArgs(parser):
|
23
|
+
"""@brief Add cmd line arguments to enable, disable and show the systemd boot state.
|
24
|
+
@param parser An instance of argparse.ArgumentParser."""
|
25
|
+
parser.add_argument(BootManager.ENABLE_CMD_OPT, help="Auto start when this computer starts.", action="store_true", default=False)
|
26
|
+
parser.add_argument(BootManager.DISABLE_CMD_OPT, help="Disable auto starting when this computer starts.", action="store_true", default=False)
|
27
|
+
parser.add_argument(BootManager.CHECK_CMD_OPT, help="Check the status of an auto started icons_gw instance.", action="store_true", default=False)
|
28
|
+
|
29
|
+
@staticmethod
|
30
|
+
def HandleOptions(uio, options, enable_syslog, serviceName=None, restartSeconds=1):
|
31
|
+
"""@brief Handle one of the bot manager command line options if the
|
32
|
+
user passed it on the cmd line.
|
33
|
+
@param uio A UIO instance.
|
34
|
+
@param options As returned from parser.parse_args() where parser
|
35
|
+
is an instance of argparse.ArgumentParser.
|
36
|
+
@param enable_syslog True to enable systemd syslog output.
|
37
|
+
@param serviceName The name of the service. If not set then the name of the initially executed
|
38
|
+
python file is used.
|
39
|
+
@param restartSeconds The number of seconds to sleep before restarting a service that has stopped (default=1).
|
40
|
+
@return True if handled , False if not."""
|
41
|
+
handled = False
|
42
|
+
if options.check_auto_start:
|
43
|
+
BootManager.CheckAutoStartStatus(uio, serviceName)
|
44
|
+
handled = True
|
45
|
+
|
46
|
+
elif options.enable_auto_start:
|
47
|
+
BootManager.EnableAutoStart(uio, enable_syslog, serviceName, restartSeconds)
|
48
|
+
handled = True
|
49
|
+
|
50
|
+
elif options.disable_auto_start:
|
51
|
+
BootManager.DisableAutoStart(uio, serviceName)
|
52
|
+
handled = True
|
53
|
+
|
54
|
+
return handled
|
55
|
+
|
56
|
+
@staticmethod
|
57
|
+
def EnableAutoStart(uio, enable_syslog, serviceName, restartSeconds):
|
58
|
+
"""@brief Enable this program to auto start when the computer on which it is installed starts.
|
59
|
+
@param uio A UIO instance.
|
60
|
+
@param options As returned from parser.parse_args() where parser
|
61
|
+
is an instance of argparse.ArgumentParser.
|
62
|
+
@param enable_syslog True to enable systemd syslog output.
|
63
|
+
@param serviceName The name of the service. If not set then the name of the initially executed
|
64
|
+
python file is used.
|
65
|
+
@param restartSeconds The number of seconds to sleep before restarting a service that has stopped (default=1)."""
|
66
|
+
bootManager = BootManager(uio=uio, ensureRootUser=True, serviceName=serviceName, restartSeconds=restartSeconds)
|
67
|
+
arsString = " ".join(sys.argv)
|
68
|
+
bootManager.add(argString=arsString, enableSyslog=enable_syslog)
|
69
|
+
|
70
|
+
@staticmethod
|
71
|
+
def DisableAutoStart(uio, serviceName):
|
72
|
+
"""@brief Enable this program to auto start when the computer on which it is installed starts.
|
73
|
+
@param uio A UIO instance.
|
74
|
+
@param serviceName The name of the service. If not set then the name of the initially executed
|
75
|
+
python file is used."""
|
76
|
+
bootManager = BootManager(uio=uio, ensureRootUser=True, serviceName=serviceName)
|
77
|
+
bootManager.remove()
|
78
|
+
|
79
|
+
@staticmethod
|
80
|
+
def CheckAutoStartStatus(uio, serviceName):
|
81
|
+
"""@brief Check the status of a process previously set to auto start.
|
82
|
+
@param uio A UIO instance.
|
83
|
+
@param serviceName The name of the service. If not set then the name of the initially executed
|
84
|
+
python file is used."""
|
85
|
+
bootManager = BootManager(uio=uio, serviceName=serviceName)
|
86
|
+
lines = bootManager.getStatus()
|
87
|
+
if lines and len(lines) > 0:
|
88
|
+
for line in lines:
|
89
|
+
uio.info(line)
|
90
|
+
|
91
|
+
def __init__(self, uio=None, allowRootUser=True, ensureRootUser=False, serviceName=None, restartSeconds=1):
|
92
|
+
"""@brief Constructor
|
93
|
+
@param uio A UIO instance to display user output. If unset then no output
|
94
|
+
is displayed to user.
|
95
|
+
@param allowRootUser If True then allow root user to to auto start
|
96
|
+
programs. Note that as the BootManager is responsible for
|
97
|
+
ensuring programs are started up when a machine boots up
|
98
|
+
the installed program should be installed for the root
|
99
|
+
user on Linux systems.
|
100
|
+
@param ensureRootUser If True the current user must be root user (Linux systems).
|
101
|
+
@param serviceName The name of the service. If not set then the name of the initially executed
|
102
|
+
python file is used.
|
103
|
+
@param restartSeconds The number of seconds to sleep before restarting a service that has stopped (default=1)."""
|
104
|
+
self._uio = uio
|
105
|
+
self._allowRootUser=allowRootUser
|
106
|
+
self._osName = platform.system()
|
107
|
+
self._platformBootManager = None
|
108
|
+
if self._osName == BootManager.LINUX_OS_NAME:
|
109
|
+
self._platformBootManager = LinuxBootManager(uio, self._allowRootUser, ensureRootUser, serviceName, restartSeconds)
|
110
|
+
else:
|
111
|
+
raise Exception("{} is an unsupported OS.".format(self._osName) )
|
112
|
+
|
113
|
+
def add(self, user=None, argString=None, enableSyslog=False):
|
114
|
+
"""@brief Add an executable file to the processes started at boot time.
|
115
|
+
@param exeFile The file/program to be executed. This should be an absolute path.
|
116
|
+
@param user The user that will run the executable file. If left as None then the current user will be used.
|
117
|
+
@param argString The argument string that the program is to be launched with.
|
118
|
+
@param enableSyslog If True enable stdout and stderr to be sent to syslog."""
|
119
|
+
if self._platformBootManager:
|
120
|
+
self._platformBootManager.add(user, argString, enableSyslog)
|
121
|
+
|
122
|
+
def remove(self):
|
123
|
+
"""@brief Remove an executable file to the processes started at boot time.
|
124
|
+
@param exeFile The file/program to be removed. This should be an absolute path.
|
125
|
+
@param user The Linux user that will run the executable file."""
|
126
|
+
if self._platformBootManager:
|
127
|
+
self._platformBootManager.remove()
|
128
|
+
|
129
|
+
def getStatus(self):
|
130
|
+
"""@brief Get a status report.
|
131
|
+
@return Lines of text indicating the status of a previously started process."""
|
132
|
+
statusLines = []
|
133
|
+
if self._platformBootManager:
|
134
|
+
statusLines = self._platformBootManager.getStatusLines()
|
135
|
+
return statusLines
|
136
|
+
|
137
|
+
|
138
|
+
class LinuxBootManager(object):
|
139
|
+
"""@brief Responsible for adding/removing Linux services using systemd."""
|
140
|
+
|
141
|
+
LOG_PATH ="/var/log"
|
142
|
+
ROOT_SERVICE_FOLDER = "/etc/systemd/system/"
|
143
|
+
SYSTEM_CTL_1 = "/bin/systemctl"
|
144
|
+
SYSTEM_CTL_2 = "/usr/bin/systemctl"
|
145
|
+
|
146
|
+
@staticmethod
|
147
|
+
def GetSystemCTLBin():
|
148
|
+
"""@brief Get the location of the systemctl binary file on this system.
|
149
|
+
@return The systemctl bin file."""
|
150
|
+
binFile = None
|
151
|
+
if os.path.isfile(LinuxBootManager.SYSTEM_CTL_1):
|
152
|
+
binFile = LinuxBootManager.SYSTEM_CTL_1
|
153
|
+
elif os.path.isfile(LinuxBootManager.SYSTEM_CTL_2):
|
154
|
+
binFile = LinuxBootManager.SYSTEM_CTL_2
|
155
|
+
else:
|
156
|
+
raise Exception("Failed to find the location of the systemctl bin file on this machine.")
|
157
|
+
return binFile
|
158
|
+
|
159
|
+
@staticmethod
|
160
|
+
def GetServiceFolder(rootUser):
|
161
|
+
""""@brief Get the service folder to use.
|
162
|
+
@param rootUser False if non root user.
|
163
|
+
@return The folder that should hold the systemctl service files."""
|
164
|
+
serviceFolder = None
|
165
|
+
if rootUser:
|
166
|
+
serviceFolder = LinuxBootManager.ROOT_SERVICE_FOLDER
|
167
|
+
else:
|
168
|
+
homeFolder = os.path.expanduser('~')
|
169
|
+
serviceFolder = os.path.join(homeFolder, '.config/systemd/user/')
|
170
|
+
if not os.path.isdir(serviceFolder):
|
171
|
+
os.makedirs(serviceFolder)
|
172
|
+
|
173
|
+
if not os.path.isdir(serviceFolder):
|
174
|
+
raise Exception(f"{serviceFolder} folder not found.")
|
175
|
+
return serviceFolder
|
176
|
+
|
177
|
+
def __init__(self, uio, allowRootUser, ensureRootUser, serviceName, restartSeconds):
|
178
|
+
"""@brief Constructor
|
179
|
+
@param uio A UIO instance to display user output. If unset then no output is displayed to user.
|
180
|
+
@param allowRootUser If True then allow root user to to auto start programs.
|
181
|
+
@param ensureRootUser If True the current user must be root user.
|
182
|
+
@param serviceName The name of the service. If not set then the name of the initially executed
|
183
|
+
python file is used.
|
184
|
+
@param restartSeconds The number of seconds to sleep before restarting a service that has stopped."""
|
185
|
+
self._uio = uio
|
186
|
+
self._logFile = None
|
187
|
+
self._allowRootUser=allowRootUser
|
188
|
+
self._info("OS: {}".format(platform.system()) )
|
189
|
+
self._rootMode = False # If True run as root, else False.
|
190
|
+
self._systemCtlBin = LinuxBootManager.GetSystemCTLBin()
|
191
|
+
if ensureRootUser and os.geteuid() != 0:
|
192
|
+
self._fatalError(self.__class__.__name__ + ": Not root user. Ensure that you are root user and try again.")
|
193
|
+
|
194
|
+
if os.geteuid() == 0:
|
195
|
+
if not allowRootUser:
|
196
|
+
self._fatalError(self.__class__.__name__ + f": You are running as root user but allowRootUser={allowRootUser}.")
|
197
|
+
else:
|
198
|
+
self._rootMode = True
|
199
|
+
if not self._rootMode:
|
200
|
+
self._cmdLinePrefix = self._systemCtlBin + " --user"
|
201
|
+
else:
|
202
|
+
self._cmdLinePrefix = self._systemCtlBin
|
203
|
+
self._username = getpass.getuser()
|
204
|
+
self._serviceFolder = LinuxBootManager.GetServiceFolder(self._rootMode)
|
205
|
+
self._serviceName = serviceName
|
206
|
+
self._restartSeconds = restartSeconds
|
207
|
+
|
208
|
+
def _getInstallledStartupScript(self):
|
209
|
+
"""@brief Get the startup script full path. The startup script must be
|
210
|
+
named the same as the python file executed without the .py suffix.
|
211
|
+
@return The startup script file (absolute path)."""""
|
212
|
+
startupScript=None
|
213
|
+
argList = sys.argv
|
214
|
+
# If the first argument in the arg is a file.
|
215
|
+
if len(argList) > 0:
|
216
|
+
firstArg = argList[0]
|
217
|
+
if os.path.isfile(firstArg) or os.path.islink(firstArg):
|
218
|
+
startupScript = firstArg
|
219
|
+
if startupScript is None:
|
220
|
+
raise Exception("Failed to find the startup script.")
|
221
|
+
return startupScript
|
222
|
+
|
223
|
+
def _getPaths(self):
|
224
|
+
"""@brief Get a list of the paths from the PATH env var.
|
225
|
+
@return A list of paths or None if PATH env var not found."""
|
226
|
+
pathEnvVar = os.getenv("PATH")
|
227
|
+
envPaths = pathEnvVar.split(os.pathsep)
|
228
|
+
return envPaths
|
229
|
+
|
230
|
+
def _fatalError(self, msg):
|
231
|
+
"""@brief Record a fatal error.
|
232
|
+
@param msg The message detailing the error."""
|
233
|
+
raise Exception(msg)
|
234
|
+
|
235
|
+
def _info(self, msg):
|
236
|
+
"""@brief Display an info level message to the user
|
237
|
+
@param msg The message to be displayed."""
|
238
|
+
self._log(msg)
|
239
|
+
if self._uio:
|
240
|
+
self._uio.info(msg)
|
241
|
+
|
242
|
+
def _error(self, msg):
|
243
|
+
"""@brief Display an error level message to the user
|
244
|
+
@param msg The message to be displayed."""
|
245
|
+
self._log(msg)
|
246
|
+
if self._uio:
|
247
|
+
self._uio.error(msg)
|
248
|
+
|
249
|
+
def _log(self, msg):
|
250
|
+
"""@brief Save a message to the log file.
|
251
|
+
@param msg The message to save"""
|
252
|
+
if self._logFile:
|
253
|
+
timeStr = datetime.now().strftime("%d/%m/%Y-%H:%M:%S.%f")
|
254
|
+
fd = open(self._logFile, 'a')
|
255
|
+
fd.write("{}: {}\n".format(timeStr, msg) )
|
256
|
+
fd.close()
|
257
|
+
|
258
|
+
def _runLocalCmd(self, cmd):
|
259
|
+
"""@brief Run a command
|
260
|
+
@param cmd The command to run.
|
261
|
+
@return The return code of the external cmd."""
|
262
|
+
self._log("Running: {}".format(cmd) )
|
263
|
+
check_call(cmd, shell=True, stdout=DEVNULL, stderr=STDOUT)
|
264
|
+
|
265
|
+
def _getApp(self):
|
266
|
+
"""@brief Get details of the app to run
|
267
|
+
@return a tuple containing
|
268
|
+
0 = The name of the app
|
269
|
+
1 = The absolute path to the app to run"""
|
270
|
+
exeFile = self._getInstallledStartupScript()
|
271
|
+
exePath = os.path.dirname(exeFile)
|
272
|
+
if len(exePath) == 0:
|
273
|
+
self._fatalError("{} is invalid as executable path is undefined.".format(exeFile) )
|
274
|
+
|
275
|
+
if not os.path.isdir(exePath):
|
276
|
+
self._fatalError("{} path not found".format(exePath))
|
277
|
+
|
278
|
+
appName = os.path.basename(exeFile)
|
279
|
+
if len(appName) == 0:
|
280
|
+
self._fatalError("No app found to execute.")
|
281
|
+
|
282
|
+
absApp = os.path.join(exePath, appName)
|
283
|
+
if not os.path.isfile( absApp ):
|
284
|
+
self._fatalError("{} file not found.".format(absApp) )
|
285
|
+
|
286
|
+
appName = appName.replace(".py", "")
|
287
|
+
if self._rootMode:
|
288
|
+
# We can only save to /var/log/ is we are root user.
|
289
|
+
self._logFile = os.path.join(LinuxBootManager.LOG_PATH, appName)
|
290
|
+
|
291
|
+
return (appName, absApp)
|
292
|
+
|
293
|
+
def _getServiceName(self):
|
294
|
+
"""@brief Get the name of the service.
|
295
|
+
@return The name of the service."""
|
296
|
+
if self._serviceName:
|
297
|
+
serviceName = self._serviceName
|
298
|
+
else:
|
299
|
+
appName, _ = self._getApp()
|
300
|
+
serviceName = appName
|
301
|
+
return "{}.service".format(serviceName)
|
302
|
+
|
303
|
+
def _getServiceFile(self, appName):
|
304
|
+
"""@brief Get the name of the service file.
|
305
|
+
@param appName The name of the app to execute.
|
306
|
+
@return The absolute path to the service file """
|
307
|
+
serviceName = self._getServiceName()
|
308
|
+
serviceFile = os.path.join(self._serviceFolder, serviceName)
|
309
|
+
self._uio.info(f"SERVICE FILE: {serviceFile}")
|
310
|
+
return serviceFile
|
311
|
+
|
312
|
+
def add(self, user, argString=None, enableSyslog=False):
|
313
|
+
"""@brief Add an executable file to the processes started at boot time.
|
314
|
+
This will also start the process. The executable file must be
|
315
|
+
named the same as the python file executed without the .py suffix.
|
316
|
+
@param user The Linux user that will run the executable file.
|
317
|
+
This should not be root as config files will be be saved
|
318
|
+
to non root user paths on Linux systems and the startup
|
319
|
+
script should then be executed with the same username in
|
320
|
+
order that the same config file is used.
|
321
|
+
If set to None then the current user is used.
|
322
|
+
@param argString The argument string that the program is to be launched with.
|
323
|
+
@param enableSyslog If True enable stdout and stderr to be sent to syslog."""
|
324
|
+
if user is None:
|
325
|
+
user = self._username
|
326
|
+
|
327
|
+
appName, absApp = self._getApp()
|
328
|
+
serviceName = self._getServiceName()
|
329
|
+
|
330
|
+
serviceFile = self._getServiceFile(serviceName)
|
331
|
+
|
332
|
+
lines = []
|
333
|
+
lines.append("[Unit]")
|
334
|
+
lines.append("After=network.target")
|
335
|
+
lines.append("StartLimitIntervalSec=0")
|
336
|
+
lines.append("")
|
337
|
+
lines.append("[Service]")
|
338
|
+
lines.append("Type=simple")
|
339
|
+
lines.append("Restart=always")
|
340
|
+
lines.append(f"RestartSec={self._restartSeconds}")
|
341
|
+
if enableSyslog:
|
342
|
+
lines.append("StandardOutput=syslog")
|
343
|
+
lines.append("StandardError=syslog")
|
344
|
+
else:
|
345
|
+
lines.append("StandardOutput=null")
|
346
|
+
lines.append("StandardError=journal")
|
347
|
+
if self._rootMode and user != 'root':
|
348
|
+
lines.append("User={}".format(user))
|
349
|
+
|
350
|
+
#We add the home path env var so that config files (if stored in/under
|
351
|
+
# the users home dir) can be found by the prgram.
|
352
|
+
if user and len(user) > 0:
|
353
|
+
lines.append('Environment="HOME=/home/{}"'.format(user))
|
354
|
+
if argString:
|
355
|
+
argString = argString.strip()
|
356
|
+
if argString.startswith(absApp):
|
357
|
+
argString=argString.replace(absApp, "")
|
358
|
+
# We don't want the enable cmd opt in the cmd we add to the systemd file.
|
359
|
+
if argString.find(BootManager.ENABLE_CMD_OPT):
|
360
|
+
argString = argString.replace(BootManager.ENABLE_CMD_OPT, "")
|
361
|
+
argString = argString.strip()
|
362
|
+
lines.append("ExecStart={} {}".format(absApp, argString))
|
363
|
+
else:
|
364
|
+
lines.append("ExecStart={}".format(absApp))
|
365
|
+
lines.append("")
|
366
|
+
lines.append("[Install]")
|
367
|
+
lines.append("WantedBy=multi-user.target")
|
368
|
+
lines.append("")
|
369
|
+
|
370
|
+
try:
|
371
|
+
fd = open(serviceFile, 'w')
|
372
|
+
fd.write( "\n".join(lines) )
|
373
|
+
fd.close()
|
374
|
+
self._info(f"Created {serviceFile}")
|
375
|
+
except IOError:
|
376
|
+
self._fatalError("Failed to create {}".format(serviceFile) )
|
377
|
+
|
378
|
+
cmd = "{} daemon-reload".format(self._cmdLinePrefix)
|
379
|
+
self._runLocalCmd(cmd)
|
380
|
+
cmd = "{} enable {}".format(self._cmdLinePrefix, serviceName)
|
381
|
+
self._info("Enabled {} on restart".format(serviceName))
|
382
|
+
self._runLocalCmd(cmd)
|
383
|
+
cmd = "{} start {}".format(self._cmdLinePrefix, serviceName)
|
384
|
+
self._runLocalCmd(cmd)
|
385
|
+
self._info("Started {}".format(serviceName))
|
386
|
+
|
387
|
+
def remove(self):
|
388
|
+
"""@brief Remove the executable file to the processes started at boot time.
|
389
|
+
Any running processes will be stopped. The executable file must be
|
390
|
+
named the same as the python file executed without the .py suffix."""
|
391
|
+
appName, _ = self._getApp()
|
392
|
+
|
393
|
+
serviceName = self._getServiceName()
|
394
|
+
serviceFile = self._getServiceFile(appName)
|
395
|
+
if os.path.isfile(serviceFile):
|
396
|
+
cmd = "{} disable {}".format(self._cmdLinePrefix, serviceName)
|
397
|
+
self._runLocalCmd(cmd)
|
398
|
+
self._info("Disabled {} on restart".format(serviceName))
|
399
|
+
|
400
|
+
cmd = "{} stop {}".format(self._cmdLinePrefix, serviceName)
|
401
|
+
self._runLocalCmd(cmd)
|
402
|
+
self._info("Stopped {}".format(serviceName))
|
403
|
+
|
404
|
+
os.remove(serviceFile)
|
405
|
+
self._log("Removed {}".format(serviceFile))
|
406
|
+
else:
|
407
|
+
self._info("{} service not found".format(serviceName))
|
408
|
+
|
409
|
+
def getStatusLines(self):
|
410
|
+
"""@brief Get a status report.
|
411
|
+
@return Lines of text indicating the status of a previously started process."""
|
412
|
+
serviceName = self._getServiceName()
|
413
|
+
if self._rootMode:
|
414
|
+
p = Popen([self._systemCtlBin, 'status', serviceName], stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
415
|
+
else:
|
416
|
+
p = Popen([self._systemCtlBin, '--user', 'status', serviceName], stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
417
|
+
output, err = p.communicate(b"input data that is passed to subprocess' stdin")
|
418
|
+
response = output.decode() + "\n" + err.decode()
|
419
|
+
lines = response.split("\n")
|
420
|
+
return lines
|
p3lib/conduit.py
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
|
3
|
+
from queue import Queue, Empty
|
4
|
+
|
5
|
+
class Conduit(object):
|
6
|
+
"""@brief A generalised conduit implementation. A conduit is used for
|
7
|
+
communication between two processes. Each Conduit has an A
|
8
|
+
and a B end. Messages can be pushed into each end and
|
9
|
+
pulled from each end.
|
10
|
+
"""
|
11
|
+
CONDUIT_TYPE_QUEUE = 1
|
12
|
+
CONDUIT_TYPE_TCP_CONNECTION = 2
|
13
|
+
|
14
|
+
def __init__(self, uio=None, cName="", conduitType=CONDUIT_TYPE_QUEUE, readBlock=True, readBlockTimeoutSeconds=None, maxSize = 0, ):
|
15
|
+
"""@brief Responsible for providing a conduit for data between entities.
|
16
|
+
@param uio A User input/output object. If supplied then debug info for the conduit will be recorded.
|
17
|
+
@param cName The conduit name. Only useful if a uio object has been passed for debugging purposes.
|
18
|
+
@param readBlock If true then all getX() methods will block until data is available.
|
19
|
+
@param readBlockTimeoutSeconds The time in seconds for a read (when readBlock=True) to timeout. The default = None (block indefinatley).
|
20
|
+
@param maxSize Maximum number of elements in the queue. Only valid if conduitType = CONDUIT_TYPE_QUEUE."""
|
21
|
+
|
22
|
+
if conduitType == Conduit.CONDUIT_TYPE_QUEUE:
|
23
|
+
self._conduit = QueueConduit(uio=uio, cName=cName, maxQueueSize=maxSize, readBlock=readBlock, readBlockTimeoutSeconds=readBlockTimeoutSeconds)
|
24
|
+
|
25
|
+
elif conduitType == Conduit.CONDUIT_TYPE_TCP_CONNECTION:
|
26
|
+
raise Exception("TCP conduits not yet implemented.")
|
27
|
+
|
28
|
+
else:
|
29
|
+
raise Exception("%d is an invalid conduit type." % (conduitType) )
|
30
|
+
|
31
|
+
def putA(self, data):
|
32
|
+
"""@brief put some data in the A -> B side conduit.
|
33
|
+
@param data The data object to be pushed into the conduit."""
|
34
|
+
self._conduit.putA(data)
|
35
|
+
|
36
|
+
def putB(self, data):
|
37
|
+
"""@brief put some data in the B -> A side conduit.
|
38
|
+
@param data The data object to be pushed into the conduit."""
|
39
|
+
self._conduit.putB(data)
|
40
|
+
|
41
|
+
def getA(self):
|
42
|
+
"""@brief Get some data from the B -> A conduit.
|
43
|
+
@return The data from the conduit or None of no data is available."""
|
44
|
+
return self._conduit.getA()
|
45
|
+
|
46
|
+
def getB(self):
|
47
|
+
"""@brief Get some data from the A -> B conduit.
|
48
|
+
@return The data from the conduit or None of no data is available."""
|
49
|
+
return self._conduit.getB()
|
50
|
+
|
51
|
+
def aReadAvailable(self):
|
52
|
+
"""@return True if there is data available to be read from the A side of the conduit."""
|
53
|
+
return self._conduit.aReadAvailable()
|
54
|
+
|
55
|
+
def bReadAvailable(self):
|
56
|
+
"""@return True if there is data available to be read from the B side of the conduit."""
|
57
|
+
return self._conduit.bReadAvailable()
|
58
|
+
|
59
|
+
class QueueConduit(Conduit):
|
60
|
+
"""@brief Responsible for providing the functionality required to communicate between
|
61
|
+
threads (ITC= Inter Thread Communication).
|
62
|
+
|
63
|
+
The ITC has an A side and a B side. data can be sent from the A and B sides
|
64
|
+
and is forwarded to the other side."""
|
65
|
+
|
66
|
+
def __init__(self, uio=None, cName="", maxQueueSize = 0, readBlock=True, readBlockTimeoutSeconds=0):
|
67
|
+
"""@brief Constructor
|
68
|
+
@param uio A User input/output object. If supplied then debug info for the conduit will be recorded.
|
69
|
+
@param cName The conduit name. Only useful if a uio object has been passed for debugging purposes.
|
70
|
+
@param maxQueueSize The maximum queue size that we will allow (default = 0, no limit)
|
71
|
+
@param readBlock If True then reads will block until data is available or a timeout (if > 0) occurs."""
|
72
|
+
|
73
|
+
self._uio = uio
|
74
|
+
self._cName = cName
|
75
|
+
self._maxQueueSize = maxQueueSize
|
76
|
+
self._readBlock = readBlock
|
77
|
+
self._readBlockTimeoutSeconds = readBlockTimeoutSeconds
|
78
|
+
self._aToBQueue = Queue(maxQueueSize)
|
79
|
+
self._bToAQueue = Queue(maxQueueSize)
|
80
|
+
|
81
|
+
def _checkQueueSize(self):
|
82
|
+
"""@brief check that we have not reached the max queue size."""
|
83
|
+
if self._maxQueueSize > 0:
|
84
|
+
if self._aToBQueue.qsize() >= self._maxQueueSize:
|
85
|
+
raise Exception("%s: A -> B queue full." % (self.__class__.__name__) )
|
86
|
+
|
87
|
+
if self._bToAQueue.qsize() >= self._maxQueueSize:
|
88
|
+
raise Exception("%s: B -> A queue full." % (self.__class__.__name__) )
|
89
|
+
|
90
|
+
def putA(self, data):
|
91
|
+
"""@brief put some data in the A -> B side queue.
|
92
|
+
@param data The data object to be pushed into the queue."""
|
93
|
+
self._checkQueueSize()
|
94
|
+
|
95
|
+
if self._uio:
|
96
|
+
qSize = self._aToBQueue.qsize()
|
97
|
+
self._uio.debug("%s: A -> B queue size = %d" % (self._cName, qSize) )
|
98
|
+
|
99
|
+
self._aToBQueue.put(data)
|
100
|
+
|
101
|
+
def putB(self, data):
|
102
|
+
"""@brief put some data in the B -> A side queue.
|
103
|
+
@param data The data object to be pushed into the queue."""
|
104
|
+
self._checkQueueSize()
|
105
|
+
|
106
|
+
if self._uio:
|
107
|
+
qSize = self._bToAQueue.qsize()
|
108
|
+
self._uio.debug("%s: B -> A queue size = %d" % (self._cName, qSize) )
|
109
|
+
|
110
|
+
self._bToAQueue.put(data)
|
111
|
+
|
112
|
+
def getA(self, block=True, timeoutSeconds=0.0):
|
113
|
+
"""@brief Get some data from the B -> A queue.
|
114
|
+
@param block If true then the call will block
|
115
|
+
@param timeoutSeconds The time in seconds before exiting (returning None) if no data can be read from the queue.
|
116
|
+
@return The data from the queue or None of no data is available."""
|
117
|
+
try:
|
118
|
+
return self._bToAQueue.get(block=self._readBlock, timeout=self._readBlockTimeoutSeconds)
|
119
|
+
except Empty:
|
120
|
+
return None
|
121
|
+
|
122
|
+
def getB(self, block=True, timeoutSeconds=0.0):
|
123
|
+
"""@brief Get some data from the A -> B queue.
|
124
|
+
@param block If true then the call will block
|
125
|
+
@param timeoutSeconds The time in seconds before exiting (returning None) if no data can be read from the queue.
|
126
|
+
@return The data from the queue or None of no data is available."""
|
127
|
+
try:
|
128
|
+
return self._aToBQueue.get(block=self._readBlock, timeout=self._readBlockTimeoutSeconds)
|
129
|
+
except Empty:
|
130
|
+
return None
|
131
|
+
|
132
|
+
def aReadAvailable(self):
|
133
|
+
"""@return True if there is data available to be read from the A side of the queue."""
|
134
|
+
return not self._bToAQueue.empty()
|
135
|
+
|
136
|
+
def bReadAvailable(self):
|
137
|
+
"""@return True if there is data available to be read from the B side of the queue."""
|
138
|
+
return not self._aToBQueue.empty()
|
139
|
+
|
140
|
+
|
141
|
+
|
142
|
+
|
143
|
+
|
144
|
+
|
145
|
+
|