Merge remote-tracking branch 'upstream/rlm2' into rlm2

Conflicts:
	ChangeLog
This commit is contained in:
Patrick Linstruth
2018-12-12 15:18:40 -08:00
23 changed files with 805 additions and 122 deletions

3
.gitignore vendored
View File

@@ -52,6 +52,7 @@ docs/rivwebcapi/*.html
docs/rivwebcapi/*.pdf
helpers/cwrap
helpers/docbook
helpers/install_python.sh
helpers/jsmin
importers/nexgen_filter
importers/panel_copy
@@ -81,10 +82,10 @@ rdlogedit/rdlogedit
rdlogin/rdlogin
rdlogmanager/rdlogmanager
rdmonitor/rdmonitor
rdpadd/rdpadd
rdpanel/rdpanel
rdrepld/rdrepld
rdrepld-suse
rdrlmd/rdrlmd
rdselect/rdselect
rdservice/rdservice
rdvairplayd/rdvairplayd

View File

@@ -18141,5 +18141,42 @@
'PyPAD.Update::resolvePadFields()'.
* Added a 'PyPAD.Update::padField()' method.
* Added a 'PyPAD.Update::escape()' method.
2018-12-10 Patrick Linstruth <patrick@deltecent.com>
2018-12-10 Fred Gleason <fredg@paravelsystems.com>
* Changed the name of the JSON PAD field 'logMachine' to 'machine'.
* Changed the name of the 'PyPAD.Update::logMachine()' method to
'PyPAD.Update::machine()'.
* Changed the name of the JSON PAD field 'logMode' to 'mode'.
* Added a 'PyPAD.Update::mode()' method.
* Added a 'cutNumber' field to the JSON PAD 'now' and 'next' objects.
* Added a 'PyPAD.FIELD_CUT_NUMBER' define.
* Added 'api/PyPAD/examples/pypad_test.py'.
2018-12-10 Fred Gleason <fredg@paravelsystems.com>
* Fixed a bug in 'PyPAD.Update::resolvePadFields()' that caused
incorrect rendering of 'dddd' placeholders when processing '%d(<dt>)'
wildcards.
2018-12-10 Fred Gleason <fredg@paravelsystems.com>
* Added support for the 'ProcessNullUpdates=' directive in the
'pypad_udp.py' script.
* Added support for the log selection directives in the
'pypad_udp.py' script.
2018-12-10 Fred Gleason <fredg@paravelsystems.com>
* Fixed a bug in the 'pypad_udp.py' script that threw an exception
when processing multi-byte UTF-8 characters.
2018-12-11 Fred Gleason <fredg@paravelsystems.com>
* Renamed 'apis/PyPAD/examples/pypad_test.py' to
'apisPyPAD/tests/pad_test.py'.
* Added a 'hostName' field to the JSON PAD 'padUpdate' object.
* Added a 'PyPAD.Update::hostName()' method.
* Added a 'shortHostName' field to the JSON PAD 'padUpdate' object.
* Added a 'PyPAD.Update::shortHostName()' method.
* Added a 'PyPAD.Update::resolveFilepath()' method.
* Added 'apis/PyPAD/tests/filepath_test.py.
* Added 'api/PyPAD/examples/pypad_filewrite.py'.
2018-12-12 Fred Gleason <fredg@paravelsystems.com>
* Updated the PyPAD classes and scripts to use Python 3.
2018-12-12 Fred Gleason <fredg@paravelsystems.com>
* Documented the Python 3.4 dependency in 'INSTALL'.
2018-12-12 Fred Gleason <fredg@paravelsystems.com>
* Renamed rdrlmd(8) to rdpadd(8).
2018-12-12 Patrick Linstruth <patrick@deltecent.com>
* Added a 'pypad_tunein.py' PyPAD script.

View File

@@ -42,6 +42,10 @@ OggVorbis - Open Source Audio Coding Library. Needed for OggVorbis
importing and exporting. Included with most distros, or available at:
http://www.xiph.org/.
Python, v3.4 or later
Open source scripting language. Included with most distros, or available at:
https://www.python.org/.
Qt Toolkit, v4.6 or better
Most modern Linux distros include this. It's typically installed as part
of the KDE Desktop Environment, although KDE is by no means required.

View File

@@ -50,7 +50,7 @@ SUBDIRS = icons\
rdmonitor\
rdpanel\
rdrepld\
rdrlmd\
rdpadd\
rdselect\
rdservice\
rdvairplayd\

View File

@@ -21,7 +21,8 @@
## Use automake to process this into a Makefile.in
SUBDIRS = api\
examples
examples\
tests
CLEANFILES = *~\
*.idb\

View File

@@ -23,6 +23,10 @@
rivendelldir = $(pyexecdir)
rivendell_PYTHON = PyPAD.py
##install-exec-am:
## ../../../helpers/install_python.sh PyPAD.py $(pyexecdir)/PyPAD.py
##EXTRA_DIST = PyPAD.py
CLEANFILES = *~\
*.idb\
*ilk\

View File

@@ -1,5 +1,3 @@
#!/usr/bin/python
# PyPAD.py
#
# PAD processor for Rivendell
@@ -46,6 +44,7 @@ TYPE_NEXT='next'
FIELD_START_DATETIME='startDateTime'
FIELD_CART_NUMBER='cartNumber'
FIELD_CART_TYPE='cartType'
FIELD_CUT_NUMBER='cutNumber'
FIELD_LENGTH='length'
FIELD_YEAR='year'
FIELD_GROUP_NAME='groupName'
@@ -128,7 +127,7 @@ class Update(object):
def __replaceWildcard(self,wildcard,sfield,stype,string,esc):
try:
if isinstance(self.__fields['padUpdate'][stype][sfield],unicode):
if isinstance(self.__fields['padUpdate'][stype][sfield],str):
string=string.replace('%'+wildcard,self.escape(self.__fields['padUpdate'][stype][sfield],esc))
else:
string=string.replace('%'+wildcard,str(self.__fields['padUpdate'][stype][sfield]))
@@ -164,19 +163,9 @@ class Update(object):
dt_pattern=pattern[3:-1]
try:
dt_pattern=dt_pattern.replace('dddd',dt.strftime('%A'))
dt_pattern=dt_pattern.replace('ddd',dt.strftime('%a'))
dt_pattern=dt_pattern.replace('dd',dt.strftime('%d'))
dt_pattern=dt_pattern.replace('d',str(dt.day))
dt_pattern=dt_pattern.replace('MMMM',dt.strftime('%B'))
dt_pattern=dt_pattern.replace('MMM',dt.strftime('%b'))
dt_pattern=dt_pattern.replace('MM',dt.strftime('%m'))
dt_pattern=dt_pattern.replace('M',str(dt.month))
dt_pattern=dt_pattern.replace('yyyy',dt.strftime('%Y'))
dt_pattern=dt_pattern.replace('yy',dt.strftime('%y'))
#
# Process Times
#
miltime=(dt_pattern.find('ap')<0)and(dt_pattern.find('AP')<0)
if not miltime:
if dt.hour<13:
@@ -200,6 +189,23 @@ class Update(object):
dt_pattern=dt_pattern.replace('ss',dt.strftime('%S'))
dt_pattern=dt_pattern.replace('s',str(dt.second))
#
# Process Dates
#
dt_pattern=dt_pattern.replace('MMMM',dt.strftime('%B'))
dt_pattern=dt_pattern.replace('MMM',dt.strftime('%b'))
dt_pattern=dt_pattern.replace('MM',dt.strftime('%m'))
dt_pattern=dt_pattern.replace('M',str(dt.month))
dt_pattern=dt_pattern.replace('dddd',dt.strftime('%A'))
dt_pattern=dt_pattern.replace('ddd',dt.strftime('%a'))
dt_pattern=dt_pattern.replace('dd',dt.strftime('%d'))
dt_pattern=dt_pattern.replace('d',str(dt.day))
dt_pattern=dt_pattern.replace('yyyy',dt.strftime('%Y'))
dt_pattern=dt_pattern.replace('yy',dt.strftime('%y'))
except AttributeError:
string=string.replace(pattern,'')
return string
@@ -226,7 +232,7 @@ class Update(object):
"""
Returns the date-time of the PAD update (datetime)
"""
return self.__fromIso8601(pad_data['padUpdate']['dateTime'])
return self.__fromIso8601(self.__fields['padUpdate']['dateTime'])
def escape(self,string,esc):
"""
@@ -252,12 +258,33 @@ class Update(object):
return self.__escapeJson(string)
raise ValueError('invalid esc value')
def logMachine(self):
def hostName(self):
"""
Returns the host name of the machine whence this PAD update
originated (string).
"""
return self.__fields['padUpdate']['hostName']
def shortHostName(self):
"""
Returns the short host name of the machine whence this PAD update
originated (string).
"""
return self.__fields['padUpdate']['shortHostName']
def machine(self):
"""
Returns the log machine number to which this update pertains
(integer).
"""
return self.__fields['padUpdate']['logMachine']
return self.__fields['padUpdate']['machine']
def mode(self):
"""
Returns the operating mode of the host log machine to which
this update pertains (string).
"""
return self.__fields['padUpdate']['mode']
def onairFlag(self):
"""
@@ -411,6 +438,8 @@ class Update(object):
PyPAD.FIELD_CLIENT - The 'Client' field (string)
PyPAD.FIELD_COMPOSER - The 'Composer' field (string)
PyPAD.FIELD_CONDUCTOR - The 'Conductor' field (string)
PyPAD.FIELD_CUT_NUMER - The 'Cut Number' field
(integer)
PyPAD.FIELD_DESCRIPTION - The 'Description' field
(string)
PyPAD.FIELD_EXTERNAL_ANNC_TYPE - The 'EXT_ANNC_TYPE'
@@ -437,7 +466,196 @@ class Update(object):
"""
return self.__fields['padUpdate'][pad_type][pad_field]
def resolveFilepath(self,string,dt):
"""
Returns a string with any Rivendell Filepath wildcards resolved
(See Appdendix C of the Rivendell Operations Guide for a list).
Takes two arguments:
string - The string to resolve.
dt - A Python 'datetime' object to use for the resolution.
"""
ret=''
upper_case=False
initial_case=False
offset=0
i=0
while i<len(string):
field=''
offset=0;
if string[i]!='%':
ret+=string[i]
else:
i=i+1
offset=offset+1
if string[i]=='^':
upper_case=True
i=i+1
offset=offset+1
else:
upper_case=False
if string[i]=='$':
initial_case=True
i=i+1
offset=offset+1
else:
initial_case=False
found=False
if string[i]=='a': # Abbreviated weekday name
field=dt.strftime('%a').lower()
found=True
if string[i]=='A': # Full weekday name
field=dt.strftime('%A').lower()
found=True
if (string[i]=='b') or (string[i]=='h'): # Abrev. month Name
field=dt.strftime('%b').lower()
found=True
if string[i]=='B': # Full month name
field=dt.strftime('%B').lower()
found=True
if string[i]=='C': # Century
field=dt.strftime('%C').lower()
found=True
if string[i]=='d': # Day (01 - 31)
field='%02d' % dt.day
found=True
if string[i]=='D': # Date (mm-dd-yy)
field=dt.strftime('%m-%d-%y')
found=True
if string[i]=='e': # Day, padded ( 1 - 31)
field='%2d' % dt.day
found=True
if string[i]=='E': # Day, unpadded (1 - 31)
field='%d' % dt.day
found=True
if string[i]=='F': # Date (yyyy-mm-dd)
field=dt.strftime('%F')
found=True
if string[i]=='g': # Two digit year number (as per ISO 8601)
field=dt.strftime('%g').lower()
found=True
if string[i]=='G': # Four digit year number (as per ISO 8601)
field=dt.strftime('%G').lower()
found=True
if string[i]=='H': # Hour, zero padded, 24 hour
field=dt.strftime('%H').lower()
found=True
if string[i]=='I': # Hour, zero padded, 12 hour
field=dt.strftime('%I').lower()
found=True
if string[i]=='i': # Hour, space padded, 12 hour
hour=dt.hour
if hour>12:
hour=hour-12
if hour==0:
hour=12
field='%2d' % hour
found=True
if string[i]=='J': # Hour, unpadded, 12 hour
hour=dt.hour
if hour>12:
hour=hour-12
if hour==0:
hour=12
field=str(hour)
found=True
if string[i]=='j': # Day of year
field=dt.strftime('%j')
found=True
if string[i]=='k': # Hour, space padded, 24 hour
field=dt.strftime('%k')
found=True
if string[i]=='M': # Minute, zero padded
field=dt.strftime('%M')
found=True
if string[i]=='m': # Month (01 - 12)
field=dt.strftime('%m')
found=True
if string[i]=='p': # AM/PM string
field=dt.strftime('%p')
found=True
if string[i]=='r': # Rivendell host name
field=self.hostName()
found=True
if string[i]=='R': # Rivendell short host name
field=self.shortHostName()
found=True
if string[i]=='S': # Second (SS)
field=dt.strftime('%S')
found=True
if string[i]=='s': # Rivendell service name
if self.hasService():
field=self.serviceName()
else:
field=''
found=True
if string[i]=='u': # Day of week (numeric, 1..7, 1=Monday)
field=dt.strftime('%u')
found=True
if (string[i]=='V') or (string[i]=='W'): # Week # (as per ISO 8601)
field=dt.strftime('%V')
found=True
if string[i]=='w': # Day of week (numeric, 0..6, 0=Sunday)
field=dt.strftime('%w')
found=True
if string[i]=='y': # Year (yy)
field=dt.strftime('%y')
found=True
if string[i]=='Y': # Year (yyyy)
field=dt.strftime('%Y')
found=True
if string[i]=='%':
field='%'
found=True
if not found: # No recognized wildcard, rollback!
i=-offset
field=string[i]
if upper_case:
field=field.upper();
if initial_case:
field=field[0].upper()+field[1::]
ret+=field
upper_case=False
initial_case=False
i=i+1
return ret
class Receiver(object):
@@ -467,18 +685,18 @@ class Receiver(object):
"""
sock=socket.socket(socket.AF_INET)
conn=sock.connect((hostname,port))
c=""
line=""
c=bytes()
line=bytes()
msg=""
while 1<2:
c=sock.recv(1)
line+=c
if c[0]=="\n":
msg+=line
if line=="\r\n":
if c[0]==10:
msg+=line.decode('utf-8')
if line.decode('utf-8')=="\r\n":
self.__PyPAD_Process(Update(json.loads(msg)))
msg=""
line=""
line=bytes()

View File

@@ -21,6 +21,7 @@
## Use automake to process this into a Makefile.in
EXTRA_DIST = now_and_next.py\
pypad_filewrite.py\
pypad_udp.py
CLEANFILES = *~\

View File

@@ -1,4 +1,4 @@
#!/usr/bin/python
#%PYTHON_BANGPATH%
# now_and_next.py
#
@@ -36,14 +36,16 @@ import PyPAD
#
def ProcessPad(update):
print
print('Filepath: '+update.resolveFilepath('string %$a',update.dateTime()))
if update.hasPadType(PyPAD.TYPE_NOW):
print "Log %03d NOW: " % update.logMachine()+update.resolvePadFields("%a - %t",PyPAD.ESCAPE_NONE)
print("Log %03d NOW: " % update.machine()+update.resolvePadFields("%a - %t",PyPAD.ESCAPE_NONE))
else:
print "Log %03d NOW: [none]" % update.logMachine()
print("Log %03d NOW: [none]" % update.machine())
if update.hasPadType(PyPAD.TYPE_NEXT):
print "Log %03d NEXT: " % update.logMachine()+update.resolvePadFields("%A - %T",PyPAD.ESCAPE_NONE)
print("Log %03d NEXT: " % update.machine()+update.resolvePadFields("%A - %T",PyPAD.ESCAPE_NONE))
else:
print "Log %03d NEXT: [none]" % update.logMachine()
print("Log %03d NEXT: [none]" % update.machine())
#
# Create an instance of 'PyPADReceiver'

View File

@@ -0,0 +1,88 @@
#%PYTHON_BANGPATH%
# pypad_filewrite.py
#
# Write PAD updates to files
#
# (C) Copyright 2018 Fred Gleason <fredg@paravelsystems.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
import sys
import configparser
import PyPAD
def eprint(*args,**kwargs):
print(*args,file=sys.stderr,**kwargs)
def processUpdate(update,section):
try:
if config.get(section,'ProcessNullUpdates')=='0':
return True
if config.get(section,'ProcessNullUpdates')=='1':
return update.hasPadType(PyPAD.TYPE_NOW)
if config.get(section,'ProcessNullUpdates')=='2':
return update.hasPadType(PyPAD.TYPE_NEXT)
if config.get(section,'ProcessNullUpdates')=='3':
return update.hasPadType(PyPAD.TYPE_NOW) and update.hasPadType(PyPAD.TYPE_NEXT)
except configparser.NoOptionError:
return True
log_dict={1: 'MasterLog',2: 'Aux1Log',3: 'Aux2Log',
101: 'VLog101',102: 'VLog102',103: 'VLog103',104: 'VLog104',
105: 'VLog105',106: 'VLog106',107: 'VLog107',108: 'VLog108',
109: 'VLog109',110: 'VLog110',111: 'VLog111',112: 'VLog112',
113: 'VLog113',114: 'VLog114',115: 'VLog115',116: 'VLog116',
117: 'VLog117',118: 'VLog118',119: 'VLog119',120: 'VLog120'}
if config.get(section,log_dict[update.machine()]).lower()=='yes':
return True
if config.get(section,log_dict[update.machine()]).lower()=='no':
return False
if config.get(section,log_dict[update.machine()]).lower()=='onair':
return update.onairFlag()
def ProcessPad(update):
n=1
try:
while(True):
section='File'+str(n)
if processUpdate(update,section):
fmtstr=config.get(section,'FormatString')
mode='w'
if config.get(section,'Append')=='1':
mode='a'
f=open(update.resolveFilepath(config.get(section,'Filename'),update.dateTime()),mode)
f.write(update.resolvePadFields(fmtstr,int(config.get(section,'Encoding'))))
f.close()
n=n+1
except configparser.NoSectionError:
return
#
# Read Configuration
#
if len(sys.argv)>=2:
fp=open(sys.argv[1])
config=configparser.ConfigParser(interpolation=None)
config.readfp(fp)
fp.close()
else:
eprint('pypad_filewrite.py: you must specify a configuration file')
sys.exit(1)
rcvr=PyPAD.Receiver()
rcvr.setCallback(ProcessPad)
rcvr.start('localhost',PyPAD.PAD_TCP_PORT)

View File

@@ -1,4 +1,4 @@
#!/usr/bin/python
#%PYTHON_BANGPATH%
# pypad_udp.py
#
@@ -20,26 +20,48 @@
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
from __future__ import print_function
import sys
import socket
import ConfigParser
import configparser
import PyPAD
def eprint(*args,**kwargs):
print(*args,file=sys.stderr,**kwargs)
def processUpdate(update,section):
if config.get(section,'ProcessNullUpdates')=='0':
return True
if config.get(section,'ProcessNullUpdates')=='1':
return update.hasPadType(PyPAD.TYPE_NOW)
if config.get(section,'ProcessNullUpdates')=='2':
return update.hasPadType(PyPAD.TYPE_NEXT)
if config.get(section,'ProcessNullUpdates')=='3':
return update.hasPadType(PyPAD.TYPE_NOW) and update.hasPadType(PyPAD.TYPE_NEXT)
log_dict={1: 'MasterLog',2: 'Aux1Log',3: 'Aux2Log',
101: 'VLog101',102: 'VLog102',103: 'VLog103',104: 'VLog104',
105: 'VLog105',106: 'VLog106',107: 'VLog107',108: 'VLog108',
109: 'VLog109',110: 'VLog110',111: 'VLog111',112: 'VLog112',
113: 'VLog113',114: 'VLog114',115: 'VLog115',116: 'VLog116',
117: 'VLog117',118: 'VLog118',119: 'VLog119',120: 'VLog120'}
if config.get(section,log_dict[update.machine()]).lower()=='yes':
return True
if config.get(section,log_dict[update.machine()]).lower()=='no':
return False
if config.get(section,log_dict[update.machine()]).lower()=='onair':
return update.onairFlag()
def ProcessPad(update):
n=1
while(True):
section='Udp'+str(n)
try:
fmtstr=config.get(section,'FormatString')
send_sock.sendto(update.resolvePadFields(fmtstr,int(config.get(section,'Encoding'))),
(config.get(section,'IpAddress'),int(config.get(section,'UdpPort'))))
if processUpdate(update,section):
fmtstr=config.get(section,'FormatString')
send_sock.sendto(update.resolvePadFields(fmtstr,int(config.get(section,'Encoding'))).encode('utf-8'),
(config.get(section,'IpAddress'),int(config.get(section,'UdpPort'))))
n=n+1
except ConfigParser.NoSectionError:
except configparser.NoSectionError:
return
#
@@ -47,7 +69,7 @@ def ProcessPad(update):
#
if len(sys.argv)>=2:
fp=open(sys.argv[1])
config=ConfigParser.ConfigParser()
config=configparser.ConfigParser(interpolation=None)
config.readfp(fp)
fp.close()
else:

View File

@@ -0,0 +1,39 @@
## automake.am
##
## Automake.am for Rivendell PyPAD/tests
##
## (C) Copyright 2018 Fred Gleason <fredg@paravelsystems.com>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as
## published by the Free Software Foundation; either version 2 of
## the License, or (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public
## License along with this program; if not, write to the Free Software
## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
##
## Use automake to process this into a Makefile.in
EXTRA_DIST = filepath_test.py
pad_test.py
CLEANFILES = *~\
*.idb\
*ilk\
*.obj\
*.pdb\
*.qm\
moc_*
MAINTAINERCLEANFILES = *~\
*.tar.gz\
aclocal.m4\
configure\
Makefile.in\
moc_*

View File

@@ -0,0 +1,78 @@
#%PYTHON_BANGPATH%
# filepath_test.py
#
# PyPAD regression test script for Rivendell
#
# Exercise every filepath wildcard in 'PyPAD.Update::resolveFilepath()'
#
# (C) Copyright 2018 Fred Gleason <fredg@paravelsystems.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
import PyPAD
def ProcessPad(update):
print()
print('DateTime: '+update.dateTime().isoformat(' '))
print()
print('Abbreviated weekday name [%a | %$a | %^a]: '+update.resolveFilepath('%a | %$a | %^a',update.dateTime()))
print('Full weekday name [%A | %$A | %^A]: '+update.resolveFilepath('%A | %$A | %^A',update.dateTime()))
print('Abbreviated month name [%b | %$b | %^b]: '+update.resolveFilepath('%b | %$b | %^b',update.dateTime()))
print('Full month name [%B | %$B | %^B]: '+update.resolveFilepath('%B | %$B | %^B',update.dateTime()))
print('Century [%C | %$C | %^C]: '+update.resolveFilepath('%C | %$C | %^C',update.dateTime()))
print('Day of the month, zero padded [01 - 31] [%d | %$d | %^d]: '+update.resolveFilepath('%d | %$d | %^d',update.dateTime()))
print('Date (mm-dd-yy) [%D | %$D | %^D]: '+update.resolveFilepath('%D | %$D | %^D',update.dateTime()))
print('Day of the month, space padded [ 1 - 31] [%e | %$e | %^e]: '+update.resolveFilepath('%e | %$e | %^e',update.dateTime()))
print('Day of the month, unpadded [ 1 - 31] [%E | %$E | %^E]: '+update.resolveFilepath('%E | %$E | %^E',update.dateTime()))
print('Date (yyyy-mm-dd) [%F | %$F | %^F]: '+update.resolveFilepath('%F | %$F | %^F',update.dateTime()))
print('Two digit year [%g | %$g | %^g]: '+update.resolveFilepath('%g | %$g | %^g',update.dateTime()))
print('Four digit year [%G | %$G | %^G]: '+update.resolveFilepath('%G | %$G | %^G',update.dateTime()))
print('Abbreviated month name [%h | %$h | %^h]: '+update.resolveFilepath('%h | %$h | %^h',update.dateTime()))
print('Hour, 24 hour, zero padded [00 - 23] [%H | %$H | %^H]: '+update.resolveFilepath('%H | %$H | %^H',update.dateTime()))
print('Hour, 12 hour, space padded [00 - 23] [%i | %$i | %^i]: '+update.resolveFilepath('%i | %$i | %^i',update.dateTime()))
print('Hour, 12 hour, zero padded [00 - 23] [%I | %$I | %^I]: '+update.resolveFilepath('%I | %$I | %^I',update.dateTime()))
print('Day of year, zero padded [%j | %$j | %^j]: '+update.resolveFilepath('%j | %$j | %^j',update.dateTime()))
print('Hour, 12 hour, unpadded [00 - 23] [%J | %$J | %^J]: '+update.resolveFilepath('%J | %$J | %^J',update.dateTime()))
print('Hour, 24 hour, space padded [%k | %$k | %^k]: '+update.resolveFilepath('%k | %$k | %^k',update.dateTime()))
print('Month, zero padded (01 - 12) [%m | %$m | %^m]: '+update.resolveFilepath('%m | %$m | %^m',update.dateTime()))
print('Minute, zero padded (00 - 59) [%M | %$M | %^M]: '+update.resolveFilepath('%M | %$M | %^M',update.dateTime()))
print('AM/PM string [%p | %$p | %^p]: '+update.resolveFilepath('%p | %$p | %^p',update.dateTime()))
print('Rivendell host name [%r | %$r | %^r]: '+update.resolveFilepath('%r | %$r | %^r',update.dateTime()))
print('Rivendell short host name [%R | %$R | %^R]: '+update.resolveFilepath('%R | %$R | %^R',update.dateTime()))
print('Rivendell service name [%s | %$s | %^s]: '+update.resolveFilepath('%s | %$s | %^s',update.dateTime()))
print('Seconds, zero padded (SS) [%S | %$S | %^S]: '+update.resolveFilepath('%S | %$S | %^S',update.dateTime()))
print('Day of the week, numeric, 1=Monday, 7=Sunday [%u | %$u | %^u]: '+update.resolveFilepath('%u | %$u | %^u',update.dateTime()))
print('Week number, as per ISO 8601 [00 - 23] [%V | %$V | %^V]: '+update.resolveFilepath('%V | %$V | %^V',update.dateTime()))
print('Two digit year [%y | %$y | %^y]: '+update.resolveFilepath('%y | %$y | %^y',update.dateTime()))
print('Four digit year [00 - 23] [%Y | %$Y | %^Y]: '+update.resolveFilepath('%Y | %$Y | %^Y',update.dateTime()))
print("Literal '%' [%%]: "+update.resolveFilepath('%%',update.dateTime()))
#
# Create an instance of 'PyPADReceiver'
#
rcvr=PyPAD.Receiver()
#
# Tell it to use the callback
#
rcvr.setCallback(ProcessPad)
#
# Start the receiver, giving it the hostname or IP address and TCP port of
# the target Rivendell system. Once started, all further processing can only
# be done in the callback method!
#
rcvr.start(localhost',PyPAD.PAD_TCP_PORT)

147
apis/PyPAD/tests/pad_test.py Executable file
View File

@@ -0,0 +1,147 @@
#%PYTHON_BANGPATH%
# pad_test.py
#
# PyPAD regression test script for Rivendell
#
# Exercise every PAD accessor method of 'PyPAD.Update' for each update.
#
# (C) Copyright 2018 Fred Gleason <fredg@paravelsystems.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
import PyPAD
def ProcessPad(update):
print
print('*** Log %03d Update ***********************************************' % update.machine())
print('** HEADER INFO **')
print(' dateTime(): '+update.dateTime().isoformat(' '))
print(' hostName(): '+update.hostName())
print(' shortHostName(): '+update.shortHostName())
print(' machine(): %d' % update.machine())
print( ' mode(): '+update.mode())
print(' onairFlag(): '+str(update.onairFlag()))
print
if update.hasLog():
print('** LOG INFO **')
print(' logName(): '+update.logName())
print()
else:
print('**NO LOG INFO PRESENT**')
print()
if update.hasService():
print('** SERVICE INFO **')
print(' serviceName(): '+update.serviceName())
print('serviceDescription(): '+update.serviceDescription())
print('serviceProgramCode(): '+update.serviceProgramCode())
print()
else:
print('** NO SERVICE INFO PRESENT **')
print()
if update.hasPadType(PyPAD.TYPE_NOW):
print('** NOW PLAYING INFO **')
try:
print(' startDateTime(): '+update.startDateTime(PyPAD.TYPE_NOW).isoformat(' '))
except AttributeError:
print(' startDateTime(): None')
print(' cartType(): '+update.padField(PyPAD.TYPE_NOW,PyPAD.FIELD_CART_TYPE))
print(' cartNumber(): %u / ' % update.padField(PyPAD.TYPE_NOW,PyPAD.FIELD_CART_NUMBER)+update.resolvePadFields("%n",PyPAD.ESCAPE_NONE))
print(' cutNumber(): %u / ' % update.padField(PyPAD.TYPE_NOW,PyPAD.FIELD_CUT_NUMBER)+update.resolvePadFields("%j",PyPAD.ESCAPE_NONE))
print(' length(): %u / ' % update.padField(PyPAD.TYPE_NOW,PyPAD.FIELD_LENGTH)+update.resolvePadFields("%h",PyPAD.ESCAPE_NONE))
try:
print(' year(): %u / ' % update.padField(PyPAD.TYPE_NOW,PyPAD.FIELD_YEAR)+update.resolvePadFields("%y",PyPAD.ESCAPE_NONE))
except TypeError:
print(' year(): None / '+update.resolvePadFields("%Y",PyPAD.ESCAPE_NONE))
print(' groupName(): '+update.padField(PyPAD.TYPE_NOW,PyPAD.FIELD_GROUP_NAME)+' / '+update.resolvePadFields('%g',PyPAD.ESCAPE_NONE))
print(' title(): '+update.padField(PyPAD.TYPE_NOW,PyPAD.FIELD_TITLE)+' / '+update.resolvePadFields('%t',PyPAD.ESCAPE_NONE))
print(' artist(): '+update.padField(PyPAD.TYPE_NOW,PyPAD.FIELD_ARTIST)+' / '+update.resolvePadFields('%a',PyPAD.ESCAPE_NONE))
print(' publisher(): '+update.padField(PyPAD.TYPE_NOW,PyPAD.FIELD_PUBLISHER)+' / '+update.resolvePadFields('%p',PyPAD.ESCAPE_NONE))
print(' composer(): '+update.padField(PyPAD.TYPE_NOW,PyPAD.FIELD_COMPOSER)+' / '+update.resolvePadFields('%m',PyPAD.ESCAPE_NONE))
print(' album(): '+update.padField(PyPAD.TYPE_NOW,PyPAD.FIELD_ALBUM)+' / '+update.resolvePadFields('%l',PyPAD.ESCAPE_NONE))
print(' label(): '+update.padField(PyPAD.TYPE_NOW,PyPAD.FIELD_LABEL)+' / '+update.resolvePadFields('%b',PyPAD.ESCAPE_NONE))
print(' client(): '+update.padField(PyPAD.TYPE_NOW,PyPAD.FIELD_CLIENT)+' / '+update.resolvePadFields('%c',PyPAD.ESCAPE_NONE))
print(' agency(): '+update.padField(PyPAD.TYPE_NOW,PyPAD.FIELD_AGENCY)+' / '+update.resolvePadFields('%e',PyPAD.ESCAPE_NONE))
print(' conductor(): '+update.padField(PyPAD.TYPE_NOW,PyPAD.FIELD_CONDUCTOR)+' / '+update.resolvePadFields('%r',PyPAD.ESCAPE_NONE))
print(' userDefined(): '+update.padField(PyPAD.TYPE_NOW,PyPAD.FIELD_USER_DEFINED)+' / '+update.resolvePadFields('%u',PyPAD.ESCAPE_NONE))
print(' songId(): '+update.padField(PyPAD.TYPE_NOW,PyPAD.FIELD_SONG_ID)+' / '+update.resolvePadFields('%s',PyPAD.ESCAPE_NONE))
print(' outcue(): '+update.padField(PyPAD.TYPE_NOW,PyPAD.FIELD_OUTCUE)+' / '+update.resolvePadFields('%o',PyPAD.ESCAPE_NONE))
print(' description(): '+update.padField(PyPAD.TYPE_NOW,PyPAD.FIELD_DESCRIPTION)+' / '+update.resolvePadFields('%i',PyPAD.ESCAPE_NONE))
print(' externalEventId(): '+update.padField(PyPAD.TYPE_NOW,PyPAD.FIELD_EXTERNAL_EVENT_ID))
print(' externalData(): '+update.padField(PyPAD.TYPE_NOW,PyPAD.FIELD_EXTERNAL_DATA))
print(' externalAnncType(): '+update.padField(PyPAD.TYPE_NOW,PyPAD.FIELD_EXTERNAL_EVENT_ID))
print()
else:
print('** NO NOW PLAYING INFO **')
print()
if update.hasPadType(PyPAD.TYPE_NEXT):
print('** NEXT PLAYING INFO **')
try:
print(' startDateTime(): '+update.startDateTime(PyPAD.TYPE_NEXT).isoformat(' '))
except AttributeError:
print(' startDateTime(): None')
print(' cartType(): '+update.padField(PyPAD.TYPE_NEXT,PyPAD.FIELD_CART_TYPE))
print(' cartNumber(): %u / ' % update.padField(PyPAD.TYPE_NEXT,PyPAD.FIELD_CART_NUMBER)+update.resolvePadFields("%N",PyPAD.ESCAPE_NONE))
print(' cutNumber(): %u / ' % update.padField(PyPAD.TYPE_NEXT,PyPAD.FIELD_CUT_NUMBER)+update.resolvePadFields("%J",PyPAD.ESCAPE_NONE))
print(' length(): %u / ' % update.padField(PyPAD.TYPE_NEXT,PyPAD.FIELD_LENGTH)+update.resolvePadFields("%H",PyPAD.ESCAPE_NONE))
try:
print(' year(): %u / ' % update.padField(PyPAD.TYPE_NEXT,PyPAD.FIELD_YEAR)+update.resolvePadFields("%Y",PyPAD.ESCAPE_NONE))
except TypeError:
print(' year(): None / '+update.resolvePadFields("%Y",PyPAD.ESCAPE_NONE))
print(' groupName(): '+update.padField(PyPAD.TYPE_NEXT,PyPAD.FIELD_GROUP_NAME)+' / '+update.resolvePadFields('%G',PyPAD.ESCAPE_NONE))
print(' title(): '+update.padField(PyPAD.TYPE_NEXT,PyPAD.FIELD_TITLE)+' / '+update.resolvePadFields('%T',PyPAD.ESCAPE_NONE))
print(' artist(): '+update.padField(PyPAD.TYPE_NEXT,PyPAD.FIELD_ARTIST)+' / '+update.resolvePadFields('%A',PyPAD.ESCAPE_NONE))
print(' publisher(): '+update.padField(PyPAD.TYPE_NEXT,PyPAD.FIELD_PUBLISHER)+' / '+update.resolvePadFields('%P',PyPAD.ESCAPE_NONE))
print(' composer(): '+update.padField(PyPAD.TYPE_NEXT,PyPAD.FIELD_COMPOSER)+' / '+update.resolvePadFields('%M',PyPAD.ESCAPE_NONE))
print(' album(): '+update.padField(PyPAD.TYPE_NEXT,PyPAD.FIELD_ALBUM)+' / '+update.resolvePadFields('%L',PyPAD.ESCAPE_NONE))
print(' label(): '+update.padField(PyPAD.TYPE_NEXT,PyPAD.FIELD_LABEL)+' / '+update.resolvePadFields('%B',PyPAD.ESCAPE_NONE))
print(' client(): '+update.padField(PyPAD.TYPE_NEXT,PyPAD.FIELD_CLIENT)+' / '+update.resolvePadFields('%C',PyPAD.ESCAPE_NONE))
print(' agency(): '+update.padField(PyPAD.TYPE_NEXT,PyPAD.FIELD_AGENCY)+' / '+update.resolvePadFields('%E',PyPAD.ESCAPE_NONE))
print(' conductor(): '+update.padField(PyPAD.TYPE_NEXT,PyPAD.FIELD_CONDUCTOR)+' / '+update.resolvePadFields('%R',PyPAD.ESCAPE_NONE))
print(' userDefined(): '+update.padField(PyPAD.TYPE_NEXT,PyPAD.FIELD_USER_DEFINED)+' / '+update.resolvePadFields('%U',PyPAD.ESCAPE_NONE))
print(' songId(): '+update.padField(PyPAD.TYPE_NEXT,PyPAD.FIELD_SONG_ID)+' / '+update.resolvePadFields('%S',PyPAD.ESCAPE_NONE))
print(' outcue(): '+update.padField(PyPAD.TYPE_NEXT,PyPAD.FIELD_OUTCUE)+' / '+update.resolvePadFields('%O',PyPAD.ESCAPE_NONE))
print(' description(): '+update.padField(PyPAD.TYPE_NEXT,PyPAD.FIELD_DESCRIPTION)+' / '+update.resolvePadFields('%I',PyPAD.ESCAPE_NONE))
print(' externalEventId(): '+update.padField(PyPAD.TYPE_NEXT,PyPAD.FIELD_EXTERNAL_EVENT_ID))
print(' externalData(): '+update.padField(PyPAD.TYPE_NEXT,PyPAD.FIELD_EXTERNAL_DATA))
print(' externalAnncType(): '+update.padField(PyPAD.TYPE_NEXT,PyPAD.FIELD_EXTERNAL_EVENT_ID))
print()
else:
print('** NO NEXT PLAYING INFO **')
print()
print ('******************************************************************')
#
# Create an instance of 'PyPADReceiver'
#
rcvr=PyPAD.Receiver()
#
# Tell it to use the callback
#
rcvr.setCallback(ProcessPad)
#
# Start the receiver, giving it the hostname or IP address and TCP port of
# the target Rivendell system. Once started, all further processing can only
# be done in the callback method!
#
rcvr.start('localhost',PyPAD.PAD_TCP_PORT)

View File

@@ -250,7 +250,7 @@ AC_CHECK_HEADER(soundtouch/SoundTouch.h,[],[AC_MSG_ERROR([*** SoundTouch not fou
#
# Check for Python
#
AM_PATH_PYTHON([2.7])
AM_PATH_PYTHON([3])
#
# Check for FLAC
@@ -466,10 +466,12 @@ AC_CONFIG_FILES([rivendell.spec \
conf/rd-bin.conf \
icons/Makefile \
helpers/Makefile \
helpers/install_python.sh \
apis/Makefile \
apis/PyPAD/Makefile \
apis/PyPAD/api/Makefile \
apis/PyPAD/examples/Makefile \
apis/PyPAD/tests/Makefile \
apis/rivwebcapi/Makefile \
apis/rivwebcapi/rivwebcapi.pc \
apis/rivwebcapi/rivwebcapi/Makefile \
@@ -515,7 +517,7 @@ AC_CONFIG_FILES([rivendell.spec \
rdmonitor/Makefile \
rdpanel/Makefile \
rdrepld/Makefile \
rdrlmd/Makefile \
rdpadd/Makefile \
rdselect/Makefile \
rdservice/Makefile \
rdvairplayd/Makefile \
@@ -552,6 +554,8 @@ AC_CONFIG_FILES([rivendell.spec \
])
AC_OUTPUT()
chmod 755 helpers/install_python.sh
#
# Create symlinks in 'utils/rdselect_helper/'
#

View File

@@ -33,7 +33,8 @@ dist_cwrap_SOURCES = cwrap.cpp cwrap.h
dist_jsmin_SOURCES = jsmin.c
EXTRA_DIST = rdpack.sh\
EXTRA_DIST = install_python.sh.in\
rdpack.sh\
rdtrans.sh\
rdtransgui.sh\
setenvvar.sh
@@ -42,7 +43,8 @@ CLEANFILES = *~\
*.tar.gz\
moc_*
DISTCLEANFILES = docbook
DISTCLEANFILES = docbook\
install_python.sh
MAINTAINERCLEANFILES = *~\
*.tar.gz\

View File

@@ -0,0 +1,24 @@
#!/bin/sh
# install_python.sh
#
# (C) Copyright 2018 Fred Gleason <fredg@paravelsystems.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
cat $1 | sed -e s^%PYTHON_BANGPATH%^@PYTHON@^ > $2
chmod 755 $2

View File

@@ -2963,10 +2963,15 @@ void RDLogPlay::SendNowNext()
//
play_pad_socket->write(QString("{\r\n").toUtf8());
play_pad_socket->write(QString(" \"padUpdate\": {\r\n").toUtf8());
play_pad_socket->write(RDJsonField("dateTime",QDateTime::currentDateTime(),8).toUtf8());
play_pad_socket->write(RDJsonField("logMachine",play_id+1,8));
play_pad_socket->write(RDJsonField("dateTime",QDateTime::currentDateTime(),8).
toUtf8());
play_pad_socket->write(RDJsonField("hostName",
rda->station()->name(),8).toUtf8());
play_pad_socket->write(RDJsonField("shortHostName",
rda->station()->shortName(),8).toUtf8());
play_pad_socket->write(RDJsonField("machine",play_id+1,8));
play_pad_socket->write(RDJsonField("onairFlag",play_onair_flag,8));
play_pad_socket->write(RDJsonField("logMode",RDAirPlayConf::logModeText(play_op_mode),8));
play_pad_socket->write(RDJsonField("mode",RDAirPlayConf::logModeText(play_op_mode),8));
//
// Service
@@ -3065,6 +3070,12 @@ QString RDLogPlay::GetPadJson(const QString &name,RDLogLine *ll,
}
ret+=RDJsonField("cartNumber",ll->cartNumber(),4+padding);
ret+=RDJsonField("cartType",RDCart::typeText(ll->cartType()),4+padding);
if(ll->cartType()==RDCart::Audio) {
ret+=RDJsonField("cutNumber",ll->cutNumber(),4+padding);
}
else {
ret+=RDJsonNullField("cutNumber",4+padding);
}
ret+=RDJsonField("length",ll->forcedLength(),4+padding);
if(ll->year().isValid()) {
ret+=RDJsonField("year",ll->year().year(),4+padding);

View File

@@ -1,6 +1,6 @@
## Makefile.am
##
## Rivendell RLM Consolidation Server
## Rivendell PAD Consolidation Server
##
## (C) Copyright 2018 Fred Gleason <fredg@paravelsystems.com>
##
@@ -29,13 +29,13 @@ moc_%.cpp: %.h
$(MOC) $< -o $@
sbin_PROGRAMS = rdrlmd
sbin_PROGRAMS = rdpadd
dist_rdrlmd_SOURCES = rdrlmd.cpp rdrlmd.h
dist_rdpadd_SOURCES = rdpadd.cpp rdpadd.h
nodist_rdrlmd_SOURCES = moc_rdrlmd.cpp
nodist_rdpadd_SOURCES = moc_rdpadd.cpp
rdrlmd_LDADD = @LIB_RDLIBS@ @LIBVORBIS@ @QT4_LIBS@ -lQt3Support
rdpadd_LDADD = @LIB_RDLIBS@ @LIBVORBIS@ @QT4_LIBS@ -lQt3Support
CLEANFILES = *~\
*.idb\

View File

@@ -1,6 +1,6 @@
// rdrlmd.cpp
// rdpadd.cpp
//
// Rivendell RLM Consolidation Server
// Rivendell PAD Consolidation Server
//
// (C) Copyright 2018 Fred Gleason <fredg@paravelsystems.com>
//
@@ -27,7 +27,7 @@
#include <rd.h>
#include <rdcmd_switch.h>
#include "rdrlmd.h"
#include "rdpadd.h"
MetadataSource::MetadataSource(QTcpSocket *sock)
{
@@ -73,20 +73,20 @@ QTcpSocket *MetadataSource::socket() const
MainObject::MainObject(QObject *parent)
: QObject(parent)
{
new RDCmdSwitch(qApp->argc(),qApp->argv(),"rdrlmd",RDRLMD_USAGE);
new RDCmdSwitch(qApp->argc(),qApp->argv(),"rdpadd",RDPADD_USAGE);
//
// Client Server
//
rlm_client_disconnect_mapper=new QSignalMapper(this);
connect(rlm_client_disconnect_mapper,SIGNAL(mapped(int)),
pad_client_disconnect_mapper=new QSignalMapper(this);
connect(pad_client_disconnect_mapper,SIGNAL(mapped(int)),
this,SLOT(clientDisconnected(int)));
rlm_client_server=new QTcpServer(this);
connect(rlm_client_server,SIGNAL(newConnection()),
pad_client_server=new QTcpServer(this);
connect(pad_client_server,SIGNAL(newConnection()),
this,SLOT(newClientConnectionData()));
if(!rlm_client_server->listen(QHostAddress::Any,RD_RLM2_CLIENT_TCP_PORT)) {
fprintf(stderr,"rdrlmd: unable to bind client port %d\n",
if(!pad_client_server->listen(QHostAddress::Any,RD_RLM2_CLIENT_TCP_PORT)) {
fprintf(stderr,"rdpadd: unable to bind client port %d\n",
RD_RLM2_CLIENT_TCP_PORT);
exit(1);
}
@@ -94,20 +94,20 @@ MainObject::MainObject(QObject *parent)
//
// Source Server
//
rlm_source_ready_mapper=new QSignalMapper(this);
connect(rlm_source_ready_mapper,SIGNAL(mapped(int)),
pad_source_ready_mapper=new QSignalMapper(this);
connect(pad_source_ready_mapper,SIGNAL(mapped(int)),
this,SLOT(sourceReadyReadData(int)));
rlm_source_disconnect_mapper=new QSignalMapper(this);
connect(rlm_source_disconnect_mapper,SIGNAL(mapped(int)),
pad_source_disconnect_mapper=new QSignalMapper(this);
connect(pad_source_disconnect_mapper,SIGNAL(mapped(int)),
this,SLOT(sourceDisconnected(int)));
rlm_source_server=new RDUnixServer(this);
connect(rlm_source_server,SIGNAL(newConnection()),
pad_source_server=new RDUnixServer(this);
connect(pad_source_server,SIGNAL(newConnection()),
this,SLOT(newSourceConnectionData()));
if(!rlm_source_server->listenToAbstract(RD_RLM2_SOURCE_UNIX_ADDRESS)) {
fprintf(stderr,"rdrlmd: unable to bind source socket [%s]\n",
(const char *)rlm_source_server->errorString().toUtf8());
if(!pad_source_server->listenToAbstract(RD_RLM2_SOURCE_UNIX_ADDRESS)) {
fprintf(stderr,"rdpadd: unable to bind source socket [%s]\n",
(const char *)pad_source_server->errorString().toUtf8());
exit(1);
}
}
@@ -115,10 +115,10 @@ MainObject::MainObject(QObject *parent)
void MainObject::newClientConnectionData()
{
QTcpSocket *sock=rlm_client_server->nextPendingConnection();
connect(sock,SIGNAL(disconnected()),rlm_client_disconnect_mapper,SLOT(map()));
rlm_client_disconnect_mapper->setMapping(sock,sock->socketDescriptor());
rlm_client_sockets[sock->socketDescriptor()]=sock;
QTcpSocket *sock=pad_client_server->nextPendingConnection();
connect(sock,SIGNAL(disconnected()),pad_client_disconnect_mapper,SLOT(map()));
pad_client_disconnect_mapper->setMapping(sock,sock->socketDescriptor());
pad_client_sockets[sock->socketDescriptor()]=sock;
SendState(sock->socketDescriptor());
// printf("client connection %d opened\n",sock->socketDescriptor());
@@ -129,9 +129,9 @@ void MainObject::clientDisconnected(int id)
{
QTcpSocket *sock=NULL;
if((sock=rlm_client_sockets.value(id))!=NULL) {
if((sock=pad_client_sockets.value(id))!=NULL) {
sock->deleteLater();
rlm_client_sockets.remove(id);
pad_client_sockets.remove(id);
// printf("client connection %d closed\n",id);
}
else {
@@ -142,19 +142,19 @@ void MainObject::clientDisconnected(int id)
void MainObject::newSourceConnectionData()
{
QTcpSocket *sock=rlm_source_server->nextPendingConnection();
QTcpSocket *sock=pad_source_server->nextPendingConnection();
if(sock==NULL) {
fprintf(stderr,"rdrlmd: UNIX socket error [%s]\n",
(const char *)rlm_source_server->errorString().toUtf8());
fprintf(stderr,"rdpadd: UNIX socket error [%s]\n",
(const char *)pad_source_server->errorString().toUtf8());
exit(1);
}
connect(sock,SIGNAL(readyRead()),rlm_source_ready_mapper,SLOT(map()));
rlm_source_ready_mapper->setMapping(sock,sock->socketDescriptor());
connect(sock,SIGNAL(readyRead()),pad_source_ready_mapper,SLOT(map()));
pad_source_ready_mapper->setMapping(sock,sock->socketDescriptor());
connect(sock,SIGNAL(disconnected()),rlm_source_disconnect_mapper,SLOT(map()));
rlm_source_disconnect_mapper->setMapping(sock,sock->socketDescriptor());
connect(sock,SIGNAL(disconnected()),pad_source_disconnect_mapper,SLOT(map()));
pad_source_disconnect_mapper->setMapping(sock,sock->socketDescriptor());
rlm_sources[sock->socketDescriptor()]=new MetadataSource(sock);
pad_sources[sock->socketDescriptor()]=new MetadataSource(sock);
// printf("source connection %d opened\n",sock->socketDescriptor());
}
@@ -162,11 +162,11 @@ void MainObject::newSourceConnectionData()
void MainObject::sourceReadyReadData(int id)
{
if(rlm_sources[id]!=NULL) {
if(rlm_sources[id]->appendBuffer(rlm_sources[id]->socket()->readAll())) {
for(QMap<int,QTcpSocket *>::const_iterator it=rlm_client_sockets.begin();
it!=rlm_client_sockets.end();it++) {
it.value()->write(rlm_sources[id]->buffer());
if(pad_sources[id]!=NULL) {
if(pad_sources[id]->appendBuffer(pad_sources[id]->socket()->readAll())) {
for(QMap<int,QTcpSocket *>::const_iterator it=pad_client_sockets.begin();
it!=pad_client_sockets.end();it++) {
it.value()->write(pad_sources[id]->buffer());
}
}
}
@@ -175,10 +175,10 @@ void MainObject::sourceReadyReadData(int id)
void MainObject::sourceDisconnected(int id)
{
if(rlm_sources.value(id)!=NULL) {
rlm_sources.value(id)->socket()->deleteLater();
delete rlm_sources.value(id);
rlm_sources.remove(id);
if(pad_sources.value(id)!=NULL) {
pad_sources.value(id)->socket()->deleteLater();
delete pad_sources.value(id);
pad_sources.remove(id);
// printf("source connection %d closed\n",id);
}
else {
@@ -189,10 +189,10 @@ void MainObject::sourceDisconnected(int id)
void MainObject::SendState(int id)
{
for(QMap<int,MetadataSource *>::const_iterator it=rlm_sources.begin();
it!=rlm_sources.end();it++) {
for(QMap<int,MetadataSource *>::const_iterator it=pad_sources.begin();
it!=pad_sources.end();it++) {
if(it.value()->isCommitted()) {
rlm_client_sockets.value(id)->write(it.value()->buffer());
pad_client_sockets.value(id)->write(it.value()->buffer());
}
}
}

View File

@@ -1,6 +1,6 @@
// rdrlmd.h
// rdpadd.h
//
// Rivendell RLM Consolidation Server
// Rivendell PAD Consolidation Server
//
// (C) Copyright 2018 Fred Gleason <fredg@paravelsystems.com>
//
@@ -18,8 +18,8 @@
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
#ifndef RDRLMD_H
#define RDRLMD_H
#ifndef RDPADD_H
#define RDPADD_H
#include <qmap.h>
#include <qobject.h>
@@ -29,7 +29,7 @@
#include <rdunixserver.h>
#define RDRLMD_USAGE "\n\n"
#define RDPADD_USAGE "\n\n"
class MetadataSource
{
@@ -64,15 +64,15 @@ class MainObject : public QObject
private:
void SendState(int id);
QSignalMapper *rlm_client_disconnect_mapper;
QTcpServer *rlm_client_server;
QMap<int,QTcpSocket *> rlm_client_sockets;
QSignalMapper *pad_client_disconnect_mapper;
QTcpServer *pad_client_server;
QMap<int,QTcpSocket *> pad_client_sockets;
QSignalMapper *rlm_source_ready_mapper;
QSignalMapper *rlm_source_disconnect_mapper;
RDUnixServer *rlm_source_server;
QMap<int,MetadataSource *> rlm_sources;
QSignalMapper *pad_source_ready_mapper;
QSignalMapper *pad_source_disconnect_mapper;
RDUnixServer *pad_source_server;
QMap<int,MetadataSource *> pad_sources;
};
#endif // RDRLMD_H
#endif // RDPADD_H

View File

@@ -31,7 +31,7 @@
#define RDSERVICE_CAED_ID 0
#define RDSERVICE_RIPCD_ID 1
#define RDSERVICE_RDCATCHD_ID 2
#define RDSERVICE_RDRLMD_ID 3
#define RDSERVICE_RDPADD_ID 3
#define RDSERVICE_RDVAIRPLAYD_ID 4
#define RDSERVICE_RDREPLD_ID 5
#define RDSERVICE_LOCALMAINT_ID 6

View File

@@ -41,7 +41,7 @@ bool MainObject::Startup(QString *err_msg)
//
KillProgram("rdrepld");
KillProgram("rdvairplayd");
KillProgram("rdrlmd");
KillProgram("rdpadd");
KillProgram("rdcatchd");
KillProgram("ripcd");
KillProgram("caed");
@@ -85,15 +85,15 @@ bool MainObject::Startup(QString *err_msg)
}
//
// rdrlmd(8)
// rdpadd(8)
//
svc_processes[RDSERVICE_RDRLMD_ID]=new Process(RDSERVICE_RDRLMD_ID,this);
svc_processes[RDSERVICE_RDPADD_ID]=new Process(RDSERVICE_RDPADD_ID,this);
args.clear();
svc_processes[RDSERVICE_RDRLMD_ID]->
start(QString(RD_PREFIX)+"/sbin/rdrlmd",args);
if(!svc_processes[RDSERVICE_RDRLMD_ID]->process()->waitForStarted(-1)) {
*err_msg=tr("unable to start rdrlmd(8)")+": "+
svc_processes[RDSERVICE_RDRLMD_ID]->errorText();
svc_processes[RDSERVICE_RDPADD_ID]->
start(QString(RD_PREFIX)+"/sbin/rdpadd",args);
if(!svc_processes[RDSERVICE_RDPADD_ID]->process()->waitForStarted(-1)) {
*err_msg=tr("unable to start rdpadd(8)")+": "+
svc_processes[RDSERVICE_RDPADD_ID]->errorText();
return false;
}