"""
The server deals with all operations controlling gst-switch-srv
These include all OS related tasks
"""
import os
import signal
import subprocess
from distutils import spawn
from errno import ENOENT
from .exception import PathError, ServerProcessError
from time import sleep
__all__ = ["Server", ]
TOOLS_DIR = '/'.join(os.getcwd().split('/')[:-1]) + '/tools/'
[docs]class Server(object):
"""Control all server related operations
:param path: Path where the executable gst-switch-srv
is located. Provide the full path.
By default looks in the current $PATH.
:param video_port: The video port number - default = 3000
:param audio_port: The audio port number - default = 4000
:param control_port: The control port number - default = 5000
:param record_file: The record file format
:returns: nothing
"""
SLEEP_TIME = 0.5
def __init__(
self,
path=None,
video_port=3000,
audio_port=4000,
control_port=5000,
record_file=False):
super(Server, self).__init__()
self._path = None
self._video_port = None
self._audio_port = None
self._control_port = None
self._record_file = None
self.gst_option_string = ''
self.path = path
self.video_port = video_port
self.audio_port = audio_port
self.control_port = control_port
self.record_file = record_file
self.proc = None
self.pid = -1
@property
def path(self):
"""Get the path"""
return self._path
@path.setter
[docs] def path(self, path):
"""Set path
:raises ValueError: Path cannot be left blank
"""
self._path = path
@property
def video_port(self):
"""Get the video port"""
return self._video_port
@video_port.setter
[docs] def video_port(self, video_port):
"""Set Video Port
:raises ValueError: Video Port cannot be left blank
:raises ValueError: Video Port must be in range 1 to 65535
:raises TypeError: Video Port must be a string or a number
"""
if not video_port:
raise ValueError("Video Port '{0}' cannot be blank"
.format(video_port))
else:
try:
i = int(video_port)
if i < 1 or i > 65535:
raise ValueError('Video Port must be in range 1 to 65535')
else:
self._video_port = video_port
except TypeError:
raise TypeError("Video Port must be a string or a number, "
"not '{0}'".format(type(video_port)))
@property
def audio_port(self):
"""Get the audio port"""
return self._audio_port
@audio_port.setter
[docs] def audio_port(self, audio_port):
"""Set Audio Port
:raises ValueError: Audio Port cannot be left blank
:raises ValueError: Audio Port must be in range 1 to 65535
:raises TypeError: Audio Port must be a string or a number
"""
if not audio_port:
raise ValueError("Audio Port '{0}' cannot be blank"
.format(audio_port))
else:
try:
i = int(audio_port)
if i < 1 or i > 65535:
raise ValueError('Audio Port must be in range 1 to 65535')
else:
self._audio_port = audio_port
except TypeError:
raise TypeError("Audio Port must be a string or a number,"
" not '{0}'".format(type(audio_port)))
@property
def control_port(self):
"""Get the control port"""
return self._control_port
@control_port.setter
[docs] def control_port(self, control_port):
"""Set Control Port
:raises ValueError: Control Port cannot be left blank
:raises ValueError: Control Port must be in range 1 to 65535
:raises TypeError: Control Port must be a string or a number
"""
if not control_port:
raise ValueError("Control Port '{0}' cannot be blank"
.format(control_port))
else:
try:
i = int(control_port)
if i < 1 or i > 65535:
raise ValueError(
'Control Port must be in range 1 to 65535')
else:
self._control_port = control_port
except TypeError:
raise TypeError("Control Port must be a string or a number,"
" not '{0}'".format(type(control_port)))
@property
def record_file(self):
"""Get the record file"""
return self._record_file
@record_file.setter
[docs] def record_file(self, record_file):
"""Set Record File
:raises ValueError: Non-string file format
:raises ValueError: Record File cannot have forward slashes
"""
if record_file is False:
self._record_file = False
elif record_file is True:
self._record_file = True
else:
if not record_file:
raise ValueError("Record File: '{0}' "
"Non-string file format".format(record_file))
else:
rec = str(record_file)
if rec.find('/') < 0:
self._record_file = rec
else:
raise ValueError("Record File: '{0}' "
"cannot have forward slashes".format(rec))
[docs] def run(self, gst_option=''):
"""Launch the server process
:param: None
:gst-option: Any gstreamer option.
Refer to http://www.linuxmanpages.com/man1/gst-launch-0.8.1.php#lbAF.
Multiple can be added separated by spaces
:returns: nothing
:raises IOError: Fail to open /dev/null (os.devnull)
:raises PathError: Unable to find gst-switch-srv at path specified
:raises ServerProcessError: Running gst-switch-srv
gives a OS based error.
"""
self.gst_option_string = gst_option
print "Starting server"
self.proc = self._run_process()
if self.proc:
self.pid = self.proc.pid
sleep(self.SLEEP_TIME)
def _run_process(self):
"""Non-public method: Runs the gst-switch-srv process
"""
cmd = ['']
if not self.path:
srv_location = spawn.find_executable('gst-switch-srv')
if srv_location:
cmd[0] = srv_location
else:
raise PathError("Cannot find gst-switch-srv in $PATH.\
Please specify the path.")
else:
cmd[0] += os.path.join(self.path, 'gst-switch-srv')
if self.gst_option_string:
cmd += [self.gst_option_string]
cmd.append("--video-input-port={0}".format(self.video_port))
cmd.append("--audio-input-port={0}".format(self.audio_port))
cmd.append("--control-port={0}".format(self.control_port))
if self.record_file is False:
pass
elif self.record_file is True:
cmd.append("-r")
else:
if self.record_file is not False:
cmd.append("--record={0}".format(self.record_file))
proc = self._start_process(cmd)
return proc
def _start_process(self, cmd):
"""Non-public method: Start a process
:param cmd: The command which needs to be executed
:returns: process created
"""
print 'Creating process %s' % (cmd)
try:
with open('server.log', 'w') as tempf:
process = subprocess.Popen(
cmd,
stdout=tempf,
stderr=tempf,
bufsize=-1,
shell=False)
print cmd
return process
except OSError as error:
if error.errno == ENOENT:
raise PathError("Cannot find gst-switch-srv at path:"
" '{0}'".format(self.path))
else:
raise ServerProcessError("Internal error "
"while launching process")
@classmethod
[docs] def make_coverage(cls):
"""Generate coverage
Calls 'make coverage' to generate coverage in .gcov files
"""
cmd = 'make -C {0} coverage'.format(TOOLS_DIR)
print TOOLS_DIR
with open(os.devnull, 'w'):
proc = subprocess.Popen(
cmd.split(),
bufsize=-1,
shell=False)
out, _ = proc.communicate()
print out
[docs] def terminate(self, cov=False):
"""Terminate the server.
self.proc is made None on success
:param: None
:returns: True when success
:raises ServerProcessError: Process does not exist
:raises ServerProcessError: Cannot terminate process. Try killing it
"""
print 'Killing server'
proc = self.proc
if proc is None:
raise ServerProcessError('Server Process does not exist')
else:
try:
if cov:
self.gcov_flush()
self.make_coverage()
proc.terminate()
print 'Server Killed'
self.proc = None
return True
except OSError:
raise ServerProcessError("Cannot terminate server process. "
"Try killing it")
[docs] def kill(self, cov=False):
"""Kill the server process by sending signal.SIGKILL
self.proc is made None on success
:param: None
:returns: True when success
:raises ServerProcessError: Process does not exist
:raises ServerProcessError: Cannot kill process
"""
if self.proc is None:
raise ServerProcessError('Server Process does not exist')
else:
try:
if cov:
self.gcov_flush()
self.make_coverage()
os.kill(self.pid, signal.SIGKILL)
self.proc = None
return True
except OSError:
raise ServerProcessError('Cannot kill process')
[docs] def gcov_flush(self):
"""Generate gcov coverage by sending the signal SIGUSR1
The generated gcda files are dumped in tools directory.
Does not kill the process
:param: None
:returns: True when success
:raises ServerProcessError: If Server is not running
:raises ServerProcessError: Unable to send signal
"""
if self.proc is None:
raise ServerProcessError('Server process does not exist')
else:
try:
print "GCOV FLUSH"
os.kill(self.pid, signal.SIGUSR1)
return True
except OSError:
raise ServerProcessError('Unable to send signal')