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'.
This commit is contained in:
Fred Gleason 2018-12-11 16:50:28 -05:00
parent e2a313a07d
commit e6b46cca76
11 changed files with 436 additions and 5 deletions

View File

@ -18162,3 +18162,13 @@
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'.

View File

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

View File

@ -260,6 +260,20 @@ class Update(object):
return self.__escapeJson(string)
raise ValueError('invalid esc value')
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
@ -454,8 +468,197 @@ 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):
def __init__(self):

View File

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

View File

@ -36,6 +36,8 @@ 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.machine()+update.resolvePadFields("%a - %t",PyPAD.ESCAPE_NONE)
else:

View File

@ -0,0 +1,90 @@
#!/usr/bin/python
# 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.
#
#from __future__ import print_function
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'))).encode('utf-8'))
f.close()
n=n+1
except ConfigParser.NoSectionError:
return
#
# Read Configuration
#
if len(sys.argv)>=2:
fp=open(sys.argv[1])
config=ConfigParser.ConfigParser()
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

@ -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 @@
#!/usr/bin/python
# 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)

View File

@ -1,10 +1,10 @@
#!/usr/bin/python
# pypad_test.py
# pad_test.py
#
# PyPAD regression test script for Rivendell
#
# Exercise every method of 'PyPAD.Update' for each update.
# Exercise every PAD accessor method of 'PyPAD.Update' for each update.
#
# (C) Copyright 2018 Fred Gleason <fredg@paravelsystems.com>
#
@ -29,6 +29,8 @@ def ProcessPad(update):
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())

View File

@ -470,6 +470,7 @@ AC_CONFIG_FILES([rivendell.spec \
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 \

View File

@ -2963,7 +2963,12 @@ 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("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("mode",RDAirPlayConf::logModeText(play_op_mode),8));