mirror of
https://github.com/ElvishArtisan/rivendell.git
synced 2025-06-07 07:32:34 +02:00
Merge remote-tracking branch 'upstream/rlm2' into rlm2
Conflicts: ChangeLog
This commit is contained in:
commit
1793a2474d
2
.gitignore
vendored
2
.gitignore
vendored
@ -69,6 +69,7 @@ missing
|
||||
Makefile.in
|
||||
Makefile
|
||||
moc_*
|
||||
py-compile
|
||||
rdadmin/rdadmin
|
||||
rdairplay/rdairplay
|
||||
rdcastmanager/rdcastmanager
|
||||
@ -83,6 +84,7 @@ rdmonitor/rdmonitor
|
||||
rdpanel/rdpanel
|
||||
rdrepld/rdrepld
|
||||
rdrepld-suse
|
||||
rdrlmd/rdrlmd
|
||||
rdselect/rdselect
|
||||
rdservice/rdservice
|
||||
rdvairplayd/rdvairplayd
|
||||
|
50
ChangeLog
50
ChangeLog
@ -18094,6 +18094,50 @@
|
||||
2018-11-30 Patrick Linstruth <patrick@deltecent.com>
|
||||
* Fixed regression with rdimport(1) that threw SQL errors when
|
||||
importing into an existing cart.
|
||||
2018-12-10 Patrick Linstruth <patrick@deltecent.com>
|
||||
* Fixed a bug that was causing rdairplay(1) to segfault when no
|
||||
log was loaded.
|
||||
2018-12-04 Fred Gleason <fredg@paravelsystems.com>
|
||||
* Added an rdrlmd(8) service.
|
||||
* Implemented JSON-formatted PAD output on TCP port 34289.
|
||||
2018-12-05 Fred Gleason <fredg@paravelsystems.com>
|
||||
* Added a set of enclosing '{}' braces around the JSON-formatted PAD
|
||||
output to make it well-formed.
|
||||
2018-12-05 Fred Gleason <fredg@paravelsystems.com>
|
||||
* Added a set of Python classes for processing PAD updates.
|
||||
2018-12-06 Fred Gleason <fredg@paravelsystems.com>
|
||||
* Added comments to the 'now_and_next.py' PyPAD script.
|
||||
2018-12-06 Fred Gleason <fredg@paravelsystems.com>
|
||||
* Removed support for the 'priv' argument in callbacks in PyPAD
|
||||
scripts.
|
||||
2018-12-06 Fred Gleason <fredg@paravelsystems.com>
|
||||
* Added support for '\b', '\f', '\n' '\r' and '\t' control escapes
|
||||
in the 'PyPADUpdate::padFields()' method.
|
||||
2018-12-06 Fred Gleason <fredg@paravelsystems.com>
|
||||
* Added a 'pypad_udp.py' PyPAD script.
|
||||
2018-12-07 Fred Gleason <fredg@paravelsystems.com>
|
||||
* Changed the Python namespace of the PyPAD classes from
|
||||
'rivendell.PyPAD' to 'PyPAD'.
|
||||
* Renamed the 'PyPAD.PyPADReceiver' class to 'PyPAD.Receiver'.
|
||||
* Renamed the 'PyPAD.PyPADUpdate' class to 'PyPAD.Update'.
|
||||
* Added a 'port' argument to the 'PyPAD.Receiver::start()' method.
|
||||
* Added an 'escaping' argument to the 'PyPAD.Update::padFields()'
|
||||
method.
|
||||
* Added support for the 'Encoding=' directive to the 'pypad_udp.py'
|
||||
script.
|
||||
2018-12-08 Fred Gleason <fredg@paravelsystems.com>
|
||||
* Renamed the 'PyPAD.Update::dateTime()' method to
|
||||
'PyPAD.Update::dateTimeString()'.
|
||||
* Changed the format of the 'dateTime' field in the PAD JSON structure
|
||||
from RFC822 to ISO 8601.
|
||||
* Added a 'startDateTime' field to the 'now' and 'next' objects in
|
||||
the PAD JSON structure.
|
||||
* Added a PAD Type enumeration for use in 'PyPAD.Update'.
|
||||
* Added 'PyPAD.Update::hasPadType()' and 'PyPAD.Update.startDateTime()'
|
||||
methods.
|
||||
* Removed 'PyPAD.Update::hasNowPad()' and 'PyPAD.Update:hasNextPad()'
|
||||
methods.
|
||||
* Added support for '%d(<dt>)' and '%D(<dt>)' wildcards in
|
||||
'PyPAD.Update::padFields()'.
|
||||
2018-12-09 Fred Gleason <fredg@paravelsystems.com>
|
||||
* Renamed the 'PyPAD.Update::padFields()' method to
|
||||
'PyPAD.Update::resolvePadFields()'.
|
||||
* Added a 'PyPAD.Update::padField()' method.
|
||||
* Added a 'PyPAD.Update::escape()' method.
|
||||
|
@ -50,6 +50,7 @@ SUBDIRS = icons\
|
||||
rdmonitor\
|
||||
rdpanel\
|
||||
rdrepld\
|
||||
rdrlmd\
|
||||
rdselect\
|
||||
rdservice\
|
||||
rdvairplayd\
|
||||
|
@ -20,7 +20,8 @@
|
||||
##
|
||||
## Use automake to process this into a Makefile.in
|
||||
|
||||
SUBDIRS = rivwebcapi\
|
||||
SUBDIRS = PyPAD\
|
||||
rivwebcapi\
|
||||
rlm
|
||||
|
||||
CLEANFILES = *~\
|
||||
|
39
apis/PyPAD/Makefile.am
Normal file
39
apis/PyPAD/Makefile.am
Normal file
@ -0,0 +1,39 @@
|
||||
## automake.am
|
||||
##
|
||||
## Automake.am for Rivendell PyPAD
|
||||
##
|
||||
## (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
|
||||
|
||||
SUBDIRS = api\
|
||||
examples
|
||||
|
||||
CLEANFILES = *~\
|
||||
*.idb\
|
||||
*ilk\
|
||||
*.obj\
|
||||
*.pdb\
|
||||
*.qm\
|
||||
moc_*
|
||||
|
||||
MAINTAINERCLEANFILES = *~\
|
||||
*.tar.gz\
|
||||
aclocal.m4\
|
||||
configure\
|
||||
Makefile.in\
|
||||
moc_*
|
39
apis/PyPAD/api/Makefile.am
Normal file
39
apis/PyPAD/api/Makefile.am
Normal file
@ -0,0 +1,39 @@
|
||||
## automake.am
|
||||
##
|
||||
## Automake.am for Rivendell PyPAD/api
|
||||
##
|
||||
## (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
|
||||
|
||||
rivendelldir = $(pyexecdir)
|
||||
rivendell_PYTHON = PyPAD.py
|
||||
|
||||
CLEANFILES = *~\
|
||||
*.idb\
|
||||
*ilk\
|
||||
*.obj\
|
||||
*.pdb\
|
||||
*.qm\
|
||||
moc_*
|
||||
|
||||
MAINTAINERCLEANFILES = *~\
|
||||
*.tar.gz\
|
||||
aclocal.m4\
|
||||
configure\
|
||||
Makefile.in\
|
||||
moc_*
|
484
apis/PyPAD/api/PyPAD.py
Normal file
484
apis/PyPAD/api/PyPAD.py
Normal file
@ -0,0 +1,484 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# PyPAD.py
|
||||
#
|
||||
# PAD processor for Rivendell
|
||||
#
|
||||
# (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 datetime
|
||||
import socket
|
||||
import json
|
||||
|
||||
#
|
||||
# Enumerated Constants (sort of)
|
||||
#
|
||||
# Escape types
|
||||
#
|
||||
ESCAPE_NONE=0
|
||||
ESCAPE_XML=1
|
||||
ESCAPE_URL=2
|
||||
ESCAPE_JSON=3
|
||||
|
||||
#
|
||||
# PAD Types
|
||||
#
|
||||
TYPE_NOW='now'
|
||||
TYPE_NEXT='next'
|
||||
|
||||
#
|
||||
# Field Names
|
||||
#
|
||||
FIELD_START_DATETIME='startDateTime'
|
||||
FIELD_CART_NUMBER='cartNumber'
|
||||
FIELD_CART_TYPE='cartType'
|
||||
FIELD_LENGTH='length'
|
||||
FIELD_YEAR='year'
|
||||
FIELD_GROUP_NAME='groupName'
|
||||
FIELD_TITLE='title'
|
||||
FIELD_ARTIST='artist'
|
||||
FIELD_PUBLISHER='publisher'
|
||||
FIELD_COMPOSER='composer'
|
||||
FIELD_ALBUM='album'
|
||||
FIELD_LABEL='label'
|
||||
FIELD_CLIENT='client'
|
||||
FIELD_AGENCY='agency'
|
||||
FIELD_CONDUCTOR='conductor'
|
||||
FIELD_USER_DEFINED='userDefined'
|
||||
FIELD_SONG_ID='songId'
|
||||
FIELD_OUTCUE='outcue'
|
||||
FIELD_DESCRIPTION='description'
|
||||
FIELD_ISRC='isrc'
|
||||
FIELD_ISCI='isci'
|
||||
FIELD_EXTERNAL_EVENT_ID='externalEventId'
|
||||
FIELD_EXTERNAL_DATA='externalData'
|
||||
FIELD_EXTERNAL_ANNC_TYPE='externalAnncType'
|
||||
|
||||
#
|
||||
# Default TCP port for connecting to Rivendell's PAD service
|
||||
#
|
||||
PAD_TCP_PORT=34289
|
||||
|
||||
class Update(object):
|
||||
def __init__(self,pad_data):
|
||||
self.__fields=pad_data;
|
||||
|
||||
def __fromIso8601(self,string):
|
||||
try:
|
||||
return datetime.datetime.strptime(string.strip()[:19],'%Y-%m-%dT%H:%M:%S')
|
||||
except AttributeError:
|
||||
return ''
|
||||
|
||||
def __escapeXml(self,string):
|
||||
string=string.replace("&","&")
|
||||
string=string.replace("<","<")
|
||||
string=string.replace(">",">")
|
||||
string=string.replace("'","'")
|
||||
string=string.replace("\"",""")
|
||||
return string
|
||||
|
||||
def __escapeWeb(self,string):
|
||||
string=string.replace("%","%25")
|
||||
string=string.replace(" ","%20")
|
||||
string=string.replace("<","%3C")
|
||||
string=string.replace(">","%3E")
|
||||
string=string.replace("#","%23")
|
||||
string=string.replace("\"","%22")
|
||||
string=string.replace("{","%7B")
|
||||
string=string.replace("}","%7D")
|
||||
string=string.replace("|","%7C")
|
||||
string=string.replace("\\","%5C")
|
||||
string=string.replace("^","%5E")
|
||||
string=string.replace("[","%5B")
|
||||
string=string.replace("]","%5D")
|
||||
string=string.replace("`","%60")
|
||||
string=string.replace("\a","%07")
|
||||
string=string.replace("\b","%08")
|
||||
string=string.replace("\f","%0C")
|
||||
string=string.replace("\n","%0A")
|
||||
string=string.replace("\r","%0D")
|
||||
string=string.replace("\t","%09")
|
||||
string=string.replace("\v","%0B")
|
||||
return string
|
||||
|
||||
def __escapeJson(self,string):
|
||||
string=string.replace("\\","\\\\")
|
||||
string=string.replace("\"","\\\"")
|
||||
string=string.replace("/","\\/")
|
||||
string=string.replace("\b","\\b")
|
||||
string=string.replace("\f","\\f")
|
||||
string=string.replace("\n","\\n")
|
||||
string=string.replace("\r","\\r")
|
||||
string=string.replace("\t","\\t")
|
||||
return string
|
||||
|
||||
def __replaceWildcard(self,wildcard,sfield,stype,string,esc):
|
||||
try:
|
||||
if isinstance(self.__fields['padUpdate'][stype][sfield],unicode):
|
||||
string=string.replace('%'+wildcard,self.escape(self.__fields['padUpdate'][stype][sfield],esc))
|
||||
else:
|
||||
string=string.replace('%'+wildcard,str(self.__fields['padUpdate'][stype][sfield]))
|
||||
except TypeError:
|
||||
string=string.replace('%'+wildcard,'')
|
||||
except KeyError:
|
||||
string=string.replace('%'+wildcard,'')
|
||||
return string
|
||||
|
||||
def __replaceWildcardPair(self,wildcard,sfield,string,esc):
|
||||
string=self.__replaceWildcard(wildcard,sfield,'now',string,esc);
|
||||
string=self.__replaceWildcard(wildcard.upper(),sfield,'next',string,esc);
|
||||
return string;
|
||||
|
||||
def __findDatetimePattern(self,pos,wildcard,string):
|
||||
start=string.find('%'+wildcard+'(',pos)
|
||||
if start>=0:
|
||||
end=string.find(")",start+3)
|
||||
if end>0:
|
||||
return (end+2,string[start:end+1])
|
||||
return None
|
||||
|
||||
def __replaceDatetimePattern(self,string,pattern):
|
||||
stype='now'
|
||||
if pattern[1]=='D':
|
||||
stype='next'
|
||||
try:
|
||||
dt=self.__fromIso8601(self.__fields['padUpdate'][stype]['startDateTime'])
|
||||
except TypeError:
|
||||
string=string.replace(pattern,'')
|
||||
return string
|
||||
|
||||
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'))
|
||||
|
||||
miltime=(dt_pattern.find('ap')<0)and(dt_pattern.find('AP')<0)
|
||||
if not miltime:
|
||||
if dt.hour<13:
|
||||
dt_pattern=dt_pattern.replace('ap','am')
|
||||
dt_pattern=dt_pattern.replace('AP','AM')
|
||||
else:
|
||||
dt_pattern=dt_pattern.replace('ap','pm')
|
||||
dt_pattern=dt_pattern.replace('AP','PM')
|
||||
if miltime:
|
||||
dt_pattern=dt_pattern.replace('hh',dt.strftime('%H'))
|
||||
dt_pattern=dt_pattern.replace('h',str(dt.hour))
|
||||
else:
|
||||
dt_pattern=dt_pattern.replace('hh',dt.strftime('%I'))
|
||||
hour=dt.hour
|
||||
if hour==0:
|
||||
hour=12
|
||||
dt_pattern=dt_pattern.replace('h',str(hour))
|
||||
|
||||
dt_pattern=dt_pattern.replace('mm',dt.strftime('%M'))
|
||||
dt_pattern=dt_pattern.replace('m',str(dt.minute))
|
||||
|
||||
dt_pattern=dt_pattern.replace('ss',dt.strftime('%S'))
|
||||
dt_pattern=dt_pattern.replace('s',str(dt.second))
|
||||
except AttributeError:
|
||||
string=string.replace(pattern,'')
|
||||
return string
|
||||
|
||||
string=string.replace(pattern,dt_pattern)
|
||||
return string
|
||||
|
||||
def __replaceDatetimePair(self,string,wildcard):
|
||||
pos=0
|
||||
pattern=(0,'')
|
||||
while(pattern!=None):
|
||||
pattern=self.__findDatetimePattern(pattern[0],wildcard,string)
|
||||
if pattern!=None:
|
||||
string=self.__replaceDatetimePattern(string,pattern[1])
|
||||
return string
|
||||
|
||||
def dateTimeString(self):
|
||||
"""
|
||||
Returns the date-time of the update in ISO 8601 format (string).
|
||||
"""
|
||||
return self.__fields['padUpdate']['dateTime']
|
||||
|
||||
def dateTime(self):
|
||||
"""
|
||||
Returns the date-time of the PAD update (datetime)
|
||||
"""
|
||||
return self.__fromIso8601(pad_data['padUpdate']['dateTime'])
|
||||
|
||||
def escape(self,string,esc):
|
||||
"""
|
||||
Returns an 'escaped' version of the specified string.
|
||||
Take two arguments:
|
||||
|
||||
string - The string to be processed.
|
||||
|
||||
esc - The type of escaping to be applied. The following values
|
||||
are valid:
|
||||
PyPAD.ESCAPE_JSON - Escaping for JSON string values
|
||||
PyPAD.ESCAPE_NONE - No escaping applied
|
||||
PyPAD.ESCAPE_URL - Escaping for using in URLs
|
||||
PyPAD.ESCAPE_XML - Escaping for use in XML
|
||||
"""
|
||||
if(esc==0):
|
||||
return string
|
||||
if(esc==1):
|
||||
return self.__escapeXml(string)
|
||||
if(esc==2):
|
||||
return self.__escapeWeb(string)
|
||||
if(esc==3):
|
||||
return self.__escapeJson(string)
|
||||
raise ValueError('invalid esc value')
|
||||
|
||||
def logMachine(self):
|
||||
"""
|
||||
Returns the log machine number to which this update pertains
|
||||
(integer).
|
||||
"""
|
||||
return self.__fields['padUpdate']['logMachine']
|
||||
|
||||
def onairFlag(self):
|
||||
"""
|
||||
Returns the state of the on-air flag (boolean).
|
||||
"""
|
||||
return self.__fields['padUpdate']['onairFlag']
|
||||
|
||||
def hasService(self):
|
||||
"""
|
||||
Indicates if service information is included with this update
|
||||
(boolean).
|
||||
"""
|
||||
try:
|
||||
return self.__fields['padUpdate']['service']!=None
|
||||
except TypeError:
|
||||
return False;
|
||||
|
||||
def serviceName(self):
|
||||
"""
|
||||
Returns the name of the service associated with this update (string).
|
||||
"""
|
||||
return self.__fields['padUpdate']['service']['name']
|
||||
|
||||
def serviceDescription(self):
|
||||
"""
|
||||
Returns the description of the service associated with this update
|
||||
(string).
|
||||
"""
|
||||
return self.__fields['padUpdate']['service']['description']
|
||||
|
||||
def serviceProgramCode(self):
|
||||
"""
|
||||
Returns the Program Code of the service associated with this update
|
||||
(string).
|
||||
"""
|
||||
return self.__fields['padUpdate']['service']['programCode']
|
||||
|
||||
def hasLog(self):
|
||||
"""
|
||||
Indicates if log information is included with this update
|
||||
(boolean).
|
||||
"""
|
||||
try:
|
||||
return self.__fields['padUpdate']['log']!=None
|
||||
except TypeError:
|
||||
return False;
|
||||
|
||||
def logName(self):
|
||||
"""
|
||||
Returns the name of the log associated with this update (string).
|
||||
"""
|
||||
return self.__fields['padUpdate']['log']['name']
|
||||
|
||||
def resolvePadFields(self,string,esc):
|
||||
"""
|
||||
Takes two arguments:
|
||||
|
||||
string - A string containing one or more PAD wildcards, which it
|
||||
will resolve into the appropriate values. See the
|
||||
'Metadata Wildcards' section of the Rivendell Operations
|
||||
Guide for a list of recognized wildcards.
|
||||
|
||||
esc - Character escaping to be applied to the PAD fields.
|
||||
Must be one of the following:
|
||||
|
||||
PyPAD.ESCAPE_NONE - No escaping
|
||||
PyPAD.ESCAPE_XML - "XML" escaping: Escape reserved
|
||||
characters as per XML-v1.0
|
||||
PyPAD.ESCAPE_URL - "URL" escaping: Escape reserved
|
||||
characters as per RFC 2396
|
||||
Section 2.4
|
||||
PyPAD.ESCAPE_JSON - "JSON" escaping: Escape reserved
|
||||
characters as per ECMA-404.
|
||||
"""
|
||||
string=self.__replaceWildcardPair('a','artist',string,esc)
|
||||
string=self.__replaceWildcardPair('b','label',string,esc)
|
||||
string=self.__replaceWildcardPair('c','client',string,esc)
|
||||
string=self.__replaceDatetimePair(string,'d') # %d(<dt>) Handler
|
||||
string=self.__replaceDatetimePair(string,'D') # %D(<dt>) Handler
|
||||
string=self.__replaceWildcardPair('e','agency',string,esc)
|
||||
#string=self.__replaceWildcardPair('f',sfield,string,esc) # Unassigned
|
||||
string=self.__replaceWildcardPair('g','groupName',string,esc)
|
||||
string=self.__replaceWildcardPair('h','length',string,esc)
|
||||
string=self.__replaceWildcardPair('i','description',string,esc)
|
||||
string=self.__replaceWildcardPair('j','cutNumber',string,esc)
|
||||
#string=self.__replaceWildcardPair('k',sfield,string,esc) # Start time for rdimport
|
||||
string=self.__replaceWildcardPair('l','album',string,esc)
|
||||
string=self.__replaceWildcardPair('m','composer',string,esc)
|
||||
string=self.__replaceWildcardPair('n','cartNumber',string,esc)
|
||||
string=self.__replaceWildcardPair('o','outcue',string,esc)
|
||||
string=self.__replaceWildcardPair('p','publisher',string,esc)
|
||||
#string=self.__replaceWildcardPair('q',sfield,string,esc) # Start date for rdimport
|
||||
string=self.__replaceWildcardPair('r','conductor',string,esc)
|
||||
string=self.__replaceWildcardPair('s','songId',string,esc)
|
||||
string=self.__replaceWildcardPair('t','title',string,esc)
|
||||
string=self.__replaceWildcardPair('u','userDefined',string,esc)
|
||||
#string=self.__replaceWildcardPair('v',sfield,string,esc) # Length, rounded down
|
||||
#string=self.__replaceWildcardPair('w',sfield,string,esc) # Unassigned
|
||||
#string=self.__replaceWildcardPair('x',sfield,string,esc) # Unassigned
|
||||
string=self.__replaceWildcardPair('y','year',string,esc)
|
||||
#string=self.__replaceWildcardPair('z',sfield,string,esc) # Unassigned
|
||||
string=string.replace('\\b','\b')
|
||||
string=string.replace('\\f','\f')
|
||||
string=string.replace('\\n','\n')
|
||||
string=string.replace('\\r','\r')
|
||||
string=string.replace('\\t','\t')
|
||||
return string
|
||||
|
||||
def hasPadType(self,pad_type):
|
||||
"""
|
||||
Indicates if this update includes the specified PAD type
|
||||
Takes one argument:
|
||||
pad_type - The type of PAD value. Valid values are:
|
||||
PyPAD.NOW - Now playing data
|
||||
PyPAD.NEXT - Next to play data
|
||||
"""
|
||||
try:
|
||||
return self.__fields['padUpdate'][pad_type]!=None
|
||||
except TypeError:
|
||||
return False;
|
||||
|
||||
def startDateTime(self,pad_type):
|
||||
"""
|
||||
Returns the start datetime of the specified PAD type
|
||||
Takes one argument:
|
||||
pad_type - The type of PAD value. Valid values are:
|
||||
PyPAD.NOW - Now playing data
|
||||
PyPAD.NEXT - Next to play data
|
||||
"""
|
||||
try:
|
||||
return self.__fromIso8601(self.__fields['padUpdate'][pad_type]['startDateTime'])
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
def padField(self,pad_type,pad_field):
|
||||
"""
|
||||
Returns the raw value of the specified PAD field.
|
||||
Takes two arguments:
|
||||
pad_type - The type of PAD value. Valid values are:
|
||||
PyPAD.NOW - Now playing data
|
||||
PyPAD.NEXT - Next to play data
|
||||
|
||||
pad_field - The specific field. Valid values are:
|
||||
PyPAD.FIELD_AGENCY - The 'Agency' field (string)
|
||||
PyPAD.FIELD_ALBUM - The 'Album' field (string)
|
||||
PyPAD.FIELD_ARTIST - The 'Artist' field (string)
|
||||
PyPAD.FIELD_CART_NUMBER - The 'Cart Number' field
|
||||
(integer)
|
||||
PyPAD.FIELD_CART_TYPE - 'The 'Cart Type' field
|
||||
(string)
|
||||
PyPAD.FIELD_CLIENT - The 'Client' field (string)
|
||||
PyPAD.FIELD_COMPOSER - The 'Composer' field (string)
|
||||
PyPAD.FIELD_CONDUCTOR - The 'Conductor' field (string)
|
||||
PyPAD.FIELD_DESCRIPTION - The 'Description' field
|
||||
(string)
|
||||
PyPAD.FIELD_EXTERNAL_ANNC_TYPE - The 'EXT_ANNC_TYPE'
|
||||
field (string)
|
||||
PyPAD.FIELD_EXTERNAL_DATA - The 'EXT_DATA' field
|
||||
(string)
|
||||
PyPAD.FIELD_EXTERNAL_EVENT_ID - The 'EXT_EVENT_ID'
|
||||
field (string)
|
||||
PyPAD.FIELD_GROUP_NAME - The 'GROUP_NAME' field
|
||||
(string)
|
||||
PyPAD.FIELD_ISRC - The 'ISRC' field (string)
|
||||
PyPAD.FIELD_ISCI - The 'ISCI' field (string)
|
||||
PyPAD.FIELD_LABEL - The 'Label' field (string)
|
||||
PyPAD.FIELD_LENGTH - The 'Length' field (integer)
|
||||
PyPAD.FIELD_OUTCUE - The 'Outcue' field (string)
|
||||
PyPAD.FIELD_PUBLISHER - The 'Publisher' field (string)
|
||||
PyPAD.FIELD_SONG_ID - The 'Song ID' field (string)
|
||||
PyPAD.FIELD_START_DATETIME - The 'Start DateTime field
|
||||
(string)
|
||||
PyPAD.FIELD_TITLE - The 'Title' field (string)
|
||||
PyPAD.FIELD_USER_DEFINED - 'The 'User Defined' field
|
||||
(string)
|
||||
PyPAD.FIELD_YEAR - The 'Year' field (integer)
|
||||
"""
|
||||
return self.__fields['padUpdate'][pad_type][pad_field]
|
||||
|
||||
|
||||
|
||||
|
||||
class Receiver(object):
|
||||
def __init__(self):
|
||||
self.__callback=None
|
||||
|
||||
def __PyPAD_Process(self,pad):
|
||||
self.__callback(pad)
|
||||
|
||||
def setCallback(self,cb):
|
||||
"""
|
||||
Set the processing callback.
|
||||
"""
|
||||
self.__callback=cb
|
||||
|
||||
def start(self,hostname,port):
|
||||
"""
|
||||
Connect to a Rivendell system and begin processing PAD events.
|
||||
Once started, a PyPAD object can be interacted with
|
||||
only within one of its callback methods.
|
||||
Takes the following arguments:
|
||||
|
||||
hostname - The hostname or IP address of the Rivendell system.
|
||||
|
||||
port - The TCP port to connect to. For most cases, just use
|
||||
'PyPAD.PAD_TCP_PORT'.
|
||||
"""
|
||||
sock=socket.socket(socket.AF_INET)
|
||||
conn=sock.connect((hostname,port))
|
||||
c=""
|
||||
line=""
|
||||
msg=""
|
||||
|
||||
while 1<2:
|
||||
c=sock.recv(1)
|
||||
line+=c
|
||||
if c[0]=="\n":
|
||||
msg+=line
|
||||
if line=="\r\n":
|
||||
self.__PyPAD_Process(Update(json.loads(msg)))
|
||||
msg=""
|
||||
line=""
|
||||
|
||||
|
39
apis/PyPAD/examples/Makefile.am
Normal file
39
apis/PyPAD/examples/Makefile.am
Normal file
@ -0,0 +1,39 @@
|
||||
## automake.am
|
||||
##
|
||||
## Automake.am for Rivendell PyPAD/examples
|
||||
##
|
||||
## (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 = now_and_next.py\
|
||||
pypad_udp.py
|
||||
|
||||
CLEANFILES = *~\
|
||||
*.idb\
|
||||
*ilk\
|
||||
*.obj\
|
||||
*.pdb\
|
||||
*.qm\
|
||||
moc_*
|
||||
|
||||
MAINTAINERCLEANFILES = *~\
|
||||
*.tar.gz\
|
||||
aclocal.m4\
|
||||
configure\
|
||||
Makefile.in\
|
||||
moc_*
|
63
apis/PyPAD/examples/now_and_next.py
Executable file
63
apis/PyPAD/examples/now_and_next.py
Executable file
@ -0,0 +1,63 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# now_and_next.py
|
||||
#
|
||||
# Example PyPAD script for Rivendell
|
||||
#
|
||||
# (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.
|
||||
#
|
||||
|
||||
#
|
||||
# To see the full documentation of these classes, enter the following at
|
||||
# a python interactive prompt:
|
||||
#
|
||||
# import PyPAD
|
||||
# help(PyPAD)
|
||||
#
|
||||
import PyPAD
|
||||
|
||||
#
|
||||
# First, we create a callback method, that will be called every time a
|
||||
# log machine updates its PAD. An instance of 'PyPAD.Update' that contains
|
||||
# the PAD information is supplied as the single argument.
|
||||
#
|
||||
def ProcessPad(update):
|
||||
print
|
||||
if update.hasPadType(PyPAD.TYPE_NOW):
|
||||
print "Log %03d NOW: " % update.logMachine()+update.resolvePadFields("%a - %t",PyPAD.ESCAPE_NONE)
|
||||
else:
|
||||
print "Log %03d NOW: [none]" % update.logMachine()
|
||||
if update.hasPadType(PyPAD.TYPE_NEXT):
|
||||
print "Log %03d NEXT: " % update.logMachine()+update.resolvePadFields("%A - %T",PyPAD.ESCAPE_NONE)
|
||||
else:
|
||||
print "Log %03d NEXT: [none]" % update.logMachine()
|
||||
|
||||
#
|
||||
# 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)
|
64
apis/PyPAD/examples/pypad_udp.py
Executable file
64
apis/PyPAD/examples/pypad_udp.py
Executable file
@ -0,0 +1,64 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# pypad_udp.py
|
||||
#
|
||||
# Send PAD updates via UDP
|
||||
#
|
||||
# (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 socket
|
||||
import ConfigParser
|
||||
import PyPAD
|
||||
|
||||
def eprint(*args,**kwargs):
|
||||
print(*args,file=sys.stderr,**kwargs)
|
||||
|
||||
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'))))
|
||||
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_udp.py: you must specify a configuration file')
|
||||
sys.exit(1)
|
||||
|
||||
#
|
||||
# Create Send Socket
|
||||
#
|
||||
send_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
|
||||
|
||||
rcvr=PyPAD.Receiver()
|
||||
rcvr.setCallback(ProcessPad)
|
||||
rcvr.start("localhost",PyPAD.PAD_TCP_PORT)
|
@ -247,6 +247,11 @@ AC_CHECK_HEADER(security/pam_appl.h,[],[AC_MSG_ERROR([*** PAM not found ***])])
|
||||
#
|
||||
AC_CHECK_HEADER(soundtouch/SoundTouch.h,[],[AC_MSG_ERROR([*** SoundTouch not found ***])])
|
||||
|
||||
#
|
||||
# Check for Python
|
||||
#
|
||||
AM_PATH_PYTHON([2.7])
|
||||
|
||||
#
|
||||
# Check for FLAC
|
||||
#
|
||||
@ -462,6 +467,9 @@ AC_CONFIG_FILES([rivendell.spec \
|
||||
icons/Makefile \
|
||||
helpers/Makefile \
|
||||
apis/Makefile \
|
||||
apis/PyPAD/Makefile \
|
||||
apis/PyPAD/api/Makefile \
|
||||
apis/PyPAD/examples/Makefile \
|
||||
apis/rivwebcapi/Makefile \
|
||||
apis/rivwebcapi/rivwebcapi.pc \
|
||||
apis/rivwebcapi/rivwebcapi/Makefile \
|
||||
@ -507,6 +515,7 @@ AC_CONFIG_FILES([rivendell.spec \
|
||||
rdmonitor/Makefile \
|
||||
rdpanel/Makefile \
|
||||
rdrepld/Makefile \
|
||||
rdrlmd/Makefile \
|
||||
rdselect/Makefile \
|
||||
rdservice/Makefile \
|
||||
rdvairplayd/Makefile \
|
||||
|
@ -233,6 +233,8 @@ dist_librd_la_SOURCES = dbversion.h\
|
||||
rdurl.cpp rdurl.h\
|
||||
rduser.cpp rduser.h\
|
||||
rdupload.cpp rdupload.h\
|
||||
rdunixserver.cpp rdunixserver.h\
|
||||
rdunixsocket.cpp rdunixsocket.h\
|
||||
rdversion.cpp rdversion.h\
|
||||
rdwavedata.cpp rdwavedata.h\
|
||||
rdwavedata_dialog.cpp rdwavedata_dialog.h\
|
||||
@ -333,6 +335,8 @@ nodist_librd_la_SOURCES = moc_rdadd_cart.cpp\
|
||||
moc_rdtransportbutton.cpp\
|
||||
moc_rdtrimaudio.cpp\
|
||||
moc_rdttydevice.cpp\
|
||||
moc_rdunixserver.cpp\
|
||||
moc_rdunixsocket.cpp\
|
||||
moc_rdupload.cpp\
|
||||
moc_rdwavedata_dialog.cpp\
|
||||
moc_schedcartlist.cpp
|
||||
|
6
lib/rd.h
6
lib/rd.h
@ -593,5 +593,11 @@
|
||||
*/
|
||||
#define RD_STATUS_BACKGROUND_COLOR "#AAFFFF"
|
||||
|
||||
/*
|
||||
* RLM2 Connection Points
|
||||
*/
|
||||
#define RD_RLM2_CLIENT_TCP_PORT 34289
|
||||
#define RD_RLM2_SOURCE_UNIX_ADDRESS "m4w8n8fsfddf-473fdueusurt-8954"
|
||||
|
||||
|
||||
#endif // RD_H
|
||||
|
@ -991,7 +991,15 @@ int RDTimeZoneOffset()
|
||||
tm=gmtime(&t);
|
||||
time_t gmt_time=3600*tm->tm_hour+60*tm->tm_min+tm->tm_sec;
|
||||
|
||||
return gmt_time-local_time;
|
||||
int offset=gmt_time-local_time;
|
||||
if(offset>43200) {
|
||||
offset=offset-86400;
|
||||
}
|
||||
if(offset<-43200) {
|
||||
offset=offset+86400;
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include "rdmixer.h"
|
||||
#include "rdnownext.h"
|
||||
#include "rdsvc.h"
|
||||
#include "rdweb.h"
|
||||
|
||||
//
|
||||
// Debug Settings
|
||||
@ -41,9 +42,8 @@
|
||||
//#define SHOW_METER_SLOTS
|
||||
|
||||
RDLogPlay::RDLogPlay(int id,RDEventPlayer *player,Q3SocketDevice *nn_sock,
|
||||
QString logname,std::vector<RDRLMHost *> *rlm_hosts,
|
||||
QObject *parent)
|
||||
: QObject(parent),RDLogEvent(logname)
|
||||
std::vector<RDRLMHost *> *rlm_hosts,QObject *parent)
|
||||
: QObject(parent),RDLogEvent("")
|
||||
{
|
||||
//
|
||||
// Initialize Data Structures
|
||||
@ -51,6 +51,7 @@ RDLogPlay::RDLogPlay(int id,RDEventPlayer *player,Q3SocketDevice *nn_sock,
|
||||
play_log=NULL;
|
||||
play_id=id;
|
||||
play_event_player=player;
|
||||
// play_pad_socket=pad_sock;
|
||||
play_rlm_hosts=rlm_hosts;
|
||||
play_onair_flag=false;
|
||||
play_segue_length=rda->airplayConf()->segueLength()+1;
|
||||
@ -76,6 +77,14 @@ RDLogPlay::RDLogPlay(int id,RDEventPlayer *player,Q3SocketDevice *nn_sock,
|
||||
play_slot_id[i]=i;
|
||||
}
|
||||
|
||||
//
|
||||
// RLM2 Connection
|
||||
//
|
||||
play_pad_socket=new RDUnixSocket(this);
|
||||
if(!play_pad_socket->connectToAbstract(RD_RLM2_SOURCE_UNIX_ADDRESS)) {
|
||||
fprintf(stderr,"RLMHost: unable to connect to rdrlmd\n");
|
||||
}
|
||||
|
||||
//
|
||||
// CAE Connection
|
||||
//
|
||||
@ -2869,10 +2878,12 @@ void RDLogPlay::SendNowNext()
|
||||
//
|
||||
// Get NOW PLAYING Event
|
||||
//
|
||||
/*
|
||||
if(play_nownext_address.isNull()&&play_nownext_rml.isEmpty()&&
|
||||
(play_rlm_hosts->size()==0)) {
|
||||
return;
|
||||
}
|
||||
*/
|
||||
QString cmd=play_nownext_string;
|
||||
int lines[TRANSPORT_QUANTITY];
|
||||
int running=runningEvents(lines,false);
|
||||
@ -2946,10 +2957,75 @@ void RDLogPlay::SendNowNext()
|
||||
if(svcname.isEmpty()) {
|
||||
svcname=play_defaultsvc_name;
|
||||
}
|
||||
|
||||
//
|
||||
// RLM2
|
||||
//
|
||||
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("onairFlag",play_onair_flag,8));
|
||||
play_pad_socket->write(RDJsonField("logMode",RDAirPlayConf::logModeText(play_op_mode),8));
|
||||
|
||||
//
|
||||
// Service
|
||||
//
|
||||
RDSvc *svc=new RDSvc(svcname,rda->station(),rda->config(),this);
|
||||
play_pad_socket->write(QString(" \"service\": {\r\n").toUtf8());
|
||||
play_pad_socket->write(RDJsonField("name",svcname,12).toUtf8());
|
||||
play_pad_socket->
|
||||
write(RDJsonField("description",svc->description(),12).toUtf8());
|
||||
play_pad_socket->
|
||||
write(RDJsonField("programCode",svc->programCode(),12,true).toUtf8());
|
||||
play_pad_socket->write(QString(" },\r\n").toUtf8());
|
||||
delete svc;
|
||||
|
||||
//
|
||||
// Log
|
||||
//
|
||||
play_pad_socket->write(QString(" \"log\": {\r\n").toUtf8());
|
||||
play_pad_socket->write(RDJsonField("name",logName(),12,true).toUtf8());
|
||||
play_pad_socket->write(QString(" },\r\n").toUtf8());
|
||||
|
||||
//
|
||||
// Now
|
||||
//
|
||||
QDateTime start_datetime;
|
||||
if(logline[0]!=NULL) {
|
||||
start_datetime=
|
||||
QDateTime(QDate::currentDate(),logline[0]->startTime(RDLogLine::Actual));
|
||||
}
|
||||
play_pad_socket->
|
||||
write(GetPadJson("now",logline[0],start_datetime,8,false).toUtf8());
|
||||
|
||||
//
|
||||
// Next
|
||||
//
|
||||
QDateTime next_datetime;
|
||||
if((mode()==RDAirPlayConf::Auto)&&(logline[0]!=NULL)) {
|
||||
next_datetime=start_datetime.addSecs(logline[0]->forcedLength()/1000);
|
||||
}
|
||||
play_pad_socket->write(GetPadJson("next",logline[1],
|
||||
next_datetime,8,true).toUtf8());
|
||||
|
||||
//
|
||||
// Commit the update
|
||||
//
|
||||
play_pad_socket->write(QString(" }\r\n").toUtf8());
|
||||
play_pad_socket->write(QString("}\r\n\r\n").toUtf8());
|
||||
|
||||
//
|
||||
// Old-style RLM Hosts
|
||||
//
|
||||
for(unsigned i=0;i<play_rlm_hosts->size();i++) {
|
||||
play_rlm_hosts->at(i)->
|
||||
sendEvent(svcname,logName(),play_id,logline,play_onair_flag,play_op_mode);
|
||||
}
|
||||
|
||||
//
|
||||
// Premordial integrated interface
|
||||
//
|
||||
RDResolveNowNext(&cmd,logline,0);
|
||||
play_nownext_socket->
|
||||
writeBlock(cmd,cmd.length(),play_nownext_address,play_nownext_port);
|
||||
@ -2970,6 +3046,63 @@ void RDLogPlay::SendNowNext()
|
||||
}
|
||||
|
||||
|
||||
QString RDLogPlay::GetPadJson(const QString &name,RDLogLine *ll,
|
||||
const QDateTime &start_datetime,int padding,
|
||||
bool final) const
|
||||
{
|
||||
QString ret;
|
||||
|
||||
if(ll==NULL) {
|
||||
ret=RDJsonNullField(name,padding,final);
|
||||
}
|
||||
else {
|
||||
ret+=RDJsonPadding(padding)+"\""+name+"\": {\r\n";
|
||||
if(start_datetime.isValid()) {
|
||||
ret+=RDJsonField("startDateTime",start_datetime,4+padding);
|
||||
}
|
||||
else {
|
||||
ret+=RDJsonNullField("startDateTime",4+padding);
|
||||
}
|
||||
ret+=RDJsonField("cartNumber",ll->cartNumber(),4+padding);
|
||||
ret+=RDJsonField("cartType",RDCart::typeText(ll->cartType()),4+padding);
|
||||
ret+=RDJsonField("length",ll->forcedLength(),4+padding);
|
||||
if(ll->year().isValid()) {
|
||||
ret+=RDJsonField("year",ll->year().year(),4+padding);
|
||||
}
|
||||
else {
|
||||
ret+=RDJsonNullField("year",4+padding);
|
||||
}
|
||||
ret+=RDJsonField("groupName",ll->groupName(),4+padding);
|
||||
ret+=RDJsonField("title",ll->title(),4+padding);
|
||||
ret+=RDJsonField("artist",ll->artist(),4+padding);
|
||||
ret+=RDJsonField("publisher",ll->publisher(),4+padding);
|
||||
ret+=RDJsonField("composer",ll->composer(),4+padding);
|
||||
ret+=RDJsonField("album",ll->album(),4+padding);
|
||||
ret+=RDJsonField("label",ll->label(),4+padding);
|
||||
ret+=RDJsonField("client",ll->client(),4+padding);
|
||||
ret+=RDJsonField("agency",ll->agency(),4+padding);
|
||||
ret+=RDJsonField("conductor",ll->conductor(),4+padding);
|
||||
ret+=RDJsonField("userDefined",ll->userDefined(),4+padding);
|
||||
ret+=RDJsonField("songId",ll->songId(),4+padding);
|
||||
ret+=RDJsonField("outcue",ll->outcue(),4+padding);
|
||||
ret+=RDJsonField("description",ll->description(),4+padding);
|
||||
ret+=RDJsonField("isrc",ll->isrc(),4+padding);
|
||||
ret+=RDJsonField("isci",ll->isci(),4+padding);
|
||||
ret+=RDJsonField("externalEventId",ll->extEventId(),4+padding);
|
||||
ret+=RDJsonField("externalData",ll->extData(),4+padding);
|
||||
ret+=RDJsonField("externalAnncType",ll->extAnncType(),4+padding,true);
|
||||
if(final) {
|
||||
ret+=RDJsonPadding(padding)+"}\r\n";
|
||||
}
|
||||
else {
|
||||
ret+=RDJsonPadding(padding)+"},\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
void RDLogPlay::LogTraffic(RDLogLine *logline,RDLogLine::PlaySource src,
|
||||
RDAirPlayConf::TrafficAction action,bool onair_flag)
|
||||
const
|
||||
|
@ -39,6 +39,7 @@
|
||||
#include <rdplay_deck.h>
|
||||
#include <rdrlmhost.h>
|
||||
#include <rdsimpleplayer.h>
|
||||
#include <rdunixsocket.h>
|
||||
|
||||
//
|
||||
// Widget Settings
|
||||
@ -53,8 +54,8 @@ class RDLogPlay : public QObject,public RDLogEvent
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
RDLogPlay(int id,RDEventPlayer *player,Q3SocketDevice *nn_sock,QString logname,
|
||||
std::vector<RDRLMHost *> *rlm_hosts,QObject *parent=0);
|
||||
RDLogPlay(int id,RDEventPlayer *player,Q3SocketDevice *nn_sock,
|
||||
std::vector<RDRLMHost *> *rlm_hosts,QObject *parent=0);
|
||||
QString serviceName() const;
|
||||
void setServiceName(const QString &svcname);
|
||||
QString defaultServiceName() const;
|
||||
@ -188,6 +189,9 @@ class RDLogPlay : public QObject,public RDLogEvent
|
||||
RDLogLine::TransType GetTransType(const QString &logname,int line);
|
||||
bool ClearBlock(int start_line);
|
||||
void SendNowNext();
|
||||
QString GetPadJson(const QString &name,RDLogLine *ll,
|
||||
const QDateTime &start_datetime,int padding,
|
||||
bool final=false) const;
|
||||
void LogTraffic(RDLogLine *logline,RDLogLine::PlaySource src,
|
||||
RDAirPlayConf::TrafficAction action,bool onair_flag) const;
|
||||
RDCae *play_cae;
|
||||
@ -246,6 +250,7 @@ class RDLogPlay : public QObject,public RDLogEvent
|
||||
bool play_audition_head_played;
|
||||
int play_audition_preroll;
|
||||
RDEventPlayer *play_event_player;
|
||||
RDUnixSocket *play_pad_socket;
|
||||
};
|
||||
|
||||
|
||||
|
191
lib/rdunixserver.cpp
Normal file
191
lib/rdunixserver.cpp
Normal file
@ -0,0 +1,191 @@
|
||||
// rdunixserver.cpp
|
||||
//
|
||||
// UNIX Socket Server
|
||||
//
|
||||
// (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.
|
||||
//
|
||||
|
||||
#include <errno.h>
|
||||
#include <linux/un.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "rdunixserver.h"
|
||||
|
||||
RDUnixServer::RDUnixServer(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
unix_socket=-1;
|
||||
unix_notifier=NULL;
|
||||
unix_is_listening=false;
|
||||
unix_max_pending_connections=3;
|
||||
unix_error_string="ok";
|
||||
}
|
||||
|
||||
|
||||
RDUnixServer::~RDUnixServer()
|
||||
{
|
||||
close();
|
||||
if(unix_notifier!=NULL) {
|
||||
delete unix_notifier;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void RDUnixServer::close()
|
||||
{
|
||||
if(unix_socket>=0) {
|
||||
shutdown(unix_socket,SHUT_RDWR);
|
||||
unix_socket=-1;
|
||||
unix_is_listening=false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QString RDUnixServer::errorString() const
|
||||
{
|
||||
return unix_error_string;
|
||||
}
|
||||
|
||||
|
||||
bool RDUnixServer::hasPendingConnections() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool RDUnixServer::isListening() const
|
||||
{
|
||||
return unix_is_listening;
|
||||
}
|
||||
|
||||
|
||||
bool RDUnixServer::listenToPathname(const QString &pathname)
|
||||
{
|
||||
struct sockaddr_un sa;
|
||||
|
||||
if((unix_socket=socket(AF_UNIX,SOCK_STREAM,0))<0) {
|
||||
unix_error_string=QString("unable to create socket")+" ["+
|
||||
QString(strerror(errno))+"]";
|
||||
return false;
|
||||
}
|
||||
memset(&sa,0,sizeof(sa));
|
||||
sa.sun_family=AF_UNIX;
|
||||
strncpy(sa.sun_path,pathname.toUtf8(),UNIX_PATH_MAX);
|
||||
if(bind(unix_socket,(struct sockaddr *)(&sa),sizeof(sa))<0) {
|
||||
unix_error_string=QString("unable to bind address")+" ["+
|
||||
QString(strerror(errno))+"]";
|
||||
return false;
|
||||
}
|
||||
if(listen(unix_socket,unix_max_pending_connections)<0) {
|
||||
unix_error_string=QString("unable to listen")+" ["+
|
||||
QString(strerror(errno))+"]";
|
||||
return false;
|
||||
}
|
||||
unix_is_listening=true;
|
||||
unix_notifier=new QSocketNotifier(unix_socket,QSocketNotifier::Read,this);
|
||||
connect(unix_notifier,SIGNAL(activated(int)),
|
||||
this,SLOT(newConnectionData(int)));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool RDUnixServer::listenToAbstract(const QString &addr)
|
||||
{
|
||||
struct sockaddr_un sa;
|
||||
|
||||
if((unix_socket=socket(AF_UNIX,SOCK_STREAM,0))<0) {
|
||||
unix_error_string=QString("unable to create socket")+" ["+
|
||||
QString(strerror(errno))+"]";
|
||||
return false;
|
||||
}
|
||||
memset(&sa,0,sizeof(sa));
|
||||
sa.sun_family=AF_UNIX;
|
||||
strncpy(sa.sun_path+1,addr.toUtf8(),UNIX_PATH_MAX-1);
|
||||
if(bind(unix_socket,(struct sockaddr *)(&sa),sizeof(sa))<0) {
|
||||
unix_error_string=QString("unable to bind address")+" ["+
|
||||
QString(strerror(errno))+"]";
|
||||
return false;
|
||||
}
|
||||
if(listen(unix_socket,unix_max_pending_connections)<0) {
|
||||
unix_error_string=QString("unable to listen")+" ["+
|
||||
QString(strerror(errno))+"]";
|
||||
return false;
|
||||
}
|
||||
unix_is_listening=true;
|
||||
unix_notifier=new QSocketNotifier(unix_socket,QSocketNotifier::Read,this);
|
||||
connect(unix_notifier,SIGNAL(activated(int)),
|
||||
this,SLOT(newConnectionData(int)));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
QTcpSocket *RDUnixServer::nextPendingConnection()
|
||||
{
|
||||
int sock;
|
||||
QTcpSocket *tcpsock=NULL;
|
||||
struct sockaddr_un sa;
|
||||
socklen_t sa_len=sizeof(sa);
|
||||
|
||||
memset(&sa,0,sizeof(sa));
|
||||
|
||||
if((sock=accept(unix_socket,(struct sockaddr *)(&sa),&sa_len))<0) {
|
||||
unix_error_string=QString("accept failed [")+QString(strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
tcpsock=new QTcpSocket(this);
|
||||
tcpsock->setSocketDescriptor(sock,QAbstractSocket::ConnectedState);
|
||||
|
||||
return tcpsock;
|
||||
}
|
||||
|
||||
|
||||
int RDUnixServer::maxPendingConnections() const
|
||||
{
|
||||
return unix_max_pending_connections;
|
||||
}
|
||||
|
||||
|
||||
void RDUnixServer::setMaxPendingConnections(int num)
|
||||
{
|
||||
unix_max_pending_connections=num;
|
||||
}
|
||||
|
||||
|
||||
int RDUnixServer::socketDescriptor() const
|
||||
{
|
||||
return unix_socket;
|
||||
}
|
||||
|
||||
|
||||
void RDUnixServer::setSocketDescriptor(int sock)
|
||||
{
|
||||
unix_socket=sock;
|
||||
if(unix_notifier!=NULL) {
|
||||
delete unix_notifier;
|
||||
}
|
||||
unix_notifier=new QSocketNotifier(unix_socket,QSocketNotifier::Read,this);
|
||||
connect(unix_notifier,SIGNAL(activated(int)),
|
||||
this,SLOT(newConnectionData(int)));
|
||||
}
|
||||
|
||||
|
||||
void RDUnixServer::newConnectionData(int fd)
|
||||
{
|
||||
emit newConnection();
|
||||
}
|
68
lib/rdunixserver.h
Normal file
68
lib/rdunixserver.h
Normal file
@ -0,0 +1,68 @@
|
||||
// rdunixserver.h
|
||||
//
|
||||
// UNIX Socket Server
|
||||
//
|
||||
// (C) Copyright 2018 Fred Gleason <fredg@paravelsystems.com>
|
||||
//
|
||||
// This class works much the same way as QTcpServer, only it supports
|
||||
// SOCK_STREAM connections via UNIX sockets rather than TCP/IP.
|
||||
//
|
||||
// See the unix(7) man page for a description of the difference between
|
||||
// 'pathname' and 'abstract' socket addresses.
|
||||
//
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#ifndef RDUNIXSERVER_H
|
||||
#define RDUNIXSERVER_H
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qsocketnotifier.h>
|
||||
#include <qtcpsocket.h>
|
||||
|
||||
class RDUnixServer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
RDUnixServer(QObject *parent=0);
|
||||
~RDUnixServer();
|
||||
void close();
|
||||
QString errorString() const;
|
||||
bool hasPendingConnections() const;
|
||||
bool isListening() const;
|
||||
bool listenToPathname(const QString &pathname);
|
||||
bool listenToAbstract(const QString &addr);
|
||||
QTcpSocket *nextPendingConnection();
|
||||
int maxPendingConnections() const;
|
||||
void setMaxPendingConnections(int num);
|
||||
int socketDescriptor() const;
|
||||
void setSocketDescriptor(int sock);
|
||||
|
||||
signals:
|
||||
void newConnection();
|
||||
|
||||
private slots:
|
||||
void newConnectionData(int fd);
|
||||
|
||||
private:
|
||||
int unix_socket;
|
||||
bool unix_is_listening;
|
||||
int unix_max_pending_connections;
|
||||
QSocketNotifier *unix_notifier;
|
||||
QString unix_error_string;
|
||||
};
|
||||
|
||||
|
||||
#endif // RDUNIXSERVER_H
|
58
lib/rdunixsocket.cpp
Normal file
58
lib/rdunixsocket.cpp
Normal file
@ -0,0 +1,58 @@
|
||||
// rdunixsocket.cpp
|
||||
//
|
||||
// UNIX Socket
|
||||
//
|
||||
// (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.
|
||||
//
|
||||
|
||||
#include <linux/un.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "rdunixsocket.h"
|
||||
|
||||
RDUnixSocket::RDUnixSocket(QObject *parent)
|
||||
: QTcpSocket(parent)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
bool RDUnixSocket::connectToPathname(const QString &pathname,
|
||||
QAbstractSocket::OpenMode mode)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool RDUnixSocket::connectToAbstract(const QString &addr,
|
||||
QAbstractSocket::OpenMode mode)
|
||||
{
|
||||
int sock;
|
||||
struct sockaddr_un sa;
|
||||
|
||||
if((sock=::socket(AF_UNIX,SOCK_STREAM,0))<0) {
|
||||
return false;
|
||||
}
|
||||
memset(&sa,0,sizeof(sa));
|
||||
sa.sun_family=AF_UNIX;
|
||||
strncpy(sa.sun_path+1,addr.toUtf8(),UNIX_PATH_MAX-1);
|
||||
if(::connect(sock,(struct sockaddr *)(&sa),sizeof(sa))<0) {
|
||||
return false;
|
||||
}
|
||||
setSocketDescriptor(sock,QAbstractSocket::ConnectedState,mode);
|
||||
|
||||
return true;
|
||||
}
|
43
lib/rdunixsocket.h
Normal file
43
lib/rdunixsocket.h
Normal file
@ -0,0 +1,43 @@
|
||||
// rdunixsocket.h
|
||||
//
|
||||
// UNIX Socket
|
||||
//
|
||||
// (C) Copyright 2018 Fred Gleason <fredg@paravelsystems.com>
|
||||
//
|
||||
// This class works much the same way as QTcpSocket, only it supports
|
||||
// SOCK_STREAM connections via UNIX sockets rather than TCP/IP.
|
||||
//
|
||||
// See the unix(7) man page for a description of the difference between
|
||||
// 'pathname' and 'abstract' socket addresses.
|
||||
//
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#ifndef RDUNIXSOCKET_H
|
||||
#define RDUNIXSOCKET_H
|
||||
|
||||
#include <qtcpsocket.h>
|
||||
|
||||
class RDUnixSocket : public QTcpSocket
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
RDUnixSocket(QObject *parent=0);
|
||||
bool connectToPathname(const QString &pathname,OpenMode mode=ReadWrite);
|
||||
bool connectToAbstract(const QString &addr,OpenMode mode=ReadWrite);
|
||||
};
|
||||
|
||||
|
||||
#endif // RDUNIXSOCKET_H
|
117
lib/rdweb.cpp
117
lib/rdweb.cpp
@ -1043,6 +1043,110 @@ QString RDXmlUnescape(const QString &str)
|
||||
}
|
||||
|
||||
|
||||
QString RDJsonPadding(int padding)
|
||||
{
|
||||
QString ret="";
|
||||
|
||||
for(int i=0;i<padding;i++) {
|
||||
ret+=" ";
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
QString RDJsonNullField(const QString &name,int padding,bool final)
|
||||
{
|
||||
QString comma=",";
|
||||
|
||||
if(final) {
|
||||
comma="";
|
||||
}
|
||||
|
||||
return RDJsonPadding(padding)+"\""+name+"\": null"+comma+"\r\n";
|
||||
}
|
||||
|
||||
|
||||
QString RDJsonField(const QString &name,bool value,int padding,bool final)
|
||||
{
|
||||
QString comma=",";
|
||||
|
||||
if(final) {
|
||||
comma="";
|
||||
}
|
||||
|
||||
if(value) {
|
||||
return RDJsonPadding(padding)+"\""+name+"\": true"+comma+"\r\n";
|
||||
}
|
||||
return RDJsonPadding(padding)+"\""+name+"\": false"+comma+"\r\n";
|
||||
}
|
||||
|
||||
|
||||
QString RDJsonField(const QString &name,int value,int padding,bool final)
|
||||
{
|
||||
QString comma=",";
|
||||
|
||||
if(final) {
|
||||
comma="";
|
||||
}
|
||||
|
||||
return RDJsonPadding(padding)+"\""+name+"\": "+QString().sprintf("%d",value)+
|
||||
comma+"\r\n";
|
||||
}
|
||||
|
||||
|
||||
QString RDJsonField(const QString &name,unsigned value,int padding,bool final)
|
||||
{
|
||||
QString comma=",";
|
||||
|
||||
if(final) {
|
||||
comma="";
|
||||
}
|
||||
|
||||
return RDJsonPadding(padding)+"\""+name+"\": "+QString().sprintf("%u",value)+
|
||||
comma+"\r\n";
|
||||
}
|
||||
|
||||
|
||||
QString RDJsonField(const QString &name,const QString &value,int padding,
|
||||
bool final)
|
||||
{
|
||||
QString str=value;
|
||||
QString comma=",";
|
||||
|
||||
if(final) {
|
||||
comma="";
|
||||
}
|
||||
|
||||
str.replace("\\","\\\\");
|
||||
str.replace("\"","\\\"");
|
||||
str.replace("/","\\/");
|
||||
str.replace("\b","\\b");
|
||||
str.replace("\f","\\f");
|
||||
str.replace("\n","\\n");
|
||||
str.replace("\r","\\r");
|
||||
str.replace("\t","\\t");
|
||||
|
||||
return RDJsonPadding(padding)+"\""+name+"\": \""+str+"\""+comma+"\r\n";
|
||||
}
|
||||
|
||||
|
||||
QString RDJsonField(const QString &name,const QDateTime &value,int padding,
|
||||
bool final)
|
||||
{
|
||||
QString comma=",";
|
||||
|
||||
if(final) {
|
||||
comma="";
|
||||
}
|
||||
|
||||
if(!value.isValid()) {
|
||||
return RDJsonNullField(name,padding,final);
|
||||
}
|
||||
return RDJsonPadding(padding)+"\""+name+"\": \""+RDXmlDateTime(value)+"\""+
|
||||
comma+"\r\n";
|
||||
}
|
||||
|
||||
|
||||
QString RDUrlEscape(const QString &str)
|
||||
{
|
||||
/*
|
||||
@ -1098,7 +1202,18 @@ QString RDWebDateTime(const QDateTime &datetime)
|
||||
if(!datetime.isValid()) {
|
||||
return QString();
|
||||
}
|
||||
return RDLocalToUtc(datetime).toString("ddd, dd MMM yyyy hh:mm:ss")+" GMT";
|
||||
int offset=RDTimeZoneOffset();
|
||||
QString tzstr="-";
|
||||
if(offset<0) {
|
||||
tzstr="+";
|
||||
}
|
||||
tzstr+=QString().sprintf("%02d%02d",
|
||||
offset/3600,(offset/60)-((offset/3600)*60));
|
||||
if(offset==0) {
|
||||
tzstr="GMT";
|
||||
}
|
||||
|
||||
return datetime.toString("ddd, dd MMM yyyy hh:mm:ss")+" "+tzstr;
|
||||
}
|
||||
|
||||
|
||||
|
15
lib/rdweb.h
15
lib/rdweb.h
@ -88,6 +88,21 @@ extern QString RDXmlDateTime(const QDateTime &datetime);
|
||||
extern QString RDXmlTimeZoneSuffix();
|
||||
extern QString RDXmlEscape(const QString &str);
|
||||
extern QString RDXmlUnescape(const QString &str);
|
||||
extern QString RDJsonPadding(int padding);
|
||||
extern QString RDJsonNullField(const QString &name,int padding=0,
|
||||
bool final=false);
|
||||
extern QString RDJsonField(const QString &name,bool value,int padding=0,
|
||||
bool final=false);
|
||||
extern QString RDJsonField(const QString &name,int value,int padding=0,
|
||||
bool final=false);
|
||||
extern QString RDJsonField(const QString &name,unsigned value,int padding=0,
|
||||
bool final=false);
|
||||
extern QString RDJsonField(const QString &name,const QString &value,
|
||||
int padding=0,bool final=false);
|
||||
extern QString RDJsonField(const QString &name,const QDateTime &value,
|
||||
int padding=0,bool final=false);
|
||||
|
||||
|
||||
extern QString RDUrlEscape(const QString &str);
|
||||
extern QString RDUrlUnescape(const QString &str);
|
||||
extern QString RDWebDateTime(const QDateTime &datetime);
|
||||
|
@ -315,6 +315,16 @@ MainWidget::MainWidget(QWidget *parent)
|
||||
//
|
||||
air_nownext_socket=new Q3SocketDevice(Q3SocketDevice::Datagram);
|
||||
|
||||
/*
|
||||
//
|
||||
// RLM2 Connection
|
||||
//
|
||||
air_pad_socket=new RDUnixSocket(this);
|
||||
if(!air_pad_socket->connectToAbstract(RD_RLM2_SOURCE_UNIX_ADDRESS)) {
|
||||
fprintf(stderr,"RLMHost: unable to connect to rdrlmd\n");
|
||||
}
|
||||
*/
|
||||
|
||||
//
|
||||
// Log Machines
|
||||
//
|
||||
@ -324,8 +334,8 @@ MainWidget::MainWidget(QWidget *parent)
|
||||
connect(rename_mapper,SIGNAL(mapped(int)),this,SLOT(logRenamedData(int)));
|
||||
QString default_svcname=rda->airplayConf()->defaultSvc();
|
||||
for(int i=0;i<RDAIRPLAY_LOG_QUANTITY;i++) {
|
||||
air_log[i]=new RDLogPlay(i,rdevent_player,air_nownext_socket,"",
|
||||
&air_plugin_hosts);
|
||||
air_log[i]=
|
||||
new RDLogPlay(i,rdevent_player,air_nownext_socket,&air_plugin_hosts);
|
||||
air_log[i]->setDefaultServiceName(default_svcname);
|
||||
air_log[i]->setNowCart(rda->airplayConf()->logNowCart(i));
|
||||
air_log[i]->setNextCart(rda->airplayConf()->logNextCart(i));
|
||||
@ -813,6 +823,7 @@ MainWidget::MainWidget(QWidget *parent)
|
||||
}
|
||||
delete q;
|
||||
|
||||
//
|
||||
// Create the HotKeyList object
|
||||
//
|
||||
rdkeylist=new RDHotKeyList();
|
||||
|
@ -52,6 +52,7 @@
|
||||
#include <rdsound_panel.h>
|
||||
#include <rdstereometer.h>
|
||||
#include <rdttydevice.h>
|
||||
#include <rdunixsocket.h>
|
||||
#include <rduser.h>
|
||||
|
||||
#include "button_log.h"
|
||||
|
48
rdrlmd/Makefile.am
Normal file
48
rdrlmd/Makefile.am
Normal file
@ -0,0 +1,48 @@
|
||||
## Makefile.am
|
||||
##
|
||||
## Rivendell RLM Consolidation Server
|
||||
##
|
||||
## (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.
|
||||
##
|
||||
##
|
||||
## Use automake to process this into a Makefile.in
|
||||
|
||||
AM_CPPFLAGS = -Wall -DPREFIX=\"$(prefix)\" -I$(top_srcdir)/lib @QT4_CFLAGS@ -I/usr/include/Qt3Support
|
||||
LIBS = -L$(top_srcdir)/lib -L$(top_srcdir)/rdhpi
|
||||
MOC = @QT_MOC@
|
||||
|
||||
# The dependency for qt's Meta Object Compiler (moc)
|
||||
moc_%.cpp: %.h
|
||||
$(MOC) $< -o $@
|
||||
|
||||
|
||||
sbin_PROGRAMS = rdrlmd
|
||||
|
||||
dist_rdrlmd_SOURCES = rdrlmd.cpp rdrlmd.h
|
||||
|
||||
nodist_rdrlmd_SOURCES = moc_rdrlmd.cpp
|
||||
|
||||
rdrlmd_LDADD = @LIB_RDLIBS@ @LIBVORBIS@ @QT4_LIBS@ -lQt3Support
|
||||
|
||||
CLEANFILES = *~\
|
||||
*.idb\
|
||||
*ilk\
|
||||
*.obj\
|
||||
*.pdb\
|
||||
moc_*
|
||||
|
||||
MAINTAINERCLEANFILES = *~\
|
||||
Makefile.in
|
207
rdrlmd/rdrlmd.cpp
Normal file
207
rdrlmd/rdrlmd.cpp
Normal file
@ -0,0 +1,207 @@
|
||||
// rdrlmd.cpp
|
||||
//
|
||||
// Rivendell RLM Consolidation Server
|
||||
//
|
||||
// (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.
|
||||
//
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <qcoreapplication.h>
|
||||
#include <qhostaddress.h>
|
||||
|
||||
#include <rd.h>
|
||||
#include <rdcmd_switch.h>
|
||||
|
||||
#include "rdrlmd.h"
|
||||
|
||||
MetadataSource::MetadataSource(QTcpSocket *sock)
|
||||
{
|
||||
meta_socket=sock;
|
||||
meta_committed=true;
|
||||
}
|
||||
|
||||
|
||||
QByteArray MetadataSource::buffer() const
|
||||
{
|
||||
return meta_buffer;
|
||||
}
|
||||
|
||||
|
||||
bool MetadataSource::appendBuffer(const QByteArray &data)
|
||||
{
|
||||
printf("data: %s\n",(const char *)data);
|
||||
|
||||
if(meta_committed) {
|
||||
meta_buffer.clear();
|
||||
}
|
||||
meta_buffer+=data;
|
||||
meta_committed=meta_buffer.endsWith("\r\n\r\n");
|
||||
|
||||
return meta_committed;
|
||||
}
|
||||
|
||||
|
||||
bool MetadataSource::isCommitted() const
|
||||
{
|
||||
return meta_committed;
|
||||
}
|
||||
|
||||
|
||||
QTcpSocket *MetadataSource::socket() const
|
||||
{
|
||||
return meta_socket;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
MainObject::MainObject(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
new RDCmdSwitch(qApp->argc(),qApp->argv(),"rdrlmd",RDRLMD_USAGE);
|
||||
|
||||
//
|
||||
// Client Server
|
||||
//
|
||||
rlm_client_disconnect_mapper=new QSignalMapper(this);
|
||||
connect(rlm_client_disconnect_mapper,SIGNAL(mapped(int)),
|
||||
this,SLOT(clientDisconnected(int)));
|
||||
|
||||
rlm_client_server=new QTcpServer(this);
|
||||
connect(rlm_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",
|
||||
RD_RLM2_CLIENT_TCP_PORT);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
//
|
||||
// Source Server
|
||||
//
|
||||
rlm_source_ready_mapper=new QSignalMapper(this);
|
||||
connect(rlm_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)),
|
||||
this,SLOT(sourceDisconnected(int)));
|
||||
|
||||
rlm_source_server=new RDUnixServer(this);
|
||||
connect(rlm_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());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
|
||||
SendState(sock->socketDescriptor());
|
||||
// printf("client connection %d opened\n",sock->socketDescriptor());
|
||||
}
|
||||
|
||||
|
||||
void MainObject::clientDisconnected(int id)
|
||||
{
|
||||
QTcpSocket *sock=NULL;
|
||||
|
||||
if((sock=rlm_client_sockets.value(id))!=NULL) {
|
||||
sock->deleteLater();
|
||||
rlm_client_sockets.remove(id);
|
||||
// printf("client connection %d closed\n",id);
|
||||
}
|
||||
else {
|
||||
fprintf(stderr,"unknown client connection %d attempted to close\n",id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void MainObject::newSourceConnectionData()
|
||||
{
|
||||
QTcpSocket *sock=rlm_source_server->nextPendingConnection();
|
||||
if(sock==NULL) {
|
||||
fprintf(stderr,"rdrlmd: UNIX socket error [%s]\n",
|
||||
(const char *)rlm_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(disconnected()),rlm_source_disconnect_mapper,SLOT(map()));
|
||||
rlm_source_disconnect_mapper->setMapping(sock,sock->socketDescriptor());
|
||||
|
||||
rlm_sources[sock->socketDescriptor()]=new MetadataSource(sock);
|
||||
|
||||
// printf("source connection %d opened\n",sock->socketDescriptor());
|
||||
}
|
||||
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
// printf("source connection %d closed\n",id);
|
||||
}
|
||||
else {
|
||||
fprintf(stderr,"unknown source connection %d attempted to close\n",id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void MainObject::SendState(int id)
|
||||
{
|
||||
for(QMap<int,MetadataSource *>::const_iterator it=rlm_sources.begin();
|
||||
it!=rlm_sources.end();it++) {
|
||||
if(it.value()->isCommitted()) {
|
||||
rlm_client_sockets.value(id)->write(it.value()->buffer());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main(int argc,char *argv[])
|
||||
{
|
||||
QCoreApplication a(argc,argv);
|
||||
|
||||
new MainObject();
|
||||
return a.exec();
|
||||
}
|
78
rdrlmd/rdrlmd.h
Normal file
78
rdrlmd/rdrlmd.h
Normal file
@ -0,0 +1,78 @@
|
||||
// rdrlmd.h
|
||||
//
|
||||
// Rivendell RLM Consolidation Server
|
||||
//
|
||||
// (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.
|
||||
//
|
||||
|
||||
#ifndef RDRLMD_H
|
||||
#define RDRLMD_H
|
||||
|
||||
#include <qmap.h>
|
||||
#include <qobject.h>
|
||||
#include <qsignalmapper.h>
|
||||
#include <qtcpserver.h>
|
||||
#include <qtcpsocket.h>
|
||||
|
||||
#include <rdunixserver.h>
|
||||
|
||||
#define RDRLMD_USAGE "\n\n"
|
||||
|
||||
class MetadataSource
|
||||
{
|
||||
public:
|
||||
MetadataSource(QTcpSocket *sock);
|
||||
QByteArray buffer() const;
|
||||
bool appendBuffer(const QByteArray &data);
|
||||
bool isCommitted() const;
|
||||
QTcpSocket *socket() const;
|
||||
|
||||
private:
|
||||
QByteArray meta_buffer;
|
||||
bool meta_committed;
|
||||
QTcpSocket *meta_socket;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
class MainObject : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
MainObject(QObject *parent=0);
|
||||
|
||||
private slots:
|
||||
void newClientConnectionData();
|
||||
void clientDisconnected(int id);
|
||||
void newSourceConnectionData();
|
||||
void sourceReadyReadData(int id);
|
||||
void sourceDisconnected(int id);
|
||||
|
||||
private:
|
||||
void SendState(int id);
|
||||
QSignalMapper *rlm_client_disconnect_mapper;
|
||||
QTcpServer *rlm_client_server;
|
||||
QMap<int,QTcpSocket *> rlm_client_sockets;
|
||||
|
||||
QSignalMapper *rlm_source_ready_mapper;
|
||||
QSignalMapper *rlm_source_disconnect_mapper;
|
||||
RDUnixServer *rlm_source_server;
|
||||
QMap<int,MetadataSource *> rlm_sources;
|
||||
};
|
||||
|
||||
|
||||
#endif // RDRLMD_H
|
@ -31,12 +31,13 @@
|
||||
#define RDSERVICE_CAED_ID 0
|
||||
#define RDSERVICE_RIPCD_ID 1
|
||||
#define RDSERVICE_RDCATCHD_ID 2
|
||||
#define RDSERVICE_RDVAIRPLAYD_ID 3
|
||||
#define RDSERVICE_RDREPLD_ID 4
|
||||
#define RDSERVICE_LOCALMAINT_ID 5
|
||||
#define RDSERVICE_SYSTEMMAINT_ID 6
|
||||
#define RDSERVICE_PURGECASTS_ID 7
|
||||
#define RDSERVICE_LAST_ID 8
|
||||
#define RDSERVICE_RDRLMD_ID 3
|
||||
#define RDSERVICE_RDVAIRPLAYD_ID 4
|
||||
#define RDSERVICE_RDREPLD_ID 5
|
||||
#define RDSERVICE_LOCALMAINT_ID 6
|
||||
#define RDSERVICE_SYSTEMMAINT_ID 7
|
||||
#define RDSERVICE_PURGECASTS_ID 8
|
||||
#define RDSERVICE_LAST_ID 9
|
||||
#define RDSERVICE_FIRST_DROPBOX_ID 100
|
||||
|
||||
class MainObject : public QObject
|
||||
|
@ -41,6 +41,7 @@ bool MainObject::Startup(QString *err_msg)
|
||||
//
|
||||
KillProgram("rdrepld");
|
||||
KillProgram("rdvairplayd");
|
||||
KillProgram("rdrlmd");
|
||||
KillProgram("rdcatchd");
|
||||
KillProgram("ripcd");
|
||||
KillProgram("caed");
|
||||
@ -83,6 +84,19 @@ bool MainObject::Startup(QString *err_msg)
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// rdrlmd(8)
|
||||
//
|
||||
svc_processes[RDSERVICE_RDRLMD_ID]=new Process(RDSERVICE_RDRLMD_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();
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// rdvairplayd(8)
|
||||
//
|
||||
|
@ -123,7 +123,7 @@ MainObject::MainObject(QObject *parent)
|
||||
QString default_svcname=rda->airplayConf()->defaultSvc();
|
||||
for(int i=0;i<RD_RDVAIRPLAY_LOG_QUAN;i++) {
|
||||
air_logs[i]=new RDLogPlay(i+RD_RDVAIRPLAY_LOG_BASE,air_event_player,
|
||||
air_nownext_socket,"",&air_plugin_hosts);
|
||||
air_nownext_socket,&air_plugin_hosts);
|
||||
air_logs[i]->setDefaultServiceName(default_svcname);
|
||||
//
|
||||
// FIXME: Add the ability to specify default carts for vLogs!
|
||||
@ -134,12 +134,6 @@ MainObject::MainObject(QObject *parent)
|
||||
connect(air_logs[i],SIGNAL(reloaded()),reload_mapper,SLOT(map()));
|
||||
rename_mapper->setMapping(air_logs[i],i);
|
||||
connect(air_logs[i],SIGNAL(renamed()),rename_mapper,SLOT(map()));
|
||||
// connect(air_logs[i],SIGNAL(refreshStatusChanged(bool)),
|
||||
// this,SLOT(refreshStatusChangedData(bool)));
|
||||
// connect(air_logs[i],SIGNAL(channelStarted(int,int,int,int)),
|
||||
// this,SLOT(logChannelStartedData(int,int,int,int)));
|
||||
// connect(air_logs[i],SIGNAL(channelStopped(int,int,int,int)),
|
||||
// this,SLOT(logChannelStoppedData(int,int,int,int)));
|
||||
int cards[2]={0,0};
|
||||
cards[0]=rda->airplayConf()->virtualCard(i+RD_RDVAIRPLAY_LOG_BASE);
|
||||
cards[1]=rda->airplayConf()->virtualCard(i+RD_RDVAIRPLAY_LOG_BASE);
|
||||
|
@ -388,6 +388,7 @@ rm -rf $RPM_BUILD_ROOT
|
||||
@LOCAL_PREFIX@/sbin/rdcatchd
|
||||
@LOCAL_PREFIX@/sbin/rdvairplayd
|
||||
@LOCAL_PREFIX@/sbin/rdrepld
|
||||
@LOCAL_PREFIX@/sbin/rdrlmd
|
||||
@LOCAL_PREFIX@/sbin/sas_shim
|
||||
@LOCAL_PREFIX@/sbin/rdmarkerset
|
||||
@LOCAL_PREFIX@/sbin/rdcleandirs
|
||||
|
Loading…
x
Reference in New Issue
Block a user