From 56feb0889697dc275e11ac1c4584a9cdd40189dd Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Sun, 11 Aug 2019 17:44:44 -0400 Subject: [PATCH 01/35] 2019-08-11 Fred Gleason * Fixed a bug in the 'pypad.update.shouldBeProcessed()' method that caused log selection directives to be ignored. * Refactored the 'pypad_icecast2.py' script to work properly with the fixed 'pypad.update.shouldBeProcessed()' method. * Refactored the 'pypad_shoutcast2.py' script to work properly with the fixed 'pypad.update.shouldBeProcessed()' method. --- ChangeLog | 7 ++++++ apis/pypad/api/pypad.py | 35 +++++++++++++++++--------- apis/pypad/scripts/pypad_icecast2.py | 28 +++++++++++++++------ apis/pypad/scripts/pypad_shoutcast1.py | 26 +++++++++---------- 4 files changed, 63 insertions(+), 33 deletions(-) diff --git a/ChangeLog b/ChangeLog index bef29da2..95606aac 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18918,3 +18918,10 @@ 2019-08-09 Fred Gleason * Refactored 'RDClock' to store events on the heap rather than on the stack. +2019-08-11 Fred Gleason + * Fixed a bug in the 'pypad.update.shouldBeProcessed()' method + that caused log selection directives to be ignored. + * Refactored the 'pypad_icecast2.py' script to work properly with + the fixed 'pypad.update.shouldBeProcessed()' method. + * Refactored the 'pypad_shoutcast2.py' script to work properly with + the fixed 'pypad.update.shouldBeProcessed()' method. diff --git a/apis/pypad/api/pypad.py b/apis/pypad/api/pypad.py index 220e1175..a635d2d3 100644 --- a/apis/pypad/api/pypad.py +++ b/apis/pypad/api/pypad.py @@ -84,7 +84,6 @@ PAD_TCP_PORT=34289 class Update(object): def __init__(self,pad_data,config,rd_config): self.__fields=pad_data - #print('PAD: '+str(self.__fields)) self.__config=config self.__rd_config=rd_config @@ -726,17 +725,20 @@ class Update(object): section - The '[
]' of the INI configuration from which to take the parameters. """ + result=True try: if self.__config.get(section,'ProcessNullUpdates')=='0': - return True + result=result and True if self.__config.get(section,'ProcessNullUpdates')=='1': - return self.hasPadType(pypad.TYPE_NOW) + result=result and self.hasPadType(pypad.TYPE_NOW) if self.__config.get(section,'ProcessNullUpdates')=='2': - return self.hasPadType(pypad.TYPE_NEXT) + result=result and self.hasPadType(pypad.TYPE_NEXT) if self.__config.get(section,'ProcessNullUpdates')=='3': - return self.hasPadType(pypad.TYPE_NOW) and self.hasPadType(pypad.TYPE_NEXT) + result=result and self.hasPadType(pypad.TYPE_NOW) and self.hasPadType(pypad.TYPE_NEXT) except configparser.NoOptionError: - return True + result=result and True + except configparser.NoSectionError: + result=result and True log_dict={1: 'MasterLog',2: 'Aux1Log',3: 'Aux2Log', 101: 'VLog101',102: 'VLog102',103: 'VLog103',104: 'VLog104', @@ -744,12 +746,21 @@ class Update(object): 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 self.__config.get(section,log_dict[self.machine()]).lower()=='yes': - return True - if self.__config.get(section,log_dict[self.machine()]).lower()=='no': - return False - if self.__config.get(section,log_dict[self.machine()]).lower()=='onair': - return self.onairFlag() + try: + #print('machine(): '+str(self.machine())) + if self.__config.get(section,log_dict[self.machine()]).lower()=='yes': + result=result and True + if self.__config.get(section,log_dict[self.machine()]).lower()=='no': + result=result and False + if self.__config.get(section,log_dict[self.machine()]).lower()=='onair': + result=result and self.onairFlag() + except configparser.NoOptionError: + result=result and False + except configparser.NoSectionError: + result=result and False + #print('result: '+str(result)) + return result + def syslog(self,priority,msg): """ diff --git a/apis/pypad/scripts/pypad_icecast2.py b/apis/pypad/scripts/pypad_icecast2.py index 095436c6..390ed6d9 100755 --- a/apis/pypad/scripts/pypad_icecast2.py +++ b/apis/pypad/scripts/pypad_icecast2.py @@ -34,25 +34,37 @@ def ProcessPad(update): if update.hasPadType(pypad.TYPE_NOW): n=1 while(True): + # + # First, get all of our configuration values + # section='Icecast'+str(n) try: values={} values['mount']=update.config().get(section,'Mountpoint') values['song']=update.resolvePadFields(update.config().get(section,'FormatString'),pypad.ESCAPE_NONE) values['mode']='updinfo' - update.syslog(syslog.LOG_INFO,'Updating '+update.config().get(section,'Hostname')+': song='+values['song']) - url="http://%s:%s/admin/metadata" % (update.config().get(section,'Hostname'),update.config().get(section,'Tcpport')) - try: - response=requests.get(url,auth=HTTPBasicAuth(update.config().get(section,'Username'),update.config().get(section,'Password')),params=values) - response.raise_for_status() - except requests.exceptions.RequestException as e: - update.syslog(syslog.LOG_WARNING,str(e)) - n=n+1 + hostname=update.config().get(section,'Hostname') + tcpport=update.config().get(section,'Tcpport') + username=update.config().get(section,'Username') + password=update.config().get(section,'Password') + url="http://%s:%s/admin/metadata" % (hostname,tcpport) except configparser.NoSectionError: if(n==1): update.syslog(syslog.LOG_WARNING,'No icecast config found') return + # + # Now, send the update + # + if update.shouldBeProcessed(section): + try: + response=requests.get(url,auth=HTTPBasicAuth(username,password),params=values) + response.raise_for_status() + update.syslog(syslog.LOG_INFO,'Updating '+hostname+': song='+values['song']) + except requests.exceptions.RequestException as e: + update.syslog(syslog.LOG_WARNING,str(e)) + n=n+1 + # # Program Name # diff --git a/apis/pypad/scripts/pypad_shoutcast1.py b/apis/pypad/scripts/pypad_shoutcast1.py index fa434d74..5d9fb1ba 100755 --- a/apis/pypad/scripts/pypad_shoutcast1.py +++ b/apis/pypad/scripts/pypad_shoutcast1.py @@ -27,23 +27,18 @@ import pycurl import pypad from io import BytesIO -last_updates={} - def eprint(*args,**kwargs): print(*args,file=sys.stderr,**kwargs) def ProcessPad(update): - try: - last_updates[update.machine()] - except KeyError: - last_updates[update.machine()]=None - - n=1 - try: + if update.hasPadType(pypad.TYPE_NOW): + n=1 while(True): + # + # First, get all of our configuration values + # section='Shoutcast'+str(n) - if update.shouldBeProcessed(section) and update.hasPadType(pypad.TYPE_NOW) and (last_updates[update.machine()] != update.startDateTimeString(pypad.TYPE_NOW)): - last_updates[update.machine()]=update.startDateTimeString(pypad.TYPE_NOW) + try: song=update.resolvePadFields(update.config().get(section,'FormatString'),pypad.ESCAPE_URL) url='http://'+update.config().get(section,'Hostname')+':'+str(update.config().get(section,'Tcpport'))+'/admin.cgi?pass='+update.escape(update.config().get(section,'Password'),pypad.ESCAPE_URL)+'&mode=updinfo&song='+song curl=pycurl.Curl() @@ -55,6 +50,13 @@ def ProcessPad(update): # headers.append('User-Agent: '+'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2) Gecko/20070219 Firefox/2.0.0.2') curl.setopt(curl.HTTPHEADER,headers); + except configparser.NoSectionError: + return + + # + # Now, send the update + # + if update.shouldBeProcessed(section): try: curl.perform() code=curl.getinfo(pycurl.RESPONSE_CODE) @@ -65,8 +67,6 @@ def ProcessPad(update): curl.close() n=n+1 - except configparser.NoSectionError: - return # # 'Main' function From 1b42a3ec36dd610e9afcfb2e656f9d279ed6350b Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Wed, 14 Aug 2019 11:51:14 -0400 Subject: [PATCH 02/35] 2019-08-14 Fred Gleason * Refactored the 'pypad.Update.shouldBeProcessed()' method to be LBYL. --- ChangeLog | 2 ++ apis/pypad/api/pypad.py | 60 ++++++++++++++++++++++------------------- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/ChangeLog b/ChangeLog index 95606aac..e340dc41 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18925,3 +18925,5 @@ the fixed 'pypad.update.shouldBeProcessed()' method. * Refactored the 'pypad_shoutcast2.py' script to work properly with the fixed 'pypad.update.shouldBeProcessed()' method. +2019-08-14 Fred Gleason + * Refactored the 'pypad.Update.shouldBeProcessed()' method to be LBYL. diff --git a/apis/pypad/api/pypad.py b/apis/pypad/api/pypad.py index a635d2d3..b1684423 100644 --- a/apis/pypad/api/pypad.py +++ b/apis/pypad/api/pypad.py @@ -726,38 +726,42 @@ class Update(object): to take the parameters. """ result=True - try: - if self.__config.get(section,'ProcessNullUpdates')=='0': + if self.__config.has_section(section): + if self.__config.has_option(section,'ProcessNullUpdates'): + if self.__config.get(section,'ProcessNullUpdates')=='0': + result=result and True + if self.__config.get(section,'ProcessNullUpdates')=='1': + result=result and self.hasPadType(pypad.TYPE_NOW) + if self.__config.get(section,'ProcessNullUpdates')=='2': + result=result and self.hasPadType(pypad.TYPE_NEXT) + if self.__config.get(section,'ProcessNullUpdates')=='3': + result=result and self.hasPadType(pypad.TYPE_NOW) and self.hasPadType(pypad.TYPE_NEXT) + else: result=result and True - if self.__config.get(section,'ProcessNullUpdates')=='1': - result=result and self.hasPadType(pypad.TYPE_NOW) - if self.__config.get(section,'ProcessNullUpdates')=='2': - result=result and self.hasPadType(pypad.TYPE_NEXT) - if self.__config.get(section,'ProcessNullUpdates')=='3': - result=result and self.hasPadType(pypad.TYPE_NOW) and self.hasPadType(pypad.TYPE_NEXT) - except configparser.NoOptionError: - result=result and True - except configparser.NoSectionError: - result=result and 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'} - try: - #print('machine(): '+str(self.machine())) - if self.__config.get(section,log_dict[self.machine()]).lower()=='yes': - result=result and True - if self.__config.get(section,log_dict[self.machine()]).lower()=='no': + 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'} + option=log_dict[self.machine()] + if self.__config.has_option(section,option): + if self.__config.get(section,option).lower()=='yes': + result=result and True + else: + if self.__config.get(section,option).lower()=='no': + result=result and False + else: + if self.__config.get(section,option).lower()=='onair': + result=result and self.onairFlag() + else: + result=result and False + else: result=result and False - if self.__config.get(section,log_dict[self.machine()]).lower()=='onair': - result=result and self.onairFlag() - except configparser.NoOptionError: - result=result and False - except configparser.NoSectionError: + else: result=result and False + #print('machine(): '+str(self.machine())) #print('result: '+str(result)) return result From b9722a3be71baeafcda134f2705f3bd16a370994 Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Tue, 20 Aug 2019 18:50:56 -0400 Subject: [PATCH 03/35] 2019-08-20 Fred Gleason * Added a 'DROPBOXES.LOG_TO_SYSLOG' field to the database. * Incremented the database version to 309. * Added a 'Log Events in Syslog' checkbox to the 'Dropbox Configuration' dialog in rdadmin(1). * Added special name logic to RDApplication to detect when a dropbox is being started. * Modified the '--log-filename=' switch in rdimport(1) to accept both directory and filename components. * Removed the '--log-directory=' switch from rdimport(1). * Added an 'ID' column to the list of dropbox configurations in the 'Rivendell Dropbox Configurations; dialog in rdadmin(1). --- ChangeLog | 12 + docs/manpages/rdimport.xml | 64 +-- .../rdadmin.dropbox_configuration_dialog.png | Bin 57718 -> 64110 bytes ...ivendell_dropbox_configurations_dialog.png | Bin 19320 -> 22127 bytes docs/opsguide/rdadmin.xml | 13 + docs/tables/dropboxes.txt | 1 + lib/dbversion.h | 2 +- lib/rdapplication.cpp | 9 + lib/rddropbox.cpp | 13 + lib/rddropbox.h | 2 + rdadmin/edit_dropbox.cpp | 162 ++++--- rdadmin/edit_dropbox.h | 3 + rdadmin/list_dropboxes.cpp | 65 +-- rdadmin/rdadmin_cs.ts | 8 + rdadmin/rdadmin_de.ts | 8 + rdadmin/rdadmin_es.ts | 8 + rdadmin/rdadmin_fr.ts | 8 + rdadmin/rdadmin_nb.ts | 8 + rdadmin/rdadmin_nn.ts | 8 + rdadmin/rdadmin_pt_BR.ts | 8 + rdservice/startup.cpp | 65 +-- utils/rddbmgr/revertschema.cpp | 13 + utils/rddbmgr/schemamap.cpp | 1 + utils/rddbmgr/updateschema.cpp | 16 + utils/rdimport/rdimport.cpp | 454 +++++++++--------- utils/rdimport/rdimport.h | 4 +- 26 files changed, 526 insertions(+), 429 deletions(-) diff --git a/ChangeLog b/ChangeLog index 31f03a48..1a8fd5b9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18920,3 +18920,15 @@ on the stack. 2019-08-12 Fred Gleason * Added 'py-compile' to the 'CLEANFILES' rule in 'Makefile.am'. +2019-08-20 Fred Gleason + * Added a 'DROPBOXES.LOG_TO_SYSLOG' field to the database. + * Incremented the database version to 309. + * Added a 'Log Events in Syslog' checkbox to the + 'Dropbox Configuration' dialog in rdadmin(1). + * Added special name logic to RDApplication to detect when a + dropbox is being started. + * Modified the '--log-filename=' switch in rdimport(1) to accept + both directory and filename components. + * Removed the '--log-directory=' switch from rdimport(1). + * Added an 'ID' column to the list of dropbox configurations in the + 'Rivendell Dropbox Configurations; dialog in rdadmin(1). diff --git a/docs/manpages/rdimport.xml b/docs/manpages/rdimport.xml index dde9d775..1e7ba9b4 100644 --- a/docs/manpages/rdimport.xml +++ b/docs/manpages/rdimport.xml @@ -222,21 +222,6 @@ - - - directory - - - - The directory to write logs to. - Overrides the [Logs] section of - rd.conf5. - The option must also be specified. - This option is mutually exclusive with the option. - - - - filename @@ -244,52 +229,15 @@ The filename to write logs to. - Overrides the [Logs] section of - rd.conf5. - The option must also be specified. - This option is mutually exclusive with the option. + This option is mutually exclusive with the + option. - The following wildcards can be used can be used in filename: + Rivendell "Filepath" wildcards can be used in + filename. See the + rivendell-wildcards7 + man page for details. - - - %d - - The day of the month (01 - 31) - - - - %h - - The hour (00 - 23) - - - - %M - - The month (01 - 12) - - - - %n - - The name of the originating module --e.g. 'rdairplay', 'caed'. - - - - %s - - The second (00 - 60) - - - - %Y - - The four digit year - - - diff --git a/docs/opsguide/rdadmin.dropbox_configuration_dialog.png b/docs/opsguide/rdadmin.dropbox_configuration_dialog.png index 251ccf1cf1e1d426a1e1514c496b0ae11e2ab7ec..1d527cafbd9559355558aa68f05e2c454273d55c 100644 GIT binary patch literal 64110 zcmb@uWn5Klw>P>_K^m2mM!LI{MnOVKX$k2R0qGJD5a|v{0Rcgf5Tr}GTTnt$y1UL; z_r3Qyd!P6D`1(P9ELd~RdBqt2x(HTLlEuL!#Y7+wIC764sv!_49SFp=Y;-hu<*iS# z27I~UC?)p{9sYTuo4i9Hs1b4xC7-#btf#tY5y+A?`rIT}mw11xFwd|xn#sy2f?b9? z-ZS~(v-OhJhYw3$G$rR*j1XMBV`3n^U<+m#V>*0C${%mhNXo{i>Rxwo+4$?#EApFu z?|Qb}mVBpD-Ih})>b#aylTuY9vFz;Zn2&^gC8%$q>pR7>>wfBRW218Q@K88d8_&|K zaTXF5u5<0Zam`mk#S8-rtG%%?qkNQ!$LGq+e{I|@G9{&Ft~G>kyGOzu-b3-gIUC-S zQ;>j@^pTX&!lD+MZF$yZ;M}oUQUbTL*|!wQk&h` zkG21d4@YTf>F&N%yt<;IVkBKDj>#s^#^w23yRnEI-`lC)1BxG$z;m{XcGkBAB5$ea~P1_p-P#787#WV$TRz7Cw}ct4JQJ0@HegU2~0B=C%+ zBzxWjq3=?tOsF8cDN+}4FX-;;&$Eqd;W%$>=7r|}It=j)cM3ljzBo{IXJ+U~|K_`P z@~8dF{>o@f@O(R=cHgh9eTf{kl8qokmWf})S%I8X#SYrWRN`iTqH~_tyT|SCzdqex z?d(fGGPE;qzY?%xu|0Ubdv8-=Jgx4raz7$N@55Nn-Usb_Cj>YD2-@)&^|+q6_T=5h zc5N_Vwt6O1+D?s?z`(P6ThMO$T88t6j?n#jjqKW(=sQ&oi!5Pef^)lz-6MtCA@Xr7 zD7PpmdQ%^`ymGYeNcYii>-|DQL`1kB$zpj#-BA7g(Y#iU8h6HJP3?|O1vr>fyT%&Yem`it5J8sDX`zqfoMOO&(DNqBW)af`d81VKVVVrXhA zXDle#<|Tp|WG+Z&PHt>W%cApZ;H%_yv14WN)$i379WivlctP0oJUp0hGMr}AvfTM8 zNKM9bgM*pP%$DdF8AUbS3cs*^RAExdmp5~FezE#7n|Y;lX{o1Al@6EYa>OT2_9(HjvAB^?Aw~ zLHWpGxla!bo8*mDrs1y&+c$pW`i^X`r|s?SzgO{Rd!6C(+5BybXa8WTxB0uOSacV zg2(E_Xms&Sg9{8^+NYs7p?nJqeYtFC{8-Dqe~yf4$}KkgO7I!A4@N7Q2+-W-$fqkK z|v{;pB>f*P;f7+mjmK8HAzG-@$Wl(Bn&qM#jo-nAMY67xpQY>9+Qy8(B^#0=3>rO zCilq>&HfMW>8)Qk5l^4;_Vn~DuE|P;b6XdQS5yeB|E`V;$!u_3S-(rlXYoerg@l5Y=r;N+nlUAu7*pl*7ZrA8l<8i(RJhTl>)%CyGOZZ53;0uDNEq zhtv1Fe(_IdTlWf#eAbF9@8Lrm+iF^B@cwgjM!d2z6!pbtqgz(K=_D4`^^m=_O8WI6 z+#ThIH;9Oc=;(e}|4?Ph$noHn!K3OLA~Yy{-I`!?=1pr=d@=B+1u;^fo3JMMa(nIa zB9vtHk-R();>909?P?=>LA$BG+yM5S!v~^#eN1sOKPM*DGX3a}wq|6n{rW=ItZ_9 zd(U*FXt8J9poor#<^3P3h?arIH%A|K7A@YS<dTtBc|F);_Xgs_dqR8bpOGb5GP8gG zHb&mgI$^wb?afNrGJ?zD?=3O%0r7rbt)Cux$L7!OZ9RCrr!~&I+F*ep8%{YtAJwul zQ*2jX1s@lgoSe)^B^P~sShH1X+M5*iEsjt*?FZqku=ViCA{{NQe<9n3fzSfo{rkzf zl`rR4R)VwU8>T;IWzkZ3AK(5UV2X$HllbGWsgaRbQvMVST->D=JnUZ2%vHa)wHI^?XZNS8UE-^kGWB|t(-x)e++H=&2O*_(`V z3m?B_%I97rT`&~3a7uBE#sy_YBU8)HmY|654jZJ=bWzF4UFF(k7!7VAqpa~|*1saO zv~`wZi=t*^sjz1|F1-?yl6RPRFcrEn&rgRrev|Ue9(Tr&i@7V-<5}6**r+c2C0-dV zyV>sZ;33razPD+MjhQX|PFwq5u=wp<{x$};1RdMX6eoVrofUPuPtzXG>#~IH{kuHs zW&|a-+vMwen=|8&$M>c-$*(qUQT(c$PpCUP@!pGY>q<6ADKF)E9* z57y5u1r68?crROxzaB#C*QSs`f{d-LQYODJ0~YoEx)6qgPMjW zZDizGU3dJ9sP^T_y=j|?J8zQepH{s-#FkteyEZX(B_L3-%S%Ay9TV4GZ@4^0RpNPw zos^tx^x{QkR6=TMoo6uuDh`*RVE+;2#Lu6*4ZR<}zgps3R!%oL9bqOHcZ=@7eoci5 z8(rrJ=U{CV)o^sz^!a^{z#ZduBP**CL%6g1n1|LJ`4w5GNbmrz3g24{KIEe znpa9&;;s@VCIs?1(HQAZK0384d2)4xP}F#L#c2!;JS#}{F)km9*iMvsZudM zKOD5;?f7@3?xt)6Rjzsnk>_FOjiKS;y)ZlDr>d7%E5o@YcbQK__t}DjgMUs=4y=cb zyOpV9Ar3di->Bs;te;?sgom``IJ4_CoG?@f2$b9WNwcH$azAM9=^>Dny@_yl-U`n# zMNP3biLey&B!J80_c?g+86p@M(i3xx(Wh^=I>_nLqFNc{Pe7y>gj!kOSe`M@6WEw6Z<_84)YydbgemH5+~F2MDc(m}<8dw&&!{}-nq_IM z7#ddfYDd}a&uYp6KGjuhVxmP=(8A2AkS^PS>T?SZ8S^I}r2YmQWABh_(IqLTnTZqM zy?eL(wTt=8W;)EC*@2+7uopwQ);`A*=o+0J9c)}CrlwKx@ojY;N9dTC(Ze@UQFr>q zuOg$OeC6YE5(~I^_Cw--J zsQKwVt;u#N~-m$=i1ru$V zczOEV&bzoloBPx4B|(h~v-jX1=su zx3#r>EGHMW#>gN*L`!R$R%#iNr7RXt$TCo8t*M=skjuETvI2v#z+Cpt$$7GT|Mi_a z_esD|)G=E2lfJj|&tTTAVLaM?u`?1>w9MIH&U0k%-JeG%_oH2${{H><3k%uTrt#g@ zl=Udzyws~UG_!i|Q6`8uhP~3(*496?Jete)c7Gqcw3G)6`_|p=EeRnS4+c$nyRn> zW3tUD7aH)Cr#Wu&QwdTuhLyU%py zd0@+=HcaAsp9{&Jq=z{P3kzGWiyj@@|1P_0HNk1>@!&zMd`=+lN$VhEdQil@iF!w3 zhW@r!4d_~uo}RU1)s_oO3?9YpN+}Y*-ljCiJ{cDs)MyYcDf@PPwljwDYH3ev@9w?! zk5t^q3{Blva&KDcxfpqzcjrYbh)n&5RfUXQ^*oR_+A+I_PM z{t|`*bkP3r-0d>>9rgaYHv56L*8Ak8nSo3_vhXWsiLn}>HQ(C0BmOEi-d)`k_hx^!mxmBXe=F{6-vW~wl05q*sg@zK9WPoJb) zX;;Q)hzida!17_!RB*$gGA%Lfy`UhL)QdJ&X z9GS~~Yq14l=~INL3=D0LSqQH#HXpd5WstHK*i0NziBLC~XFb|mYJEmk?NdD2e-Tn- z{`zvRe`Tn}-}Zs?LQKM@XILoq%6JVi;tZFZT8zebM!Mg~(6F&)s(Pn*nR!u8SK!(N z&PS;m#>U1kr>a}O=vH!Zb2GnocX}5Qkt-MT3EJ%)Lc-QS9LZ+|8evwWpG#jz--68u zuMcI*J)N7MKZwS7UyFshu;GWm2u96qc%jpw7LIeChAGdh#>?NX&K5`XJRG;h_^e%v zskfZ;v2AHL(O@3}Aw}Im+uQ4MnU7Mx|7=06(`f^ODmwySrB_*&=Bg&fQOr@brJ- zRk#jG$ji&8F|W8Wf9Kd;W8o7JSlUCqv^l=KX?E5RbU8qWKUKT5kt$*GTd>!d7y8%b z@w)XBC(fm%CD(n|gpw@T&Z%_NR%WL3Q3IdeMb>eP!CGN2kA@(RvTC=W+@EEpZZ`f$ zL|k0M>jKA^(K~sIcYosTP9e_o{_OmEw@q2^{vFv*d$lZ6^lGLX=Jz8sN z>5%oT7kSgFkWgZ{X;Wpb8WtRUd8FgwwV>=hl^)N5MhW*7J6SYocqXp`VA&;=!<_|voXcc8}Auc$q_7PTfdBTFZ z!)@|K=iAS(H0TIoVogypDYugH@`VWTONnSY4*lB4qo39DwMyoA>z>^}LxUB7&++`b zMC$$1MOwK#n4k%Ewwsr>1yp|!EoXS#t2n>xZ7^J4{Xp7#XEEV}EVqOt=XmABfH61p z-ak{eSzMY{>F5sgEmHw(huv~H@^M^TTnm7&c4ccdG{zE>k^t~E&9|30karYW9ALLC zEYP_b1%!pMLpOn*+)+k}5*ix1F!2_;0T2KqQ_~#oBvfi)VeQMphjrH;DJt@5Yis|U zG;^U6^E!PC?U#+6om;SSAc^6~J^+*O>(@M3CYU=rljaP>pXy(a1_*eKz=e>M53$BK${qN1rT?gx@NYWA}UQ$qKJI^3YCM@Qb`Y@HufPf z$@-(mqH)tde(?2oa|;Lr0_P763o9utjW=y?ZH-DxiRa3J!Yf_XdhI>%ujt6g$gO)68X6jjNsAZjbmpRFWZGrsrag4b%u&`pdgeWh>0LXF zPtM=gZymUAwo2#|JaE~}2K9^--$b8IPOekL>+}X(OG_(p&5K^tZE^CZUG+ezo{HFEoMK;&0=Yo10MPj?=vY?@*S&IR! z_al_o=P%HAnC-EBE~Eh$`eMpsW#0>P#JJ%%_i67J#mUww&M~KsUvLRG@EhZvyr_}S z+D|VP=hYclSW0Ifb7CYV-33s>V$1*jA#9rOul`77if#K$4E~ake^V93+@$H#HrW#& z+ruZdnJVe)S6=wKu-A6N-HVDy-th%FDrcyd_}0i@XZ(en(inV4^WH|7*~?gJUS9w2 z-@l9CTix*cI5#`{CN7R2xSf`c4q-|LcE+g0^zkc~`uRw-{%V$$vUz5B>En9JYz|tC30__H zFxHN%H7OEV5P)3|N`OCs1Ug=9kRZOr-2Hdmh%oR8QhyG}G-`d%XFV1=Fi?gn>)q*g zK>Xw%g&FHn{AkkCbs_n|oxu{9V-KIuL z_S6G@T#^60ZXrqX82NMba%${sPlYP_#R z2I`CdzKmALw-o!i9~jLoEeg6}4q+i7Shkbp%(nbDu1TVxU)K&-A#2ZU5$CW~?-IE> zo6(r|Y4C{}D#ANQlOU1vou!w3T@p_VZ$6pLqWRIzIQx7`?%XT9fw_|zz9V4X-~P*5 z>*@<*zI}KY@9D{K-Kb<&Gc_ZF8qJgMEt-?H{mO3GkVdZJ-mvlmx2-y{NAe%yPM!b@eU8kIhOCRu><8t3( zuiy-FBfkDS;A{Jg(&V$pk_H^`YUA{%8#$K9H}m8f5|E<+#+tEZ{{dVDq>?O>{4%zpLw` zYy@SYR>?J8U0t#BZE27&z1NIye1 zB26)sM{eyPMe^6dfFtqU`!+X>+d~&tN4p(TI;Zh1e`hl{P>A*I#F*!)<**EUGFtyt zC2A&nLPp?E4(r0Nv{Av58kkYieJ2(yA~tFvA(A(7teQlLn}-{}pk{YXo7=zInii!L zag})Z@W$=iw@JtS{QQpRBg9F>JcSz0cd--_I2Mn0=4%fpt=aUd%e8hno#}$jifb~} zPX?+U#_zxrKMa8O+lxmTT^FFaLU*Zo#mY<{g+qkFknzpCf#>h7DI976dV z?)i4*?2q5Vl&Uz5g*+~KSmNH&trX!;Nxrtjx?$>@FeI^*ZQ32LrK{_>GDLs!TtWiz zB$=;ibTqEX|0X^W5gN6Jo6FX8>+o>Q*RNcs`z!usWxVh}pS;cv$?v~@93N9drmkE0 zIX5?#&2gb4igY}jQWUfCXNmEmBS4w;pC$f;tQwI?N%=G`;GDU-x>C#CDbOr#o0_6v zWMuq3HKqN{DsEl-?@%{@cNZ5I2BnmD@DbP1u&G&CFy9cd>lhjvXO)T3Q}6%I5uwaJ zz54mnLcj(++qMQ*r`Q^6vZ!)vAc{5NDmG%qvw$DJ&8z3n3~n@Zv-~7xg?cTGRmvAH z{5{HKPV^nW=&{dGi#+ztSl<~Ud)y&_iAN=N!|?2I_+G!6DCTt^0saInAKx<MVSW zrQz}@Rpfvj3{&r}A8cdazYA4$zn-c0dK~@Z{A7=R?%|_He`aSh>^Yd2-hB|Xb2wbr zzC7Pk2I(m{Je)(I?z3ibUyee;cgJO1n9BL>KP}jIc^=k#Z%p!Q+sQ?v+Liw_R|pvL*w~oc5Za9!10}|?A3uIXvdg|c4&@X8{0OKVm|t|; z!zoaX=R!E4fbOr3hQR1e`&?a!*cYf51m@(>Z*O6#WvW=5tGjd($#4cp(d3R}$szA) zVaHeCcz&Gq z&&-{-6YLE5x(W(9j_3E^xi${b6O4zXO@q zYn(TXhCe;wvz;XH@$muM1RlBkd92~Hzm!Ya3pPbll zOn&n`;O61E2eSIWfJ#_N6YT%Ji=&w`<96JaGxbl(%zD4O?%caN-;3zmtvj0bNV)Iy zUNuW*u-HJn!fpmvVr#k%1#^T|SU73y+6$Kd&H{jflBJgaSV^g^rRDk?ZsWFFw{JJW zXYTdtUQ4odswknq(stZe z-2jcttBs9L&S($sgT?gkI*BSyGTUhf9}S;L(Z~FnMFEQ*deF}^Y_`?hBf(E%)2~fz zY?L6Upg6fa+cL#-7|4)ROcO~0qyNvwM%afBWZ)mPHd5QEdU|^1X?!8|_4NgiGxS-V z6#?5Fp;c~i8y6SXX1Z3p{!^I!eA^2Pi;hy$ZoWP;QqofMe#*9R3I$J3kzbW|%iqTJ z=PPHthgU|!$c3Zb_Lidl4H(G5gU`$uZ0fT=<|HO2USD4qvY)#FcVOD&hl)rM_esNN zRQj8zEfH<87wGW>)HRHi7a(3CHR@B?vbcLrK3y`WneYRui^;=lwVX-gsR*& zD5auu%j?vuaX435L85F|@~q+u$bBw{m#0`$GaIUOcY&{vi#kZK@iX44efutFsf+v0 z1m{Vlajt@H9FJT_$?(Kf0f85r>gy6%i`QRJcawmJ+RrEvEky31_Ii&H9E0+uR~>S6 zgX%1|YIkd6jU)Y{rK9PlHwSdi+2>|v!czvesatglA=nT?;?E|VC#d*x?7VkKBd5pl z?*Phumrb4Q^5y>2w$|21Hi>M=yWs9{cz+t;d6`1ueJED#b;>H%l7ME{M_4fhBjWUO4j~c*Bt=VuD^R!R5MAaU%ZBAl3SYg zJbWbWNZZbr%4rr`b-;Pds+BMG{QUi~HdONy;@GVJ#f^SX|+M@h-o$km&eFGWsem*($C`gtN0VCr<{LCY??pa{qFg}a}h<#+MTblxA%ldd@Tn_nbG!y#9Z09=IiV{9xQDlL!WlA=bj zK90)lm z2j=(s#g{=0V2Z2lW`O~l16$eE&CPy$R#HS%R6&=%iS=y}`|qOvgn;yaH4xI(G(PE3 zrsP!dR%I4M<6(sDjQ|RIhlof9#^sf>^CvKy%k5_L1zxQZ_e|Be+!c1nP60f7etw>( zS0fbaxpnK7L=3}|S58i*+p|q?0|HP05Cu0hh(j9W6X0h+Mcd;BKAk5O7K7Kw$UaDo zw`a*lymD}0d0jonkW0Ck7BU(R0M1UK!9zF-3g00fxx+Y$}NY{F)%XiX6k#= zL_fev<8oftLI4m#Ga;)5gQTkz$c)?eEDGeQ7Dn=)_IwajrY)vdi069Id_(Q&(6SKh4{L6+Di9-->l%IrxztBv!Nj;+wFH+uA*h(l|8qc8JH-aR3fZ&7o?s<#IR|m{u6qaHxM``v_Ol23>3h@^q@Zd z{cDk03awPT@+F1!SRqQO?v^m9JVr1}DWV?nK9?s!kYgGuGgnqhz2DlC#JlJU@W0TY zAq`qE__#t23+Mo}p%Et*-#*dFeNJQQFYFxI=20uOm&kaQ- zO9p4cgB`m)f@(Z*H#Ii)2NWqt*JTuL<)(YThbOPJn^^+L4ta~^9hK*Ldcv<}5x}`5 z@}KR(ZYr_;B>>ZWy>X#4W(AOu;LG2$U2&|iEQ26$w>W6L+?R@Y{{59?z4Ph5B`(GN z?5AyTVR;P=B1mp6KuC*pK3Ggl40Z*e*zd1b?&K<^4(Z&iHdz>Zlzu-rB!u?fJwNA- zNp8y_x-e_-5k*|L-@tXaKoYpNR^@YbIr3RO1jrS){oF&blLdTOzI^+dOa$L+YK+az zgF-?=#?IJp5hPy|c?>IaeBh7G#SF(Gl}KoIbmA&61~63DSK{ePc^=O`Yyzm3Y0F*Y=m*jw5p7a{Ni^dfT_y-nTSYfcHxUZ<`AHXc+SMq{$3JT=zVdT`HTf6k;fDh@HecJP9EpY-<^P)3Zg!# z<;b6`M#W5&nH$^L6+dlD6ZQD+e!vnRA1`s39S7?X0YlviwTPRS_qri0yV8^tx3%af zU1G1*AKl$p($dm0p(ITo?5s9F^1S}5a*|wAQ=@-S>waLB%x^6N_%?~_g`d6?<1G=+ z=xh&>VxU$wzb<+E`uIzb$a@ugdS&c(ocujjg;rl_*?gUEqzLd!M1nm&(iCU7CM9XV z@l9di;kiSbQ*!=bFz3TIb-F*TYI9LL=DJb;vin`5ulkL~W*sEQ( z`e3~!J#gWGZiboxmCB(O4A($Bs=lu;S^yP)0u=?Ro`Bm8k%1opSj5P#0>xhjbge2! zb3|L{-4;y)uYjPSn}EQC!#O?Lh{=*Z=5y%Y5L0!zZGWU><8 zuNyyz1WqRaIJ;_chFh>7Xg%uE_+t;4!YiQL9o)vuWeS)R=XQF&8yaovJm z8_#Lb2R~SF<>26eUWk<7s+~8w{7|t-_$+Qgc{T$I>gwS!zqHgg_C*(DZ^OAC!J$;* zX~5Kx2xoP?xF2Y|sT+(9=PR?9FLB9*B*u$Sx2%zC->fH5Qb&h0KR+K(l0S4R0+wR< zpUdacGdd;)B=a0(2TT`lP;vhoy|Y=f#pAYX0@NSCz8Ms9hx>SVc%PKhl|Ur3w6p~5 zW%=d09?yF<#Mss;Co61qGZf{bkzEC7UFMSy0uFcg zzdZ2G$RijD5cX*@>gnlS86Wpg+j6E|-y%EiJ%rZoncM#`r|Y9`LZ#bLs?yJ&Kj(N& zb^fXPpZd4wx5xx7epRDB^%OI1YVI%j`V|b6$!}{Cl^hfb*&*sSiO=QXEepRkbk%O4 zqFS=?C%}B?sb-;LW5>>e@ep=X-s>Dvfnpc?PjM+l-brOdCg<3pT zUN7taS!qWFK7Z}mW{u~?!8mv(DC{dsP|~bM;YYp1?v!E zP6le~H?W;c-S*5d{ z>72xCDy0ZDRXeT0NYe50;u{zk+;>`iHfe=%!!)pT@+W?omJ>C06r!=eKLYr$O0E)x zoAX9Ay@EVnKf7*4Y@Vm@?4zApW4D}Okg4ll+1u0b@x{XR;wxBkq$DKX)J8`o!_?jB zE{5Vk&tgE$gxS>8^ypMZ!^VZ)HAl7oBQ`EBhv^fb5te`E=G6YyQICeiB|HKl`!B34 zTg;1jP_`EV`w)9iR>hh1&aesXDs-GU=7!XMj)l)enTT=ySJJ+xN)Q2hIq^Z6)8+3K zwXtjqk^U`dlE;!&vbN+wxL4oH)tT?CU2j0SC8MTd6g!^A2!!yi>BQ5agyss87Erbg zGl(%Tg6Z?rI|s`&#ShPG>z>y)-tdclNYdf(c2Woc??PQGDL7QRhxE%U5UuvJX zzCa7Z<_qehm8PnlUmvQcm{v`yKgTv0;qq=!P+sW8ZPd>!I z>* zH{0a3)SJ{g;h0Ingl%!Kq}#LmA;jO`*w?{YSaWGC^=51SXEb_CYH?`eb@M(aiwU5` z=rea+IBTNr^gY;`U0Vw&F6Km1v0Zy;q5IKM(b3Jn>>7IEXhPo8T--YMgE=^G@Ooo1 z4rYx|yD|`m4A4^lyd+jw&&j?0o)A3V!bo4YM|eC>K%0-1FsYTb&+N3TO}|! z0JmPjWimIksE zx5WT8sC;UL)8;LKIQiOTW|^UO+Ff=)djYcXn)jvL>1`h$Cw}mt=1y-GwZr&E$cUV4 zWWO(+T%mp)DQLkdDJid9T+HDOZu%_n`zVG`CU&c!ayuSv>eV05VIi8EoBs~y;`R0Q zAuS4c5YP~4gzKwE%c1d$h=_oYcViYFDU@9J4pK}e`0f!b@c`8p{cK6sHj!)7828Hg zU2H6YVsDSuZmR>G+&yp|C%lI(XDSG7bt@Y^h8V#dU7on5p;^mlZ|})-X{=K%n4^&E zy#DJ9E1HgbY5q21fAvHu*altq7Btqa49-=VC1^zprg)k~&$Vst7m!7O%nyS}@Zom? zeFMlvG0Dj+9)IW|Yl0!4Q~GdkAfTe!3+C(k33;JzWjKr!u=V$_p5WjIw2#G!+QTOQ z^_^UpVe6F2($ePNzscWN4u86R=Z+NUx5&i|=++SE3+WDkQmryG9304t0?I}JohWtL z(nkPSI+Qv}@Ejc&4XYBFRk-i8*_=GeY60vo|EU4DT8V3>IM_QTGrleBCkF@^ z(_>fVp5fNdtkZs|j`?ceNEwv^czpicG3(m~P7ua#;Dyy#lRsz$^G&=RvHj+<%R1 zwTf&!Ab-Bfak^bgzqY0TdcD1i3nL#Nfn2m!y{9m|_2Y|{K%0ruoX2`)%@Sfa#jnnJ z5zq@w;WD0kPY&NR0Bae0m-}+D^~Vnj(DW`vU^Wn?g7i2*mVs+Lulwgz$w|;hnP@sG zY3aSGPx&%oq%bvNK22S;sI}*Swe_Wg$_{*Le)?>4Mavs~?x&-Zo11OA@Vsv?5;nCu zbRH(-S;g}vg!rmsUUK!l%lhBjGU23QVDk5VsBzp@K}SPFBVAEw@n^HVjxoVm6@*81 z?W@H|WYXz&q-=C^B-&Z=27 z0Du`de=|Uhg)fN=%4wmwxr~7LdW}{773odohzp+IlZ)=NuP5sL@YVLLU+BU3kLhWY z^n6~|TwU2`EWw3rgg0?1a;!$J5a8Br(`^O^*80tvnwg=XIV6Ma7#iCKf>GCs(~5pP zP{0_nsDD^(wg~4hpt9gmEEs#u6EbXqFol8?kom1fW;ZHlmZs}GfYgG5oaf?(96$)r zO_Uhl1fj$P<_07uv64R^Vv9~8 zn%^<`&00=DVIIW5g~2RYC>qz_K(ieF`a;@$|F0z290$<}_%DFnfXE;5G*^k+YJ>^a zPCFD=l)qCol`^&PHhdD24!|tmoi|89^jQC0g%AI#Ox+ebZcjX&sC6r|A$PhE3JVQ= z7aq<4_ytNYmst-91ow7bzh1i-_!g~H<4mFT%|UqB{FsJjr{xH#=~Kw54c2?ztUqz* z=QTTphRMfVXL!7ubD!6rGtvg)EOiV%1^?_iC~8q# zjw(aOk@RvF7EBNujx*4QLIb=Nlnm%*aD)wL5o9^P$=ru2(qK5(HM2cPPD{g;fM2LL z-90#1gp&db1DVp~VxEa0>mb)t#mgxjL$3uJlQms2uYZzvJ8^qIXJ*b=_uX%G#L8Z_1XD(Y3RLxA%6@FMQeP~LAiD3PPE*f?QO%cLT!f@*kx%@CXo~X2AF?nsb>qGJQuJK#6t%1 zpE5#fs0t*$`U9kM0@mVP0UHvqfdDY6+ojUfWa4tcGSnz~-U$k{Z!M0K*ILo{4!xqj z)Ca^mngfNpUMpi?$PfTDj6pwg+f1lO$1A<{qqWtNoRt8RK{2uMEe1ZO;@a$NBS7ih zI@pgcsWW_wePo7xDZ-?~wXHW&m!&lYKMnnSE9^*Y@dN^Nog!DQCtYWIT{Ea{WhLNf ze=h#^$>3si^4b9_vwZV04aCm&iC|!+egw)5EXdc_bt57HUv*} z`p#khyahmjMEv~xaqb7Jf3~*L?Yy9kLEN_oHvY%7eHI9u{s8suzO@Y84;l3yeZXwj@69ZK>mxJx;ldmpMc;Q$Cy!ASQtG-95^{S>*gRCKbSjn z6Vh1aCWNds|MH_XJ$)|>$jzJMuC~Cyzt_7dIf0DnYHE(FS(m3ja6#tFNd;fR^y9#= z@mY<$gIePVQJz%G;UP7oX2*14IVYu6JRR zm_RRhy)~UG7sF8EZ0pP!>i$>W;H|GG3i|xf?$aS-H+nfa(#J3MZ#>F`vmL;(o0^;b z04yT~OBVIdKVVT|P4%*1n+P|9I1L+Y6^iZT=U66aE-+>U_fvmv%2T`Ye+9`C+!a#e zjz^M62IKDY@L*TJm<5E0fN3nAU;YGS48T%+Zljf^48lE1kws`y{12jKtN#86l4vb( zW8Jtm1zr);Q63pKxLv^4-pij}-WP0B!@!2KW z`|9Fo(Gea!zCVHQ(FAk#*ImazAgG{PK7`bSJex8y5f#hPx+Plb7BUF?cA93|E1#_KPIj-~T#*G!f6^TXNCw zLPA=antb!}@~#a}*SJL7#G`~LV(`0n8KASb=4g~GzOc6L2ARxv{bC$?ljiJnNY&o6 z{Acv~O2}Iwz-wKS)X~+QUtdSsRNp;McmSEYIp^V`qi4W;1B~K<>;@14#Qy;M(f=u} zg5II8VSTc@h+Jjx$X}t}XxA6L4(QCHhR%(E!$IuN(yAhyMlsHRIU$upAP@_VuoHo% zB!KKNx4FsDf6sdGV=LCm5D}!zb|p%jOF+1b1xy&zoxs^mmhpL_-s=IBcx+0MMDT&_ zFHW7%aLLgdON`qQNCOCDSR|;5jJ$PvxX}&TMV0+L8iY(xOONvQj*i+v8H3$aVKZ@W zIg?fI#qNL%oVfz@{Jh>X2^FhUux|spMdoggS7CwD2#|`efOz5jC3Sj@+a3eB-mllj z!T@N2%8u6vp#R6GPM8%HMS?YRN|>u9_&}1ocXvQ2aXeU6KbZs|7fC1Qom3F~{(ZD4 z4?n0R;8DZ?Gu>TI_lZtRQ+oZH5Ancx9bpKO4nCWok@|J+gK*(%!;7D|>}h_jJypI@ zT2k`g#sT>K#PUi?`}q*CLIy^(xXcNPG`A@!U82LM5U&tX-h zoP@Ea=imqj8wi;zAIedHbSg)AO7oZ;v{k-PS`)iPIM57JKKg+3pJf(qBp3gEMi z63iI$&3V7o?5{vg>Kl{3!9Y_F;03^+C|H>1y^FxdA2dB;fqG6CpNx-wqgCF}L}AAz1n5D!c{p+Z zENb`KeM%Ec<9~~s_E$sS+H1YQZsdd}mwLmT(pTkgrVD z2!f-T3rrE2Hv+Y*YyA4PYZgP<7+~)p%L|maljLd-hktW*1c{o^1R+AZ1dAQDYipv6 z5%F(w5C6@7F=OGa|3z{W%>5S`pGMPxoUW856NZ$jpoWbP6h8_fa^EVthn(-JShwpd z^YbkL`N4&L0rGZ=m{&3oVwC^LE>&o~Ak_#4A(HpHFPa-0x3mP|Va57Du|Xg$I_LF7 z4Q=q(=mZ4fVPnE!?>_K{K*+_!Y@V5+0$^QRTMIdekic4)j!qd3r=6jHgeFeF=EGIZ9O8ExJ1QO6g?RQ&BGJkCfeMQmA|CZFdA<=z|AxIe_ zwM>w->i+Zs`nisJvmTq`ek=?CbPZf!c8?7WsX=j|=j6n*wzdW%40$BY&CP9LdHE@1 z>GUo(H#Zp=+kgJlLfW}texN9Ro=F7tBttF#YgyUw6J8B-Z*MUK5fLhdkAi|i%oy$4 zTOZdcPie-0TSmpiNCQ8Fc*@6@-%AV{yb+)iM1pS%M+*J|sumCsAQ$&0f5V|y4P;$G zQ4t%K0yZh%J4o*HP%t^dG6tiy!8gcU~L%z38>xe zVs-BhCll<;_R5z+r;$GzIHJx~P7l}lVup;L9Zk8oy1OI26j<`8jfL7}=vY|BAQJ#_ z9)v%p0+KWe(qso#0Q#VgOHL5<*A;iePv4!^h@fG$_4VPx2@vFP0nTRma``cIR2VoX zY%!?Kf4VsvOn!cSks|CUQ@Nfg9kh=tbw>(a)%!D0an{Vtr--lLzOm+fca41{E8Cs3 z^!8R95(5_Me3KuybQ>`>H%H-e*{lund8dv}^Oh?p$o<`Dtm7$MoJxWYp62o;o$eOu}oKC(P?cX$7O@eD&Q(ua2P?3-mVGwa)1go}{Y zX5T`^h^D@BMp^~7Gd*x)NefIGG;eP;Bjh6C@^4Y1sRM3Fpipv{ zP>N?(e$NU<0o&ulpIafM5$EZurr;yRK_-ybiW`=IJ3zS`1Qk!;>p&e1kFxNn`k50O z+#Jy;kE9jzd#P8ixDap}3wcGpIQd5=CME&(8C0gA93gEKDxdQb#qNNB0I(s998Ms( zgDj5Q5kBYs$bJWWuoNUJpu6qh>n4e~IcQ50OTD}KagyP2m`n?_c_w7x%o#Pq%y1P#cSw7|O{oeOL| z^dl3z%9!rijIS)>8xesAzBa$rw`4H+2@1?1!3W6|G+1&F;D-3f4G4QD!u%m~Er?Hw zNm8(=ojP-}7c≥W-Z>_k$cth!PjPzCVa

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%0DTfv)PMF6E{KGK=8^|pc6fLg9Sdt3u4yf|wAqdP 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 S9mR)~TC~7GFILxb|9=4>P$XIa diff --git a/docs/opsguide/rdadmin.rivendell_dropbox_configurations_dialog.png b/docs/opsguide/rdadmin.rivendell_dropbox_configurations_dialog.png index f5a204411a5cd5e114e73cf1700a331fbd7fb49e..529fb0045bf9e954b4eb06fa7d50988ad0b78699 100644 GIT binary patch literal 22127 zcmbrmbyQVR+dX8?iT4T=@#kk?(XjHIK*!wzVA2g{r{MdoU_l`Yd`B*bIvCgLDFBv-XP#0fIy%(pFau9flq&3N`tjpO--BUP;45-Hfz>4T6S#a!@YAP2>0%Xm7PC3DZ1I@iZoPrP*IDag z{VGaGC$u_Zz0LNJ*+QKi?)&%e?J(CL*$xPk{?G?5j6h=ZKBf;(QRH)6 zMFley#9j-7i>lc^A0f4%r~M5j=tpqMi1C8}m7I8E5LXcS%%TnaMqel%I9P;4bYECl z_`3~)2n)x;e!)zxBzd@gr~#wj61y zl|WMe&!2k(BO-j_;@A-|?U`Lth`osfju#HCU~~3)Vgr*;Y(lI+o^yPYC$aU&1Z1Kg zf1y9dP^&>!xg3`o_91FjAOql`U_6;aA1Tbw)o!4+kf}JQzG@6lW)xyoyxwOxx z4yhFWP(5nY7o>+6M3Y{J5Le~dn4d8?q_LL|7iBkfkNkGa%uHQfYJ>v`bOyVhoW4)k zzemQKv6!-_Rjp#;9^BX(YfXEkmJD^x6Po1RAQ48ep2b#!#BwEDopAr^&yP%I%~ zDC1gKKuC%#NcZTle9X4vijb#9cUIgFHm$ec-qv1 z^A1BlRUIj427V6pPO;KIUR}E{e;byN`0CaM#y4R%ZwbS#{_!&r z0olRf=1!sI&vCPlB*kD9&s52B;5jCJrc`NaJO>?A9GzzCR`EPBM#$=}@L-@VW3%Ck0uc-Tg*)y zkJRZ&xIQe(9n4lEQ^$A`HYa7CuQ8bIIT+&Lh3Zm%UCT}LXdPYeK3#YCCe44&{Q+X- zY|!yZfLa!PpLTEBCzx?Pw z#;{#e2Cb_%AOS|gYq?0MSY2HW&tLv3*jOj%c(KE%P^t>6s5m`PT2|IX6hT4?0YZ0k zi%d-ox7O=l>1pCUxw!a~5Dp+Z9B2<3TKg<+ zohYgo1S;mX_ZQD@yOaMQ-JrxcxK6{AfLLiV*)jc&vSo0RRdZgVo<>6GsV&tF%xK!9 zb#but26WP2#0fWFk1Q16>aDT3EM_Z-h(WI&-mh3n!mA#>H(Bs^+iewB+wSQX(QW-H z%y<25k#=6bYBM-v71?;-8H~~CKj}eC&Bay0GyMZt!QiH*o9_Z14ft5;)l25)=7viw zSXutaK9U7s!HUEBlsD$OG-+f)mth^2aA8phJ%}1D5FO}&Nf=au74?4w@a#HvrrKN z#Q$*r+Wqb}4(|{a>*)HL%b{%twf#qQv@ad6^DB`3BN$mtP3@$+P2^^Kxp-&3kkTI! zC%u|S*~#|!oOtcsTM$S?B$%ko*$(l^mci9kJpE~9_+;l)7u530qWKjF5qpNv&E>k! zice*3`Y!wwy--1S`l#{KCrMumKZKpJeA2mUqhjW|6mt5r{-Lq+T$eOt#Hy6?J47s| zi35Aq$)`Cx){D73Hy)$7wBzC&z3<<#@GeF$-o5h)3(w|v9U3$$2D@0z&J(gk56QrRj5#xtxEJ%a3B<1 z+>j>%UVkFEAN`vgt`{62tp&`q_Vz`5sWiU!N?dj#I7r~rCzI^UB=;InTMt~gPv&aK z0Gb|7*qwpXr7Z9k@clp1`JK>E;~X9NmD}_;!y&@80Kj9la)LU@_@lGGUv)byQzF;F z%JzwG0uHfbm)W4)U>Tw?99LqlzrP>%a4zroki&V63^2!!$VjhsN-04ajI3_(u@G6T z)Y#=10C^r>QIXx(l@mT79Ix|QQ>yDu-ea}5!T{@fl8h%jbYiyjTQ?F%od=-`i%Bqk zy}j<`+$Cq9p<&X+g>BaDALCI-+9dYw#z5Ga20dK;fz;Ok1zYJh3us*m1a|7AaA`JK zL-+RX^`8wUvWZw*GmVXp19blU0CDu1M6|Sn2X)wcav z#w+RNww2Az;@#`|)oTWaDMZtyGJ>OohoiMMNOpGil!jmo+ML}FAS$^$6g74AH&>B> z3IQk|0aGfi<2I_Bakn3y!}*@WJ%JgPBoYf4F;iud8sxcbFx<9Tk;`a(V_8#!Huh_K zb2BV76tQrAeQfiCQn~S)J($VXNH-UT%XtgShpsOjo!#3UbYCuOId3;VF=K8=lvgn; z5ZcVk)5WPyJk4t2R$SX%o!Kxp_H0^H#fV4sAI>|l2r9_QMdzesWoE7b9=_D*vOZNj zub-XW_#N6G*yCRkSw@xrOe-gr#@G4r z&0_Is6kl$Uin$s>+QlL=wIA-Vn4Qr!tZQ2j8ykWRAHPaU`bI^eDCHsCTwK=fn#_>k+LTFtyY~^s%59V?odmu|Sj*d#MN8}v7{#yW& zr2~B^!sg^P4WAnZw83_AHKrjr-NAx`TS6xL_g!jru2kAyL(5id?V)_3=$mLN4ZP_R z&F=FW$K+;9RQjl&BW3PxfTlz24##g_@A(9!P#T`o8nz1D%vsRxv(qYp86J?WfZ05SW};+9M#p+)6-JI z3$}Il_#-u(YH1&vDKyUpn&^%)sgl7Lng>Q5`p%c-@8J(944u_939HRHRw#CNSl&82 zA!}>daH)29_&wyjAtr2u`dUFcso_zWt&N1vnbU;G$c$OK_wV6@g5bGs8}_b)pH+m8 zNaILZ>gxGtG7Ma`IFb5@wY7=sh^|gvv5y~l_9hA-3=$AA>D9fWqUw<54$s#_NOW{{ zSrlP4D!Y{n=Sexw31;UVj&z2b8XY5VAlJKH!vQrXND}$IpWb3lQ`LN!P{_=nfN$&1 z{_Hs>&EC_&#U-mU1*uSYD577lNWL5iFE2haA)m0JVV?fe$cQwL^939>Hnwzr^s~QF zq3x{;XY|yTmfLO7zUS8fGv9SeCtJfD7!YV3>TW`K*%Y7t4#l=57 zySn-)E*?zrCR;q)AyO1m`E{mOUb_c}{x zzDlW9pBAJg(CiLS1z_Jn83`$=%cfmxP7Mn3-9}Rjqs0MEzKV#P!hNj5&q5i}M8NW; ziXf&*?QPrAqv|h<)<;BBqsP)|dI4LPn2wL`99n~xm=U$a&z3)Ww_i%#V1J%8?<@X9 zA+B%5_KdT0?&f;Z|A)|8?PxgSs`}zyhAKwsw{2}}RUt0gcY8NL7D<|#gEha@ zmsT*lHN8u3>Xy!Daf?VvOQTmPxtFMriUrVE=k`nwQVR-Z{7Me^y_jK zTj;umdB*M#{~u1@&dQ^5L3zhPaCr*oQmH(hyA|6YP*SyN=N4_LdjSKm2LbRBDj$$g zK0Q56Oxj*we`o!x^h^H4AV2uaweluojDNn{@AH!rU&*b|i;HB^kBMdjrlnJ~s#=Hs zVKdkm{*+Q_t%HMa0QM46QNa<9m5%U-nJfv@3&Gs#Ate`ah!)UP4|Z;@D_@YVS4oU$ zVq=}s_YV&N?6gPgF-2vmmaLG#eB4bfUl2VM&{tFASi`|h%BR7rqF7`o^C??4)bFQkn>>B+A`(%-fgk9q4p_||57r}%i@%gfTvPDETBAsmm}|4vYlb4h?UKgm7` z%f!TOSnty1PNUvygxX~zA;l-7z0g(>nXtknL3ToB8 zp@+n9+ACOOY%^|lMa8U3KqCz9dPr#EhE+mrem~;%2e>FNi4*&HrhGkdTb?>*%LxPk z$`|e%?qnyEm?krt&+O#n&=G|ET`gWd08#tp<>d{m$oqP|f^F;WhJCoZZ2zq%K^!A) zqt-Yo<+)Y#&0|kmaQRCSNucytF^5{R8WCzx#+VHn0?GD z*9K;795F5t10o1~qN3h8+qy^@R4vwp;YKDWhtkeh18PSq&s`8r(p;CC(2wL*^FV4? z^n{!w9zT+qYIWBkw?monAs6o@`pT9H9BF_%0uWUz+ib0k#ql2VGm2stJj$}0CZ*2n9fB3UT&BcZSMwQjvuFyM9yT2 zP(%@vkU#=iHA{Q@Do2k)5gLW&Rmmi_FUOD|o${Vc7R?4nuS8apR=|ZuM@N;)NznBA zV?_Y0Dbo?5%FN1I1@si~{wk1;(a(ktw03W;Y$7&-fOO}oMbX4h3?d~g5!E&#{`KBg z%wQqgyavYxGvx^zf!&C>(Tg~szWB1>0SdyBRt|13iA``OweiMfT>p>xtXjq3hDCFL z9JNR!NopRZ9IOxQ8v!i=J2J8k-Q`vxkY`sIjv!qeDIXsaNV-^@9Mn?&juIjg?pyN% z%sB>4TMvB~4p!L2wSW9W`H_uN1@e!?#DeRyjEp~Ts^gBXfPBrDLW~->Z6r|oc|Cq) zY%C1Gx%qOzX&#Be@+MQdT?e&}w`b(fT97V6R1@5@2M1Ajh<_Q@(CExvIWFmaceh?uc`V2>7@U5az7r z9yf~s&cdb2$(^)3?LSAFt|HIl{x;}oFy1c!nXqlK9iMmU*gqAB*Hsda%C&mS^{AQ; z%yf2d#|uQ5)oSerwdSD}O8o;O2yu!cY7dRSK5Z8j9_c?qM#5U4Q8!(-bW7yDNIaE+;(%iZmS@tJga?+j|U zg<{q~K;tANuEP>U4vlVh$I zlR}!RApeNhZo7L^^Sl*2BvfeFpTxzz0fhyoTTBP?v$Lg#cwAsiE;Fps?@* z0=o62>#Jvz{rsu1_eST8g#W=P(cv*qjylTBponj)s*v-+;>gKt&y`X=iQS@0)uJ(* zS+E_Ij53#qkr4ve7%N*_##dZISBLW!?pk?ha&R;X@Ra;20XxwgJfDL_wHPnq#$)1D zsWf_U^?1cIdiUIG(A-|VjU*GpK|~C~i?Y|ELJheQ&F0!o@}3?->iD@w<}W! zRTjuI9*h&8oL_D#{%C*zZqq+JjEqUI@#iKae_2=`)*lk2NQqh0(1f1q*AQ9osXHSe zm)IL_SDceay|H{%0c+t`2_iz!5`(rX`@Y&-X7V%n9XBT+IyfK$UJU0A0+%jG_=UnjZ-IbU*xLH{78~)Hl!caK{V|)*HFhxbakHhJb!+!LB?xk5^#BRNTR$a( zC8){|7KxsuG<{^ZrL%SWC@pN zKwaODVDiZ3FGviq4mueiW1(Oax{Fn-Su~l2Ik6j~2Ry@{3qt_#i=d#P`2vw<;?m8V zPC`}+pti*B4^YB-*D)GUTN(p0*IIXRbI;Y1ov?Ifot=ZCr;z;TUwas{b90Rzw*sGU zf(oIf^$94F2q$1+y?y>21mpkwYjlv;|Essl)~bI-;B)%}mT91Q4Sc@Q|Nh|t6%Q|& z-iUdrESdEovap4rFbWb9(mLin@IGfaF1tBMAQd)1`Lrv&wf&4mU99?G7#d_fO<0S7 zSmE9F#}>LovpzYAj7EXCi~=-6(|o^1bxCwyKD^9xaPGajb7($%xxZM?US+uvKQa^Y&iavHR0>Sx+s)L~nzNK6jA`qLk^}l7PxSyWe)o z>LJ;)6a>Z>G8cdKaUAGb5x76eyLt(F(|4i|Sat-V<-BUNH~S+3TW z|Lfgd6kqnjCZ`r8QAByC$k4^DI8D54g717+w@CqR8Vq=ii};Q1b69WZ6jEJVD}P`Q zzV`M!)05b{S0AlLq`r$4oa}4bSt@1*>WJADjhrFn_fUc@Sz^eH&J&gsrK+zd3vM#L zbyv+a>=K5Y_a1l3)$3KtX9sVdXiPg;oo~GfIUh0ipo4CX-#gdb9{63zH{kxbC02J8 zuc5J}w|6NH#O!%j^M;GRjl8aolg5q?=PSn=s*Ib5?DuA-)|eLS?A|@_?S$cY$xBy% zeIt3#1ofF-bM6=KfZOuma1)cl)4hJ-`j8BU$%c&9jq#{EBFN#Gw{6iS!t&@|9`SDD z8zR`0p+vK}q#`1}Q_21F=g(LtrvWF|rM@23M{SY1xr~M<2Ju{W;`)|`?tHh6@y69f zRa`EQ)GAe`K0%t{%pVq!`FDz|A8OI=bqawez%_r^ zXmvOL=p`x5Z+nB1d7L1ZmX;g|uJNiZmWSkBw(Pl0z^itq*D3W^MjEP>ng-S8%Vsin z!>PROVuV+M!y0auYa33ccMjaCd``jdzfo)#xNlwMa(2fwRrT#RG08vOD~xze?bmE5 zG%e`nJ#g$Fe=I36TwYc0=<3pXBy3*2c6xnI);wFGpKh0AkHvUqGiak;Z|@UO&uzX? zV=lkG!7g;Tb#xT&^@_jso_{M*{&BrKe8MM;NxeO@y9!#;g?2ZsXj{TG9-{-LI?C~}5NndBuJjmhgyx`4Y4C3E@_@Ve{{8r%fRpg^JDQ%85j zda!3`Kd1l|bFrS8QA6oc3LMtFFe~CTQet9_iZo5XF|J)h{;NW1oc>rkQ6Fd4(?^%% zwPx7s^CE(|i1qc<^`s%wP>~a*bE*d%TZ$ha0f1osIGP?Sns}Qcef(+V z2#+T-Hk_$aKV{}z-Kl}J(FSGGi4x^EAs9o} z#GNV~H3ctLM=_dg*=B*U`aiI{mGu13Jx*QR8QbJ-n9panKbXm>SQ^=nuy?^R5pp|B zeZU(1I-k7Wi8{9^otP6YcDT7P%WODal`>?r`60TSKdHK{_p{E~!q`#B!9z4=DS6Ib zo&Kffv!T6>VRb}yWTPJRjULXUK;c+()*TG|x~`4EnDkzG2E%7Gje#z!RLZ}jx2&cf zNR=gGLokPQHw)};wD+oie9i_c253kGd=Lq8!AAUFSh<@!I)w4~oscf~*gK^7-O~&% zN9bG@({W}hZD1eS-Td_zEo=<3Tp~1TEO)su=j5Tz0^a2s zzpH^CL;I+-f^{B?D>jtV^@0qEkT+=21v%68_ALJFP%g`8mz!>|_}j+1*i02K;`rnB z>zt=!u_(Q9w!>V)k#ud+18z$wZ5N~#pdwDJEBXplXYWD?Y!YY?ko!KEtHtMaxniu7 z(%`l_<0w!lhDrNLE>o)E*5Dc$SnqHYzaUl58&;XoWIya4YRJ6p$QaOAT~8d_KqxHA zCmQsYPjsBw&Rp-(FXNn)|KaSMrAVn3!^2|E`vH6f$*^QGoAB-Fp4_tBu$MPUU3};* zKEB^uK0XDJiaBsBjWR+60q=xbIDCZkve?h$WZy&!#c@;ay>ogXI}s)Xi>!6M^y)|N zcaSF|@%pKG0Gnuk(L;^Pbt=$nvNKta>}-FW_`DSLO=eYxGhrw74yaWouwPSnye1~( zeDsd4N4rs=ASNLTNP)6N0h8>~n1iW&A%-W!E7;ZkQ(~p+Rl>pZExbbIZo4U)!#M{a z#eVmP;B1!x5bG??x#dGvK;-i4p&F=JVx~n1gv1t~j&{Eyg7fZt-UP^e_i#XzDA+D4 z8lmYnd_0TqUumMS%d>r`jt0CgU&t1JcB$z2$b&Imq9L-`Cohv@s{HKx{c3aIqnRpn zAdNnHJ5pz}%@$h1GNq+aYb2!G9eBlM6OO5S*{gGWXujiQGsh&6FPn{ed4Sy)OSgp(gO=eqMlZ}uy;l&z8$f9+RF8Q^ul zwmKQH8cb3&Ha6bM7K8vw!o@1pA~Pi^91ce`&X-581yMqebPEu-dgc@}q_u{Z7CEdB z(14`p8xV){21CvR3woAlL~nUx#x$Xu2d?v*SE0>U2{sK_1rcm6<_{GGI#*+nM z>h)E+CqFV0w{aX@~N+{Lavrd(&`tW1WuoWT?6ni|bgX77IIIB$UBebp0BzzC&yp}zZe1`pz0y4l< zG>z3d7u&jJZeWE0@Pi_c%aw4{u=j1V5Q$ZvOt}7FB7n7;KwR$s=a1Y#;_d6iYQucx zQM|L0hycD?O1y zAV8iARg``to=ytSx2@ec)-g}x=KRd?u$>Uk?Bc)W^sT#~c3b1BJ13mn4NRlKLTFu! z_pa%_uV8&Fkt^Zc^&Ep1sNUK{{`?twbF+=bXbSfjf1RZ7HeGVOdfYs{eqYOMxR?{B z3|4YR!rnXA6+_Ol%^w4R^Ve4G-5VStCZ@LzM+<-z-5+GAHuNhqI7?x-2Ty3b z=T}W=@DaZA1}LX(>kDpV^u^n2(j)DIcp1axCemQE_d27sF^y?O#hR8;Iq{t99*(CZ zsPLy&@q7V?}&rzaDSJQHbDHbXs-7T%YO^jpgrU zii7z9W)0cwk6Qy2hD@cgr#$+E#`89OGMx*qcQ{UWsV*^`uOq)YKxzj{hO3&r{9*|d zZoJS&Mn;B9MT{zJ7T!Qp1cg*;rZT_zeOx??6=a?5UV3fX$9g>R?6+aKlkExPxeT}a z763N`-8h<}VFW(zgcqW@4)dgRbV#kOUl?`^;ZdN{mCrE@Co}VxY$db%$vrS7?(Pq9 zSV!xJVH3B%;1}i$@e1vS;!F6C0gr2d$7>Bon+RtKEk$gEOS-+Kw2UHKWmc0U7aP;4|I9~wRZrt80`-i z-~|~T@1iDCbLK`CreE1a#7#FYhE4X%sT$LhOT@PQYW^c8%?-Z{`S#trGLK`r z=jg*-^Mp>jT_EgA+ao1*7VnPk^ibR%%zDQ9T#hPT=bKHshzypUc^> zvw$RzS>pc+qpGj((5eL9>ms9-^?M-+v9q0&W;Rj(Bg2epSO7cdbUzLf>duWKh&1nY}-4! zxai&lj>2Jc<7n4A=&ZD~CUl~fIgYv(saE5hUD&!Vc>K`kj}h0iOnC@9+cTKF#yV;! z^mRD+e7}tsfBeMH`)4HQww&Oezw~k0Niirl00YC3^M-%eM(k-B2rpCjP)f9kflIlP7jG$JpI>pjG? zD1QYTk@Y=acIQb(Gnvx{;mLf_4~PiXriznxrYcGA@2NAD%GY3~4`(We_3fVF)?yfG z-Xnt8=OfV7+2>JJk61{64<#CblXHHb%FBIf^&}`=5DfyAD?tf(3ZLz*6_(qT_HVo^ zq$8gWSJN3hdQKa3&7K%+m@n1=c?OrADWK~KP=EOKS8L0~+^Yb}FKX8V?I&}U`ykNG z+Ii#+Fm8@>s{>5Bg4bP&s+?P7AZHrJ+|BTa#>wP0cGuLMAcGGLHk#d#xZb^0XY#@{iHl& zfRPyP&KWt+lAE>4hwMp3!@B|_vsfd#rQ!{{>ZaFanf~6i$Gq`tmRYzb>2n6;d$Z}i zYOfbWjHJ2EVV62?G}$$WPM+TC%n(;Z)c8itTU0+ga49jbP=-SiO|u!7A1<u<#Ipe3rhg7Qi~rM6mw)-X+n%kSPuYn-+$nOO;vW1xKN4?v-MOEa#)xf}buoDFrdM%@e2JjBtB%$4G`C`5@yY@IOoG=4&T^$j*=& z^c}`03Z1lF1M_;NEK@6F*SXq~_9|`n-unVdgmH)q_>*zA+*d}cb(SP-Z}=#J$^>%*?zM*aAy z`w-dx)H*(hEmS`t#7QUxYN>zoAwBa+Pmr(cQbv?>p8=6Wbg+!^3CNdZtcZ zYbar8;zGQ`z1#KN7%>F0<7xSXH3BNx8R%DYlOHS3IvMkXQoQMY4!Twxc=oc(9_kjT z^Lj|CTfr^UaBGFEMG_n^KMWXQ>hc|rj_lltG1B8Tz1h8tD<$=(H34V3S2CSg4`W|c z=BuI($--06&ISw}%L#6a%9j}*>zM>+WgzWMpgxrsKCOBP&9#*NiEA~J%(hxR`WAXT zjs?t0xA*EC?t&Hqq*QZB^Y-V2Cc^IOUZ@oJv|qIgaY*0Z>Q-pSFWZo-UgWt=%ScXQ z&Mz87PR-%J`#i`=5H1TFq4knI-L{X>ZOadWuNd|A*F&rW-1KXWH-j;4QSpA>5eRkT zXY?aO+rOWk;PsEt)g;=Qzz=mSbq}|@EQs#2J6{dnX@TjW z$!%uu{HAorB6R)N<<6#aR7<FQ~-_@s8jRu!8|tE+aR&|7@@HF ze};I#<%aocZhA^hyti*&2}k*IfV%(s15$!%R9HYs7O?0%9+BR9^>ic@tCQ^8>hU ziPHt1+W^^_PD2ZKQ{tL!r}t%#{tlWS!;CEdbCFiEW zh&y9;wUwuoYe`bNE|g##@wtcd8nj=UM^z5sPqrE+0%oTA=~V0T&}uT{azya!>swnh zHT1M=!@z|0dE#Hw+*BGJzd_Ywsv@Wa*tt#zG%+gyF#)Ih%efCcqZ8g3Sn0WdM?as?;qJH#|g?no)P@-UzaLkv&uh}8A@O<0D@ zQXv4W+FFn*ZK8Eorz(JQAIys4-0S8?w?0E0URAjOC9*bliEG`xU?Hk+3qlnh*EtX< zL7u%5*XsezNLe~Y7L8_FG+_sNjJaRb)PKd-IMN@9c4Zh;fKu#q%HcHW=0dn5bVduE z(tGXsJhtI=-pP(o^dL8?$aQ;#T%VF%Wq-Ye1|g@C6LEeidFR^%A6l7m#h873lj+^c z$6WL+i<-mf0kvyrIm}OWC{yfwkK%gNoYF=ktw**BoT4qJbYR~xf|U>j^|l;txs=0q ztJ3vUJ1H@~X)BMlb2RXaWb1L#S0OS;R|)3~53tJ4EC~uOl16{|;D;{U(MgelTGQmj zR4^A?dUsO6s;hAI_2#){N_1cQ)eTK&oueY-Sv-f9^W3BN3N#$JJ1vUY?& zrI3L<1(x65@Ldowp;3ccV%Mur2fGJt$Lbuf-$zql-tlg?Wys}Q?rRrOgsD7|kNy&M zN^#T3>3b6^G=CN4@FSX;k-6gyoii9kTg(=enHNm|Pi6j)n z(6ICxOofL_Xj>PsvzS*2+WaLvp|T>2GP$zv<>*w=eh*B`*p!={6Nn+2<+kXPH-Gxv z{rDQyF)}$^2=h2g9$(WWI8!J?j+mMc-qV?^*dS!jQ2LNIrA68W_ZxNWm49Ic8v9cS z1ju!)9Em!b#ybJ`D-q-SH|&I6L}$0JCracH{9gAC&>0B$t<^jdschF4C5+GRCcgtB zYNcNld>^Qw(w!K`C@?}!F2^1caSQdnzOB)VmDl9_L#_wo(KXiNK4#3179`9SQRzc< z*btT-*+|1VwQm$fBOU{ae3X~YhoY+N)1~=hkpB*7q2hVX-Ts-x|6@MWti8os8?es- zELgHRVt&eZ;w7!a?=VQc>XyCt6Ja#zuhU z;c-mV`XXbCMn@);D;hJJFM|$XFP~aUwj)-Ip;nS&$dl?4=hU^K0)CoZ{%eSqBj&yS zQNVN49Z}gsj~B5TRiV}zSB!7&-7Zc4ymm-UR3Gv@>tt=sVVph|%Q9Inu>2WFZ1*%EOngbMBT(wAaf+~lI6oz_k5M?AR#N-cjaPL!tbHbQo469+ zptJf1#7TWdtnLwj5xgq=59w2-MOgG(D2LIUmpV3te`Mscd}$zu}z{UB&%006TV) zxC=PFM%XiUTXf#bKu3gyuF*!$mkttCcY-;La`;}Qx^wR#E+_xD(c0h*=xjMx6WvOkLeRJ zH`x(fCNblBK*49O`r61E`YL(6(cxk4!SMd(^M^qeq$p+V-7_YCkPssxrxiMO7c#KuA0+^^a^9;AJyHhkQG%kx1^j*ob>S8t zXo~)f6pfg`TcvXF+o`X+K|gM_Yc94ee;H7*JUD6hGWZTGhkfC{=*-8BVBM9gZwAdv zEwv_EIIq=@`20A2z@Yy*E^~gWb6>gro8sYl2D?MAl~`+Go0RQAyDrU>y_)rEfC*~; z*m6+N%=y@TbJiA_A(yLV*UTkAX7|;)_g?wj);873m`m#>~w!F6kS54q~4511rnTl)kz ztEKIMi%h7^4>kyjvEd+4hXoixlYTJ*e;m9zF!(9uVatp!ENuCbZU3@&^l{h&nm=RR zPNfK&5zP-ca<|?c@e0Jjopen;o{^CO97hzGuQIbqVSFxW`UeKWnJ(6$zk)&R8MhS@ z5;|Q1H=<%w=wW@pJgJUmQi8zAhw zaWHIUG(KPsrFl0udz4)1{jKNPzvtk<^5hyJKEDNnP3DGVfxVIri)!y>=5*(m3dm{V%jGPoOer%nne~&w= zLUQOBzBic*c^RD(#=olV-}SKnwJfKof9y*3cVKL5N$(a^AzYmoi-tr=(R9Bo1WBRQ z%S>@#QDdJf6P*29{P9P+;`7n499*YEFC_)WJbOkM9ry4rGk$?B(VbR<$=ZI zQL3?kW)tm=Jo*9pkaUghg9Ak1fG7dKd#J&1YFlqFd}O4Qbe-+)J*_2y_TRZ*NuVH@ zo&^CXQv_SqM9$027ma(y$Dc!bu6_NXcqIPjV5N#XdQc1C?x&E!dFYkC)NRVD%bmtns5Nm`82`Ur>P@&`oXJROgi;OwzhSEEM_Uhv%0k(~iW+52qT{1T~lhB{sd>`^ONk_*!$m;FdeC zx(;aGQC|3TqZp_kN^7@DKipNbmzW;k%IEu*fEa=S(pm2BhjJ*A~LjdQWfAItR@W_l} zhQEkY-`PtUyZ+D3fjr>1e~-b)^atuC8=`I1F=GGXr;Bg@JZ9f_|C{dfZTyOi=pX-K z$iL}uhGVJxbAN3{hClfKc014xQm%InGXZa`G$P_utj6Ji@c8&xI~Vt|2eVgJv_N|T z0R!8|$E8Nx5--!QM+Z7c_K5;$x|MsMpBc*Y@71V!FMClK^wO;&L-EqB^8fgDv+MIo zV4%=it_z2ZOuwVkpl}oA2)wjm`P;iq&?f->8AJOY)j*(H1SIU|R+vb21Y~5fG-Oz! z_wQ@dL(WuoUph;Y{?3GxKoA9?4$cDcvAZ-Zy}c>Mwu zcyEP1&ab^J94k0%ev?m4$a!hg^c>xck5#5Wqls{<{D`8&XTy{xr+5e7+n3 z{#@k*xJEqt%y2q~vnln0d=I)YY}g-B*i=$7{ExvqII0(LxinAPNeq74j>vN1(HyMV5Y8!E@3SiI5LY5QRZ!2Iap<3Fqx8^7=`je=BS> zPtYtB?-ZMahDid$!#Rx!zx|@X@K)c>>*A0(5cfx98i==e&+5AfVl-)JA`|C8ISSTt>Z{)*wirO&)P0o51UxAXMb1WiHn2j}Rc~d^zce znS2&7oiYz0;0!Gz+N7h6Vp4cKWtdi0R?d55z@c;qpr!P_!U-2R^aZp>MvIdJ6-yf9 zSK)tmt8U+>o6RXuLc0d5kx^1=&ri{!1PNo^?@E7BdG7YPzd?1sz4fSZ*{)Ks-@DAu ze~-njj|QBY0UAd~a?I-SuCest&BuV_PZ}+Xn*Qlu=C5vSdP$y31N~L%38Z0=!Xzup zUpx$8{D3ZgfJ(gnhfPF}Swxj)zTTf-!#p1i2O6oofgWA9hx$c*&9gz5zsjp*Q(wW{ z)s^Qlo*i?4szmg3jr;`M;FqURT&JHDr0M1Zobb}^Z+=R*PpR8;Ju!YlKzEh-705o6 z${D6VsG=Nv?;gSlv#-R24L{>Fh z^j!vM*kN$L=Ky(DRD>vV`}zBm5D|gQ=Bp~{8~{IZJ`@oa*4m#c#$wPCNX@|o>Rd;> z{4M=)^ye(<-wG7Z14=bTOqa{&b8ffL{XTlV0^27n67KFAfjpn9Re#0UEWHUuno^1b z#HQ+x7ixKU``aTW^b+zFHMW`qkZ4z{w_^Wr&NDczz;WvfAQ+y4hEzk1^F#RHne(FM z8T?<>`j2rK0RptRqDsyFF99}0MhPJNgG!rFMB3KB@Mo>`Kkx^j=6|qM6~IAG4RN4# zlBBx>2+sb|F&?Rq#Ea0x!}uS0M=Q9m+2`>>lJ4-3fYj>&@ZN&_-SdQiKAUI8NLdfe z(EB%Sj9339-QMM8LRB^kfs}OrzjD~`Ja^^zIIYm_KRknk1e10?=_Sq*VmKV2{AYjYBuA?fA8B%xAnvZRMnfE#>R7QHpN!LFS73P(p z2Qb#qUwe{(UbKe`1sq^{lowVbW+eMV`(i0|z*2|*gh*G*2vft~kSIJzFyqTVWgQFH zRHZNR>Yw-y_xi7J>4NyLWdS)u{@=9+Vgmnva%FAfUH|F6@iPPV7{AP<9$>M8+U*vNyeWD<{qB)8V2=^rT)2?8!Bi zelGghJ;e8Y8T|_=Ljr4tw$a@K{>du+ty9?l)6SWPHFaeHJdUl{0mV*B3kswZ6eVsf zf1jlzM6BYWLmK)h!B^deGFx%K1z zP|l-wxNzL$`)`|ffMSBnsv%*6>kFD&qIAqF9b6}{b?D>bqbCh2>Em5gUTEgjj~Oej z<~HgDP5!tF!9BPpuMtXkutV^=tAg9iD}Aq=O=(}@sE|)wL8Z;UYf0~W+A|d_1rMxQ zNy2+ii_(xa%ArB1>W;1Ll5RoN`~pvWBT+IppplITQSvbh?k{HD%#7Dc zqK(l9_ZofGu3Dmkd1TGvlj6hA55V^k?Gc_$SXC0=8CAz~S-S#MhYHwe)D}I%+Lnus zqcz15e&X{1)}MgV%dFT)gntM|Ht4BgASFwDepHVX(?2kfkJ2OtyO(*pIIaHJMO5^U zrASPST-IlTG2Qix@^M(2W{P9dR`(=$jrHFu#xkSlo-gFyal-m=)(M?`1NHodr}=e~ z)QEQM!L)JJWY@F#9M7ikHdCBn>+hC!fF4MXnja-aQyYtH!hdTmJUsu~B~eqRh7s>D zi?1k5<_?^tSMMpnZ^{m-xKH#NE)3fWva9I}f?a-`-^qQf1Q~sIH=Y2VBd)!?6|@-W zr^mu+bsy!{vbN@czH6yd{+e-c_hwLIUbm&t{qQHCoyAu!ZifJusFAI;Ya23F2?__g zrQshycPo7Wc~62d5Lx-OZX!u5$nq{PX#lnSag8h*_^cYO_)<*jwr zXp*|ZV%GcUmw<2Q0Mh#6`*L2#2%XBu>~-!~+{Gcze_gB%hDNUnABtkfs@_KGxDk%F zLUW|%gJ%YQRUgwq89iSP)iI;PcKqDsRAJJ#mTF+AEwY@rAYj@;G5!ZJy6*YDa4h4hM48UIJ86I2{d!#7% zyaqT%%O}EcpqUEBSio(5dv@NJd4}gBhoARdOP8IS%?OUw6zwHQVY8=>Y|GIBAN{5> zMA>h;h7|N=A3EWfFfg-R1ynG^F=o;4ocB@ULa8QJB{+tBT;M{FT7WBeCbeGcHPewU za~O4(hfyXdX(C#+ME7O=+aT*h_FB~t%}U_SCbJoM+@JLHO)`2B@==549qA3}UO!Vt z`0=;gC6mHWJH>~pYu-AX5LX$dk|{bZEh2|&423K=Y#8NCoB7Ft+dI+jC$@LK(D=Iv zT+0^b=SGYjS4!EnQt?4`PWictFNt0q7Ze`c(I&TRv$~L^m2kOGoXMi0X<@^8cXtX& z{-K%l|Cm!6KMD|i*8mQCkI{=qNdb}F{#aFfz=s+000ts67dpMU&`GRoZ8xR}@u?Ur9Jw7#*+<#U@1OUa!vSjfdThZ%kn1BOT6$WrJ&^;~^ zcE-Girq}|yP{o@XxS)2VAsUZ>S6-wx*AAp{>X?tg{tI2mf@B-RAE3tauc;cHXZhW8V*3w)GfA=W+#-;)30`$vkl=YrmI^@VQ;+a!Z?kV5rs|n$X{7&DW%rF~zS<7iZeQ5FS)dV)wT6_Gtjob`$r&EdA+AGt&C2mcS@w}ca|-mo9A}&0Cj01 zGzK8&!|;z%8gHtrA4cfs9fgFd_>ffN@9&)@{ycA&pmGE1a`NZDIN0^%tST9+ynX8< zOkZO&UNT=&eBB#^D4GWg>JuYYm);k!<_hyNmj#wY`-G0^ZlZ12i|it&8d>{@fx)LO z_sre)2v%S2^>5d$fMou;?G2EPkHimM;o^74A`pJQ^zHPJg+w4xp2{^27{wV^zQO3h z*3zO649qyik) zH#FooIGJl?yT=xx4pFa5Mlw>~L8w(dzWyPko4Qy6aExB`Ol^4K(M0c486Tb{B;@EP z^nC7GWGmq)3(=k3%6SupSX|&A_4dX0(8(@q<*ldW&c3oHoc)YW>zJ;EUz}6T3=hO6HEyQe`9mKxdnn-nR^+fJh+W=T{U2KlqPzm7SntPmTLM_V+b|WZ9qqJCCS!(;Nj{c&Xv##c zv*$V3*2q{SQtlnPaT!>4J?f6k^r8W|Q4%XjtGQ*T10wjp=j>Joa@8f4{hEvsYJc_m zUqzFfK&8ZLpa=f+^2&?N`cXj1VT#EP5J-pwT1o-W^fcaXH&;s|DJGwTKq*HOM(Q7H bHdp?IRIvH&b186wEC_z`?1`G=VK@E;O&P8< literal 19320 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 diff --git a/docs/opsguide/rdadmin.xml b/docs/opsguide/rdadmin.xml index f35e1fe7..fb31f4fb 100644 --- a/docs/opsguide/rdadmin.xml +++ b/docs/opsguide/rdadmin.xml @@ -1684,6 +1684,19 @@ + + + Log events in Syslog + + + + If ticked, log messages for this dropbox will be sent to + the system syslog. Otherwise, these events will be sent to + the file specified by the + Log File: setting below. + + + Log File: diff --git a/docs/tables/dropboxes.txt b/docs/tables/dropboxes.txt index f77eb466..5368eab8 100644 --- a/docs/tables/dropboxes.txt +++ b/docs/tables/dropboxes.txt @@ -24,6 +24,7 @@ METADATA_PATTERN varchar(64) STARTDATE_OFFSET int(11) ENDDATE_OFFSET int(11) FIX_BROKEN_FORMATS enum('N','Y') +LOG_TO_SYSLOG enum('N','Y') LOG_PATH varchar(191) IMPORT_CREATE_DATES enum('N','Y') CREATE_STARTDATE_OFFSET int(11) diff --git a/lib/dbversion.h b/lib/dbversion.h index 8587f872..57e47ee0 100644 --- a/lib/dbversion.h +++ b/lib/dbversion.h @@ -24,7 +24,7 @@ /* * Current Database Version */ -#define RD_VERSION_DATABASE 308 +#define RD_VERSION_DATABASE 309 #endif // DBVERSION_H diff --git a/lib/rdapplication.cpp b/lib/rdapplication.cpp index 81d976fc..37b0f26e 100644 --- a/lib/rdapplication.cpp +++ b/lib/rdapplication.cpp @@ -112,6 +112,8 @@ bool RDApplication::open(QString *err_msg,RDApplication::ErrorType *err_type, int schema=0; QString db_err; bool skip_db_check=false; + int persistent_dropbox_id=-1; + bool ok=false; if(err_type!=NULL) { *err_type=RDApplication::ErrorOk; @@ -127,6 +129,13 @@ bool RDApplication::open(QString *err_msg,RDApplication::ErrorType *err_type, skip_db_check=true; app_cmd_switch->setProcessed(i,true); } + if(app_cmd_switch->key(i)=="--persistent-dropbox-id") { + persistent_dropbox_id=app_cmd_switch->value(i).toUInt(&ok); + if(ok) { + app_command_name=QString().sprintf("dropbox[%u]",persistent_dropbox_id); + } + app_cmd_switch->setProcessed(i,true); + } } // diff --git a/lib/rddropbox.cpp b/lib/rddropbox.cpp index 5f8710b7..623911f4 100644 --- a/lib/rddropbox.cpp +++ b/lib/rddropbox.cpp @@ -300,6 +300,19 @@ void RDDropbox::setFixBrokenFormats(bool state) const } +bool RDDropbox::logToSyslog() const +{ + return RDBool(RDGetSqlValue("DROPBOXES","ID",box_id,"LOG_TO_SYSLOG"). + toString()); +} + + +void RDDropbox::setLogToSyslog(bool state) const +{ + SetRow("LOG_TO_SYSLOG",state); +} + + QString RDDropbox::logPath() const { return RDGetSqlValue("DROPBOXES","ID",box_id,"LOG_PATH").toString(); diff --git a/lib/rddropbox.h b/lib/rddropbox.h index 86b09339..bd0334af 100644 --- a/lib/rddropbox.h +++ b/lib/rddropbox.h @@ -64,6 +64,8 @@ class RDDropbox void setEnddateOffset(int offset) const; bool fixBrokenFormats() const; void setFixBrokenFormats(bool state) const; + bool logToSyslog() const; + void setLogToSyslog(bool state) const; QString logPath() const; void setLogPath(const QString &path) const; bool createDates() const; diff --git a/rdadmin/edit_dropbox.cpp b/rdadmin/edit_dropbox.cpp index 6fb5bc95..4150be87 100644 --- a/rdadmin/edit_dropbox.cpp +++ b/rdadmin/edit_dropbox.cpp @@ -154,26 +154,40 @@ EditDropbox::EditDropbox(int id,bool duplicate,QWidget *parent) label->setAlignment(Qt::AlignRight|Qt::AlignVCenter); // - // Log Path + // Logging // - box_log_path_edit=new QLineEdit(this); - box_log_path_edit->setGeometry(120,120,sizeHint().width()-190,19); - box_log_path_edit->setMaxLength(255); - label= - new QLabel(box_log_path_edit,tr("&Log File:"),this); - label->setGeometry(10,120,105,19); + box_log_to_syslog_check=new QCheckBox(this); + box_log_to_syslog_check->setGeometry(50,124,15,15); + label=new QLabel(box_log_to_syslog_check,tr("Log events in Syslog"),this); + label->setGeometry(70,122,250,19); label->setFont(font); - label->setAlignment(Qt::AlignRight|Qt::AlignVCenter); - button=new QPushButton(tr("Select"),this); - button->setGeometry(sizeHint().width()-60,118,50,23); - button->setFont(normal_font); - connect(button,SIGNAL(clicked()),this,SLOT(selectLogPathData())); + label->setAlignment(Qt::AlignLeft|Qt::AlignVCenter); + + box_log_path_edit=new QLineEdit(this); + box_log_path_edit->setGeometry(120,141,sizeHint().width()-190,19); + box_log_path_edit->setMaxLength(255); + connect(box_log_to_syslog_check,SIGNAL(toggled(bool)), + box_log_path_edit,SLOT(setDisabled(bool))); + box_log_path_label= + new QLabel(box_log_path_edit,tr("&Log File:"),this); + box_log_path_label->setGeometry(10,141,105,19); + box_log_path_label->setFont(font); + box_log_path_label->setAlignment(Qt::AlignRight|Qt::AlignVCenter); + connect(box_log_to_syslog_check,SIGNAL(toggled(bool)), + box_log_path_label,SLOT(setDisabled(bool))); + box_log_path_button=new QPushButton(tr("Select"),this); + box_log_path_button->setGeometry(sizeHint().width()-60,138,50,23); + box_log_path_button->setFont(normal_font); + connect(box_log_path_button,SIGNAL(clicked()), + this,SLOT(selectLogPathData())); + connect(box_log_to_syslog_check,SIGNAL(toggled(bool)), + box_log_path_button,SLOT(setDisabled(bool))); // // Scheduler Codes // box_schedcodes_button=new QPushButton(tr("Scheduler Codes"),this); - box_schedcodes_button->setGeometry(110,145,200,25); + box_schedcodes_button->setGeometry(110,167,200,25); box_schedcodes_button->setFont(font); connect(box_schedcodes_button,SIGNAL(clicked()),this,SLOT(schedcodesData())); @@ -181,10 +195,10 @@ EditDropbox::EditDropbox(int id,bool duplicate,QWidget *parent) // Delete Source // box_delete_source_box=new QCheckBox(this); - box_delete_source_box->setGeometry(90,177,15,15); + box_delete_source_box->setGeometry(90,199,15,15); label=new QLabel(box_delete_source_box,tr("Delete source files after import"), this); - label->setGeometry(110,175,sizeHint().width()-120,20); + label->setGeometry(110,197,sizeHint().width()-120,20); label->setFont(font); label->setAlignment(Qt::AlignLeft|Qt::AlignVCenter); @@ -192,9 +206,9 @@ EditDropbox::EditDropbox(int id,bool duplicate,QWidget *parent) // Force To Mono // box_force_to_mono_box=new QCheckBox(this); - box_force_to_mono_box->setGeometry(90,199,15,15); + box_force_to_mono_box->setGeometry(90,221,15,15); label=new QLabel(box_force_to_mono_box,tr("Force to Monaural"),this); - label->setGeometry(110,197,sizeHint().width()-120,20); + label->setGeometry(110,219,sizeHint().width()-120,20); label->setFont(font); label->setAlignment(Qt::AlignLeft|Qt::AlignVCenter); @@ -202,20 +216,20 @@ EditDropbox::EditDropbox(int id,bool duplicate,QWidget *parent) // Normalization // box_normalization_box=new QCheckBox(this); - box_normalization_box->setGeometry(90,221,15,15); + box_normalization_box->setGeometry(90,243,15,15); label=new QLabel(box_normalization_box,tr("Normalize Levels"),this); - label->setGeometry(110,219,100,20); + label->setGeometry(110,241,100,20); label->setFont(font); label->setAlignment(Qt::AlignLeft|Qt::AlignVCenter); box_normalization_level_spin=new QSpinBox(this); - box_normalization_level_spin->setGeometry(275,219,50,20); + box_normalization_level_spin->setGeometry(275,241,50,20); box_normalization_level_spin->setRange(-100,-1); box_normalization_level_label=new QLabel(tr("Level:"),this); - box_normalization_level_label->setGeometry(210,219,60,20); + box_normalization_level_label->setGeometry(210,241,60,20); box_normalization_level_label->setFont(font); box_normalization_level_label->setAlignment(Qt::AlignVCenter|Qt::AlignRight); box_normalization_level_unit=new QLabel(tr("dBFS"),this); - box_normalization_level_unit->setGeometry(330,219,60,20); + box_normalization_level_unit->setGeometry(330,241,60,20); box_normalization_level_unit->setAlignment(Qt::AlignVCenter|Qt::AlignLeft); connect(box_normalization_box,SIGNAL(toggled(bool)), this,SLOT(normalizationToggledData(bool))); @@ -224,20 +238,20 @@ EditDropbox::EditDropbox(int id,bool duplicate,QWidget *parent) // Autotrim // box_autotrim_box=new QCheckBox(this); - box_autotrim_box->setGeometry(90,245,15,15); + box_autotrim_box->setGeometry(90,267,15,15); label=new QLabel(box_autotrim_box,tr("Autotrim Cuts"),this); - label->setGeometry(110,243,100,20); + label->setGeometry(110,265,100,20); label->setFont(font); label->setAlignment(Qt::AlignLeft|Qt::AlignVCenter); box_autotrim_level_spin=new QSpinBox(this); - box_autotrim_level_spin->setGeometry(275,243,50,20); + box_autotrim_level_spin->setGeometry(275,265,50,20); box_autotrim_level_spin->setRange(-100,-1); box_autotrim_level_label=new QLabel(tr("Level:"),this); - box_autotrim_level_label->setGeometry(210,243,60,20); + box_autotrim_level_label->setGeometry(210,265,60,20); box_autotrim_level_label->setFont(font); box_autotrim_level_label->setAlignment(Qt::AlignVCenter|Qt::AlignRight); box_autotrim_level_unit=new QLabel(tr("dBFS"),this); - box_autotrim_level_unit->setGeometry(330,243,60,20); + box_autotrim_level_unit->setGeometry(330,265,60,20); box_autotrim_level_unit->setAlignment(Qt::AlignVCenter|Qt::AlignLeft); connect(box_autotrim_box,SIGNAL(toggled(bool)), this,SLOT(autotrimToggledData(bool))); @@ -246,51 +260,45 @@ EditDropbox::EditDropbox(int id,bool duplicate,QWidget *parent) // Segue // box_segue_box=new QCheckBox(this); - box_segue_box->setGeometry(90,271,15,15); - label=new QLabel(box_segue_box,tr("Insert Segue Markers"), - this); - label->setGeometry(110,269,sizeHint().width()-40,20); + box_segue_box->setGeometry(90,293,15,15); + label=new QLabel(box_segue_box,tr("Insert Segue Markers"),this); + label->setGeometry(110,291,sizeHint().width()-40,20); label->setFont(font); label->setAlignment(Qt::AlignLeft|Qt::AlignVCenter); - box_segue_level_spin= - new QSpinBox(this); - box_segue_level_spin->setGeometry(300,295,50,20); + box_segue_level_spin=new QSpinBox(this); + box_segue_level_spin->setGeometry(300,317,50,20); box_segue_level_spin->setRange(-100,0); box_segue_level_label= - new QLabel(box_segue_level_spin,tr("Segue Level:"), - this); - box_segue_level_label->setGeometry(120,295,160,20); + new QLabel(box_segue_level_spin,tr("Segue Level:"),this); + box_segue_level_label->setGeometry(120,317,160,20); box_segue_level_label->setFont(font); box_segue_level_label->setAlignment(Qt::AlignVCenter|Qt::AlignRight); - box_segue_level_unit= - new QLabel(box_segue_level_spin,("dBFS"),this); - box_segue_level_unit->setGeometry(360,296,60,20); + box_segue_level_unit=new QLabel(box_segue_level_spin,("dBFS"),this); + box_segue_level_unit->setGeometry(360,318,60,20); box_segue_level_unit->setAlignment(Qt::AlignVCenter|Qt::AlignLeft); box_segue_length_spin=new QSpinBox(this); - box_segue_length_spin->setGeometry(300,320,70,20); + box_segue_length_spin->setGeometry(300,342,70,20); box_segue_length_spin->setRange(0,180000); box_segue_length_label= - new QLabel(box_segue_length_spin,tr("Segue Length:"), - this); - box_segue_length_label->setGeometry(120,320,160,20); + new QLabel(box_segue_length_spin,tr("Segue Length:"),this); + box_segue_length_label->setGeometry(120,342,160,20); box_segue_length_label->setFont(font); box_segue_length_label->setAlignment(Qt::AlignVCenter|Qt::AlignRight); - box_segue_length_unit= - new QLabel(box_segue_length_spin,("msec"),this); - box_segue_length_unit->setGeometry(375,321,60,20); + box_segue_length_unit=new QLabel(box_segue_length_spin,("msec"),this); + box_segue_length_unit->setGeometry(375,343,60,20); box_segue_length_unit->setAlignment(Qt::AlignVCenter|Qt::AlignLeft); connect(box_segue_box,SIGNAL(toggled(bool)), - this,SLOT(segueToggledData(bool))); + this,SLOT(segueToggledData(bool))); // // Use CartChunk ID // box_use_cartchunk_id_box=new QCheckBox(this); - box_use_cartchunk_id_box->setGeometry(90,350,15,15); + box_use_cartchunk_id_box->setGeometry(90,372,15,15); label=new QLabel(box_use_cartchunk_id_box, tr("Get cart number from CartChunk CutID"),this); - label->setGeometry(110,348,sizeHint().width()-40,20); + label->setGeometry(110,370,sizeHint().width()-40,20); label->setFont(font); label->setAlignment(Qt::AlignLeft|Qt::AlignVCenter); @@ -298,10 +306,10 @@ EditDropbox::EditDropbox(int id,bool duplicate,QWidget *parent) // Title from CartChunk ID // box_title_from_cartchunk_id_box=new QCheckBox(this); - box_title_from_cartchunk_id_box->setGeometry(90,374,15,15); + box_title_from_cartchunk_id_box->setGeometry(90,396,15,15); label=new QLabel(box_title_from_cartchunk_id_box, tr("Get cart title from CartChunk CutID"),this); - label->setGeometry(110,372,sizeHint().width()-40,20); + label->setGeometry(110,394,sizeHint().width()-40,20); label->setFont(font); label->setAlignment(Qt::AlignLeft|Qt::AlignVCenter); @@ -309,10 +317,10 @@ EditDropbox::EditDropbox(int id,bool duplicate,QWidget *parent) // Fix Broken Formats // box_fix_broken_formats_box=new QCheckBox(this); - box_fix_broken_formats_box->setGeometry(90,398,15,15); + box_fix_broken_formats_box->setGeometry(90,420,15,15); label=new QLabel(box_fix_broken_formats_box, tr("Attempt to work around malformatted input files"),this); - label->setGeometry(110,396,sizeHint().width()-40,20); + label->setGeometry(110,418,sizeHint().width()-40,20); label->setFont(font); label->setAlignment(Qt::AlignLeft|Qt::AlignVCenter); @@ -321,30 +329,29 @@ EditDropbox::EditDropbox(int id,bool duplicate,QWidget *parent) // box_startoffset_spin= new QSpinBox(this); - box_startoffset_spin->setGeometry(215,422,50,20); + box_startoffset_spin->setGeometry(215,444,50,20); box_startoffset_spin->setRange(-7,7); label=new QLabel(box_startoffset_spin,tr("Offset start date by"),this); - label->setGeometry(90,422,120,20); + label->setGeometry(90,444,120,20); label->setFont(font); - label->setAlignment(Qt::AlignLeft|Qt::AlignVCenter); + label->setAlignment(Qt::AlignRight|Qt::AlignVCenter); label=new QLabel(box_startoffset_spin,tr("days"),this); - label->setGeometry(275,424,100,20); + label->setGeometry(275,446,100,20); label->setFont(font); label->setAlignment(Qt::AlignLeft|Qt::AlignVCenter); // // End Date Offset // - box_endoffset_spin= - new QSpinBox(this); - box_endoffset_spin->setGeometry(215,446,50,20); + box_endoffset_spin=new QSpinBox(this); + box_endoffset_spin->setGeometry(215,468,50,20); box_endoffset_spin->setRange(-7,7); label=new QLabel(box_endoffset_spin,tr("Offset end date by"),this); - label->setGeometry(90,446,120,20); + label->setGeometry(90,468,120,20); label->setFont(font); - label->setAlignment(Qt::AlignLeft|Qt::AlignVCenter); + label->setAlignment(Qt::AlignRight|Qt::AlignVCenter); label=new QLabel(box_endoffset_spin,tr("days"),this); - label->setGeometry(275,446,100,20); + label->setGeometry(275,468,100,20); label->setFont(font); label->setAlignment(Qt::AlignLeft|Qt::AlignVCenter); @@ -352,38 +359,38 @@ EditDropbox::EditDropbox(int id,bool duplicate,QWidget *parent) // Create Dates // box_create_dates_box=new QCheckBox(this); - box_create_dates_box->setGeometry(90,470,15,15); + box_create_dates_box->setGeometry(90,492,15,15); label=new QLabel(box_create_dates_box,tr("Create Dates when no Dates Exist"), this); - label->setGeometry(110,468,sizeHint().width()-40,20); + label->setGeometry(110,490,sizeHint().width()-40,20); label->setFont(font); label->setAlignment(Qt::AlignLeft|Qt::AlignVCenter); - box_create_startdate_offset_spin= - new QSpinBox(this); - box_create_startdate_offset_spin->setGeometry(300,494,50,20); + box_create_startdate_offset_spin=new QSpinBox(this); + box_create_startdate_offset_spin->setGeometry(285,516,50,20); box_create_startdate_offset_spin->setRange(-180,180); box_create_startdate_label= new QLabel(box_create_startdate_offset_spin,tr("Create start date offset:"), this); - box_create_startdate_label->setGeometry(120,494,160,20); + box_create_startdate_label->setGeometry(120,516,160,20); box_create_startdate_label->setFont(font); box_create_startdate_label->setAlignment(Qt::AlignVCenter|Qt::AlignRight); box_create_startdate_unit= new QLabel(box_create_startdate_offset_spin,("days"),this); - box_create_startdate_unit->setGeometry(360,495,60,20); + box_create_startdate_unit->setGeometry(345,517,60,20); box_create_startdate_unit->setAlignment(Qt::AlignVCenter|Qt::AlignLeft); + box_create_enddate_offset_spin=new QSpinBox(this); - box_create_enddate_offset_spin->setGeometry(300,524,50,20); + box_create_enddate_offset_spin->setGeometry(285,538,50,20); box_create_enddate_offset_spin->setRange(-180,180); box_create_enddate_label= new QLabel(box_create_enddate_offset_spin,tr("Create end date offset:"), this); - box_create_enddate_label->setGeometry(120,524,160,20); + box_create_enddate_label->setGeometry(120,536,160,20); box_create_enddate_label->setFont(font); box_create_enddate_label->setAlignment(Qt::AlignVCenter|Qt::AlignRight); box_create_enddate_unit= new QLabel(box_create_enddate_offset_spin,("days"),this); - box_create_enddate_unit->setGeometry(360,524,60,20); + box_create_enddate_unit->setGeometry(345,536,60,20); box_create_enddate_unit->setAlignment(Qt::AlignVCenter|Qt::AlignLeft); connect(box_create_dates_box,SIGNAL(toggled(bool)), this,SLOT(createDatesToggledData(bool))); @@ -451,6 +458,12 @@ EditDropbox::EditDropbox(int id,bool duplicate,QWidget *parent) box_title_from_cartchunk_id_box->setChecked(box_dropbox->titleFromCartchunkId()); box_log_path_edit->setText(box_dropbox->logPath()); box_fix_broken_formats_box->setChecked(box_dropbox->fixBrokenFormats()); + + box_log_to_syslog_check->setChecked(box_dropbox->logToSyslog()); + box_log_path_label->setDisabled(box_dropbox->logToSyslog()); + box_log_path_edit->setDisabled(box_dropbox->logToSyslog()); + box_log_path_button->setDisabled(box_dropbox->logToSyslog()); + box_startoffset_spin->setValue(box_dropbox->startdateOffset()); box_endoffset_spin->setValue(box_dropbox->enddateOffset()); box_create_dates_box->setChecked(box_dropbox->createDates()); @@ -647,6 +660,7 @@ void EditDropbox::okData() } box_dropbox->setUseCartchunkId(box_use_cartchunk_id_box->isChecked()); box_dropbox->setTitleFromCartchunkId(box_title_from_cartchunk_id_box->isChecked()); + box_dropbox->setLogToSyslog(box_log_to_syslog_check->isChecked()); box_dropbox->setLogPath(box_log_path_edit->text()); box_dropbox->setFixBrokenFormats(box_fix_broken_formats_box->isChecked()); box_dropbox->setStartdateOffset(box_startoffset_spin->value()); diff --git a/rdadmin/edit_dropbox.h b/rdadmin/edit_dropbox.h index c0b4a568..4b6da681 100644 --- a/rdadmin/edit_dropbox.h +++ b/rdadmin/edit_dropbox.h @@ -73,7 +73,10 @@ class EditDropbox : public QDialog QLabel *box_force_to_mono_label; QLineEdit *box_metadata_pattern_edit; QLineEdit *box_user_defined_edit; + QCheckBox *box_log_to_syslog_check; + QLabel *box_log_path_label; QLineEdit *box_log_path_edit; + QPushButton *box_log_path_button; QCheckBox *box_delete_source_box; QCheckBox *box_normalization_box; QLabel *box_normalization_level_label; diff --git a/rdadmin/list_dropboxes.cpp b/rdadmin/list_dropboxes.cpp index d36fad89..de171878 100644 --- a/rdadmin/list_dropboxes.cpp +++ b/rdadmin/list_dropboxes.cpp @@ -110,6 +110,8 @@ ListDropboxes::ListDropboxes(const QString &stationname,QWidget *parent) list_dropboxes_view=new RDListView(this); list_dropboxes_view->setFont(list_font); list_dropboxes_view->setAllColumnsShowFocus(true); + list_dropboxes_view->addColumn(tr("ID")); + list_dropboxes_view->setColumnAlignment(0,Qt::AlignVCenter|Qt::AlignRight); list_dropboxes_view->addColumn(tr("Group")); list_dropboxes_view->setColumnAlignment(0,Qt::AlignVCenter|Qt::AlignLeft); list_dropboxes_view->addColumn(tr("Path")); @@ -328,16 +330,22 @@ void ListDropboxes::RefreshItem(RDListViewItem *item) QString sql; RDSqlQuery *q; - sql=QString().sprintf("select DROPBOXES.ID,DROPBOXES.GROUP_NAME,\ - DROPBOXES.PATH,DROPBOXES.NORMALIZATION_LEVEL,\ - DROPBOXES.AUTOTRIM_LEVEL,\ - DROPBOXES.TO_CART,DROPBOXES.USE_CARTCHUNK_ID,\ - DROPBOXES.DELETE_CUTS,DROPBOXES.METADATA_PATTERN,\ - DROPBOXES.FIX_BROKEN_FORMATS,\ - DROPBOXES.SET_USER_DEFINED,GROUPS.COLOR \ - from DROPBOXES left join GROUPS on \ - DROPBOXES.GROUP_NAME=GROUPS.NAME \ - where DROPBOXES.ID=%d",item->id()); + sql=QString("select ")+ + "DROPBOXES.ID,"+ // 00 + "DROPBOXES.GROUP_NAME,"+ // 01 + "DROPBOXES.PATH,"+ // 02 + "DROPBOXES.NORMALIZATION_LEVEL,"+ // 03 + "DROPBOXES.AUTOTRIM_LEVEL,"+ // 04 + "DROPBOXES.TO_CART,"+ // 05 + "DROPBOXES.USE_CARTCHUNK_ID,"+ // 06 + "DROPBOXES.DELETE_CUTS,"+ // 07 + "DROPBOXES.METADATA_PATTERN,"+ // 08 + "DROPBOXES.FIX_BROKEN_FORMATS,"+ // 09 + "DROPBOXES.SET_USER_DEFINED,"+ // 10 + "GROUPS.COLOR "+ // 11 + "from DROPBOXES left join GROUPS on "+ + "DROPBOXES.GROUP_NAME=GROUPS.NAME where "+ + QString().sprintf("DROPBOXES.ID=%d",item->id()); q=new RDSqlQuery(sql); if(q->next()) { WriteItem(item,q); @@ -349,35 +357,36 @@ void ListDropboxes::RefreshItem(RDListViewItem *item) void ListDropboxes::WriteItem(RDListViewItem *item,RDSqlQuery *q) { item->setId(q->value(0).toInt()); - item->setText(0,q->value(1).toString()); - item->setTextColor(0,q->value(11).toString(),QFont::Bold); - item->setText(1,q->value(2).toString()); + item->setText(0,QString().sprintf("%d",q->value(0).toInt())); + item->setText(1,q->value(1).toString()); + item->setTextColor(1,q->value(11).toString(),QFont::Bold); + item->setText(2,q->value(2).toString()); if(q->value(3).toInt()<0) { - item->setText(2,QString().sprintf("%d",q->value(3).toInt()/100)); - } - else { - item->setText(2,tr("[off]")); - } - if(q->value(4).toInt()<0) { - item->setText(3,QString().sprintf("%d",q->value(4).toInt()/100)); + item->setText(3,QString().sprintf("%d",q->value(3).toInt()/100)); } else { item->setText(3,tr("[off]")); } + if(q->value(4).toInt()<0) { + item->setText(4,QString().sprintf("%d",q->value(4).toInt()/100)); + } + else { + item->setText(4,tr("[off]")); + } if(q->value(5).toUInt()>0) { - item->setText(4,QString().sprintf("%06u",q->value(5).toUInt())); + item->setText(5,QString().sprintf("%06u",q->value(5).toUInt())); } else { - item->setText(4,tr("[auto]")); + item->setText(5,tr("[auto]")); } - item->setText(5,q->value(6).toString()); - item->setText(6,q->value(7).toString()); + item->setText(6,q->value(6).toString()); + item->setText(7,q->value(7).toString()); if(q->value(8).toString().isEmpty()) { - item->setText(7,tr("[none]")); + item->setText(8,tr("[none]")); } else { - item->setText(7,q->value(8).toString()); + item->setText(8,q->value(8).toString()); } - item->setText(8,q->value(9).toString()); - item->setText(9,q->value(10).toString()); + item->setText(9,q->value(9).toString()); + item->setText(10,q->value(10).toString()); } diff --git a/rdadmin/rdadmin_cs.ts b/rdadmin/rdadmin_cs.ts index 63e28b41..57110b52 100644 --- a/rdadmin/rdadmin_cs.ts +++ b/rdadmin/rdadmin_cs.ts @@ -1294,6 +1294,10 @@ files, causing any whose files remain to be imported again. Segue Length: + + Log events in Syslog + + EditEncoder @@ -4657,6 +4661,10 @@ Jste si jistý, že chcete pokračovat? D&uplicate + + ID + + ListEncoders diff --git a/rdadmin/rdadmin_de.ts b/rdadmin/rdadmin_de.ts index 77ad2a78..79855cca 100644 --- a/rdadmin/rdadmin_de.ts +++ b/rdadmin/rdadmin_de.ts @@ -1218,6 +1218,10 @@ files, causing any whose files remain to be imported again. Segue Length: + + Log events in Syslog + + EditEncoder @@ -4519,6 +4523,10 @@ anzeigen D&uplicate + + ID + + ListEncoders diff --git a/rdadmin/rdadmin_es.ts b/rdadmin/rdadmin_es.ts index 65e8aea2..2a013c75 100644 --- a/rdadmin/rdadmin_es.ts +++ b/rdadmin/rdadmin_es.ts @@ -1296,6 +1296,10 @@ files, causing any whose files remain to be imported again. Segue Length: + + Log events in Syslog + + EditEncoder @@ -4624,6 +4628,10 @@ Are you sure you want to continue? D&uplicate + + ID + + ListEncoders diff --git a/rdadmin/rdadmin_fr.ts b/rdadmin/rdadmin_fr.ts index 41382d65..223a1d6f 100644 --- a/rdadmin/rdadmin_fr.ts +++ b/rdadmin/rdadmin_fr.ts @@ -921,6 +921,10 @@ files, causing any whose files remain to be imported again. Segue Length: + + Log events in Syslog + + EditEndpoint @@ -3770,6 +3774,10 @@ PARTICULAR PURPOSE. Touch the "View License" button for details.D&uplicate + + ID + + ListEndpoints diff --git a/rdadmin/rdadmin_nb.ts b/rdadmin/rdadmin_nb.ts index 7ac6e2e8..60dd71a8 100644 --- a/rdadmin/rdadmin_nb.ts +++ b/rdadmin/rdadmin_nb.ts @@ -1200,6 +1200,10 @@ files, causing any whose files remain to be imported again. Segue Length: + + Log events in Syslog + + EditEncoder @@ -4424,6 +4428,10 @@ Klikk på "Lisens"-knappen for fleire opplysningar. D&uplicate + + ID + + ListEncoders diff --git a/rdadmin/rdadmin_nn.ts b/rdadmin/rdadmin_nn.ts index 7ac6e2e8..60dd71a8 100644 --- a/rdadmin/rdadmin_nn.ts +++ b/rdadmin/rdadmin_nn.ts @@ -1200,6 +1200,10 @@ files, causing any whose files remain to be imported again. Segue Length: + + Log events in Syslog + + EditEncoder @@ -4424,6 +4428,10 @@ Klikk på "Lisens"-knappen for fleire opplysningar. D&uplicate + + ID + + ListEncoders diff --git a/rdadmin/rdadmin_pt_BR.ts b/rdadmin/rdadmin_pt_BR.ts index ead24f5b..c9899026 100644 --- a/rdadmin/rdadmin_pt_BR.ts +++ b/rdadmin/rdadmin_pt_BR.ts @@ -1189,6 +1189,10 @@ files, causing any whose files remain to be imported again. Segue Length: + + Log events in Syslog + + EditEncoder @@ -4499,6 +4503,10 @@ FINALIDADE PARTICULAR. Aperte o botão VER LICENÇA para mais detalhes.D&uplicate + + ID + + ListEncoders diff --git a/rdservice/startup.cpp b/rdservice/startup.cpp index 5990c9a0..dd9ecca7 100644 --- a/rdservice/startup.cpp +++ b/rdservice/startup.cpp @@ -210,18 +210,19 @@ bool MainObject::StartDropboxes(QString *err_msg) "DELETE_CUTS,"+ // 08 "METADATA_PATTERN,"+ // 09 "FIX_BROKEN_FORMATS,"+ // 10 - "LOG_PATH,"+ // 11 - "DELETE_SOURCE,"+ // 12 - "STARTDATE_OFFSET,"+ // 13 - "ENDDATE_OFFSET,"+ // 14 - "ID,"+ // 15 - "IMPORT_CREATE_DATES,"+ // 16 - "CREATE_STARTDATE_OFFSET,"+ // 17 - "CREATE_ENDDATE_OFFSET,"+ // 18 - "SET_USER_DEFINED,"+ // 19 - "FORCE_TO_MONO,"+ // 20 - "SEGUE_LEVEL,"+ // 21 - "SEGUE_LENGTH "+ // 22 + "LOG_TO_SYSLOG,"+ // 11 + "LOG_PATH,"+ // 12 + "DELETE_SOURCE,"+ // 13 + "STARTDATE_OFFSET,"+ // 14 + "ENDDATE_OFFSET,"+ // 15 + "ID,"+ // 16 + "IMPORT_CREATE_DATES,"+ // 17 + "CREATE_STARTDATE_OFFSET,"+ // 18 + "CREATE_ENDDATE_OFFSET,"+ // 19 + "SET_USER_DEFINED,"+ // 20 + "FORCE_TO_MONO,"+ // 21 + "SEGUE_LEVEL,"+ // 22 + "SEGUE_LENGTH "+ // 23 "from DROPBOXES where "+ "STATION_NAME=\""+RDEscapeString(rda->config()->stationName())+"\""; q=new RDSqlQuery(sql); @@ -229,7 +230,7 @@ bool MainObject::StartDropboxes(QString *err_msg) QStringList args; args.push_back(QString().sprintf("--persistent-dropbox-id=%d", - q->value(15).toInt())); + q->value(16).toInt())); args.push_back("--drop-box"); sql=QString("select SCHED_CODE from DROPBOX_SCHED_CODES where ")+ QString().sprintf("DROPBOX_ID=%d",q->value(0).toInt()); @@ -249,11 +250,11 @@ bool MainObject::StartDropboxes(QString *err_msg) if(q->value(6).toString()=="Y") { args.push_back("--use-cartchunk-cutid"); } - if(q->value(21).toInt()<1) { + if(q->value(22).toInt()<1) { args.push_back(QString().sprintf("--segue-level=%d", - q->value(21).toInt())); + q->value(22).toInt())); args.push_back(QString().sprintf("--segue-length=%u", - q->value(22).toUInt())); + q->value(23).toUInt())); } if(q->value(7).toString()=="Y") { args.push_back("--title-from-cartchunk-cutid"); @@ -261,7 +262,7 @@ bool MainObject::StartDropboxes(QString *err_msg) if(q->value(8).toString()=="Y") { args.push_back("--delete-cuts"); } - if(q->value(20).toString()=="Y") { + if(q->value(21).toString()=="Y") { args.push_back("--to-mono"); } if(!q->value(9).toString().isEmpty()) { @@ -270,29 +271,29 @@ bool MainObject::StartDropboxes(QString *err_msg) if(q->value(10).toString()=="Y") { args.push_back("--fix-broken-formats"); } - if(q->value(12).toString()=="Y") { + if(q->value(13).toString()=="Y") { args.push_back("--delete-source"); } - if(q->value(16).toString()=="Y") { + if(q->value(17).toString()=="Y") { args.push_back(QString().sprintf("--create-startdate-offset=%d", - q->value(17).toInt())); - args.push_back(QString().sprintf("--create-enddate-offset=%d", q->value(18).toInt())); + args.push_back(QString().sprintf("--create-enddate-offset=%d", + q->value(19).toInt())); } - if(!q->value(19).toString().isEmpty()) { - args.push_back(QString("--set-user-defined=")+q->value(19).toString()); + if(!q->value(20).toString().isEmpty()) { + args.push_back(QString("--set-user-defined=")+q->value(20).toString()); } args.push_back(QString().sprintf("--startdate-offset=%d", - q->value(13).toInt())); - args.push_back(QString().sprintf("--enddate-offset=%d", q->value(14).toInt())); - if(!q->value(11).toString().isEmpty()) { - QFileInfo *fileinfo=new QFileInfo(q->value(11).toString()); - args.push_back(QString().sprintf("--log-filename=%s", - (const char *)fileinfo->fileName())); - args.push_back(QString().sprintf("--log-directory=%s", - (const char *)fileinfo->absolutePath())); - args.push_back("--verbose"); + args.push_back(QString().sprintf("--enddate-offset=%d", + q->value(15).toInt())); + if(RDBool(q->value(11).toString())) { + args.push_back("--log-syslog"); + } + else { + if(!q->value(12).toString().isEmpty()) { + args.push_back("--log-filename="+q->value(12).toString()); + } } args.push_back(q->value(1).toString()); args.push_back(q->value(2).toString()); diff --git a/utils/rddbmgr/revertschema.cpp b/utils/rddbmgr/revertschema.cpp index 5b447220..33279d30 100644 --- a/utils/rddbmgr/revertschema.cpp +++ b/utils/rddbmgr/revertschema.cpp @@ -40,6 +40,19 @@ bool MainObject::RevertSchema(int cur_schema,int set_schema,QString *err_msg) // NEW SCHEMA REVERSIONS GO HERE... + + // + // Revert 309 + // + if((cur_schema==309)&&(set_schemacur_schema)) { + sql=QString("alter table DROPBOXES add column ")+ + "LOG_TO_SYSLOG enum('N','Y') not null "+ + "default 'Y' after FIX_BROKEN_FORMATS"; + if(!RDSqlQuery::apply(sql,err_msg)) { + return false; + } + sql=QString("update DROPBOXES set LOG_TO_SYSLOG='N' where ")+ + "LOG_PATH is not null"; + if(!RDSqlQuery::apply(sql,err_msg)) { + return false; + } + + WriteSchemaVersion(++cur_schema); + } + // NEW SCHEMA UPDATES GO HERE... diff --git a/utils/rdimport/rdimport.cpp b/utils/rdimport/rdimport.cpp index dae818bf..03010619 100644 --- a/utils/rdimport/rdimport.cpp +++ b/utils/rdimport/rdimport.cpp @@ -36,10 +36,11 @@ #include #include -#include #include +#include #include #include +#include #include #include #include @@ -72,7 +73,6 @@ MainObject::MainObject(QObject *parent) import_verbose=false; import_log_syslog=false; import_log_file=false; - import_log_directory=""; import_log_filename=""; import_single_cart=false; import_use_cartchunk_cutid=false; @@ -123,10 +123,6 @@ MainObject::MainObject(QObject *parent) import_log_syslog=true; rda->cmdSwitch()->setProcessed(i,true); } - if(rda->cmdSwitch()->key(i)=="--log-directory") { - import_log_directory=rda->cmdSwitch()->value(i); - rda->cmdSwitch()->setProcessed(i,true); - } if(rda->cmdSwitch()->key(i)=="--log-filename") { import_log_filename=rda->cmdSwitch()->value(i); rda->cmdSwitch()->setProcessed(i,true); @@ -434,6 +430,7 @@ MainObject::MainObject(QObject *parent) Log(LOG_ERR,QString().sprintf("rdimport: --metadata-pattern and --xml are mutually exclusive\n")); exit(255); } + /* if((!import_log_directory.isEmpty())&&import_log_filename.isEmpty()) { Log(LOG_ERR,QString().sprintf("rdimport: --log-directory requires --log-filename\n")); exit(255); @@ -442,6 +439,7 @@ MainObject::MainObject(QObject *parent) Log(LOG_ERR,QString().sprintf("rdimport: --log-filename requires --log-directory\n")); exit(255); } + */ if((!import_log_filename.isEmpty())&&import_log_syslog) { Log(LOG_ERR,QString().sprintf("rdimport: --log-filename and --log-syslog are mutually exclusive\n")); exit(255); @@ -581,181 +579,179 @@ MainObject::MainObject(QObject *parent) // // Print Status Messages // - if(import_verbose) { - Log(LOG_INFO,QString("rdimport started\n")); + Log(LOG_INFO,QString("rdimport started\n")); - Log(LOG_INFO,QString().sprintf("RDImport v%s\n",VERSION)); - if(import_to_mono) { - Log(LOG_INFO,QString(" Force to Mono is ON\n")); + Log(LOG_INFO,QString().sprintf("RDImport v%s\n",VERSION)); + if(import_to_mono) { + Log(LOG_INFO,QString(" Force to Mono is ON\n")); + } + else { + Log(LOG_INFO,QString(" Force to Mono is OFF\n")); + } + if(import_normalization_level==0) { + Log(LOG_INFO,QString(" Normalization is OFF\n")); + } + else { + Log(LOG_INFO,QString().sprintf(" Normalization level = %d dB\n",import_normalization_level/100)); + } + if(import_autotrim_level==0) { + Log(LOG_INFO,QString(" AutoTrim is OFF\n")); + } + else { + Log(LOG_INFO,QString().sprintf(" AutoTrim level = %d dB\n",import_autotrim_level/100)); + } + if(import_cart_number==0) { + if(import_use_cartchunk_cutid) { + Log(LOG_INFO,QString(" Destination cart is taken from CartChunk CutID\n")); } else { - Log(LOG_INFO,QString(" Force to Mono is OFF\n")); + Log(LOG_INFO,QString(" Destination cart is AUTO\n")); } - if(import_normalization_level==0) { - Log(LOG_INFO,QString(" Normalization is OFF\n")); - } - else { - Log(LOG_INFO,QString().sprintf(" Normalization level = %d dB\n",import_normalization_level/100)); - } - if(import_autotrim_level==0) { - Log(LOG_INFO,QString(" AutoTrim is OFF\n")); - } - else { - Log(LOG_INFO,QString().sprintf(" AutoTrim level = %d dB\n",import_autotrim_level/100)); - } - if(import_cart_number==0) { - if(import_use_cartchunk_cutid) { - Log(LOG_INFO,QString(" Destination cart is taken from CartChunk CutID\n")); - } - else { - Log(LOG_INFO,QString(" Destination cart is AUTO\n")); - } - } - else { - Log(LOG_INFO,QString().sprintf(" Destination cart is %06u\n",import_cart_number)); - } - if(import_single_cart) { - Log(LOG_INFO,QString(" Single cart mode is ON\n")); - } - else { - Log(LOG_INFO,QString(" Single cart mode is OFF\n")); - } - if(import_title_from_cartchunk_cutid) { - Log(LOG_INFO,QString(" Destination cart title is taken from CartChunk CutID\n")); - } - if(import_cart_number_offset!=0) { - Log(LOG_INFO,QString().sprintf(" Cart number offset is %d\n",import_cart_number_offset)); - } - if(import_delete_source) { - Log(LOG_INFO,QString(" Delete source mode is ON\n")); - } - else { - Log(LOG_INFO,QString(" Delete source mode is OFF\n")); - } - if(import_delete_cuts) { - Log(LOG_INFO,QString(" Delete cuts mode is ON\n")); - } - else { - Log(LOG_INFO,QString(" Delete cuts mode is OFF\n")); - } - if(import_drop_box) { - Log(LOG_INFO,QString(" DropBox mode is ON\n")); - } - else { - Log(LOG_INFO,QString(" DropBox mode is OFF\n")); - } - if(import_add_scheduler_codes.size()>0) { - Log(LOG_INFO,QString(" Adding Scheduler Code(s):\n")); - for(unsigned i=0;i=0) { - Log(LOG_INFO,QString().sprintf(" Persistent DropBox ID = %d\n",import_persistent_dropbox_id)); - } - if(!import_string_agency.isNull()) { - Log(LOG_INFO,QString().sprintf(" Agency set to: %s\n",(const char *)import_string_agency)); - } - if(!import_string_album.isNull()) { - Log(LOG_INFO,QString().sprintf(" Album set to: %s\n",(const char *)import_string_album)); - } - if(!import_string_artist.isNull()) { - Log(LOG_INFO,QString().sprintf(" Artist set to: %s\n",(const char *)import_string_artist)); - } - if(import_string_bpm!=0) { - Log(LOG_INFO,QString().sprintf(" BPM set to: %d\n",import_string_bpm)); - } - if(!import_string_client.isNull()) { - Log(LOG_INFO,QString().sprintf(" Client set to: %s\n",(const char *)import_string_client)); - } - if(!import_string_composer.isNull()) { - Log(LOG_INFO,QString().sprintf(" Composer set to: %s\n",(const char *)import_string_composer)); - } - if(!import_string_conductor.isNull()) { - Log(LOG_INFO,QString().sprintf(" Conductor set to: %s\n",(const char *)import_string_conductor)); - } - if(!import_string_description.isNull()) { - Log(LOG_INFO,QString().sprintf(" Description set to: %s\n", - (const char *)import_string_description)); - } - if(!import_string_label.isNull()) { - Log(LOG_INFO,QString().sprintf(" Label set to: %s\n",(const char *)import_string_label)); - } - if(!import_string_outcue.isNull()) { - Log(LOG_INFO,QString().sprintf(" Outcue set to: %s\n",(const char *)import_string_outcue)); - } - if(!import_string_publisher.isNull()) { - Log(LOG_INFO,QString().sprintf(" Publisher set to: %s\n",(const char *)import_string_publisher)); - } - if(!import_string_song_id.isNull()) { - Log(LOG_INFO,QString().sprintf(" Song ID set to: %s\n",(const char *)import_string_song_id)); - } - if(!import_string_title.isNull()) { - Log(LOG_INFO,QString().sprintf(" Title set to: %s\n",(const char *)import_string_title)); - } - if(!import_string_user_defined.isNull()) { - Log(LOG_INFO,QString().sprintf(" User Defined set to: %s\n", - (const char *)import_string_user_defined)); - } - if(import_string_year!=0) { - Log(LOG_INFO,QString().sprintf(" Year set to: %d\n",import_string_year)); - } - if(import_xml) { - Log(LOG_INFO,QString().sprintf(" Importing RDXML metadata from external file\n")); - } - import_cut_markers->dump(); - import_talk_markers->dump(); - import_hook_markers->dump(); - import_segue_markers->dump(); - import_fadedown_marker->dump(); - import_fadeup_marker->dump(); - Log(LOG_INFO,QString(" Files to process:\n")); - for(unsigned i=import_file_key;icmdSwitch()->keys();i++) { - Log(LOG_INFO,QString().sprintf(" \"%s\"\n",(const char *)rda->cmdSwitch()->key(i))); + } + else { + Log(LOG_INFO,QString().sprintf(" Destination cart is %06u\n",import_cart_number)); + } + if(import_single_cart) { + Log(LOG_INFO,QString(" Single cart mode is ON\n")); + } + else { + Log(LOG_INFO,QString(" Single cart mode is OFF\n")); + } + if(import_title_from_cartchunk_cutid) { + Log(LOG_INFO,QString(" Destination cart title is taken from CartChunk CutID\n")); + } + if(import_cart_number_offset!=0) { + Log(LOG_INFO,QString().sprintf(" Cart number offset is %d\n",import_cart_number_offset)); + } + if(import_delete_source) { + Log(LOG_INFO,QString(" Delete source mode is ON\n")); + } + else { + Log(LOG_INFO,QString(" Delete source mode is OFF\n")); + } + if(import_delete_cuts) { + Log(LOG_INFO,QString(" Delete cuts mode is ON\n")); + } + else { + Log(LOG_INFO,QString(" Delete cuts mode is OFF\n")); + } + if(import_drop_box) { + Log(LOG_INFO,QString(" DropBox mode is ON\n")); + } + else { + Log(LOG_INFO,QString(" DropBox mode is OFF\n")); + } + if(import_add_scheduler_codes.size()>0) { + Log(LOG_INFO,QString(" Adding Scheduler Code(s):\n")); + for(unsigned i=0;i=0) { + Log(LOG_INFO,QString().sprintf(" Persistent DropBox ID = %d\n",import_persistent_dropbox_id)); + } + if(!import_string_agency.isNull()) { + Log(LOG_INFO,QString().sprintf(" Agency set to: %s\n",(const char *)import_string_agency)); + } + if(!import_string_album.isNull()) { + Log(LOG_INFO,QString().sprintf(" Album set to: %s\n",(const char *)import_string_album)); + } + if(!import_string_artist.isNull()) { + Log(LOG_INFO,QString().sprintf(" Artist set to: %s\n",(const char *)import_string_artist)); + } + if(import_string_bpm!=0) { + Log(LOG_INFO,QString().sprintf(" BPM set to: %d\n",import_string_bpm)); + } + if(!import_string_client.isNull()) { + Log(LOG_INFO,QString().sprintf(" Client set to: %s\n",(const char *)import_string_client)); + } + if(!import_string_composer.isNull()) { + Log(LOG_INFO,QString().sprintf(" Composer set to: %s\n",(const char *)import_string_composer)); + } + if(!import_string_conductor.isNull()) { + Log(LOG_INFO,QString().sprintf(" Conductor set to: %s\n",(const char *)import_string_conductor)); + } + if(!import_string_description.isNull()) { + Log(LOG_INFO,QString().sprintf(" Description set to: %s\n", + (const char *)import_string_description)); + } + if(!import_string_label.isNull()) { + Log(LOG_INFO,QString().sprintf(" Label set to: %s\n",(const char *)import_string_label)); + } + if(!import_string_outcue.isNull()) { + Log(LOG_INFO,QString().sprintf(" Outcue set to: %s\n",(const char *)import_string_outcue)); + } + if(!import_string_publisher.isNull()) { + Log(LOG_INFO,QString().sprintf(" Publisher set to: %s\n",(const char *)import_string_publisher)); + } + if(!import_string_song_id.isNull()) { + Log(LOG_INFO,QString().sprintf(" Song ID set to: %s\n",(const char *)import_string_song_id)); + } + if(!import_string_title.isNull()) { + Log(LOG_INFO,QString().sprintf(" Title set to: %s\n",(const char *)import_string_title)); + } + if(!import_string_user_defined.isNull()) { + Log(LOG_INFO,QString().sprintf(" User Defined set to: %s\n", + (const char *)import_string_user_defined)); + } + if(import_string_year!=0) { + Log(LOG_INFO,QString().sprintf(" Year set to: %d\n",import_string_year)); + } + if(import_xml) { + Log(LOG_INFO,QString().sprintf(" Importing RDXML metadata from external file\n")); + } + import_cut_markers->dump(); + import_talk_markers->dump(); + import_hook_markers->dump(); + import_segue_markers->dump(); + import_fadedown_marker->dump(); + import_fadeup_marker->dump(); + Log(LOG_INFO,QString(" Files to process:\n")); + for(unsigned i=import_file_key;icmdSwitch()->keys();i++) { + Log(LOG_INFO,QString().sprintf(" \"%s\"\n",(const char *)rda->cmdSwitch()->key(i))); + } // // Setup Signal Handling @@ -845,11 +841,8 @@ void MainObject::userData() // Clean Up and Exit // delete import_group; - // delete import_cmd; - if(import_verbose) { - Log(LOG_INFO,QString("rdimport finished\n")); - } + Log(LOG_INFO,QString("rdimport finished\n")); exit(0); } @@ -898,9 +891,7 @@ void MainObject::RunDropBox() sleep(RDIMPORT_DROPBOX_SCAN_INTERVAL); } while(import_run); - if(import_verbose) { - Log(LOG_INFO,QString("rdimport stopped\n")); - } + Log(LOG_INFO,QString("rdimport stopped\n")); } @@ -971,15 +962,11 @@ MainObject::Result MainObject::ImportFile(const QString &filename, } else { if(import_fix_broken_formats) { - if(import_verbose) { - Log(LOG_WARNING,QString().sprintf(" File \"%s\" appears to be malformed, trying workaround ... ", - (const char *)RDGetBasePart(filename).utf8())); - } + Log(LOG_WARNING,QString().sprintf(" File \"%s\" appears to be malformed, trying workaround ... ", + (const char *)RDGetBasePart(filename).utf8())); delete wavefile; if((wavefile=FixFile(filename,wavedata))==NULL) { - if(import_verbose) { - Log(LOG_WARNING,QString().sprintf("failed.\n")); - } + Log(LOG_WARNING,QString().sprintf("failed.\n")); Log(LOG_WARNING,QString().sprintf( " File \"%s\" is not readable or not a recognized format, skipping...\n", (const char *)RDGetBasePart(filename).utf8())); @@ -996,9 +983,7 @@ MainObject::Result MainObject::ImportFile(const QString &filename, } return MainObject::FileBad; } - if(import_verbose) { - Log(LOG_WARNING,QString().sprintf("success.\n")); - } + Log(LOG_WARNING,QString().sprintf("success.\n")); effective_filename=import_temp_fix_filename; } else { @@ -1122,33 +1107,29 @@ MainObject::Result MainObject::ImportFile(const QString &filename, settings->setAutotrimLevel(import_autotrim_level/100); conv->setDestinationSettings(settings); conv->setUseMetadata(cart_created); - if(import_verbose) { - if(wavedata->title().length()==0 || ( (wavedata->title().length()>0) && (wavedata->title()[0] == '\0')) ) { - Log(LOG_INFO,QString().sprintf(" Importing file \"%s\" to cart %06u ... ", - (const char *)RDGetBasePart(filename).utf8(),*cartnum)); + if(wavedata->title().length()==0 || ( (wavedata->title().length()>0) && (wavedata->title()[0] == '\0')) ) { + Log(LOG_INFO,QString().sprintf(" Importing file \"%s\" to cart %06u ... ", + (const char *)RDGetBasePart(filename).utf8(),*cartnum)); + } + else { + if(import_string_title.isNull()) { + Log(LOG_INFO,QString().sprintf(" Importing file \"%s\" [%s] to cart %06u ... ", + (const char *)RDGetBasePart(filename).utf8(), + (const char *)wavedata->title().stripWhiteSpace().utf8(), + *cartnum)); } else { - if(import_string_title.isNull()) { - Log(LOG_INFO,QString().sprintf(" Importing file \"%s\" [%s] to cart %06u ... ", - (const char *)RDGetBasePart(filename).utf8(), - (const char *)wavedata->title().stripWhiteSpace().utf8(), - *cartnum)); - } - else { - Log(LOG_INFO,QString().sprintf(" Importing file \"%s\" [%s] to cart %06u ... ", - (const char *)RDGetBasePart(filename).utf8(), - (const char *)import_string_title.stripWhiteSpace().utf8(), - *cartnum)); - } + Log(LOG_INFO,QString().sprintf(" Importing file \"%s\" [%s] to cart %06u ... ", + (const char *)RDGetBasePart(filename).utf8(), + (const char *)import_string_title.stripWhiteSpace().utf8(), + *cartnum)); } } switch(conv_err=conv->runImport(rda->user()->name(),rda->user()->password(), &audio_conv_err)) { case RDAudioImport::ErrorOk: - if(import_verbose) { - Log(LOG_INFO,QString().sprintf("done.\n")); - } + Log(LOG_INFO,QString().sprintf("done.\n")); break; default: @@ -1356,9 +1337,7 @@ MainObject::Result MainObject::ImportFile(const QString &filename, if(import_delete_source) { unlink(filename.utf8()); - if(import_verbose) { - Log(LOG_INFO,QString().sprintf(" Deleted file \"%s\"\n",(const char *)RDGetBasePart(filename).utf8())); - } + Log(LOG_INFO,QString().sprintf(" Deleted file \"%s\"\n",(const char *)RDGetBasePart(filename).utf8())); } if(!import_run) { exit(0); @@ -1887,9 +1866,8 @@ bool MainObject::VerifyPattern(const QString &pattern) void MainObject::DeleteCuts(unsigned cartnum) { - if(import_verbose) { - Log(LOG_INFO,QString().sprintf(" Deleting cuts from cart %06u\n",cartnum)); - } + Log(LOG_INFO,QString().sprintf(" Deleting cuts from cart %06u\n",cartnum)); + unsigned dev; RDCart *cart=new RDCart(cartnum); cart->removeAllCuts(rda->station(),rda->user(),rda->config()); @@ -1978,23 +1956,17 @@ void MainObject::ReadXmlFile(const QString &basename,RDWaveData *wavedata) const xmlname+=f0[i]+"."; } xmlname+="xml"; - if(import_verbose) { - Log(LOG_INFO,QString().sprintf(" Reading xml metadata from \"%s\": ",(const char *)xmlname)); - } + Log(LOG_INFO,QString().sprintf(" Reading xml metadata from \"%s\": ",(const char *)xmlname)); // // Read XML // wavedata->clear(); if((f=fopen(xmlname,"r"))==NULL) { - if(import_verbose) { - Log(LOG_WARNING,QString().sprintf("failed [%s]\n",strerror(errno))); - return; - } - } - if(import_verbose) { - Log(LOG_INFO,QString("success\n")); + Log(LOG_WARNING,QString().sprintf("failed [%s]\n",strerror(errno))); + return; } + Log(LOG_INFO,QString("success\n")); while(fgets(line,1024,f)!=NULL) { xml+=line; } @@ -2023,16 +1995,30 @@ void MainObject::SendNotification(RDNotification::Action action, void MainObject::Log(int prio,const QString &msg) const { QString m=msg; + FILE *f=NULL; - if (import_drop_box||import_log_syslog||import_log_file) { - rda->syslog(prio,m.replace(QRegExp("^rdimport: "),"").simplified()); + if(import_log_syslog) { + rda->syslog(prio,msg.trimmed()); + } + if(!import_log_filename.isEmpty()) { + QDateTime now=QDateTime::currentDateTime(); + QString filename=RDDateDecode(import_log_filename,now.date(), + rda->station(),rda->config()); + if((f=fopen(filename.toUtf8(),"a"))!=NULL) { + fprintf(f,"%s: %s", + (const char *)now.toString("MMM dd hh:mm:ss").toUtf8(), + (const char *)msg.toUtf8()); + fflush(f); + fclose(f); + } + } + + if(prio==LOG_ERR) { + fprintf(stderr,"%s",(const char *)msg); + fflush(stderr); } else { - if(prio==LOG_ERR) { - fprintf(stderr,"%s",(const char *)msg); - fflush(stderr); - } - else { + if(import_verbose) { fprintf(stdout,"%s",(const char *)msg); fflush(stdout); } diff --git a/utils/rdimport/rdimport.h b/utils/rdimport/rdimport.h index 7bf8019b..d682745b 100644 --- a/utils/rdimport/rdimport.h +++ b/utils/rdimport/rdimport.h @@ -2,7 +2,7 @@ // // A Batch Importer for Rivendell. // -// (C) Copyright 2002-2009,2016-2018 Fred Gleason +// (C) Copyright 2002-2019 Fred Gleason // // 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 @@ -80,7 +80,7 @@ class MainObject : public QObject bool import_verbose; bool import_log_syslog; bool import_log_file; - QString import_log_directory; + // QString import_log_directory; QString import_log_filename; bool import_to_mono; bool import_use_cartchunk_cutid; From 602373c3e2e425450b107378b43f419d27d21605 Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Wed, 21 Aug 2019 14:51:46 -0400 Subject: [PATCH 04/35] 2019-08-21 Fred Gleason * Added code to dump the parent query to STDERR when an invalid 'RDSqlQuery::value()' is requested. --- ChangeLog | 3 +++ lib/rddb.cpp | 12 ++++++++++++ lib/rddb.h | 1 + 3 files changed, 16 insertions(+) diff --git a/ChangeLog b/ChangeLog index 1a8fd5b9..2105a5ec 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18932,3 +18932,6 @@ * Removed the '--log-directory=' switch from rdimport(1). * Added an 'ID' column to the list of dropbox configurations in the 'Rivendell Dropbox Configurations; dialog in rdadmin(1). +2019-08-21 Fred Gleason + * Added code to dump the parent query to STDERR when an invalid + 'RDSqlQuery::value()' is requested. diff --git a/lib/rddb.cpp b/lib/rddb.cpp index b41b4e5b..cff9f1fc 100644 --- a/lib/rddb.cpp +++ b/lib/rddb.cpp @@ -95,6 +95,18 @@ int RDSqlQuery::columns() const } +QVariant RDSqlQuery::value(int index) const +{ + QVariant ret=QSqlQuery::value(index); + + if(!ret.isValid()) { + fprintf(stderr,"for query: %s\n\n",(const char *)executedQuery().toUtf8()); + } + + return ret; +} + + QVariant RDSqlQuery::run(const QString &sql,bool *ok) { QVariant ret; diff --git a/lib/rddb.h b/lib/rddb.h index ac648636..f37222f6 100644 --- a/lib/rddb.h +++ b/lib/rddb.h @@ -33,6 +33,7 @@ class RDSqlQuery : public QSqlQuery public: RDSqlQuery(const QString &query = QString::null,bool reconnect=true); int columns() const; + QVariant value(int index) const; static QVariant run(const QString &sql,bool *ok=NULL); static bool apply(const QString &sql,QString *err_msg=NULL); static int rows(const QString &sql); From 51c915010ca6135221baa7b5f51255fd8feb6399 Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Wed, 21 Aug 2019 15:40:08 -0400 Subject: [PATCH 05/35] 2019-08-21 Fred Gleason * Refactored code to eliminate 'QSqlQuery::value: not positioned on a valid record' errors when starting rdairplay(1). --- ChangeLog | 3 +++ rdairplay/loglinebox.cpp | 21 +++++++++++++-------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/ChangeLog b/ChangeLog index 2105a5ec..67b91116 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18935,3 +18935,6 @@ 2019-08-21 Fred Gleason * Added code to dump the parent query to STDERR when an invalid 'RDSqlQuery::value()' is requested. +2019-08-21 Fred Gleason + * Refactored code to eliminate 'QSqlQuery::value: not positioned + on a valid record' errors when starting rdairplay(1). diff --git a/rdairplay/loglinebox.cpp b/rdairplay/loglinebox.cpp index 9830b84b..06a2e73b 100644 --- a/rdairplay/loglinebox.cpp +++ b/rdairplay/loglinebox.cpp @@ -430,7 +430,12 @@ void LogLineBox::setEvent(int line,RDLogLine::TransType next_type, case RDLogLine::Cart: line_comment_label->hide(); cart=new RDCart(logline->cartNumber()); - cut=new RDCut(logline->cartNumber(),logline->cutNumber()); + if(logline->cutNumber()>0) { + cut=new RDCut(logline->cartNumber(),logline->cutNumber()); + } + else { + cut=NULL; + } if(!cart->exists()) { line_cart_label-> setText(QString().sprintf("%06u",logline->cartNumber())); @@ -453,15 +458,15 @@ void LogLineBox::setEvent(int line,RDLogLine::TransType next_type, break; } setBackgroundColor(QColor(LOGLINEBOX_MISSING_COLOR)); - delete cart; - delete cut; } else { if(((cart->forcedLength()==0)&&(cart->type()==RDCart::Audio))|| (line_logline->state()==RDLogLine::NoCut)) { line_cart_label-> setText(QString().sprintf("%06u",logline->cartNumber())); - line_description_label->setText(cut->description()); + if(cut!=NULL) { + line_description_label->setText(cut->description()); + } line_artist_label->setText(tr("[NO AUDIO AVAILABLE]")); line_cut_label->clear(); line_group_label->clear(); @@ -474,8 +479,6 @@ void LogLineBox::setEvent(int line,RDLogLine::TransType next_type, line_icon_label->setPixmap(*line_playout_map); line_title_label->setText(logline->title()); setBackgroundColor(QColor(LOGLINEBOX_MISSING_COLOR)); - delete cart; - delete cut; } else { line_cart_label-> @@ -557,13 +560,15 @@ void LogLineBox::setEvent(int line,RDLogLine::TransType next_type, line_cut_label->clear(); line_outcue_label->setText(tr("[NO VALID CUT AVAILABLE]")); } - delete cart; - delete cut; setMode(line_mode); line_title_label->show(); line_artist_label->show(); } } + delete cart; + if(cut!=NULL) { + delete cut; + } break; case RDLogLine::Marker: From 7b9103779dd7c015705f767c3062816b1af090ea Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Wed, 21 Aug 2019 17:13:16 -0400 Subject: [PATCH 06/35] 2019-08-21 Fred Gleason * Fixed a bug in rdairplay(1) where a hard time with a 'Make Next' attribute would instead be treated as 'Start Immediately' if the target event was unplayable. * Fixed a bug in rdairplay(1) where a hard time with a 'Wait up to' attribute would instead be treated as 'Start Immediately' if the target event was unplayable. --- ChangeLog | 7 +++++ lib/rdlogplay.cpp | 80 +++++++++++++++++++++-------------------------- 2 files changed, 43 insertions(+), 44 deletions(-) diff --git a/ChangeLog b/ChangeLog index 67b91116..edb256ea 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18938,3 +18938,10 @@ 2019-08-21 Fred Gleason * Refactored code to eliminate 'QSqlQuery::value: not positioned on a valid record' errors when starting rdairplay(1). +2019-08-21 Fred Gleason + * Fixed a bug in rdairplay(1) where a hard time with a 'Make Next' + attribute would instead be treated as 'Start Immediately' if + the target event was unplayable. + * Fixed a bug in rdairplay(1) where a hard time with a 'Wait up to' + attribute would instead be treated as 'Start Immediately' if + the target event was unplayable. diff --git a/lib/rdlogplay.cpp b/lib/rdlogplay.cpp index 4c038f1f..958d7694 100644 --- a/lib/rdlogplay.cpp +++ b/lib/rdlogplay.cpp @@ -78,7 +78,7 @@ RDLogPlay::RDLogPlay(int id,RDEventPlayer *player,QObject *parent) // play_pad_socket=new RDUnixSocket(this); if(!play_pad_socket->connectToAbstract(RD_PAD_SOURCE_UNIX_ADDRESS)) { - fprintf(stderr,"RDLogPlat: unable to connect to rdpadd\n"); + fprintf(stderr,"RDLogPlay: unable to connect to rdpadd\n"); } // @@ -154,9 +154,11 @@ RDLogPlay::RDLogPlay(int id,RDEventPlayer *player,QObject *parent) // Transition Timers // play_trans_timer=new QTimer(this); + play_trans_timer->setSingleShot(true); connect(play_trans_timer,SIGNAL(timeout()), this,SLOT(transTimerData())); play_grace_timer=new QTimer(this); + play_grace_timer->setSingleShot(true); connect(play_grace_timer,SIGNAL(timeout()), this,SLOT(graceTimerData())); } @@ -1164,10 +1166,8 @@ RDLogLine::TransType RDLogPlay::nextTrans() RDLogLine::TransType RDLogPlay::nextTrans(int line) { RDLogLine *logline; - -// if((logline=logLine(nextLine(line)))!=NULL) { - int next_line; + next_line=nextLine(line); logline=logLine(next_line); if(logline!=NULL) { @@ -1395,12 +1395,35 @@ void RDLogPlay::transTimerData() RDLogLine *logline=NULL; int grace=0; int trans_line=play_trans_line; + int running_events=runningEvents(lines); if(play_grace_timer->isActive()) { play_grace_timer->stop(); } if(play_op_mode==RDAirPlayConf::Auto) { + if((logline=logLine(play_trans_line))!=NULL) { + if(logline->graceTime()==-1) { // Make Next + makeNext(play_trans_line); + SetTransTimer(); + return; + } + if(logline->graceTime()>0) { + if(running_events>0) { + if(logline->transType()==RDLogLine::Stop) { + logline->setTransType(RDLogLine::Play); + } + logline->setStartTime(RDLogLine::Predicted,logline-> + startTime(RDLogLine::Predicted). + addMSecs(grace)); + play_grace_line=play_trans_line; + play_grace_timer->start(logline->graceTime()); + return; + } + else { + } + } + } if(!GetNextPlayable(&play_trans_line,false)) { SetTransTimer(); return; @@ -1408,45 +1431,12 @@ void RDLogPlay::transTimerData() if((logline=logLine(play_trans_line))!=NULL) { grace=logline->graceTime(); } - if((runningEvents(lines)==0)) { + if(running_events==0) { makeNext(play_trans_line); if(logline->transType()!=RDLogLine::Stop || grace>=0) { StartEvent(trans_line,RDLogLine::Play,0,RDLogLine::StartTime); } } - else { - if(logline==NULL) { - SetTransTimer(); - return; - } - switch(logline->graceTime()) { - case 0: - makeNext(play_trans_line); - if(play_trans_length==0) { - StartEvent(trans_line,RDLogLine::Play,0,RDLogLine::StartTime); - } - else { - StartEvent(trans_line,RDLogLine::Segue,play_trans_length, - RDLogLine::StartTime); - } - break; - - case -1: - makeNext(play_trans_line); - break; - - default: - if(logline->transType()==RDLogLine::Stop) { - logline->setTransType(RDLogLine::Play); - } - logline->setStartTime(RDLogLine::Predicted,logline-> - startTime(RDLogLine::Predicted). - addMSecs(grace)); - play_grace_line=play_trans_line; - play_grace_timer->start(grace,true); - break; - } - } } SetTransTimer(); } @@ -1462,9 +1452,6 @@ void RDLogPlay::graceTimerData() SetTransTimer(); return; } - if(line!=play_grace_line) { - return; - } if((runningEvents(lines)==0)) { makeNext(play_grace_line); StartEvent(play_grace_line,RDLogLine::Play,0,RDLogLine::StartTime); @@ -2525,7 +2512,7 @@ void RDLogPlay::SetTransTimer(QTime current_time,bool stop) } if(next_line>=0) { play_trans_line=next_line; - play_trans_timer->start(current_time.msecsTo(next_time),true); + play_trans_timer->start(current_time.msecsTo(next_time)); } } @@ -2754,13 +2741,18 @@ void RDLogPlay::Stopped(int id) CleanupEvent(id); UpdateStartTimes(line); emit stopped(line); + LogTraffic(logLine(line),(RDLogLine::PlaySource)(play_id+1), + RDAirPlayConf::TrafficStop,play_onair_flag); + if(play_grace_timer->isActive()) { // Pending Hard Time Event + play_grace_timer->stop(); + play_grace_timer->start(0); + return; + } AdvanceActiveEvent(); UpdatePostPoint(); if(runningEvents(lines)==0) { next_channel=0; } - LogTraffic(logLine(line),(RDLogLine::PlaySource)(play_id+1), - RDAirPlayConf::TrafficStop,play_onair_flag); emit transportChanged(); } From e62b02f1790487d758cb72e00d94d9d599d59581 Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Wed, 21 Aug 2019 17:19:40 -0400 Subject: [PATCH 07/35] 2019-08-21 Fred Gleason * Re-indented switch() statements in 'lib/rdlogplay.cpp'. --- ChangeLog | 2 + lib/rdlogplay.cpp | 598 +++++++++++++++++++++++----------------------- 2 files changed, 301 insertions(+), 299 deletions(-) diff --git a/ChangeLog b/ChangeLog index edb256ea..8be7e377 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18945,3 +18945,5 @@ * Fixed a bug in rdairplay(1) where a hard time with a 'Wait up to' attribute would instead be treated as 'Start Immediately' if the target event was unplayable. +2019-08-21 Fred Gleason + * Re-indented switch() statements in 'lib/rdlogplay.cpp'. diff --git a/lib/rdlogplay.cpp b/lib/rdlogplay.cpp index 958d7694..ef0f8f3f 100644 --- a/lib/rdlogplay.cpp +++ b/lib/rdlogplay.cpp @@ -1203,14 +1203,14 @@ void RDLogPlay::transportEvents(int line[]) return; } switch(logline->status()) { - case RDLogLine::Scheduled: - if(counttype()==RDLogLine::Cart)|| - (logLine(lines[i])->type()==RDLogLine::Macro))&& - (logLine(lines[i])->status()!=RDLogLine::Paused)) { - switch(logLine(lines[i])->cartType()) { - case RDCart::Audio: - ((RDPlayDeck *)logLine(lines[i])->playDeck())->stop(); - break; + case RDLogLine::Play: + for(int i=0;itype()==RDLogLine::Cart)|| + (logLine(lines[i])->type()==RDLogLine::Macro))&& + (logLine(lines[i])->status()!=RDLogLine::Paused)) { + switch(logLine(lines[i])->cartType()) { + case RDCart::Audio: + ((RDPlayDeck *)logLine(lines[i])->playDeck())->stop(); + break; - case RDCart::Macro: - play_macro_deck->stop(); - break; + case RDCart::Macro: + play_macro_deck->stop(); + break; - case RDCart::All: - break; - } - } + case RDCart::All: + break; } } - break; + } + } + break; - case RDLogLine::Segue: - for(int i=0;istatus()==RDLogLine::Playing) { - if(((prev_logline->type()==RDLogLine::Cart)|| - (prev_logline->type()==RDLogLine::Macro))&& - (prev_logline->status()!=RDLogLine::Paused)) { - switch(logLine(lines[i])->cartType()) { - case RDCart::Audio: - prev_logline->setStatus(RDLogLine::Finishing); - ((RDPlayDeck *)prev_logline->playDeck())-> - stop(trans_length); - break; + case RDLogLine::Segue: + for(int i=0;istatus()==RDLogLine::Playing) { + if(((prev_logline->type()==RDLogLine::Cart)|| + (prev_logline->type()==RDLogLine::Macro))&& + (prev_logline->status()!=RDLogLine::Paused)) { + switch(logLine(lines[i])->cartType()) { + case RDCart::Audio: + prev_logline->setStatus(RDLogLine::Finishing); + ((RDPlayDeck *)prev_logline->playDeck())-> + stop(trans_length); + break; - case RDCart::Macro: - play_macro_deck->stop(); - break; + case RDCart::Macro: + play_macro_deck->stop(); + break; - case RDCart::All: - break; - } - } + case RDCart::All: + break; } } } - break; + } + } + break; - default: - break; + default: + break; } } @@ -1854,208 +1854,208 @@ bool RDLogPlay::StartEvent(int line,RDLogLine::TransType trans_type, // logline->setStartSource(src); switch(logline->type()) { - case RDLogLine::Cart: - if(!StartAudioEvent(line)) { - rda->airplayConf()->setLogCurrentLine(play_id,nextLine()); - return false; - } - aport=GetNextChannel(mport,&card,&port); - playdeck=(RDPlayDeck *)logline->playDeck(); - playdeck->setCard(card); - playdeck->setPort(port); - playdeck->setChannel(aport); - logline->setPauseCard(card); - logline->setPausePort(port); - logline->setPortName(GetPortName(playdeck->card(), - playdeck->port())); - if(logline->portName().toInt()==2){ - playdeck->duckVolume(play_duck_volume_port2,0); - } - else { - playdeck->duckVolume(play_duck_volume_port1,0); - } + case RDLogLine::Cart: + if(!StartAudioEvent(line)) { + rda->airplayConf()->setLogCurrentLine(play_id,nextLine()); + return false; + } + aport=GetNextChannel(mport,&card,&port); + playdeck=(RDPlayDeck *)logline->playDeck(); + playdeck->setCard(card); + playdeck->setPort(port); + playdeck->setChannel(aport); + logline->setPauseCard(card); + logline->setPausePort(port); + logline->setPortName(GetPortName(playdeck->card(), + playdeck->port())); + if(logline->portName().toInt()==2){ + playdeck->duckVolume(play_duck_volume_port2,0); + } + else { + playdeck->duckVolume(play_duck_volume_port1,0); + } - if(!playdeck->setCart(logline,logline->status()!=RDLogLine::Paused)) { - // No audio to play, so fake it - logline->setZombified(true); - playStateChangedData(playdeck->id(),RDPlayDeck::Playing); - logline->setStatus(RDLogLine::Playing); - playStateChangedData(playdeck->id(),RDPlayDeck::Finished); - logline->setStatus(RDLogLine::Finished); - rda->syslog(LOG_WARNING, - "log engine: RDLogPlay::StartEvent(): no audio,CUT=%s", - (const char *)logline->cutName().toUtf8()); - rda->airplayConf()->setLogCurrentLine(play_id,nextLine()); - return false; - } - emit modified(line); - logline->setCutNumber(playdeck->cut()->cutNumber()); - logline->setEvergreen(playdeck->cut()->evergreen()); - if(play_timescaling_available&&logline->enforceLength()) { - logline->setTimescalingActive(true); - } - RDSetMixerOutputPort(play_cae,playdeck->card(), - playdeck->stream(), - playdeck->port()); - if((int)logline->playPosition()>logline->effectiveLength()) { - rda->syslog(LOG_DEBUG,"log engine: *** position out of bounds: Line: %d Cart: %d Pos: %d ***",line,logline->cartNumber(),logline->playPosition()); - logline->setPlayPosition(0); - } - playdeck->play(logline->playPosition(),-1,-1,duck_length); - if(logline->status()==RDLogLine::RDLogLine::Paused) { - logline-> - setStartTime(RDLogLine::Actual,playdeck->startTime()); - was_paused=true; - } - else { - logline-> - setStartTime(RDLogLine::Initial,playdeck->startTime()); - } - logline->setStatus(RDLogLine::Playing); - if(!play_start_rml[aport].isEmpty()) { - play_event_player-> - exec(logline->resolveWildcards(play_start_rml[aport])); - } - /* - printf("channelStarted(%d,%d,%d,%d)\n", - play_id,playdeck->channel(), - playdeck->card(),playdeck->port()); - */ - emit channelStarted(play_id,playdeck->channel(), - playdeck->card(),playdeck->port()); - rda->syslog(LOG_INFO,"log engine: started audio cart: Line: %d Cart: %u Cut: %u Pos: %d Card: %d Stream: %d Port: %d", - line,logline->cartNumber(), - playdeck->cut()->cutNumber(), - logline->playPosition(), - playdeck->card(), - playdeck->stream(), - playdeck->port()); + if(!playdeck->setCart(logline,logline->status()!=RDLogLine::Paused)) { + // No audio to play, so fake it + logline->setZombified(true); + playStateChangedData(playdeck->id(),RDPlayDeck::Playing); + logline->setStatus(RDLogLine::Playing); + playStateChangedData(playdeck->id(),RDPlayDeck::Finished); + logline->setStatus(RDLogLine::Finished); + rda->syslog(LOG_WARNING, + "log engine: RDLogPlay::StartEvent(): no audio,CUT=%s", + (const char *)logline->cutName().toUtf8()); + rda->airplayConf()->setLogCurrentLine(play_id,nextLine()); + return false; + } + emit modified(line); + logline->setCutNumber(playdeck->cut()->cutNumber()); + logline->setEvergreen(playdeck->cut()->evergreen()); + if(play_timescaling_available&&logline->enforceLength()) { + logline->setTimescalingActive(true); + } + RDSetMixerOutputPort(play_cae,playdeck->card(), + playdeck->stream(), + playdeck->port()); + if((int)logline->playPosition()>logline->effectiveLength()) { + rda->syslog(LOG_DEBUG,"log engine: *** position out of bounds: Line: %d Cart: %d Pos: %d ***",line,logline->cartNumber(),logline->playPosition()); + logline->setPlayPosition(0); + } + playdeck->play(logline->playPosition(),-1,-1,duck_length); + if(logline->status()==RDLogLine::RDLogLine::Paused) { + logline-> + setStartTime(RDLogLine::Actual,playdeck->startTime()); + was_paused=true; + } + else { + logline-> + setStartTime(RDLogLine::Initial,playdeck->startTime()); + } + logline->setStatus(RDLogLine::Playing); + if(!play_start_rml[aport].isEmpty()) { + play_event_player-> + exec(logline->resolveWildcards(play_start_rml[aport])); + } + /* + printf("channelStarted(%d,%d,%d,%d)\n", + play_id,playdeck->channel(), + playdeck->card(),playdeck->port()); + */ + emit channelStarted(play_id,playdeck->channel(), + playdeck->card(),playdeck->port()); + rda->syslog(LOG_INFO,"log engine: started audio cart: Line: %d Cart: %u Cut: %u Pos: %d Card: %d Stream: %d Port: %d", + line,logline->cartNumber(), + playdeck->cut()->cutNumber(), + logline->playPosition(), + playdeck->card(), + playdeck->stream(), + playdeck->port()); - // - // Assign Next Event - // - if((play_next_line>=0)&&(!was_paused)) { - play_next_line=line+1; - if((next_logline=logLine(play_next_line))!=NULL) { - if(next_logline->id()==-2) { - play_start_next=false; - } - } - emit nextEventChanged(play_next_line); + // + // Assign Next Event + // + if((play_next_line>=0)&&(!was_paused)) { + play_next_line=line+1; + if((next_logline=logLine(play_next_line))!=NULL) { + if(next_logline->id()==-2) { + play_start_next=false; } - break; + } + emit nextEventChanged(play_next_line); + } + break; - case RDLogLine::Macro: - // - // Assign Next Event - // - if(play_next_line>=0) { - play_next_line=line+1; - if((next_logline=logLine(play_next_line))!=NULL) { - if(logline->id()==-2) { - play_start_next=false; - } - if(logline->forcedStop()) { - next_logline->setTransType(RDLogLine::Stop); - } - } + case RDLogLine::Macro: + // + // Assign Next Event + // + if(play_next_line>=0) { + play_next_line=line+1; + if((next_logline=logLine(play_next_line))!=NULL) { + if(logline->id()==-2) { + play_start_next=false; } - if(logline->asyncronous()) { - RDMacro *rml=new RDMacro(); - rml->setCommand(RDMacro::EX); - QHostAddress addr; - addr.setAddress("127.0.0.1"); - rml->setAddress(addr); - rml->setRole(RDMacro::Cmd); - rml->setEchoRequested(false); - rml->addArg(logline->cartNumber()); // Arg 0 - rda->ripc()->sendRml(rml); - delete rml; - emit played(line); - logline->setStartTime(RDLogLine::Actual,QTime::currentTime()); - logline->setStatus(RDLogLine::Finished); - LogTraffic(logline,(RDLogLine::PlaySource)(play_id+1), - RDAirPlayConf::TrafficMacro,play_onair_flag); - FinishEvent(line); - emit transportChanged(); - rda->syslog(LOG_INFO, - "log engine: asynchronously executed macro cart: Line: %d Cart: %u", - line,logline->cartNumber()); + if(logline->forcedStop()) { + next_logline->setTransType(RDLogLine::Stop); } - else { - play_macro_deck->load(logline->cartNumber()); - play_macro_deck->setLine(line); - rda->syslog(LOG_INFO, - "log engine: started macro cart: Line: %d Cart: %u", - line,logline->cartNumber()); - play_macro_deck->exec(); - } - break; + } + } + if(logline->asyncronous()) { + RDMacro *rml=new RDMacro(); + rml->setCommand(RDMacro::EX); + QHostAddress addr; + addr.setAddress("127.0.0.1"); + rml->setAddress(addr); + rml->setRole(RDMacro::Cmd); + rml->setEchoRequested(false); + rml->addArg(logline->cartNumber()); // Arg 0 + rda->ripc()->sendRml(rml); + delete rml; + emit played(line); + logline->setStartTime(RDLogLine::Actual,QTime::currentTime()); + logline->setStatus(RDLogLine::Finished); + LogTraffic(logline,(RDLogLine::PlaySource)(play_id+1), + RDAirPlayConf::TrafficMacro,play_onair_flag); + FinishEvent(line); + emit transportChanged(); + rda->syslog(LOG_INFO, + "log engine: asynchronously executed macro cart: Line: %d Cart: %u", + line,logline->cartNumber()); + } + else { + play_macro_deck->load(logline->cartNumber()); + play_macro_deck->setLine(line); + rda->syslog(LOG_INFO, + "log engine: started macro cart: Line: %d Cart: %u", + line,logline->cartNumber()); + play_macro_deck->exec(); + } + break; - case RDLogLine::Marker: - case RDLogLine::Track: - case RDLogLine::MusicLink: - case RDLogLine::TrafficLink: - // - // Assign Next Event - // - if(play_next_line>=0) { - play_next_line=line+1; - if((next_logline=logLine(play_next_line))!=NULL) { - if(logLine(play_next_line)->id()==-2) { - play_start_next=false; - } - } - else { - play_start_next=false; - } + case RDLogLine::Marker: + case RDLogLine::Track: + case RDLogLine::MusicLink: + case RDLogLine::TrafficLink: + // + // Assign Next Event + // + if(play_next_line>=0) { + play_next_line=line+1; + if((next_logline=logLine(play_next_line))!=NULL) { + if(logLine(play_next_line)->id()==-2) { + play_start_next=false; } + } + else { + play_start_next=false; + } + } - // - // Skip Past - // - logline->setStatus(RDLogLine::Finished); - UpdateStartTimes(line); - emit played(line); - FinishEvent(line); - emit nextEventChanged(play_next_line); - break; + // + // Skip Past + // + logline->setStatus(RDLogLine::Finished); + UpdateStartTimes(line); + emit played(line); + FinishEvent(line); + emit nextEventChanged(play_next_line); + break; - case RDLogLine::Chain: - // - // Assign Next Event - // - if(play_next_line>0) { - play_next_line=line+1; - if((next_logline=logLine(play_next_line))!=NULL) { - if(logLine(play_next_line)->id()==-2) { - play_start_next=false; - } - } - else { - play_start_next=false; - } + case RDLogLine::Chain: + // + // Assign Next Event + // + if(play_next_line>0) { + play_next_line=line+1; + if((next_logline=logLine(play_next_line))!=NULL) { + if(logLine(play_next_line)->id()==-2) { + play_start_next=false; } - if(GetTransType(logline->markerLabel(),0)!=RDLogLine::Stop) { - play_macro_deck-> - load(QString().sprintf("LL %d %s -2!", - play_id+1, - (const char *)logline->markerLabel())); - } - else { - play_macro_deck-> - load(QString().sprintf("LL %d %s -2!", - play_id+1, - (const char *)logline->markerLabel())); - } - play_macro_deck->setLine(line); - play_macro_deck->exec(); - rda->syslog(LOG_INFO,"log engine: chained to log: Line: %d Log: %s", - line,(const char *)logline->markerLabel()); - break; + } + else { + play_start_next=false; + } + } + if(GetTransType(logline->markerLabel(),0)!=RDLogLine::Stop) { + play_macro_deck-> + load(QString().sprintf("LL %d %s -2!", + play_id+1, + (const char *)logline->markerLabel())); + } + else { + play_macro_deck-> + load(QString().sprintf("LL %d %s -2!", + play_id+1, + (const char *)logline->markerLabel())); + } + play_macro_deck->setLine(line); + play_macro_deck->exec(); + rda->syslog(LOG_INFO,"log engine: chained to log: Line: %d Log: %s", + line,(const char *)logline->markerLabel()); + break; - default: - break; + default: + break; } while((play_next_linestate()==RDLogLine::Ok)|| @@ -2189,19 +2189,19 @@ void RDLogPlay::UpdateStartTimes(int line) } stop=false; switch(logline->status()) { - case RDLogLine::Playing: - case RDLogLine::Finishing: - time=logline->startTime(RDLogLine::Actual); - break; + case RDLogLine::Playing: + case RDLogLine::Finishing: + time=logline->startTime(RDLogLine::Actual); + break; - default: - time=GetStartTime(logline->startTime(RDLogLine::Logged), - logline->transType(), - logline->timeType(), - time,prev_total_length,prev_segue_length, - &stop,running); - logline->setStartTime(RDLogLine::Predicted,time); - break; + default: + time=GetStartTime(logline->startTime(RDLogLine::Logged), + logline->transType(), + logline->timeType(), + time,prev_total_length,prev_segue_length, + &stop,running); + logline->setStartTime(RDLogLine::Predicted,time); + break; } if(stop&&(!stop_set)) { next_stop=time.addMSecs(prev_total_length); @@ -2289,24 +2289,24 @@ QTime RDLogPlay::GetStartTime(QTime sched_time, return QTime(); } switch(trans_type) { - case RDLogLine::Play: - if(!prev_time.isNull()) { - time=prev_time.addMSecs(prev_total_length); - } - break; + case RDLogLine::Play: + if(!prev_time.isNull()) { + time=prev_time.addMSecs(prev_total_length); + } + break; - case RDLogLine::Segue: - if(!prev_time.isNull()) { - time=prev_time.addMSecs(prev_segue_length); - } - break; + case RDLogLine::Segue: + if(!prev_time.isNull()) { + time=prev_time.addMSecs(prev_segue_length); + } + break; - case RDLogLine::Stop: - time=QTime(); - break; + case RDLogLine::Stop: + time=QTime(); + break; - default: - break; + default: + break; } switch(time_type) { case RDLogLine::Relative: @@ -2367,18 +2367,18 @@ QTime RDLogPlay::GetNextStop(int line) if(running&&(play_op_mode==RDAirPlayConf::Auto)&& (status(i)==RDLogLine::Scheduled)) { switch(logLine(i)->transType()) { - case RDLogLine::Stop: - return time; - break; + case RDLogLine::Stop: + return time; + break; - case RDLogLine::Play: - case RDLogLine::Segue: - time=time.addMSecs(logLine(i)->segueLength(nextTrans(i))- - logLine(i)->playPosition()); - break; + case RDLogLine::Play: + case RDLogLine::Segue: + time=time.addMSecs(logLine(i)->segueLength(nextTrans(i))- + logLine(i)->playPosition()); + break; - default: - break; + default: + break; } } } @@ -2766,16 +2766,16 @@ void RDLogPlay::Finished(int id) return; } switch(logline->status()) { - case RDLogLine::Playing: - CleanupEvent(id); - FinishEvent(line); - break; + case RDLogLine::Playing: + CleanupEvent(id); + FinishEvent(line); + break; - case RDLogLine::Auditioning: - break; + case RDLogLine::Auditioning: + break; - default: - break; + default: + break; } UpdatePostPoint(); if(runningEvents(lines)==0) { From f6d220c56cd7c75daefe53f60198b6fd827497b5 Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Sat, 24 Aug 2019 09:16:18 -0400 Subject: [PATCH 08/35] 2019-08-24 Fred Gleason * Refactored rdalsaconfig(8) to use ALSA device IDs rather than ordinal numbers in asound.confO(5). --- ChangeLog | 3 + utils/rdalsaconfig/Makefile.am | 8 +- utils/rdalsaconfig/alsaitem.cpp | 27 ++- utils/rdalsaconfig/alsaitem.h | 13 +- utils/rdalsaconfig/rdalsa.cpp | 296 ---------------------------- utils/rdalsaconfig/rdalsa.h | 69 ------- utils/rdalsaconfig/rdalsacard.cpp | 114 +++++++++++ utils/rdalsaconfig/rdalsacard.h | 54 +++++ utils/rdalsaconfig/rdalsaconfig.cpp | 265 ++++++++++++------------- utils/rdalsaconfig/rdalsaconfig.h | 31 +-- utils/rdalsaconfig/rdalsamodel.cpp | 153 ++++++++++++++ utils/rdalsaconfig/rdalsamodel.h | 56 ++++++ 12 files changed, 541 insertions(+), 548 deletions(-) delete mode 100644 utils/rdalsaconfig/rdalsa.cpp delete mode 100644 utils/rdalsaconfig/rdalsa.h create mode 100644 utils/rdalsaconfig/rdalsacard.cpp create mode 100644 utils/rdalsaconfig/rdalsacard.h create mode 100644 utils/rdalsaconfig/rdalsamodel.cpp create mode 100644 utils/rdalsaconfig/rdalsamodel.h diff --git a/ChangeLog b/ChangeLog index 8be7e377..120facd6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18947,3 +18947,6 @@ the target event was unplayable. 2019-08-21 Fred Gleason * Re-indented switch() statements in 'lib/rdlogplay.cpp'. +2019-08-24 Fred Gleason + * Refactored rdalsaconfig(8) to use ALSA device IDs rather than + ordinal numbers in asound.confO(5). diff --git a/utils/rdalsaconfig/Makefile.am b/utils/rdalsaconfig/Makefile.am index 47601e1d..e17a5310 100644 --- a/utils/rdalsaconfig/Makefile.am +++ b/utils/rdalsaconfig/Makefile.am @@ -1,6 +1,6 @@ ## Makefile.am ## -## (C) Copyright 2009,2016-2018 Fred Gleason +## (C) Copyright 2009-2019 Fred Gleason ## ## 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 @@ -29,10 +29,12 @@ moc_%.cpp: %.h bin_PROGRAMS = rdalsaconfig dist_rdalsaconfig_SOURCES = alsaitem.cpp alsaitem.h\ - rdalsa.cpp rdalsa.h\ + rdalsacard.cpp rdalsacard.h\ + rdalsamodel.cpp rdalsamodel.h\ rdalsaconfig.cpp rdalsaconfig.h -nodist_rdalsaconfig_SOURCES = moc_rdalsaconfig.cpp +nodist_rdalsaconfig_SOURCES = moc_rdalsamodel.cpp\ + moc_rdalsaconfig.cpp rdalsaconfig_LDADD = @LIB_RDLIBS@ @LIBVORBIS@ @LIBALSA@ @QT4_LIBS@ -lQt3Support diff --git a/utils/rdalsaconfig/alsaitem.cpp b/utils/rdalsaconfig/alsaitem.cpp index e929b6be..17002e38 100644 --- a/utils/rdalsaconfig/alsaitem.cpp +++ b/utils/rdalsaconfig/alsaitem.cpp @@ -2,7 +2,7 @@ // // QListBoxItem for ALSA PCM devices. // -// (C) Copyright 2009-2018 Fred Gleason +// (C) Copyright 2009-2019 Fred Gleason // // 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 @@ -29,38 +29,37 @@ AlsaItem::AlsaItem(Q3ListBox *listbox,const QString &text) AlsaItem::AlsaItem(const QString &text) : Q3ListBoxText(text) { - alsa_card=-1; - alsa_device=-1; + alsa_card_number=-1; + alsa_pcm_number=-1; } AlsaItem::AlsaItem(const AlsaItem &item) { - setText(item.text()); - setCard(item.card()); - setDevice(item.device()); + setCardNumber(item.cardNumber()); + setPcmNumber(item.pcmNumber()); } -int AlsaItem::card() const +int AlsaItem::cardNumber() const { - return alsa_card; + return alsa_card_number; } -void AlsaItem::setCard(int card) +void AlsaItem::setCardNumber(int cardnum) { - alsa_card=card; + alsa_card_number=cardnum; } -int AlsaItem::device() const +int AlsaItem::pcmNumber() const { - return alsa_device; + return alsa_pcm_number; } -void AlsaItem::setDevice(int device) +void AlsaItem::setPcmNumber(int pcmnum) { - alsa_device=device; + alsa_pcm_number=pcmnum; } diff --git a/utils/rdalsaconfig/alsaitem.h b/utils/rdalsaconfig/alsaitem.h index b1feebb7..30cbb8d3 100644 --- a/utils/rdalsaconfig/alsaitem.h +++ b/utils/rdalsaconfig/alsaitem.h @@ -30,14 +30,13 @@ class AlsaItem : public Q3ListBoxText AlsaItem(Q3ListBox *listbox,const QString &text=QString::null); AlsaItem(const QString &text=QString::null); AlsaItem(const AlsaItem &item); - int card() const; - void setCard(int card); - int device() const; - void setDevice(int device); - + int cardNumber() const; + void setCardNumber(int cardnum); + int pcmNumber() const; + void setPcmNumber(int pcmnum); private: - int alsa_card; - int alsa_device; + int alsa_card_number; + int alsa_pcm_number; }; diff --git a/utils/rdalsaconfig/rdalsa.cpp b/utils/rdalsaconfig/rdalsa.cpp deleted file mode 100644 index 87cbbd99..00000000 --- a/utils/rdalsaconfig/rdalsa.cpp +++ /dev/null @@ -1,296 +0,0 @@ -// rdalsa.cpp -// -// Abstract an ALSA configuration. -// -// (C) Copyright 2009-2018 Fred Gleason -// -// 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 -#include -#include - -#include - -#include - -RDAlsa::RDAlsa() -{ - clear(); -} - - -unsigned RDAlsa::cards() const -{ - return card_ids.size(); -} - - -QString RDAlsa::cardId(unsigned cardnum) const -{ - if(cardnum>=card_ids.size()) { - return QString("[invalid card]"); - } - return card_ids[cardnum]; -} - - -QString RDAlsa::cardDriver(unsigned cardnum) const -{ - if(cardnum>=card_drivers.size()) { - return QString("[invalid card]"); - } - return card_drivers[cardnum]; -} - - -QString RDAlsa::cardName(unsigned cardnum) const -{ - if(cardnum>=card_names.size()) { - return QString("[invalid card]"); - } - return card_names[cardnum]; -} - - -QString RDAlsa::cardLongName(unsigned cardnum) const -{ - if(cardnum>=card_long_names.size()) { - return QString("[invalid card]"); - } - return card_long_names[cardnum]; -} - - -QString RDAlsa::cardMixerName(unsigned cardnum) const -{ - if(cardnum>=card_mixer_names.size()) { - return QString("[invalid card]"); - } - return card_mixer_names[cardnum]; -} - - -int RDAlsa::pcmDevices(unsigned cardnum) const -{ - if(cardnum>=card_pcm_names.size()) { - return -1; - } - return card_pcm_names[cardnum].size(); -} - - -QString RDAlsa::pcmName(unsigned cardnum,unsigned pcm) const -{ - if(cardnum>=card_pcm_names.size()) { - return QString("[invalid pcm device]"); - } - if(pcm>=card_pcm_names[cardnum].size()) { - return QString("[invalid pcm device]"); - } - return card_pcm_names[cardnum][pcm]; -} - - -int RDAlsa::rivendellCard(int slot) const -{ - return card_rivendell_cards[slot]; -} - - -void RDAlsa::setRivendellCard(int slot,int cardnum) -{ - if(slot>=RD_MAX_CARDS) { - return; - } - card_rivendell_cards[slot]=cardnum; -} - - -int RDAlsa::rivendellDevice(int slot) const -{ - return card_rivendell_devices[slot]; -} - - -void RDAlsa::setRivendellDevice(int slot,int devnum) -{ - if(slot>=RD_MAX_CARDS) { - return; - } - card_rivendell_devices[slot]=devnum; -} - - -bool RDAlsa::load(const QString &filename) -{ - LoadSystemConfig(); - return LoadAsoundConfig(filename); -} - - -bool RDAlsa::save(const QString &filename) -{ - return SaveAsoundConfig(filename); -} - - -void RDAlsa::clear() -{ - card_ids.clear(); - card_drivers.clear(); - card_names.clear(); - card_long_names.clear(); - card_mixer_names.clear(); - card_pcm_names.clear(); - for(unsigned i=0;i=0) { - snd_ctl_card_info(snd_ctl,card_info); - card_ids.push_back(snd_ctl_card_info_get_id(card_info)); - card_drivers.push_back(snd_ctl_card_info_get_driver(card_info)); - card_names.push_back(snd_ctl_card_info_get_name(card_info)); - card_long_names.push_back(snd_ctl_card_info_get_longname(card_info)); - card_mixer_names.push_back(snd_ctl_card_info_get_mixername(card_info)); - std::vector pcms; - if(snd_ctl_pcm_info(snd_ctl,pcm_info)==0) { - pcm=0; - while(pcm>=0) { - pcms.push_back(QString().sprintf("%s [%02u]", - (const char *)snd_pcm_info_get_name(pcm_info),pcm+1)); - snd_ctl_pcm_next_device(snd_ctl,&pcm); - } - } - card_pcm_names.push_back(pcms); - snd_ctl_close(snd_ctl); - card++; - } -} - - -bool RDAlsa::LoadAsoundConfig(const QString &filename) -{ - FILE *f=NULL; - char line[1024]; - int istate=0; - int port=0; - int card=0; - int device=0; - QStringList list; - - if((f=fopen(filename,"r"))==NULL) { - return false; - } - while(fgets(line,1024,f)!=NULL) { - QString str=line; - str.replace("\n",""); - if((str!=START_MARKER)&&(str!=END_MARKER)) { - switch(istate) { - case 0: - if(str.left(6)=="pcm.rd") { - port=str.mid(6,1).toInt(); - istate=1; - } - else { - if(str.left(6)=="ctl.rd") { - istate=10; - } - else { - card_other_lines.push_back(str+"\n"); - } - } - break; - - case 1: - list=str.split(" "); - if(list[0]=="}") { - if((port>=0)&&(port=0)&&(card_rivendell_devices[i]>=0)) { - fprintf(f,"pcm.rd%d {\n",i); - fprintf(f," type hw\n"); - fprintf(f," card %d\n",card_rivendell_cards[i]); - fprintf(f," device %d\n",card_rivendell_devices[i]); - fprintf(f,"}\n"); - fprintf(f,"ctl.rd%d {\n",i); - fprintf(f," type hw\n"); - fprintf(f," card %d\n",card_rivendell_cards[i]); - fprintf(f,"}\n"); - } - } - fprintf(f,"%s\n",END_MARKER); - - fclose(f); - return true; -} diff --git a/utils/rdalsaconfig/rdalsa.h b/utils/rdalsaconfig/rdalsa.h deleted file mode 100644 index 399d50fb..00000000 --- a/utils/rdalsaconfig/rdalsa.h +++ /dev/null @@ -1,69 +0,0 @@ -// rdalsa.h -// -// Abstract an ALSA configuration. -// -// (C) Copyright 2009-2018 Fred Gleason -// -// 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 RDALSA_H -#define RDALSA_H - -#include - -#include - -#include - -#define START_MARKER "# *** Start of Rivendell configuration generated by rdalsaconfig(1) ***" -#define END_MARKER "# *** End of Rivendell configuration generated by rdalsaconfig(1) ***" - -class RDAlsa -{ - public: - RDAlsa(); - unsigned cards() const; - QString cardId(unsigned cardnum) const; - QString cardDriver(unsigned cardnum) const; - QString cardName(unsigned cardnum) const; - QString cardLongName(unsigned cardnum) const; - QString cardMixerName(unsigned cardnum) const; - int pcmDevices(unsigned cardnum) const; - QString pcmName(unsigned cardnum,unsigned pcm) const; - int rivendellCard(int slot) const; - void setRivendellCard(int slot,int cardnum); - int rivendellDevice(int slot) const; - void setRivendellDevice(int slot,int devnum); - bool load(const QString &filename); - bool save(const QString &filename); - void clear(); - - private: - void LoadSystemConfig(); - bool LoadAsoundConfig(const QString &filename); - bool SaveAsoundConfig(const QString &filename); - std::vector card_ids; - std::vector card_drivers; - std::vector card_names; - std::vector card_long_names; - std::vector card_mixer_names; - std::vector > card_pcm_names; - int card_rivendell_cards[RD_MAX_CARDS]; - int card_rivendell_devices[RD_MAX_CARDS]; - QStringList card_other_lines; -}; - - -#endif // RDALSA_H diff --git a/utils/rdalsaconfig/rdalsacard.cpp b/utils/rdalsaconfig/rdalsacard.cpp new file mode 100644 index 00000000..da11295c --- /dev/null +++ b/utils/rdalsaconfig/rdalsacard.cpp @@ -0,0 +1,114 @@ +// rdalsacard.cpp +// +// Abstract ALSA 'card' information +// +// (C) Copyright 2019 Fred Gleason +// +// 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 "rdalsacard.h" + +RDAlsaCard::RDAlsaCard(snd_ctl_t *ctl,int index) +{ + snd_ctl_card_info_t *card_info; + snd_pcm_info_t *pcm_info; + int pcm=0; + + card_index=index; + + snd_ctl_card_info_malloc(&card_info); + snd_pcm_info_malloc(&pcm_info); + + snd_ctl_card_info(ctl,card_info); + card_id=QString(snd_ctl_card_info_get_id(card_info)); + card_driver=QString(snd_ctl_card_info_get_driver(card_info)); + card_name=QString(snd_ctl_card_info_get_name(card_info)); + card_long_name=QString(snd_ctl_card_info_get_longname(card_info)); + card_mixer_name=QString(snd_ctl_card_info_get_mixername(card_info)); + if(snd_ctl_pcm_info(ctl,pcm_info)==0) { + pcm=0; + while(pcm>=0) { + card_pcm_names.push_back(snd_pcm_info_get_name(pcm_info)+ + QString().sprintf("[%02d]",pcm+1)); + snd_ctl_pcm_next_device(ctl,&pcm); + } + } + snd_pcm_info_free(pcm_info); + snd_ctl_card_info_free(card_info); +} + + +int RDAlsaCard::index() const +{ + return card_index; +} + + +QString RDAlsaCard::id() const +{ + return card_id; +} + + +QString RDAlsaCard::driver() const +{ + return card_driver; +} + + +QString RDAlsaCard::name() const +{ + return card_name; +} + + +QString RDAlsaCard::longName() const +{ + return card_long_name; +} + + +QString RDAlsaCard::mixerName() const +{ + return card_long_name; +} + + +QString RDAlsaCard::dump() const +{ + QString ret=QString().sprintf("Card %d\n",index()); + + ret+=" ID: "+id()+"\n"; + ret+=" Name: "+name()+"\n"; + ret+=" LongName: "+longName()+"\n"; + ret+=" MixerName: "+mixerName()+"\n"; + for(int i=0;i +// +// 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 RDALSACARD_H +#define RDALSACARD_H + +#include + +#include +#include + +class RDAlsaCard +{ + public: + RDAlsaCard(snd_ctl_t *ctl,int index); + int index() const; + QString id() const; + QString driver() const; + QString name() const; + QString longName() const; + QString mixerName() const; + int pcmQuantity() const; + QString pcmName(int n) const; + QString dump() const; + + private: + int card_index; + QString card_id; + QString card_driver; + QString card_name; + QString card_long_name; + QString card_mixer_name; + QStringList card_pcm_names; +}; + + +#endif // RDALSACARD_H diff --git a/utils/rdalsaconfig/rdalsaconfig.cpp b/utils/rdalsaconfig/rdalsaconfig.cpp index 5c9199fe..aa3b3d7c 100644 --- a/utils/rdalsaconfig/rdalsaconfig.cpp +++ b/utils/rdalsaconfig/rdalsaconfig.cpp @@ -2,7 +2,7 @@ // // A Qt-based application to display info about ALSA cards. // -// (C) Copyright 2009-2018 Fred Gleason +// (C) Copyright 2009-2019 Fred Gleason // // 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 @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -87,36 +88,21 @@ MainWidget::MainWidget(QWidget *parent) label_font.setPixelSize(12); // - // Available Devices + // ALSA Sound Devices // - alsa_system_list=new Q3ListBox(this); + alsa_system_list=new QListView(this); alsa_system_list->setFont(font); + alsa_system_list->setSelectionMode(QAbstractItemView::MultiSelection); alsa_system_label= - new QLabel(alsa_system_list,tr("Available Sound Devices"),this); + new QLabel(alsa_system_list,tr("ALSA Sound Devices"),this); alsa_system_label->setFont(label_font); alsa_system_label->setAlignment(Qt::AlignLeft|Qt::AlignVCenter); - - // - // Up Button - // - alsa_up_button=new RDTransportButton(RDTransportButton::Up,this); - connect(alsa_up_button,SIGNAL(clicked()),this,SLOT(upData())); - - // - // Down Button - // - alsa_down_button= - new RDTransportButton(RDTransportButton::Down,this); - connect(alsa_down_button,SIGNAL(clicked()),this,SLOT(downData())); - - // - // Selected Devices - // - alsa_config_list=new Q3ListBox(this); - alsa_config_list->setFont(font); - alsa_config_label=new QLabel(alsa_config_list,tr("Active Sound Devices"),this); - alsa_config_label->setFont(label_font); - alsa_config_label->setAlignment(Qt::AlignLeft|Qt::AlignVCenter); + alsa_description_label=new QLabel(this); + alsa_description_label-> + setText(tr("Select the audio devices to dedicate for use with Rivendell. (Devices so dedicated will be unavailable for use with other applications.)")); + alsa_description_label->setFont(font); + alsa_description_label->setAlignment(Qt::AlignLeft|Qt::AlignTop); + alsa_description_label->setWordWrap(true); // // Save Button @@ -135,9 +121,9 @@ MainWidget::MainWidget(QWidget *parent) // // Load Available Devices and Configuration // - alsa_alsa=new RDAlsa(); - alsa_alsa->load(alsa_filename); - LoadList(alsa_system_list,alsa_config_list); + alsa_system_model=new RDAlsaModel(); + alsa_system_list->setModel(alsa_system_model); + LoadConfig(); // // Daemon Management @@ -168,7 +154,7 @@ MainWidget::~MainWidget() QSize MainWidget::sizeHint() const { - return QSize(400,300); + return QSize(400,400); } @@ -178,33 +164,17 @@ QSizePolicy MainWidget::sizePolicy() const } -void MainWidget::upData() -{ - MoveItem(alsa_config_list,alsa_system_list); -} - - -void MainWidget::downData() -{ - if(alsa_config_list->count()>=RD_MAX_CARDS) { - return; - } - MoveItem(alsa_system_list,alsa_config_list); -} - - void MainWidget::saveData() { + /* AlsaItem *item=NULL; for(int i=0;iitem(i))==NULL) { alsa_alsa->setRivendellCard(i,-1); - alsa_alsa->setRivendellDevice(i,-1); } else { - alsa_alsa->setRivendellCard(i,item->card()); - alsa_alsa->setRivendellDevice(i,item->device()); + alsa_alsa->setRivendellCard(i,item->cardNumber()); } } if(!alsa_alsa->save(alsa_filename)) { @@ -214,6 +184,10 @@ void MainWidget::saveData() return; } StartDaemons(); + */ + + SaveConfig(); + qApp->quit(); } @@ -227,14 +201,10 @@ void MainWidget::cancelData() void MainWidget::resizeEvent(QResizeEvent *e) { - alsa_system_label->setGeometry(20,5,size().width()-20,20); + alsa_system_label->setGeometry(10,5,size().width()-20,20); + alsa_description_label->setGeometry(10,25,size().width()-20,50); alsa_system_list-> - setGeometry(10,25,size().width()-20,(size().height()-120)/2); - alsa_up_button->setGeometry(size().width()-120,size().height()/2-28,50,30); - alsa_down_button->setGeometry(size().width()-60,size().height()/2-28,50,30); - alsa_config_label->setGeometry(20,size().height()/2-10,size().width()/2,20); - alsa_config_list->setGeometry(10,size().height()/2+10, - size().width()-20,(size().height()-120)/2); + setGeometry(10,75,size().width()-20,size().height()-130); alsa_save_button-> setGeometry(size().width()-120,size().height()-40,50,30); alsa_cancel_button-> @@ -263,93 +233,123 @@ void MainWidget::closeEvent(QCloseEvent *e) } -void MainWidget::LoadList(Q3ListBox *system,Q3ListBox *config) +void MainWidget::LoadConfig() { - for(unsigned i=0;icards();i++) { - for(int j=0;jpcmDevices(i);j++) { - if(PcmUnused(i,j)) { - AlsaItem *item= - new AlsaItem(alsa_alsa->cardLongName(i)+" - "+ - alsa_alsa->pcmName(i,j)); - item->setCard(i); - item->setDevice(j); - system->insertItem(item); + FILE *f=NULL; + char line[1024]; + int istate=0; + int port=0; + QString card_id=0; + int device=0; + QStringList list; + bool active_line=false; + QModelIndex index; + + if((f=fopen(RD_ASOUNDRC_FILE,"r"))==NULL) { + return; + } + while(fgets(line,1024,f)!=NULL) { + QString str=line; + str.replace("\n",""); + if(str==START_MARKER) { + active_line=true; + } + if(str==END_MARKER) { + active_line=false; + } + if((str!=START_MARKER)&&(str!=END_MARKER)) { + if(active_line) { + switch(istate) { + case 0: + if(str.left(6)=="pcm.rd") { + port=str.mid(6,1).toInt(); + istate=1; + } + else { + if(str.left(6)=="ctl.rd") { + istate=10; + } + else { + alsa_other_lines.push_back(str+"\n"); + } + } + break; + + case 1: + list=str.split(" ",QString::SkipEmptyParts); + if(list[0]=="}") { + if((port>=0)&&(portindexOf(card_id,device); + if(index.isValid()) { + alsa_system_list->selectionModel()-> + select(index,QItemSelectionModel::Select); + } + } + card_id=""; + device=0; + istate=0; + } + else { + if(list.size()==2) { + if(list[0]=="card") { + card_id=list[1].trimmed(); + } + if(list[0]=="device") { + device=list[1].toInt(); + } + } + } + break; + + case 10: + if(str.left(1)=="}") { + istate=0; + } + break; + } + } + else { + alsa_other_lines.push_back(str+"\n"); } } } - system->sort(); - - for(int i=0;irivendellCard(i)>=0) { - AlsaItem *item= - new AlsaItem(alsa_alsa->cardLongName(alsa_alsa->rivendellCard(i))+" - "+ - alsa_alsa->pcmName(alsa_alsa->rivendellCard(i), - alsa_alsa->rivendellDevice(i))); - item->setCard(alsa_alsa->rivendellCard(i)); - item->setDevice(alsa_alsa->rivendellDevice(i)); - config->insertItem(item); - } - } - config->sort(); + fclose(f); } -bool MainWidget::PcmUnused(int card,int device) +void MainWidget::SaveConfig() const { - for(int i=0;irivendellCard(i))&& - (device==alsa_alsa->rivendellDevice(i))) { - return false; - } - } - return true; -} + QString tempfile=QString(RD_ASOUNDRC_FILE)+"-temp"; + FILE *f=NULL; - -void MainWidget::MoveItem(Q3ListBox *src,Q3ListBox *dest) -{ - AlsaItem *item=(AlsaItem *)src->selectedItem(); - if(item==NULL) { + if((f=fopen(tempfile.toUtf8(),"w"))==NULL) { return; } - dest->insertItem(new AlsaItem(*item)); // Force a deep copy - dest->sort(); - delete item; -} - - -Autogen::Autogen(QObject *parent) -{ - StopDaemons(); - - // - // Load Available Devices - // - RDAlsa *alsa=new RDAlsa(); - alsa->load(alsa_filename); - - // - // Build Configuration - // - int slot=0; - for(unsigned i=0;icards();i++) { - for(int j=0;jpcmDevices(i);j++) { - alsa->setRivendellCard(slot,i); - alsa->setRivendellDevice(slot,j); - slot++; + for(int i=0;iselectionModel()->selectedIndexes(); + for(int i=0;icard(indexes.at(i))->id().toUtf8()); + fprintf(f," device %d\n",alsa_system_model->pcmNumber(indexes.at(i))); + if(alsa_system_model->card(indexes.at(i))->id()=="Axia") { + fprintf(f," channels 2\n"); } + fprintf(f,"}\n"); + fprintf(f,"ctl.rd%d {\n",i); + fprintf(f," type hw\n"); + fprintf(f," card %s\n", + (const char *)alsa_system_model->card(indexes.at(i))->id().toUtf8()); + fprintf(f,"}\n"); } + fprintf(f,"%s\n",END_MARKER); - // - // Save Configuration - // - if(!alsa->save(alsa_filename)) { - exit(256); - } - - StartDaemons(); - - exit(0); + fclose(f); + rename(tempfile.toUtf8(),RD_ASOUNDRC_FILE); } @@ -373,15 +373,6 @@ int main(int argc,char *argv[]) } } - // - // Autogenerate a full configuration - // - if(alsa_autogen) { - QApplication a(argc,argv,false); - new Autogen(); - return a.exec(); - } - // // Start GUI // diff --git a/utils/rdalsaconfig/rdalsaconfig.h b/utils/rdalsaconfig/rdalsaconfig.h index 213070b8..84e21bcb 100644 --- a/utils/rdalsaconfig/rdalsaconfig.h +++ b/utils/rdalsaconfig/rdalsaconfig.h @@ -2,7 +2,7 @@ // // A Qt-based application to display info about ALSA cards. // -// (C) Copyright 2009-2018 Fred Gleason +// (C) Copyright 2009-2019 Fred Gleason // // 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 @@ -22,13 +22,13 @@ #define RDALSACONFIG_H #include -#include +#include #include #include #include -#include +#include "rdalsamodel.h" #define RDALSACONFIG_USAGE "[--asoundrc-file=] [--autogen] [--manage-daemons]\n\nGenerate an ALSA sound card configuration for Rivendell.\n\nThe following options are available:\n\n --asoundrc-file=\n Read and write configuration from (default value \n \"/etc/asound.conf\").\n\n --autogen\n Generate and save a configuration containing all available PCM devices\n and then exit.\n\n --manage-daemons\n Restart the Rivendell daemons as necessary to make configuration\n changes active (requires root permission).\n\n" @@ -45,8 +45,6 @@ class MainWidget : public QWidget QSizePolicy sizePolicy() const; private slots: - void upData(); - void downData(); void saveData(); void cancelData(); @@ -55,26 +53,15 @@ class MainWidget : public QWidget void closeEvent(QCloseEvent *e); private: - void LoadList(Q3ListBox *system,Q3ListBox *config); - bool PcmUnused(int card,int device); - void MoveItem(Q3ListBox *src,Q3ListBox *dest); + void LoadConfig(); + void SaveConfig() const; QLabel *alsa_system_label; - Q3ListBox *alsa_system_list; - QLabel *alsa_config_label; - Q3ListBox *alsa_config_list; - RDTransportButton *alsa_up_button; - RDTransportButton *alsa_down_button; + QLabel *alsa_description_label; + QListView *alsa_system_list; + RDAlsaModel *alsa_system_model; + QStringList alsa_other_lines; QPushButton *alsa_save_button; QPushButton *alsa_cancel_button; - RDAlsa *alsa_alsa; -}; - - -class Autogen : public QObject -{ - Q_OBJECT - public: - Autogen(QObject *parent=0); }; diff --git a/utils/rdalsaconfig/rdalsamodel.cpp b/utils/rdalsaconfig/rdalsamodel.cpp new file mode 100644 index 00000000..497e6b25 --- /dev/null +++ b/utils/rdalsaconfig/rdalsamodel.cpp @@ -0,0 +1,153 @@ +// rdalsamodel.cpp +// +// Abstract an ALSA configuration. +// +// (C) Copyright 2009-2019 Fred Gleason +// +// 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 +#include +#include + +#include + +#include + +RDAlsaModel::RDAlsaModel(QObject *parent) + : QAbstractListModel(parent) +{ + LoadSystemConfig(); +} + + +int RDAlsaModel::rowCount(const QModelIndex &parent) const +{ + int rows=0; + + for(int i=0;ipcmQuantity(); + } + + return rows; +} + + +QVariant RDAlsaModel::data(const QModelIndex &index,int role) const +{ + int row=index.row(); + + switch((Qt::ItemDataRole)role) { + case Qt::DisplayRole: + return QVariant(model_alsa_cards.at(model_card_index.at(row))->name()+" - "+ + model_alsa_cards.at(model_card_index.at(row))-> + pcmName(model_pcm_index.at(row))); + break; + + case Qt::DecorationRole: + case Qt::EditRole: + case Qt::ToolTipRole: + case Qt::StatusTipRole: + case Qt::WhatsThisRole: + case Qt::SizeHintRole: + case Qt::FontRole: + case Qt::TextAlignmentRole: + case Qt::BackgroundColorRole: + case Qt::TextColorRole: + case Qt::CheckStateRole: + case Qt::AccessibleTextRole: + case Qt::AccessibleDescriptionRole: + case Qt::InitialSortOrderRole: + case Qt::DisplayPropertyRole: + case Qt::DecorationPropertyRole: + case Qt::ToolTipPropertyRole: + case Qt::StatusTipPropertyRole: + case Qt::WhatsThisPropertyRole: + case Qt::UserRole: + break; + } + + return QVariant(); +} + + +QVariant RDAlsaModel::headerData(int section,Qt::Orientation orient, + int role) const +{ + switch(orient) { + case Qt::Horizontal: + return QVariant(tr("ALSA Devices")); + + case Qt::Vertical: + break; + } + + return QVariant(); +} + + +QModelIndex RDAlsaModel::indexOf(const QString &card_id,int pcm_num) const +{ + bool ok=false; + int cardnum=card_id.toUInt(&ok); + + if(ok) { + for(int i=0;iid()==card_id)&& + (model_pcm_index.at(i)==pcm_num)) { + return createIndex(i,0); + } + } + } + + return QModelIndex(); +} + + +RDAlsaCard *RDAlsaModel::card(const QModelIndex &index) const +{ + return model_alsa_cards.at(model_card_index.at(index.row())); +} + + +int RDAlsaModel::pcmNumber(const QModelIndex &index) const +{ + return model_pcm_index.at(index.row()); +} + + +void RDAlsaModel::LoadSystemConfig() +{ + snd_ctl_t *snd_ctl=NULL; + int index=0; + + while(snd_ctl_open(&snd_ctl,QString().sprintf("hw:%d",index),0)>=0) { + model_alsa_cards.push_back(new RDAlsaCard(snd_ctl,index)); + for(int i=0;ipcmQuantity();i++) { + model_card_index.push_back(index); + model_pcm_index.push_back(i); + } + snd_ctl_close(snd_ctl); + index++; + } +} diff --git a/utils/rdalsaconfig/rdalsamodel.h b/utils/rdalsaconfig/rdalsamodel.h new file mode 100644 index 00000000..bd4e9148 --- /dev/null +++ b/utils/rdalsaconfig/rdalsamodel.h @@ -0,0 +1,56 @@ +// rdalsamodel.h +// +// Abstract an ALSA configuration. +// +// (C) Copyright 2009-2018 Fred Gleason +// +// 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 RDALSAMODEL_H +#define RDALSAMODEL_H + +#include +#include +#include + +#include + +#include "rdalsacard.h" + +#define START_MARKER "# *** Start of Rivendell configuration generated by rdalsaconfig(1) ***" +#define END_MARKER "# *** End of Rivendell configuration generated by rdalsaconfig(1) ***" + +class RDAlsaModel : public QAbstractListModel +{ + Q_OBJECT; + public: + RDAlsaModel(QObject *parent=0); + int rowCount(const QModelIndex &parent=QModelIndex()) const; + QVariant data(const QModelIndex &index,int role=Qt::DisplayRole) const; + QVariant headerData(int section,Qt::Orientation orient, + int role=Qt::DisplayRole) const; + QModelIndex indexOf(const QString &card_id,int pcm_num) const; + RDAlsaCard *card(const QModelIndex &index) const; + int pcmNumber(const QModelIndex &index) const; + + private: + void LoadSystemConfig(); + QList model_alsa_cards; + QList model_card_index; + QList model_pcm_index; +}; + + +#endif // RDALSAMODEL_H From 9d5cc4865c11da274a1508557d810a02e1c3bbd9 Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Mon, 26 Aug 2019 13:58:41 -0400 Subject: [PATCH 09/35] Fixed typo in 'ChangeLog' --- ChangeLog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 120facd6..8781b3bd 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18949,4 +18949,4 @@ * Re-indented switch() statements in 'lib/rdlogplay.cpp'. 2019-08-24 Fred Gleason * Refactored rdalsaconfig(8) to use ALSA device IDs rather than - ordinal numbers in asound.confO(5). + ordinal numbers in asound.conf(5). From c2e410f637e2b503cfb685e798e2e25a4398c34a Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Mon, 26 Aug 2019 13:59:27 -0400 Subject: [PATCH 10/35] 2019-08-26 Fred Gleason * Updated rdalsaconfig(8) to include a 'rate ' line in each asound.conf(5) entry. --- ChangeLog | 3 +++ utils/rdalsaconfig/rdalsaconfig.cpp | 16 +++++++++++++++- utils/rdalsaconfig/rdalsamodel.cpp | 17 ++++++++++++++++- utils/rdalsaconfig/rdalsamodel.h | 4 +++- 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index 8781b3bd..ef261711 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18950,3 +18950,6 @@ 2019-08-24 Fred Gleason * Refactored rdalsaconfig(8) to use ALSA device IDs rather than ordinal numbers in asound.conf(5). +2019-08-26 Fred Gleason + * Updated rdalsaconfig(8) to include a 'rate ' line in + each asound.conf(5) entry. diff --git a/utils/rdalsaconfig/rdalsaconfig.cpp b/utils/rdalsaconfig/rdalsaconfig.cpp index aa3b3d7c..950d2235 100644 --- a/utils/rdalsaconfig/rdalsaconfig.cpp +++ b/utils/rdalsaconfig/rdalsaconfig.cpp @@ -28,6 +28,8 @@ #include #include +#include + #include #include @@ -66,6 +68,8 @@ void StartDaemons() MainWidget::MainWidget(QWidget *parent) : QWidget(parent) { + QString err_msg; + setWindowTitle(tr("RDAlsaConfig")+" v"+VERSION); // @@ -79,6 +83,15 @@ MainWidget::MainWidget(QWidget *parent) setMinimumWidth(sizeHint().width()); setMinimumHeight(sizeHint().height()); + // + // Open the Database + // + rda=new RDApplication("RDAlsaConfig","rdalsaconfig",RDALSACONFIG_USAGE,this); + if(!rda->open(&err_msg)) { + QMessageBox::critical(this,"RDAlsaConfig - "+tr("Error"),err_msg); + exit(1); + } + // // Generate Fonts // @@ -121,7 +134,7 @@ MainWidget::MainWidget(QWidget *parent) // // Load Available Devices and Configuration // - alsa_system_model=new RDAlsaModel(); + alsa_system_model=new RDAlsaModel(rda->system()->sampleRate(),this); alsa_system_list->setModel(alsa_system_model); LoadConfig(); @@ -336,6 +349,7 @@ void MainWidget::SaveConfig() const fprintf(f," card %s\n", (const char *)alsa_system_model->card(indexes.at(i))->id().toUtf8()); fprintf(f," device %d\n",alsa_system_model->pcmNumber(indexes.at(i))); + fprintf(f," rate %u\n",rda->system()->sampleRate()); if(alsa_system_model->card(indexes.at(i))->id()=="Axia") { fprintf(f," channels 2\n"); } diff --git a/utils/rdalsaconfig/rdalsamodel.cpp b/utils/rdalsaconfig/rdalsamodel.cpp index 497e6b25..f01828cd 100644 --- a/utils/rdalsaconfig/rdalsamodel.cpp +++ b/utils/rdalsaconfig/rdalsamodel.cpp @@ -26,9 +26,11 @@ #include -RDAlsaModel::RDAlsaModel(QObject *parent) +RDAlsaModel::RDAlsaModel(unsigned samprate,QObject *parent) : QAbstractListModel(parent) { + model_sample_rate=samprate; + LoadSystemConfig(); } @@ -45,6 +47,19 @@ int RDAlsaModel::rowCount(const QModelIndex &parent) const } +Qt::ItemFlags RDAlsaModel::flags(const QModelIndex &index) const +{ + Qt::ItemFlags flags=QAbstractListModel::flags(index); + + if((model_alsa_cards.at(model_card_index.at(index.row()))->id()=="Axia")&& + (model_sample_rate!=48000)) { + flags=flags&Qt::ItemIsEnabled; + } + + return flags; +} + + QVariant RDAlsaModel::data(const QModelIndex &index,int role) const { int row=index.row(); diff --git a/utils/rdalsaconfig/rdalsamodel.h b/utils/rdalsaconfig/rdalsamodel.h index bd4e9148..c7ce6d7a 100644 --- a/utils/rdalsaconfig/rdalsamodel.h +++ b/utils/rdalsaconfig/rdalsamodel.h @@ -36,8 +36,9 @@ class RDAlsaModel : public QAbstractListModel { Q_OBJECT; public: - RDAlsaModel(QObject *parent=0); + RDAlsaModel(unsigned samprate,QObject *parent=0); int rowCount(const QModelIndex &parent=QModelIndex()) const; + Qt::ItemFlags flags(const QModelIndex &index) const; QVariant data(const QModelIndex &index,int role=Qt::DisplayRole) const; QVariant headerData(int section,Qt::Orientation orient, int role=Qt::DisplayRole) const; @@ -50,6 +51,7 @@ class RDAlsaModel : public QAbstractListModel QList model_alsa_cards; QList model_card_index; QList model_pcm_index; + unsigned model_sample_rate; }; From 4f9b8003796bb9f7029ccd692f9b51958e8fccd2 Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Mon, 26 Aug 2019 14:03:01 -0400 Subject: [PATCH 11/35] 2019-08-26 Fred Gleason * Fixed a bug in rdalsaconfig(8) that caused a 'Service not active' error to be generated at startup. --- ChangeLog | 3 +++ utils/rdalsaconfig/rdalsaconfig.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index ef261711..791c46ec 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18953,3 +18953,6 @@ 2019-08-26 Fred Gleason * Updated rdalsaconfig(8) to include a 'rate ' line in each asound.conf(5) entry. +2019-08-26 Fred Gleason + * Fixed a bug in rdalsaconfig(8) that caused a 'Service not + active' error to be generated at startup. diff --git a/utils/rdalsaconfig/rdalsaconfig.cpp b/utils/rdalsaconfig/rdalsaconfig.cpp index 950d2235..7c2ea346 100644 --- a/utils/rdalsaconfig/rdalsaconfig.cpp +++ b/utils/rdalsaconfig/rdalsaconfig.cpp @@ -87,7 +87,7 @@ MainWidget::MainWidget(QWidget *parent) // Open the Database // rda=new RDApplication("RDAlsaConfig","rdalsaconfig",RDALSACONFIG_USAGE,this); - if(!rda->open(&err_msg)) { + if(!rda->open(&err_msg,NULL,false)) { QMessageBox::critical(this,"RDAlsaConfig - "+tr("Error"),err_msg); exit(1); } From 7fe3952c5d5977385843892e6726d0d7aaf58157 Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Mon, 26 Aug 2019 14:11:38 -0400 Subject: [PATCH 12/35] 2019-08-26 Fred Gleason * Reimplemented the '--asoundrc-file=' directive in rdalsaconfig(8). --- ChangeLog | 3 +++ utils/rdalsaconfig/rdalsaconfig.cpp | 14 +++++++------- utils/rdalsaconfig/rdalsaconfig.h | 4 ++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/ChangeLog b/ChangeLog index 791c46ec..5390080e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18956,3 +18956,6 @@ 2019-08-26 Fred Gleason * Fixed a bug in rdalsaconfig(8) that caused a 'Service not active' error to be generated at startup. +2019-08-26 Fred Gleason + * Reimplemented the '--asoundrc-file=' directive in + rdalsaconfig(8). diff --git a/utils/rdalsaconfig/rdalsaconfig.cpp b/utils/rdalsaconfig/rdalsaconfig.cpp index 7c2ea346..b4e35c21 100644 --- a/utils/rdalsaconfig/rdalsaconfig.cpp +++ b/utils/rdalsaconfig/rdalsaconfig.cpp @@ -136,7 +136,7 @@ MainWidget::MainWidget(QWidget *parent) // alsa_system_model=new RDAlsaModel(rda->system()->sampleRate(),this); alsa_system_list->setModel(alsa_system_model); - LoadConfig(); + LoadConfig(alsa_filename); // // Daemon Management @@ -199,7 +199,7 @@ void MainWidget::saveData() StartDaemons(); */ - SaveConfig(); + SaveConfig(alsa_filename); qApp->quit(); } @@ -246,7 +246,7 @@ void MainWidget::closeEvent(QCloseEvent *e) } -void MainWidget::LoadConfig() +void MainWidget::LoadConfig(const QString &filename) { FILE *f=NULL; char line[1024]; @@ -258,7 +258,7 @@ void MainWidget::LoadConfig() bool active_line=false; QModelIndex index; - if((f=fopen(RD_ASOUNDRC_FILE,"r"))==NULL) { + if((f=fopen(filename.toUtf8(),"r"))==NULL) { return; } while(fgets(line,1024,f)!=NULL) { @@ -330,9 +330,9 @@ void MainWidget::LoadConfig() } -void MainWidget::SaveConfig() const +void MainWidget::SaveConfig(const QString &filename) const { - QString tempfile=QString(RD_ASOUNDRC_FILE)+"-temp"; + QString tempfile=filename+"-temp"; FILE *f=NULL; if((f=fopen(tempfile.toUtf8(),"w"))==NULL) { @@ -363,7 +363,7 @@ void MainWidget::SaveConfig() const fprintf(f,"%s\n",END_MARKER); fclose(f); - rename(tempfile.toUtf8(),RD_ASOUNDRC_FILE); + rename(tempfile.toUtf8(),filename.toUtf8()); } diff --git a/utils/rdalsaconfig/rdalsaconfig.h b/utils/rdalsaconfig/rdalsaconfig.h index 84e21bcb..c3826039 100644 --- a/utils/rdalsaconfig/rdalsaconfig.h +++ b/utils/rdalsaconfig/rdalsaconfig.h @@ -53,8 +53,8 @@ class MainWidget : public QWidget void closeEvent(QCloseEvent *e); private: - void LoadConfig(); - void SaveConfig() const; + void LoadConfig(const QString &filename); + void SaveConfig(const QString &filename) const; QLabel *alsa_system_label; QLabel *alsa_description_label; QListView *alsa_system_list; From 9df39e2bb4d56d55e092d59f09af65500c24d6b2 Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Mon, 26 Aug 2019 14:19:51 -0400 Subject: [PATCH 13/35] 2019-08-26 Fred Gleason * Reimplemented the '--manage-daemons' directive in rdalsaconfig(8). --- ChangeLog | 3 +++ utils/rdalsaconfig/rdalsaconfig.cpp | 22 ++-------------------- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/ChangeLog b/ChangeLog index 5390080e..f1414258 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18959,3 +18959,6 @@ 2019-08-26 Fred Gleason * Reimplemented the '--asoundrc-file=' directive in rdalsaconfig(8). +2019-08-26 Fred Gleason + * Reimplemented the '--manage-daemons' directive in + rdalsaconfig(8). diff --git a/utils/rdalsaconfig/rdalsaconfig.cpp b/utils/rdalsaconfig/rdalsaconfig.cpp index b4e35c21..c377a042 100644 --- a/utils/rdalsaconfig/rdalsaconfig.cpp +++ b/utils/rdalsaconfig/rdalsaconfig.cpp @@ -179,28 +179,10 @@ QSizePolicy MainWidget::sizePolicy() const void MainWidget::saveData() { - /* - AlsaItem *item=NULL; - - for(int i=0;iitem(i))==NULL) { - alsa_alsa->setRivendellCard(i,-1); - } - else { - alsa_alsa->setRivendellCard(i,item->cardNumber()); - } - } - if(!alsa_alsa->save(alsa_filename)) { - QMessageBox::warning(this,tr("RDAlsaConfig error"), - tr(QString("Unable to save configuration to \"")+ - alsa_filename+"\"")); - return; - } - StartDaemons(); - */ - SaveConfig(alsa_filename); + StartDaemons(); + qApp->quit(); } From a658fda4fce5e4ce9ab00661c08480c89bef30ca Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Mon, 26 Aug 2019 16:17:24 -0400 Subject: [PATCH 14/35] 2019-08-26 Fred Gleason * Reimplemented the '--autogen' directive in rdalsaconfig(8). * Added a '--rewrite' directive in rdalsaconfig(8). --- ChangeLog | 3 + utils/rdalsaconfig/rdalsacard.cpp | 13 +++ utils/rdalsaconfig/rdalsacard.h | 3 + utils/rdalsaconfig/rdalsaconfig.cpp | 79 ++++++++++++++ utils/rdalsaconfig/rdalsaconfig.h | 8 ++ utils/rdalsaconfig/rdalsamodel.cpp | 159 ++++++++++++++++++++++++++++ utils/rdalsaconfig/rdalsamodel.h | 5 + 7 files changed, 270 insertions(+) diff --git a/ChangeLog b/ChangeLog index f1414258..0bae73b3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18962,3 +18962,6 @@ 2019-08-26 Fred Gleason * Reimplemented the '--manage-daemons' directive in rdalsaconfig(8). +2019-08-26 Fred Gleason + * Reimplemented the '--autogen' directive in rdalsaconfig(8). + * Added a '--rewrite' directive in rdalsaconfig(8). diff --git a/utils/rdalsaconfig/rdalsacard.cpp b/utils/rdalsaconfig/rdalsacard.cpp index da11295c..b6d31c86 100644 --- a/utils/rdalsaconfig/rdalsacard.cpp +++ b/utils/rdalsaconfig/rdalsacard.cpp @@ -43,6 +43,7 @@ RDAlsaCard::RDAlsaCard(snd_ctl_t *ctl,int index) card_pcm_names.push_back(snd_pcm_info_get_name(pcm_info)+ QString().sprintf("[%02d]",pcm+1)); snd_ctl_pcm_next_device(ctl,&pcm); + card_enableds.push_back(false); } } snd_pcm_info_free(pcm_info); @@ -86,6 +87,18 @@ QString RDAlsaCard::mixerName() const } +bool RDAlsaCard::isEnabled(int pcm_num) const +{ + return card_enableds.at(pcm_num); +} + + +void RDAlsaCard::setEnabled(int pcm_num,bool state) +{ + card_enableds[pcm_num]=state; +} + + QString RDAlsaCard::dump() const { QString ret=QString().sprintf("Card %d\n",index()); diff --git a/utils/rdalsaconfig/rdalsacard.h b/utils/rdalsaconfig/rdalsacard.h index 5f867395..06ceaafc 100644 --- a/utils/rdalsaconfig/rdalsacard.h +++ b/utils/rdalsaconfig/rdalsacard.h @@ -38,6 +38,8 @@ class RDAlsaCard QString mixerName() const; int pcmQuantity() const; QString pcmName(int n) const; + bool isEnabled(int pcm_num) const; + void setEnabled(int pcm_num,bool state); QString dump() const; private: @@ -48,6 +50,7 @@ class RDAlsaCard QString card_long_name; QString card_mixer_name; QStringList card_pcm_names; + QList card_enableds; }; diff --git a/utils/rdalsaconfig/rdalsaconfig.cpp b/utils/rdalsaconfig/rdalsaconfig.cpp index c377a042..62ff65f4 100644 --- a/utils/rdalsaconfig/rdalsaconfig.cpp +++ b/utils/rdalsaconfig/rdalsaconfig.cpp @@ -43,6 +43,7 @@ // QString alsa_filename; bool alsa_autogen=false; +bool alsa_rewrite=false; bool alsa_manage_daemons=false; bool alsa_daemon_start_needed=false; @@ -230,6 +231,21 @@ void MainWidget::closeEvent(QCloseEvent *e) void MainWidget::LoadConfig(const QString &filename) { + if(!alsa_system_model->loadConfig(filename)) { + return; + } + for(int i=0;irowCount();i++) { + if(alsa_system_model->isEnabled(i)) { + alsa_system_list->selectionModel()-> + select(alsa_system_model->index(i,0),QItemSelectionModel::Select); + } + else { + alsa_system_list->selectionModel()-> + select(alsa_system_model->index(i,0),QItemSelectionModel::Deselect); + } + } + + /* FILE *f=NULL; char line[1024]; int istate=0; @@ -309,11 +325,20 @@ void MainWidget::LoadConfig(const QString &filename) } } fclose(f); + */ } void MainWidget::SaveConfig(const QString &filename) const { + for(int i=0;irowCount();i++) { + QItemSelectionModel *sel=alsa_system_list->selectionModel(); + alsa_system_model->setEnabled(i,sel->isRowSelected(i,QModelIndex())); + } + alsa_system_model->saveConfig(filename); + + + /* QString tempfile=filename+"-temp"; FILE *f=NULL; @@ -346,6 +371,51 @@ void MainWidget::SaveConfig(const QString &filename) const fclose(f); rename(tempfile.toUtf8(),filename.toUtf8()); + */ +} + + +Autogen::Autogen() + : QObject() +{ + QString err_msg; + + // + // Open the Database + // + rda=new RDApplication("RDAlsaConfig","rdalsaconfig",RDALSACONFIG_USAGE); + if(!rda->open(&err_msg,NULL,false)) { + fprintf(stderr,"rdalsaconfig: unable to open database [%s]\n", + (const char *)err_msg.toUtf8()); + exit(1); + } + + StopDaemons(); + + RDAlsaModel *model=new RDAlsaModel(rda->system()->sampleRate()); + if(alsa_rewrite) { + if(!model->loadConfig(alsa_filename)) { + fprintf(stderr,"rdalsaconfig: unable to load file \"%s\"\n", + (const char *)alsa_filename.toUtf8()); + StartDaemons(); + exit(1); + } + } + if(alsa_autogen) { + for(int i=0;irowCount();i++) { + model->setEnabled(i,true); + } + } + if(!model->saveConfig(alsa_filename)) { + fprintf(stderr,"rdalsaconfig: unable to load file \"%s\"\n", + (const char *)alsa_filename.toUtf8()); + StartDaemons(); + exit(1); + } + + StartDaemons(); + + exit(0); } @@ -364,11 +434,20 @@ int main(int argc,char *argv[]) if(cmd->key(i)=="--autogen") { alsa_autogen=true; } + if(cmd->key(i)=="--rewrite") { + alsa_rewrite=true; + } if(cmd->key(i)=="--manage-daemons") { alsa_manage_daemons=true; } } + if(alsa_autogen||alsa_rewrite) { + QCoreApplication a(argc,argv); + new Autogen(); + return a.exec(); + } + // // Start GUI // diff --git a/utils/rdalsaconfig/rdalsaconfig.h b/utils/rdalsaconfig/rdalsaconfig.h index c3826039..aee87a8f 100644 --- a/utils/rdalsaconfig/rdalsaconfig.h +++ b/utils/rdalsaconfig/rdalsaconfig.h @@ -65,4 +65,12 @@ class MainWidget : public QWidget }; +class Autogen : public QObject +{ + Q_OBJECT + public: + Autogen(); +}; + + #endif // RDALSACONFIG_H diff --git a/utils/rdalsaconfig/rdalsamodel.cpp b/utils/rdalsaconfig/rdalsamodel.cpp index f01828cd..d793802b 100644 --- a/utils/rdalsaconfig/rdalsamodel.cpp +++ b/utils/rdalsaconfig/rdalsamodel.cpp @@ -24,6 +24,7 @@ #include +#include #include RDAlsaModel::RDAlsaModel(unsigned samprate,QObject *parent) @@ -151,6 +152,164 @@ int RDAlsaModel::pcmNumber(const QModelIndex &index) const } +bool RDAlsaModel::isEnabled(int row) const +{ + return model_alsa_cards.at(model_card_index.at(row))-> + isEnabled(model_pcm_index.at(row)); +} + + +void RDAlsaModel::setEnabled(int row,bool state) +{ + return model_alsa_cards.at(model_card_index.at(row))-> + setEnabled(model_pcm_index.at(row),state); +} + + +bool RDAlsaModel::loadConfig(const QString &filename) +{ + FILE *f=NULL; + char line[1024]; + int istate=0; + int port=0; + QString card_id=0; + int card_num=0; + bool ok=false; + int device=0; + QStringList list; + bool active_line=false; + QModelIndex index; + + if((f=fopen(filename.toUtf8(),"r"))==NULL) { + return false; + } + while(fgets(line,1024,f)!=NULL) { + QString str=line; + str.replace("\n",""); + if(str==START_MARKER) { + active_line=true; + } + if(str==END_MARKER) { + active_line=false; + } + if((str!=START_MARKER)&&(str!=END_MARKER)) { + if(active_line) { + switch(istate) { + case 0: + if(str.left(6)=="pcm.rd") { + port=str.mid(6,1).toInt(); + istate=1; + } + else { + if(str.left(6)=="ctl.rd") { + istate=10; + } + else { + model_other_lines.push_back(str+"\n"); + } + } + break; + + case 1: + list=str.split(" ",QString::SkipEmptyParts); + if(list[0]=="}") { + if((port>=0)&&(portindex()) { + if((device>=0)&&(devicepcmQuantity())) { + card->setEnabled(device,true); + } + } + } + else { + if(card_id==card->id()) { + if((device>=0)&&(devicepcmQuantity())) { + card->setEnabled(device,true); + } + } + } + } + } + card_id=""; + device=0; + istate=0; + } + else { + if(list.size()==2) { + if(list[0]=="card") { + card_id=list[1].trimmed(); + } + if(list[0]=="device") { + device=list[1].toInt(); + } + } + } + break; + + case 10: + if(str.left(1)=="}") { + istate=0; + } + break; + } + } + else { + model_other_lines.push_back(str+"\n"); + } + } + } + fclose(f); + + return true; +} + + +bool RDAlsaModel::saveConfig(const QString &filename) +{ + QString tempfile=filename+"-temp"; + FILE *f=NULL; + int index=0; + + if((f=fopen(tempfile.toUtf8(),"w"))==NULL) { + return false; + } + for(int i=0;ipcmQuantity();j++) { + if(card->isEnabled(j)) { + fprintf(f,"pcm.rd%d {\n",index); + fprintf(f," type hw\n"); + fprintf(f," card %s\n",(const char *)card->id().toUtf8()); + fprintf(f," device %d\n",j); + fprintf(f," rate %u\n",rda->system()->sampleRate()); + if(card->id()=="Axia") { + fprintf(f," channels 2\n"); + } + fprintf(f,"}\n"); + fprintf(f,"ctl.rd%d {\n",index); + fprintf(f," type hw\n"); + fprintf(f," card %s\n",(const char *)card->id().toUtf8()); + fprintf(f,"}\n"); + index++; + } + } + } + fprintf(f,"%s\n",END_MARKER); + + fclose(f); + rename(tempfile.toUtf8(),filename.toUtf8()); + + return true; +} + + void RDAlsaModel::LoadSystemConfig() { snd_ctl_t *snd_ctl=NULL; diff --git a/utils/rdalsaconfig/rdalsamodel.h b/utils/rdalsaconfig/rdalsamodel.h index c7ce6d7a..e53c745d 100644 --- a/utils/rdalsaconfig/rdalsamodel.h +++ b/utils/rdalsaconfig/rdalsamodel.h @@ -45,6 +45,10 @@ class RDAlsaModel : public QAbstractListModel QModelIndex indexOf(const QString &card_id,int pcm_num) const; RDAlsaCard *card(const QModelIndex &index) const; int pcmNumber(const QModelIndex &index) const; + bool isEnabled(int row) const; + void setEnabled(int row,bool state); + bool loadConfig(const QString &filename); + bool saveConfig(const QString &filename); private: void LoadSystemConfig(); @@ -52,6 +56,7 @@ class RDAlsaModel : public QAbstractListModel QList model_card_index; QList model_pcm_index; unsigned model_sample_rate; + QStringList model_other_lines; }; From ffe62e4c884508007b68d1ccb9fcaf1a04f35fc2 Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Mon, 26 Aug 2019 17:00:38 -0400 Subject: [PATCH 15/35] 2019-08-26 Fred Gleason * Added an rdalsaconfig(1) man page. --- ChangeLog | 2 + docs/manpages/Makefile.am | 4 ++ docs/manpages/rdalsaconfig.xml | 106 ++++++++++++++++++++++++++++++ rivendell.spec.in | 1 + utils/rdalsaconfig/rdalsaconfig.h | 2 +- 5 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 docs/manpages/rdalsaconfig.xml diff --git a/ChangeLog b/ChangeLog index 0bae73b3..7745653e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18965,3 +18965,5 @@ 2019-08-26 Fred Gleason * Reimplemented the '--autogen' directive in rdalsaconfig(8). * Added a '--rewrite' directive in rdalsaconfig(8). +2019-08-26 Fred Gleason + * Added an rdalsaconfig(1) man page. diff --git a/docs/manpages/Makefile.am b/docs/manpages/Makefile.am index c63c847e..d015072b 100644 --- a/docs/manpages/Makefile.am +++ b/docs/manpages/Makefile.am @@ -32,6 +32,7 @@ xsltproc $(DOCBOOK_STYLESHEETS)/manpages/docbook.xsl $< all-local: rdairplay.1\ + rdalsaconfig.1\ rdclilogedit.1\ rdconvert.1\ rddbmgr.8\ @@ -45,6 +46,7 @@ all-local: rdairplay.1\ rdservice.8 man_MANS = rdairplay.1\ + rdalsaconfig.1\ rdclilogedit.1\ rdconvert.1\ rddbmgr.8\ @@ -59,6 +61,8 @@ man_MANS = rdairplay.1\ EXTRA_DIST = rdairplay.1\ rdairplay.xml\ + rdalsaconfig.1\ + rdalsaconfig.xml\ rdclilogedit.1\ rdclilogedit.xml\ rdconvert.1\ diff --git a/docs/manpages/rdalsaconfig.xml b/docs/manpages/rdalsaconfig.xml new file mode 100644 index 00000000..70d1fdd1 --- /dev/null +++ b/docs/manpages/rdalsaconfig.xml @@ -0,0 +1,106 @@ + + + + + rdalsaconfig + 1 + August 2019 + Linux Audio Manual + + + rdalsaconfig + + Utility for managing Rivendell ALSA configuration + + + + + + Fred + Gleason + fredg@paravelsystems.com + + Application Author + + + + + + + rdalsaconfig + options + + + + + Description + + When invoked with no options, + rdalsaconfig1 will + query the system for the list of available ALSA PCM devices and + display the results in a GUI applet, with the device(s) currently + configured for use by Rivendell highlighted. The user may select and/or + deselect devices for Rivendell and save the result. + + + + Options + + + + + + + + Load and save the Rivendell configuration file from + filename. Default value is + /etc/asound.conf. + + + + + + + + + + Generate and save a Rivendell configuration containing all + available ALSA PCM devices, then exit. This option is mutually + exclusive with the --rewrite option (see below). + + + + + + + + + + Restart the Rivendell service as necessary to make configuration + changes active (requires root permission). + + + + + + + + + + Load the current Rivendell configuration, save it back to the + same location, then exit (useful for upgrading an existing v2.x + configuration to the enhanced v3.x format). This option is mutually + exclusive with the --autogen option (see above). + + + + + + + + + diff --git a/rivendell.spec.in b/rivendell.spec.in index a1e708b2..664bff1c 100644 --- a/rivendell.spec.in +++ b/rivendell.spec.in @@ -389,6 +389,7 @@ rm -rf $RPM_BUILD_ROOT /etc/pam.d/rivendell /lib/systemd/system/rivendell.service %{_mandir}/man1/rdairplay.1.gz +%{_mandir}/man1/rdalsaconfig.1.gz %{_mandir}/man1/rdclilogedit.1.gz %{_mandir}/man1/rdconvert.1.gz %{_mandir}/man1/rdexport.1.gz diff --git a/utils/rdalsaconfig/rdalsaconfig.h b/utils/rdalsaconfig/rdalsaconfig.h index aee87a8f..a9be0cfd 100644 --- a/utils/rdalsaconfig/rdalsaconfig.h +++ b/utils/rdalsaconfig/rdalsaconfig.h @@ -30,7 +30,7 @@ #include #include "rdalsamodel.h" -#define RDALSACONFIG_USAGE "[--asoundrc-file=] [--autogen] [--manage-daemons]\n\nGenerate an ALSA sound card configuration for Rivendell.\n\nThe following options are available:\n\n --asoundrc-file=\n Read and write configuration from (default value \n \"/etc/asound.conf\").\n\n --autogen\n Generate and save a configuration containing all available PCM devices\n and then exit.\n\n --manage-daemons\n Restart the Rivendell daemons as necessary to make configuration\n changes active (requires root permission).\n\n" +#define RDALSACONFIG_USAGE "[options]\n" void StopDaemons(); void StartDaemons(); From 945aae0297c9e7176fcd936d556fab7712b83aac Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Mon, 26 Aug 2019 19:05:39 -0400 Subject: [PATCH 16/35] 2019-08-26 Fred Gleason * Fixed a regression in caed(8) that broke timescaling support. --- ChangeLog | 2 ++ cae/cae.cpp | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 7745653e..6bd28c8d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18967,3 +18967,5 @@ * Added a '--rewrite' directive in rdalsaconfig(8). 2019-08-26 Fred Gleason * Added an rdalsaconfig(1) man page. +2019-08-26 Fred Gleason + * Fixed a regression in caed(8) that broke timescaling support. diff --git a/cae/cae.cpp b/cae/cae.cpp index b9a0821e..0a76506e 100644 --- a/cae/cae.cpp +++ b/cae/cae.cpp @@ -714,10 +714,10 @@ void MainObject::timescalingSupportData(int id,unsigned card) break; } if(state) { - cae_server->sendCommand(id,"TS +!"); + cae_server->sendCommand(id,QString().sprintf("TS %u +!",card)); } else { - cae_server->sendCommand(id,"TS -!"); + cae_server->sendCommand(id,QString().sprintf("TS %u -!",card)); } } From 4503d33526cc0e8dcd78738d535d3394665c1cc7 Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Tue, 27 Aug 2019 16:41:04 -0400 Subject: [PATCH 17/35] 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_ando.py' PyPAD script that caused an infinite loop. --- ChangeLog | 3 ++ apis/pypad/scripts/pypad_ando.py | 51 +++++++++++++++----------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/ChangeLog b/ChangeLog index 9214f529..7e2ecc4e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18978,3 +18978,6 @@ * Added an rdalsaconfig(1) man page. 2019-08-26 Fred Gleason * Fixed a regression in caed(8) that broke timescaling support. +2019-08-26 Fred Gleason + * Fixed a bug in the 'pypad_ando.py' PyPAD script that caused + an infinite loop. diff --git a/apis/pypad/scripts/pypad_ando.py b/apis/pypad/scripts/pypad_ando.py index ff5b077a..60096ce9 100755 --- a/apis/pypad/scripts/pypad_ando.py +++ b/apis/pypad/scripts/pypad_ando.py @@ -32,14 +32,13 @@ def eprint(*args,**kwargs): def ProcessTimer(config): n=1 - while(True): + section='System'+str(n) + while(config.has_section(section)): + send_sock.sendto('HB'.encode('utf-8'), + (config.get(section,'IpAddress'),int(config.get(section,'UdpPort')))) + n=n+1 section='System'+str(n) - try: - send_sock.sendto('HB'.encode('utf-8'), - (config.get(section,'IpAddress'),int(config.get(section,'UdpPort')))) - n=n+1 - except configparser.NoSectionError: - return + return def ProcessPad(update): try: @@ -48,27 +47,25 @@ def ProcessPad(update): last_updates[update.machine()]=None n=1 - while(True): + section='System'+str(n) + while(update.config().has_section(section)): section='System'+str(n) - try: - if update.shouldBeProcessed(section) and update.hasPadType(pypad.TYPE_NOW) and (last_updates[update.machine()] != update.startDateTimeString(pypad.TYPE_NOW)): - last_updates[update.machine()]=update.startDateTimeString(pypad.TYPE_NOW) - title=update.resolvePadFields(update.config().get(section,'Title'),pypad.ESCAPE_NONE) - artist=update.resolvePadFields(update.config().get(section,'Artist'),pypad.ESCAPE_NONE) - album=update.resolvePadFields(update.config().get(section,'Album'),pypad.ESCAPE_NONE) - label=update.resolvePadFields(update.config().get(section,'Label'),pypad.ESCAPE_NONE) - secs=update.padField(pypad.TYPE_NOW,pypad.FIELD_LENGTH) - duration=('%02d:' % (secs//60000))+('%02d' % ((secs%60000)//1000)) - group=update.padField(pypad.TYPE_NOW,pypad.FIELD_GROUP_NAME) - if update.config().get(section,'Label')=='': - msg='^'+artist+'~'+title+'~'+duration+'~'+group+'~'+album+'~'+str(update.padField(pypad.TYPE_NOW,pypad.FIELD_CART_NUMBER))+'|' - else: - msg='^'+artist+'~'+title+'~'+duration+'~'+group+'~'+str(update.padField(pypad.TYPE_NOW,pypad.FIELD_CART_NUMBER))+'~'+album+'~'+label+'|' - send_sock.sendto(msg.encode('utf-8'), - (update.config().get(section,'IpAddress'),int(update.config().get(section,'UdpPort')))) - n=n+1 - except configparser.NoSectionError: - return + if update.shouldBeProcessed(section) and update.hasPadType(pypad.TYPE_NOW) and (last_updates[update.machine()] != update.startDateTimeString(pypad.TYPE_NOW)): + last_updates[update.machine()]=update.startDateTimeString(pypad.TYPE_NOW) + title=update.resolvePadFields(update.config().get(section,'Title'),pypad.ESCAPE_NONE) + artist=update.resolvePadFields(update.config().get(section,'Artist'),pypad.ESCAPE_NONE) + album=update.resolvePadFields(update.config().get(section,'Album'),pypad.ESCAPE_NONE) + label=update.resolvePadFields(update.config().get(section,'Label'),pypad.ESCAPE_NONE) + secs=update.padField(pypad.TYPE_NOW,pypad.FIELD_LENGTH) + duration=('%02d:' % (secs//60000))+('%02d' % ((secs%60000)//1000)) + group=update.padField(pypad.TYPE_NOW,pypad.FIELD_GROUP_NAME) + if update.config().get(section,'Label')=='': + msg='^'+artist+'~'+title+'~'+duration+'~'+group+'~'+album+'~'+str(update.padField(pypad.TYPE_NOW,pypad.FIELD_CART_NUMBER))+'|' + else: + msg='^'+artist+'~'+title+'~'+duration+'~'+group+'~'+str(update.padField(pypad.TYPE_NOW,pypad.FIELD_CART_NUMBER))+'~'+album+'~'+label+'|' + send_sock.sendto(msg.encode('utf-8'), + (update.config().get(section,'IpAddress'),int(update.config().get(section,'UdpPort')))) + n=n+1 # # 'Main' function From b19f2a9d6bc7ee0fe874a92470c4189711037f7a Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Tue, 27 Aug 2019 16:48:05 -0400 Subject: [PATCH 18/35] 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_filewrite.py' PyPAD script that caused an infinite loop. --- ChangeLog | 3 +++ apis/pypad/scripts/pypad_filewrite.py | 28 +++++++++++++-------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/ChangeLog b/ChangeLog index 7e2ecc4e..6507d116 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18981,3 +18981,6 @@ 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_ando.py' PyPAD script that caused an infinite loop. +2019-08-26 Fred Gleason + * Fixed a bug in the 'pypad_filewrite.py' PyPAD script that caused + an infinite loop. diff --git a/apis/pypad/scripts/pypad_filewrite.py b/apis/pypad/scripts/pypad_filewrite.py index 764d9b70..ce657680 100755 --- a/apis/pypad/scripts/pypad_filewrite.py +++ b/apis/pypad/scripts/pypad_filewrite.py @@ -4,7 +4,7 @@ # # Write PAD updates to files # -# (C) Copyright 2018 Fred Gleason +# (C) Copyright 2018-2019 Fred Gleason # # 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 @@ -29,21 +29,19 @@ def eprint(*args,**kwargs): def ProcessPad(update): n=1 - try: - while(True): - section='File'+str(n) - if update.shouldBeProcessed(section): - fmtstr=update.config().get(section,'FormatString') - mode='w' - if update.config().get(section,'Append')=='1': - mode='a' - f=open(update.resolveFilepath(update.config().get(section,'Filename'),update.dateTime()),mode) - f.write(update.resolvePadFields(fmtstr,int(update.config().get(section,'Encoding')))) - f.close() - n=n+1 + section='File'+str(n) + while(update.config().has_section(section)): + if update.shouldBeProcessed(section): + fmtstr=update.config().get(section,'FormatString') + mode='w' + if update.config().get(section,'Append')=='1': + mode='a' + f=open(update.resolveFilepath(update.config().get(section,'Filename'),update.dateTime()),mode) + f.write(update.resolvePadFields(fmtstr,int(update.config().get(section,'Encoding')))) + f.close() + n=n+1 + section='File'+str(n) - except configparser.NoSectionError: - return # # 'Main' function From f951b60065ec0e06f4694856cc623d704e43dee7 Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Tue, 27 Aug 2019 17:18:44 -0400 Subject: [PATCH 19/35] 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_inno713.py' PyPAD script that caused an infinite loop. --- ChangeLog | 3 + apis/pypad/scripts/pypad_inno713.py | 93 ++++++++++++++--------------- 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/ChangeLog b/ChangeLog index 6507d116..26e467a5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18984,3 +18984,6 @@ 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_filewrite.py' PyPAD script that caused an infinite loop. +2019-08-26 Fred Gleason + * Fixed a bug in the 'pypad_inno713.py' PyPAD script that caused + an infinite loop. diff --git a/apis/pypad/scripts/pypad_inno713.py b/apis/pypad/scripts/pypad_inno713.py index 5698e876..3f8412cb 100755 --- a/apis/pypad/scripts/pypad_inno713.py +++ b/apis/pypad/scripts/pypad_inno713.py @@ -32,55 +32,52 @@ def eprint(*args,**kwargs): def ProcessPad(update): n=1 - while(True): + section='Rds'+str(n) + while(update.config().has_section(section)): + if update.shouldBeProcessed(section) and update.hasPadType(pypad.TYPE_NOW): + dps='' + if(len(update.config().get(section,'DynamicPsString'))!=0): + dps='DPS='+update.resolvePadFields(update.config().get(section,'DynamicPsString'),pypad.ESCAPE_NONE)+'\r\n' + ps='' + if(len(update.config().get(section,'PsString'))!=0): + ps='PS='+update.resolvePadFields(update.config().get(section,'PsString'),pypad.ESCAPE_NONE)+'\r\n' + text='' + if(len(update.config().get(section,'RadiotextString'))!=0): + text='TEXT='+update.resolvePadFields(update.config().get(section,'RadiotextString'),pypad.ESCAPE_NONE)+'\r\n' + if(update.config().has_option(section,'Device')): + # + # Use serial output + # + tty_dev=update.config().get(section,'Device') + speed=int(update.config().get(section,'Speed')) + parity=serial.PARITY_NONE + if int(update.config().get(section,'Parity'))==1: + parity=serial.PARITY_EVEN + if int(update.config().get(section,'Parity'))==2: + parity=serial.PARITY_ODD + bytesize=int(update.config().get(section,'WordSize')) + dev=serial.Serial(tty_dev,speed,parity=parity,bytesize=bytesize) + if(len(dps)!=0): + dev.write(dps.encode('utf-8')) + if(len(ps)!=0): + dev.write(ps.encode('utf-8')) + if(len(text)!=0): + dev.write(text.encode('utf-8')) + dev.close() + else: + # + # Use UDP output + # + ipaddr=update.config().get(section,'IpAddress') + port=int(update.config().get(section,'UdpPort')) + if(len(dps)!=0): + send_sock.sendto(dps.encode('utf-8'),(ipaddr,port)) + if(len(ps)!=0): + send_sock.sendto(ps.encode('utf-8'),(ipaddr,port)) + if(len(text)!=0): + send_sock.sendto(text.encode('utf-8'),(ipaddr,port)) + n=n+1 section='Rds'+str(n) - try: - if update.shouldBeProcessed(section) and update.hasPadType(pypad.TYPE_NOW): - dps='' - if(len(update.config().get(section,'DynamicPsString'))!=0): - dps='DPS='+update.resolvePadFields(update.config().get(section,'DynamicPsString'),pypad.ESCAPE_NONE)+'\r\n' - ps='' - if(len(update.config().get(section,'PsString'))!=0): - ps='PS='+update.resolvePadFields(update.config().get(section,'PsString'),pypad.ESCAPE_NONE)+'\r\n' - text='' - if(len(update.config().get(section,'RadiotextString'))!=0): - text='TEXT='+update.resolvePadFields(update.config().get(section,'RadiotextString'),pypad.ESCAPE_NONE)+'\r\n' - try: - # - # Use serial output - # - tty_dev=update.config().get(section,'Device') - speed=int(update.config().get(section,'Speed')) - parity=serial.PARITY_NONE - if int(update.config().get(section,'Parity'))==1: - parity=serial.PARITY_EVEN - if int(update.config().get(section,'Parity'))==2: - parity=serial.PARITY_ODD - bytesize=int(update.config().get(section,'WordSize')) - dev=serial.Serial(tty_dev,speed,parity=parity,bytesize=bytesize) - if(len(dps)!=0): - dev.write(dps.encode('utf-8')) - if(len(ps)!=0): - dev.write(ps.encode('utf-8')) - if(len(text)!=0): - dev.write(text.encode('utf-8')) - dev.close() - - except configparser.NoOptionError: - # - # Use UDP output - # - ipaddr=update.config().get(section,'IpAddress') - port=int(update.config().get(section,'UdpPort')) - if(len(dps)!=0): - send_sock.sendto(dps.encode('utf-8'),(ipaddr,port)) - if(len(ps)!=0): - send_sock.sendto(ps.encode('utf-8'),(ipaddr,port)) - if(len(text)!=0): - send_sock.sendto(text.encode('utf-8'),(ipaddr,port)) - n=n+1 - except configparser.NoSectionError: - return # # 'Main' function From 533b760cd163ce0872ca499df6727026b849f809 Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Tue, 27 Aug 2019 17:32:24 -0400 Subject: [PATCH 20/35] 2019-08-26 Fred Gleason * Fixed a bug in rdadmin(1) that allowed more than one PyPAD instance to be selected at a time in the 'List PyPAD Instances' dialog. --- ChangeLog | 4 ++++ rdadmin/list_pypads.cpp | 3 +++ 2 files changed, 7 insertions(+) diff --git a/ChangeLog b/ChangeLog index 26e467a5..dfc178b2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18987,3 +18987,7 @@ 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_inno713.py' PyPAD script that caused an infinite loop. +2019-08-26 Fred Gleason + * Fixed a bug in rdadmin(1) that allowed more than one PyPAD + instance to be selected at a time in the 'List PyPAD Instances' + dialog. diff --git a/rdadmin/list_pypads.cpp b/rdadmin/list_pypads.cpp index 11ed6d49..3bb889b6 100644 --- a/rdadmin/list_pypads.cpp +++ b/rdadmin/list_pypads.cpp @@ -75,6 +75,7 @@ ListPypads::ListPypads(RDStation *station,QWidget *parent) // Instances List Box // list_list_view=new RDListView(this); + list_list_view->setSelectionMode(Q3ListView::Single); list_list_view->setAllColumnsShowFocus(true); list_list_view->setItemMargin(5); list_list_view->addColumn(" "); @@ -198,7 +199,9 @@ void ListPypads::addData() RDListViewItem *item=new RDListViewItem(list_list_view); item->setId(id); RefreshItem(item); + list_list_view->clearSelection(); list_list_view->ensureItemVisible(item); + list_list_view->setCurrentItem(item); item->setSelected(true); RDNotification notify=RDNotification(RDNotification::PypadType, RDNotification::AddAction,id); From 42c91a7cfdb3491ccde222b313233aab735f52f8 Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Tue, 27 Aug 2019 17:51:35 -0400 Subject: [PATCH 21/35] 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_liqcomp.py' PyPAD script that caused an infinite loop. --- ChangeLog | 3 +++ apis/pypad/scripts/pypad_liqcomp.py | 30 ++++++++++++++--------------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/ChangeLog b/ChangeLog index dfc178b2..d56f3858 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18991,3 +18991,6 @@ * Fixed a bug in rdadmin(1) that allowed more than one PyPAD instance to be selected at a time in the 'List PyPAD Instances' dialog. +2019-08-26 Fred Gleason + * Fixed a bug in the 'pypad_liqcomp.py' PyPAD script that caused + an infinite loop. diff --git a/apis/pypad/scripts/pypad_liqcomp.py b/apis/pypad/scripts/pypad_liqcomp.py index 33ef4328..6e0f1825 100755 --- a/apis/pypad/scripts/pypad_liqcomp.py +++ b/apis/pypad/scripts/pypad_liqcomp.py @@ -37,23 +37,21 @@ def ProcessPad(update): last_updates[update.machine()]=None n=1 - while(True): + section='System'+str(n) + while(update.config().has_section(section)): + if update.shouldBeProcessed(section) and update.hasPadType(pypad.TYPE_NOW) and (last_updates[update.machine()] != update.startDateTimeString(pypad.TYPE_NOW)): + last_updates[update.machine()]=update.startDateTimeString(pypad.TYPE_NOW) + title=update.resolvePadFields(update.config().get(section,'Title'),pypad.ESCAPE_NONE) + artist=update.resolvePadFields(update.config().get(section,'Artist'),pypad.ESCAPE_NONE) + album=update.resolvePadFields(update.config().get(section,'Album'),pypad.ESCAPE_NONE) + label=update.resolvePadFields(update.config().get(section,'Label'),pypad.ESCAPE_NONE) + secs=update.padField(pypad.TYPE_NOW,pypad.FIELD_LENGTH) + group=update.padField(pypad.TYPE_NOW,pypad.FIELD_GROUP_NAME) + msg='|'+title+'|'+artist+'|'+str(update.padField(pypad.TYPE_NOW,pypad.FIELD_CART_NUMBER))+'|'+str(secs)+'|'+group+'|'+album+'|'+label+'|\n' + send_sock.sendto(msg.encode('utf-8'), + (update.config().get(section,'IpAddress'),int(update.config().get(section,'UdpPort')))) + n=n+1 section='System'+str(n) - try: - if update.shouldBeProcessed(section) and update.hasPadType(pypad.TYPE_NOW) and (last_updates[update.machine()] != update.startDateTimeString(pypad.TYPE_NOW)): - last_updates[update.machine()]=update.startDateTimeString(pypad.TYPE_NOW) - title=update.resolvePadFields(update.config().get(section,'Title'),pypad.ESCAPE_NONE) - artist=update.resolvePadFields(update.config().get(section,'Artist'),pypad.ESCAPE_NONE) - album=update.resolvePadFields(update.config().get(section,'Album'),pypad.ESCAPE_NONE) - label=update.resolvePadFields(update.config().get(section,'Label'),pypad.ESCAPE_NONE) - secs=update.padField(pypad.TYPE_NOW,pypad.FIELD_LENGTH) - group=update.padField(pypad.TYPE_NOW,pypad.FIELD_GROUP_NAME) - msg='|'+title+'|'+artist+'|'+str(update.padField(pypad.TYPE_NOW,pypad.FIELD_CART_NUMBER))+'|'+str(secs)+'|'+group+'|'+album+'|'+label+'|\n' - send_sock.sendto(msg.encode('utf-8'), - (update.config().get(section,'IpAddress'),int(update.config().get(section,'UdpPort')))) - n=n+1 - except configparser.NoSectionError: - return # # 'Main' function From d3e3e708f1bf2fb8927bab337ee1e8a52cd6efd6 Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Tue, 27 Aug 2019 17:56:25 -0400 Subject: [PATCH 22/35] 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_live365.py' PyPAD script that caused an infinite loop. --- ChangeLog | 3 ++ apis/pypad/scripts/pypad_live365.py | 52 ++++++++++++++--------------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/ChangeLog b/ChangeLog index d56f3858..94e1aa62 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18994,3 +18994,6 @@ 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_liqcomp.py' PyPAD script that caused an infinite loop. +2019-08-26 Fred Gleason + * Fixed a bug in the 'pypad_live365.py' PyPAD script that caused + an infinite loop. diff --git a/apis/pypad/scripts/pypad_live365.py b/apis/pypad/scripts/pypad_live365.py index c154a26e..e3c0781d 100755 --- a/apis/pypad/scripts/pypad_live365.py +++ b/apis/pypad/scripts/pypad_live365.py @@ -32,34 +32,32 @@ def eprint(*args,**kwargs): def ProcessPad(update): n=1 - try: - while(True): - section='Station'+str(n) - if update.shouldBeProcessed(section) and update.hasPadType(pypad.TYPE_NOW): - member=update.escape(update.config().get(section,'MemberName'),pypad.ESCAPE_URL) - password=update.escape(update.config().get(section,'Password'),pypad.ESCAPE_URL) - title=update.resolvePadFields(update.config().get(section,'TitleString'),pypad.ESCAPE_URL) - artist=update.resolvePadFields(update.config().get(section,'ArtistString'),pypad.ESCAPE_URL) - album=update.resolvePadFields(update.config().get(section,'AlbumString'),pypad.ESCAPE_URL) - seconds=str(update.padField(pypad.TYPE_NOW,pypad.FIELD_LENGTH)//1000) - buf=BytesIO() - curl=pycurl.Curl() - url='http://www.live365.com/cgi-bin/add_song.cgi?member_name='+member+'&password='+password+'&version=2&filename=Rivendell&seconds='+seconds+'&title='+title+'&artist='+artist+'&album='+album - curl.setopt(curl.URL,url) - curl.setopt(curl.WRITEDATA,buf) - curl.setopt(curl.FOLLOWLOCATION,True) - try: - curl.perform() - code=curl.getinfo(pycurl.RESPONSE_CODE) - if (code<200) or (code>=300): - update.syslog(syslog.LOG_WARNING,'['+section+'] returned response code '+str(code)) - except pycurl.error: - update.syslog(syslog.LOG_WARNING,'['+section+'] failed: '+curl.errstr()) - curl.close() - n=n+1 + section='Station'+str(n) + while(True): + if update.shouldBeProcessed(section) and update.hasPadType(pypad.TYPE_NOW): + member=update.escape(update.config().get(section,'MemberName'),pypad.ESCAPE_URL) + password=update.escape(update.config().get(section,'Password'),pypad.ESCAPE_URL) + title=update.resolvePadFields(update.config().get(section,'TitleString'),pypad.ESCAPE_URL) + artist=update.resolvePadFields(update.config().get(section,'ArtistString'),pypad.ESCAPE_URL) + album=update.resolvePadFields(update.config().get(section,'AlbumString'),pypad.ESCAPE_URL) + seconds=str(update.padField(pypad.TYPE_NOW,pypad.FIELD_LENGTH)//1000) + buf=BytesIO() + curl=pycurl.Curl() + url='http://www.live365.com/cgi-bin/add_song.cgi?member_name='+member+'&password='+password+'&version=2&filename=Rivendell&seconds='+seconds+'&title='+title+'&artist='+artist+'&album='+album + curl.setopt(curl.URL,url) + curl.setopt(curl.WRITEDATA,buf) + curl.setopt(curl.FOLLOWLOCATION,True) + try: + curl.perform() + code=curl.getinfo(pycurl.RESPONSE_CODE) + if (code<200) or (code>=300): + update.syslog(syslog.LOG_WARNING,'['+section+'] returned response code '+str(code)) + except pycurl.error: + update.syslog(syslog.LOG_WARNING,'['+section+'] failed: '+curl.errstr()) + curl.close() + n=n+1 + section='Station'+str(n) - except configparser.NoSectionError: - return # # 'Main' function From b235a974dc8b0e41e9d0a501f2675feefe30f7af Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Tue, 27 Aug 2019 17:59:27 -0400 Subject: [PATCH 23/35] 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_live365.py' PyPAD script that caused an infinite loop. --- apis/pypad/scripts/pypad_live365.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apis/pypad/scripts/pypad_live365.py b/apis/pypad/scripts/pypad_live365.py index e3c0781d..78d75beb 100755 --- a/apis/pypad/scripts/pypad_live365.py +++ b/apis/pypad/scripts/pypad_live365.py @@ -33,7 +33,7 @@ def eprint(*args,**kwargs): def ProcessPad(update): n=1 section='Station'+str(n) - while(True): + while(update.config().has_section(section)): if update.shouldBeProcessed(section) and update.hasPadType(pypad.TYPE_NOW): member=update.escape(update.config().get(section,'MemberName'),pypad.ESCAPE_URL) password=update.escape(update.config().get(section,'Password'),pypad.ESCAPE_URL) From 428368404497db1350fd3c06abc488a212d821dd Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Tue, 27 Aug 2019 18:05:56 -0400 Subject: [PATCH 24/35] 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_serial.py' PyPAD script that caused an infinite loop. --- ChangeLog | 3 +++ apis/pypad/scripts/pypad_serial.py | 38 ++++++++++++++---------------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/ChangeLog b/ChangeLog index 94e1aa62..b6168eb2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18997,3 +18997,6 @@ 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_live365.py' PyPAD script that caused an infinite loop. +2019-08-26 Fred Gleason + * Fixed a bug in the 'pypad_serial.py' PyPAD script that caused + an infinite loop. diff --git a/apis/pypad/scripts/pypad_serial.py b/apis/pypad/scripts/pypad_serial.py index 71cca180..77f35808 100755 --- a/apis/pypad/scripts/pypad_serial.py +++ b/apis/pypad/scripts/pypad_serial.py @@ -32,27 +32,25 @@ def eprint(*args,**kwargs): def ProcessPad(update): n=1 - try: - while(True): - section='Serial'+str(n) - if update.shouldBeProcessed(section): - devname=update.config().get(section,'Device') - speed=int(update.config().get(section,'Speed')) - parity=serial.PARITY_NONE - if int(update.config().get(section,'Parity'))==1: - parity=serial.PARITY_EVEN - if int(update.config().get(section,'Parity'))==2: - parity=serial.PARITY_ODD - bytesize=int(update.config().get(section,'WordSize')) - dev=serial.Serial(devname,speed,parity=parity,bytesize=bytesize) - fmtstr=update.config().get(section,'FormatString') - esc=int(update.config().get(section,'Encoding')) - dev.write(update.resolvePadFields(fmtstr,esc).encode('utf-8')) - dev.close() - n=n+1 + section='Serial'+str(n) + while(update.config().has_section(section)): + if update.shouldBeProcessed(section): + devname=update.config().get(section,'Device') + speed=int(update.config().get(section,'Speed')) + parity=serial.PARITY_NONE + if int(update.config().get(section,'Parity'))==1: + parity=serial.PARITY_EVEN + if int(update.config().get(section,'Parity'))==2: + parity=serial.PARITY_ODD + bytesize=int(update.config().get(section,'WordSize')) + dev=serial.Serial(devname,speed,parity=parity,bytesize=bytesize) + fmtstr=update.config().get(section,'FormatString') + esc=int(update.config().get(section,'Encoding')) + dev.write(update.resolvePadFields(fmtstr,esc).encode('utf-8')) + dev.close() + n=n+1 + section='Serial'+str(n) - except configparser.NoSectionError: - return # # 'Main' function From d4503360f2233b639034bce84410c171831d6627 Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Tue, 27 Aug 2019 18:13:04 -0400 Subject: [PATCH 25/35] 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_spinitron.py' PyPAD script that caused an infinite loop. --- ChangeLog | 3 + apis/pypad/scripts/pypad_spinitron.py | 120 +++++++++++++------------- 2 files changed, 62 insertions(+), 61 deletions(-) diff --git a/ChangeLog b/ChangeLog index b6168eb2..e2503568 100644 --- a/ChangeLog +++ b/ChangeLog @@ -19000,3 +19000,6 @@ 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_serial.py' PyPAD script that caused an infinite loop. +2019-08-26 Fred Gleason + * Fixed a bug in the 'pypad_spinitron.py' PyPAD script that caused + an infinite loop. diff --git a/apis/pypad/scripts/pypad_spinitron.py b/apis/pypad/scripts/pypad_spinitron.py index fa35b32e..fdb193bd 100755 --- a/apis/pypad/scripts/pypad_spinitron.py +++ b/apis/pypad/scripts/pypad_spinitron.py @@ -48,70 +48,68 @@ def ProcessPad(update): last_updates[update.machine()]=None n=1 - try: - while(True): - section='Spinitron'+str(n) - if update.shouldBeProcessed(section) and update.hasPadType(pypad.TYPE_NOW) and (last_updates[update.machine()] != update.startDateTimeString(pypad.TYPE_NOW)): - last_updates[update.machine()]=update.startDateTimeString(pypad.TYPE_NOW) - title=update.resolvePadFields(update.config().get(section,'Title'),pypad.ESCAPE_JSON) - artist=update.resolvePadFields(update.config().get(section,'Artist'),pypad.ESCAPE_JSON) - album=update.resolvePadFields(update.config().get(section,'Album'),pypad.ESCAPE_JSON) - label=update.resolvePadFields(update.config().get(section,'Label'),pypad.ESCAPE_JSON) - composer=update.resolvePadFields(update.config().get(section,'Composer'),pypad.ESCAPE_JSON) - conductor=update.resolvePadFields(update.config().get(section,'Conductor'),pypad.ESCAPE_JSON) - notes=update.resolvePadFields(update.config().get(section,'Notes'),pypad.ESCAPE_JSON) + section='Spinitron'+str(n) + while(update.config().has_section(section)): + if update.shouldBeProcessed(section) and update.hasPadType(pypad.TYPE_NOW) and (last_updates[update.machine()] != update.startDateTimeString(pypad.TYPE_NOW)): + last_updates[update.machine()]=update.startDateTimeString(pypad.TYPE_NOW) + title=update.resolvePadFields(update.config().get(section,'Title'),pypad.ESCAPE_JSON) + artist=update.resolvePadFields(update.config().get(section,'Artist'),pypad.ESCAPE_JSON) + album=update.resolvePadFields(update.config().get(section,'Album'),pypad.ESCAPE_JSON) + label=update.resolvePadFields(update.config().get(section,'Label'),pypad.ESCAPE_JSON) + composer=update.resolvePadFields(update.config().get(section,'Composer'),pypad.ESCAPE_JSON) + conductor=update.resolvePadFields(update.config().get(section,'Conductor'),pypad.ESCAPE_JSON) + notes=update.resolvePadFields(update.config().get(section,'Notes'),pypad.ESCAPE_JSON) - json='{\r\n' - pmode=update.config().get(section,'PlaylistMode') - if pmode=='Full': - json+=' "live": false\r\n' - if pmode=='Assist': - json+=' "live:" true\r\n' - if pmode=='Follow': - if update.mode()=='Automatic': - json+=' "live": false,\r\n' - else: - json+=' "live": true,\r\n' - duration=str(update.padField(pypad.TYPE_NOW,pypad.FIELD_LENGTH)//1000) - year=update.padField(pypad.TYPE_NOW,pypad.FIELD_YEAR) - if year==None: - json+=' "released": null,\r\n' + json='{\r\n' + pmode=update.config().get(section,'PlaylistMode') + if pmode=='Full': + json+=' "live": false\r\n' + if pmode=='Assist': + json+=' "live:" true\r\n' + if pmode=='Follow': + if update.mode()=='Automatic': + json+=' "live": false,\r\n' else: - json+=' "released": '+str(year)+',\r\n' - json+=' "duration": '+duration+',\r\n' - json+=JsonField(update,'artist',artist) - json+=JsonField(update,'release',album) - json+=JsonField(update,'label',label) - json+=JsonField(update,'song',title) - json+=JsonField(update,'composer',composer) - json+=JsonField(update,'conductor',conductor) - json+=JsonField(update,'note',notes) - json+=JsonField(update,'isrc',update.padField(pypad.TYPE_NOW,pypad.FIELD_ISRC),True) - json+='}\r\n' - send_buf=BytesIO(json.encode('utf-8')) - recv_buf=BytesIO() - curl=pycurl.Curl() - curl.setopt(curl.URL,'https://spinitron.com/api/spins') - headers=[] - headers.append('Authorization: Bearer '+update.config().get(section,'APIKey')) - headers.append('Content-Type: application/json') - headers.append('Content-Length: '+str(len(json.encode('utf-8')))) - curl.setopt(curl.HTTPHEADER,headers); - curl.setopt(curl.POST,True) - curl.setopt(curl.READDATA,send_buf) - curl.setopt(curl.WRITEDATA,recv_buf) - try: - curl.perform() - code=curl.getinfo(pycurl.RESPONSE_CODE) - if (code<200) or (code>=300): - update.syslog(syslog.LOG_WARNING,'['+section+'] returned response code '+str(code)) - except pycurl.error: - update.syslog(syslog.LOG_WARNING,'['+section+'] failed: '+curl.errstr()) - curl.close() - n=n+1 + json+=' "live": true,\r\n' + duration=str(update.padField(pypad.TYPE_NOW,pypad.FIELD_LENGTH)//1000) + year=update.padField(pypad.TYPE_NOW,pypad.FIELD_YEAR) + if year==None: + json+=' "released": null,\r\n' + else: + json+=' "released": '+str(year)+',\r\n' + json+=' "duration": '+duration+',\r\n' + json+=JsonField(update,'artist',artist) + json+=JsonField(update,'release',album) + json+=JsonField(update,'label',label) + json+=JsonField(update,'song',title) + json+=JsonField(update,'composer',composer) + json+=JsonField(update,'conductor',conductor) + json+=JsonField(update,'note',notes) + json+=JsonField(update,'isrc',update.padField(pypad.TYPE_NOW,pypad.FIELD_ISRC),True) + json+='}\r\n' + send_buf=BytesIO(json.encode('utf-8')) + recv_buf=BytesIO() + curl=pycurl.Curl() + curl.setopt(curl.URL,'https://spinitron.com/api/spins') + headers=[] + headers.append('Authorization: Bearer '+update.config().get(section,'APIKey')) + headers.append('Content-Type: application/json') + headers.append('Content-Length: '+str(len(json.encode('utf-8')))) + curl.setopt(curl.HTTPHEADER,headers); + curl.setopt(curl.POST,True) + curl.setopt(curl.READDATA,send_buf) + curl.setopt(curl.WRITEDATA,recv_buf) + try: + curl.perform() + code=curl.getinfo(pycurl.RESPONSE_CODE) + if (code<200) or (code>=300): + update.syslog(syslog.LOG_WARNING,'['+section+'] returned response code '+str(code)) + except pycurl.error: + update.syslog(syslog.LOG_WARNING,'['+section+'] failed: '+curl.errstr()) + curl.close() + n=n+1 + section='Spinitron'+str(n) - except configparser.NoSectionError: - return # # 'Main' function From 3b95c723b71d2e7103c59efa86d664ebb334407b Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Tue, 27 Aug 2019 18:20:36 -0400 Subject: [PATCH 26/35] 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_spottrap.py' PyPAD script that caused an infinite loop. --- ChangeLog | 3 +++ apis/pypad/scripts/pypad_spottrap.py | 29 ++++++++++++++-------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/ChangeLog b/ChangeLog index e2503568..8d1a9d08 100644 --- a/ChangeLog +++ b/ChangeLog @@ -19003,3 +19003,6 @@ 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_spinitron.py' PyPAD script that caused an infinite loop. +2019-08-26 Fred Gleason + * Fixed a bug in the 'pypad_spottrap.py' PyPAD script that caused + an infinite loop. diff --git a/apis/pypad/scripts/pypad_spottrap.py b/apis/pypad/scripts/pypad_spottrap.py index d8331848..fee63eda 100755 --- a/apis/pypad/scripts/pypad_spottrap.py +++ b/apis/pypad/scripts/pypad_spottrap.py @@ -4,7 +4,7 @@ # # Output Now & Next data on the basis of Group and Length. # -# (C) Copyright 2018 Fred Gleason +# (C) Copyright 2018-2019 Fred Gleason # # 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 @@ -38,21 +38,20 @@ def ProcessPad(update): last_updates[update.machine()]=None n=1 - while(True): + section='Rule'+str(n) + while(update.config().has_section(section)): + if update.shouldBeProcessed(section) and update.hasPadType(pypad.TYPE_NOW) and (last_updates[update.machine()] != update.startDateTimeString(pypad.TYPE_NOW)): + last_updates[update.machine()]=update.startDateTimeString(pypad.TYPE_NOW) + length=update.padField(pypad.TYPE_NOW,pypad.FIELD_LENGTH) + if update.padField(pypad.TYPE_NOW,pypad.FIELD_GROUP_NAME)==update.config().get(section,'GroupName') and length>=int(update.config().get(section,'MinimumLength')) and length<=int(update.config().get(section,'MaximumLength')): + msg=update.resolvePadFields(update.config().get(section,'FormatString'),pypad.ESCAPE_NONE) + else: + msg=update.resolvePadFields(update.config().get(section,'DefaultFormatString'),pypad.ESCAPE_NONE) + send_sock.sendto(msg.encode('utf-8'), + (update.config().get(section,'IpAddress'),int(update.config().get(section,'UdpPort')))) + n=n+1 section='Rule'+str(n) - try: - if update.shouldBeProcessed(section) and update.hasPadType(pypad.TYPE_NOW) and (last_updates[update.machine()] != update.startDateTimeString(pypad.TYPE_NOW)): - last_updates[update.machine()]=update.startDateTimeString(pypad.TYPE_NOW) - length=update.padField(pypad.TYPE_NOW,pypad.FIELD_LENGTH) - if update.padField(pypad.TYPE_NOW,pypad.FIELD_GROUP_NAME)==update.config().get(section,'GroupName') and length>=int(update.config().get(section,'MinimumLength')) and length<=int(update.config().get(section,'MaximumLength')): - msg=update.resolvePadFields(update.config().get(section,'FormatString'),pypad.ESCAPE_NONE) - else: - msg=update.resolvePadFields(update.config().get(section,'DefaultFormatString'),pypad.ESCAPE_NONE) - send_sock.sendto(msg.encode('utf-8'), - (update.config().get(section,'IpAddress'),int(update.config().get(section,'UdpPort')))) - n=n+1 - except configparser.NoSectionError: - return + # # 'Main' function From 88bebdecc4f4609108b7cee4190d0561a7340e8a Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Tue, 27 Aug 2019 18:26:26 -0400 Subject: [PATCH 27/35] 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_tunein.py' PyPAD script that caused an infinite loop. --- ChangeLog | 3 ++ apis/pypad/scripts/pypad_tunein.py | 48 ++++++++++++++---------------- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/ChangeLog b/ChangeLog index 8d1a9d08..b57c5da6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -19006,3 +19006,6 @@ 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_spottrap.py' PyPAD script that caused an infinite loop. +2019-08-26 Fred Gleason + * Fixed a bug in the 'pypad_tunein.py' PyPAD script that caused + an infinite loop. diff --git a/apis/pypad/scripts/pypad_tunein.py b/apis/pypad/scripts/pypad_tunein.py index 5ffef9df..3a7119d2 100755 --- a/apis/pypad/scripts/pypad_tunein.py +++ b/apis/pypad/scripts/pypad_tunein.py @@ -31,33 +31,31 @@ import configparser def ProcessPad(update): if update.hasPadType(pypad.TYPE_NOW): - n=1 - while(True): section='Station'+str(n) + n=1 + while(update.config().has_section(section)): + values={} + values['id']=update.config().get(section,'StationID') + values['partnerId']=update.config().get(section,'PartnerID') + values['partnerKey']=update.config().get(section,'PartnerKey') + values['title']=update.resolvePadFields(update.config().get(section,'TitleString'),pypad.ESCAPE_NONE) + values['artist']=update.resolvePadFields(update.config().get(section,'ArtistString'),pypad.ESCAPE_NONE) + values['album']=update.resolvePadFields(update.config().get(section,'AlbumString'),pypad.ESCAPE_NONE) + update.syslog(syslog.LOG_INFO,'Updating TuneIn: artist='+values['artist']+' title='+values['title']+' album='+values['album']) try: - values={} - values['id']=update.config().get(section,'StationID') - values['partnerId']=update.config().get(section,'PartnerID') - values['partnerKey']=update.config().get(section,'PartnerKey') - values['title']=update.resolvePadFields(update.config().get(section,'TitleString'),pypad.ESCAPE_NONE) - values['artist']=update.resolvePadFields(update.config().get(section,'ArtistString'),pypad.ESCAPE_NONE) - values['album']=update.resolvePadFields(update.config().get(section,'AlbumString'),pypad.ESCAPE_NONE) - update.syslog(syslog.LOG_INFO,'Updating TuneIn: artist='+values['artist']+' title='+values['title']+' album='+values['album']) - try: - response=requests.get('http://air.radiotime.com/Playing.ashx',params=values) - response.raise_for_status() - except requests.exceptions.RequestException as e: - update.syslog(syslog.LOG_WARNING,str(e)) - else: - xml=ET.fromstring(response.text) - status=xml.find('./head/status') - if(status.text!='200'): - update.syslog(syslog.LOG_WARNING,'Update Failed: '+xml.find('./head/fault').text) - n=n+1 - except configparser.NoSectionError: - if(n==1): - update.syslog(syslog.LOG_WARNING,'No station config found') - return + response=requests.get('http://air.radiotime.com/Playing.ashx',params=values) + response.raise_for_status() + except requests.exceptions.RequestException as e: + update.syslog(syslog.LOG_WARNING,str(e)) + else: + xml=ET.fromstring(response.text) + status=xml.find('./head/status') + if(status.text!='200'): + update.syslog(syslog.LOG_WARNING,'Update Failed: '+xml.find('./head/fault').text) + n=n+1 + section='Station'+str(n) + if(n==1): + update.syslog(syslog.LOG_WARNING,'No station config found') # # Program Name From 22b96797836498a85c1f6161762dc031f44dac97 Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Tue, 27 Aug 2019 18:33:57 -0400 Subject: [PATCH 28/35] 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_udp.py' PyPAD script that caused an infinite loop. --- ChangeLog | 3 +++ apis/pypad/scripts/pypad_udp.py | 21 +++++++++++---------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/ChangeLog b/ChangeLog index b57c5da6..4d56e647 100644 --- a/ChangeLog +++ b/ChangeLog @@ -19009,3 +19009,6 @@ 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_tunein.py' PyPAD script that caused an infinite loop. +2019-08-26 Fred Gleason + * Fixed a bug in the 'pypad_udp.py' PyPAD script that caused + an infinite loop. diff --git a/apis/pypad/scripts/pypad_udp.py b/apis/pypad/scripts/pypad_udp.py index a4cfc8d6..57aad168 100755 --- a/apis/pypad/scripts/pypad_udp.py +++ b/apis/pypad/scripts/pypad_udp.py @@ -4,7 +4,7 @@ # # Send PAD updates via UDP # -# (C) Copyright 2018 Fred Gleason +# (C) Copyright 2018-2019 Fred Gleason # # 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 @@ -30,16 +30,17 @@ def eprint(*args,**kwargs): def ProcessPad(update): n=1 - while(True): + section='Udp'+str(n) + while(update.config().has_section(section)): + if update.shouldBeProcessed(section): + fmtstr=update.config().get(section,'FormatString') + send_sock.sendto(update.resolvePadFields(fmtstr,int(update.config().get(section,'Encoding'))).encode('utf-8'), + (update.config().get(section,'IpAddress'),int(update.config().get(section,'UdpPort')))) + n=n+1 section='Udp'+str(n) - try: - if update.shouldBeProcessed(section): - fmtstr=update.config().get(section,'FormatString') - send_sock.sendto(update.resolvePadFields(fmtstr,int(update.config().get(section,'Encoding'))).encode('utf-8'), - (update.config().get(section,'IpAddress'),int(update.config().get(section,'UdpPort')))) - n=n+1 - except configparser.NoSectionError: - return + if(n==1): + update.syslog(syslog.LOG_WARNING,'No UDP config found') + # # 'Main' function From 6fceb27c2f24bbdaf5b56fef8127b4d19ad2d076 Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Tue, 27 Aug 2019 18:44:31 -0400 Subject: [PATCH 29/35] 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_urlwrite.py' PyPAD script that caused an infinite loop. --- ChangeLog | 3 +++ apis/pypad/scripts/pypad_urlwrite.py | 38 +++++++++++++--------------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/ChangeLog b/ChangeLog index 4d56e647..e5450a6a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -19012,3 +19012,6 @@ 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_udp.py' PyPAD script that caused an infinite loop. +2019-08-26 Fred Gleason + * Fixed a bug in the 'pypad_urlwrite.py' PyPAD script that caused + an infinite loop. diff --git a/apis/pypad/scripts/pypad_urlwrite.py b/apis/pypad/scripts/pypad_urlwrite.py index 30b3e845..56f6d6d1 100755 --- a/apis/pypad/scripts/pypad_urlwrite.py +++ b/apis/pypad/scripts/pypad_urlwrite.py @@ -32,27 +32,25 @@ def eprint(*args,**kwargs): def ProcessPad(update): n=1 - try: - while(True): - section='Url'+str(n) - if update.shouldBeProcessed(section): - fmtstr=update.config().get(section,'FormatString') - buf=BytesIO(update.resolvePadFields(fmtstr,int(update.config().get(section,'Encoding'))).encode('utf-8')) - curl=pycurl.Curl() - curl.setopt(curl.URL,update.resolveFilepath(update.config().get(section,'Url'),update.dateTime())) - curl.setopt(curl.USERNAME,update.config().get(section,'Username')) - curl.setopt(curl.PASSWORD,update.config().get(section,'Password')) - curl.setopt(curl.UPLOAD,True) - curl.setopt(curl.READDATA,buf) - try: - curl.perform() - except pycurl.error: - update.syslog(syslog.LOG_WARNING,'['+section+'] failed: '+curl.errstr()) - curl.close() - n=n+1 + section='Url'+str(n) + while(update.config().has_section(section)): + if update.shouldBeProcessed(section): + fmtstr=update.config().get(section,'FormatString') + buf=BytesIO(update.resolvePadFields(fmtstr,int(update.config().get(section,'Encoding'))).encode('utf-8')) + curl=pycurl.Curl() + curl.setopt(curl.URL,update.resolveFilepath(update.config().get(section,'Url'),update.dateTime())) + curl.setopt(curl.USERNAME,update.config().get(section,'Username')) + curl.setopt(curl.PASSWORD,update.config().get(section,'Password')) + curl.setopt(curl.UPLOAD,True) + curl.setopt(curl.READDATA,buf) + try: + curl.perform() + except pycurl.error: + update.syslog(syslog.LOG_WARNING,'['+section+'] failed: '+curl.errstr()) + curl.close() + n=n+1 + section='Url'+str(n) - except configparser.NoSectionError: - return # # 'Main' function From 8e23937952c77605c3321353822ddedf2bfefe59 Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Tue, 27 Aug 2019 19:11:34 -0400 Subject: [PATCH 30/35] 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_walltime.py' PyPAD script that caused an infinite loop. --- ChangeLog | 3 +++ apis/pypad/scripts/pypad_walltime.py | 38 +++++++++++++--------------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/ChangeLog b/ChangeLog index e5450a6a..377e0166 100644 --- a/ChangeLog +++ b/ChangeLog @@ -19015,3 +19015,6 @@ 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_urlwrite.py' PyPAD script that caused an infinite loop. +2019-08-26 Fred Gleason + * Fixed a bug in the 'pypad_walltime.py' PyPAD script that caused + an infinite loop. diff --git a/apis/pypad/scripts/pypad_walltime.py b/apis/pypad/scripts/pypad_walltime.py index e9ab191a..c4cea975 100755 --- a/apis/pypad/scripts/pypad_walltime.py +++ b/apis/pypad/scripts/pypad_walltime.py @@ -32,27 +32,25 @@ def eprint(*args,**kwargs): def ProcessPad(update): n=1 - try: - while(True): - section='Walltime'+str(n) - if update.shouldBeProcessed(section): - fmtstr=update.config().get(section,'FormatString') - buf=BytesIO(update.resolvePadFields(fmtstr,pypad.ESCAPE_NONE).encode('utf-8')) - curl=pycurl.Curl() - curl.setopt(curl.URL,'http://'+update.config().get(section,'IpAddress')+'/webwidget'); - curl.setopt(curl.USERNAME,'user') - curl.setopt(curl.PASSWORD,update.config().get(section,'Password')) - curl.setopt(curl.UPLOAD,True) - curl.setopt(curl.READDATA,buf) - try: - curl.perform() - except pycurl.error: - update.syslog(syslog.LOG_WARNING,'['+section+'] failed: '+curl.errstr()) - curl.close() - n=n+1 + section='Walltime'+str(n) + while(update.config().has_section(section)): + if update.shouldBeProcessed(section): + fmtstr=update.config().get(section,'FormatString') + buf=BytesIO(update.resolvePadFields(fmtstr,pypad.ESCAPE_NONE).encode('utf-8')) + curl=pycurl.Curl() + curl.setopt(curl.URL,'http://'+update.config().get(section,'IpAddress')+'/webwidget'); + curl.setopt(curl.USERNAME,'user') + curl.setopt(curl.PASSWORD,update.config().get(section,'Password')) + curl.setopt(curl.UPLOAD,True) + curl.setopt(curl.READDATA,buf) + try: + curl.perform() + except pycurl.error: + update.syslog(syslog.LOG_WARNING,'['+section+'] failed: '+curl.errstr()) + curl.close() + n=n+1 + section='Walltime'+str(n) - except configparser.NoSectionError: - return # # 'Main' function From a6ef936905e95bebc44434a28e6fb74cb799989a Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Wed, 28 Aug 2019 12:46:43 -0400 Subject: [PATCH 31/35] 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_xds.py' PyPAD script that caused an infinite loop. --- ChangeLog | 3 +++ apis/pypad/scripts/pypad_xds.py | 47 ++++++++++++++++----------------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/ChangeLog b/ChangeLog index 377e0166..758c26e3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -19018,3 +19018,6 @@ 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_walltime.py' PyPAD script that caused an infinite loop. +2019-08-26 Fred Gleason + * Fixed a bug in the 'pypad_xds.py' PyPAD script that caused + an infinite loop. diff --git a/apis/pypad/scripts/pypad_xds.py b/apis/pypad/scripts/pypad_xds.py index d6f3b990..0bfff149 100755 --- a/apis/pypad/scripts/pypad_xds.py +++ b/apis/pypad/scripts/pypad_xds.py @@ -4,7 +4,7 @@ # # Send CICs via UDP or serial # -# (C) Copyright 2018 Fred Gleason +# (C) Copyright 2018-2019 Fred Gleason # # 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 @@ -61,31 +61,30 @@ def FilterField(string): def ProcessPad(update): n=1 - while(True): - section='Udp'+str(n) - try: - if update.shouldBeProcessed(section) and update.hasPadType(pypad.TYPE_NOW) and update.hasService(): - packet='0:'+update.serviceProgramCode()+':'+update.config().get(section,'IsciPrefix')+FilterField(update.padField(pypad.TYPE_NOW,pypad.FIELD_EXTERNAL_EVENT_ID))+':*' - try: - # - # Use serial output - # - tty_dev=update.config().get(section,'TtyDevice') - speed=int(update.config().get(section,'TtySpeed')) - parity=serial.PARITY_NONE - dev=serial.Serial(tty_dev,speed,parity=parity,bytesize=8) - dev.write(packet.encode('utf-8')) - dev.close() + section='Udp'+str(n) + while(update.config().has_section(section)): + if update.shouldBeProcessed(section) and update.hasPadType(pypad.TYPE_NOW) and update.hasService(): + packet='0:'+update.serviceProgramCode()+':'+update.config().get(section,'IsciPrefix')+FilterField(update.padField(pypad.TYPE_NOW,pypad.FIELD_EXTERNAL_EVENT_ID))+':*' + try: + # + # Use serial output + # + tty_dev=update.config().get(section,'TtyDevice') + speed=int(update.config().get(section,'TtySpeed')) + parity=serial.PARITY_NONE + dev=serial.Serial(tty_dev,speed,parity=parity,bytesize=8) + dev.write(packet.encode('utf-8')) + dev.close() - except configparser.NoOptionError: - # - # Use UDP output - # - send_sock.sendto(packet.encode('utf-8'), + except configparser.NoOptionError: + # + # Use UDP output + # + send_sock.sendto(packet.encode('utf-8'), (update.config().get(section,'IpAddress'),int(update.config().get(section,'UdpPort')))) - n=n+1 - except configparser.NoSectionError: - return + n=n+1 + section='Udp'+str(n) + # # 'Main' function From 0a9454cf91844957e85e0ac8c00d879fb8c501f9 Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Wed, 28 Aug 2019 12:54:10 -0400 Subject: [PATCH 32/35] 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_xmpad.py' PyPAD script that caused an infinite loop. --- ChangeLog | 3 +++ apis/pypad/scripts/pypad_xmpad.py | 32 +++++++++++++++---------------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/ChangeLog b/ChangeLog index 758c26e3..37d3f7f5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -19021,3 +19021,6 @@ 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_xds.py' PyPAD script that caused an infinite loop. +2019-08-26 Fred Gleason + * Fixed a bug in the 'pypad_xmpad.py' PyPAD script that caused + an infinite loop. diff --git a/apis/pypad/scripts/pypad_xmpad.py b/apis/pypad/scripts/pypad_xmpad.py index 23837aa7..eb5a1dd2 100755 --- a/apis/pypad/scripts/pypad_xmpad.py +++ b/apis/pypad/scripts/pypad_xmpad.py @@ -149,24 +149,22 @@ def ProcessTimer(config): def ProcessPad(update): n=1 - try: - while(True): - section='Serial'+str(n) - if update.shouldBeProcessed(section) and update.hasPadType(pypad.TYPE_NOW): - dev=OpenSerialDevice(update.config(),section) - b4=MakeB4(update,section) - a4=MakeA4(update,section) - a5=MakeA5(update,section) - dev.write(b4.encode('utf-8')) - dev.write(b4.encode('utf-8')) - dev.write(b4.encode('utf-8')) - dev.write(a4.encode('utf-8')) - dev.write(a5.encode('utf-8')) - dev.close() - n=n+1 + section='Serial'+str(n) + while(update.config().has_section(section)): + if update.shouldBeProcessed(section) and update.hasPadType(pypad.TYPE_NOW): + dev=OpenSerialDevice(update.config(),section) + b4=MakeB4(update,section) + a4=MakeA4(update,section) + a5=MakeA5(update,section) + dev.write(b4.encode('utf-8')) + dev.write(b4.encode('utf-8')) + dev.write(b4.encode('utf-8')) + dev.write(a4.encode('utf-8')) + dev.write(a5.encode('utf-8')) + dev.close() + n=n+1 + section='Serial'+str(n) - except configparser.NoSectionError: - return # # 'Main' function From bbd7cd61bb03215b740df3f6d68c7fbb294d9f1e Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Wed, 28 Aug 2019 13:22:00 -0400 Subject: [PATCH 33/35] 2019-08-26 Fred Gleason * Added a statement to explicitly disable sorting of the events list in the 'List Clocks' dialog in rdlogmanager(1). --- ChangeLog | 3 +++ rdlogmanager/edit_clock.cpp | 1 + 2 files changed, 4 insertions(+) diff --git a/ChangeLog b/ChangeLog index 37d3f7f5..1313a263 100644 --- a/ChangeLog +++ b/ChangeLog @@ -19024,3 +19024,6 @@ 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_xmpad.py' PyPAD script that caused an infinite loop. +2019-08-26 Fred Gleason + * Added a statement to explicitly disable sorting of the + events list in the 'List Clocks' dialog in rdlogmanager(1). diff --git a/rdlogmanager/edit_clock.cpp b/rdlogmanager/edit_clock.cpp index ab3dd4c5..41d708bc 100644 --- a/rdlogmanager/edit_clock.cpp +++ b/rdlogmanager/edit_clock.cpp @@ -100,6 +100,7 @@ EditClock::EditClock(QString clockname,bool new_clock, edit_clocks_list->setGeometry(10,35,CENTER_LINE-20,sizeHint().height()-250); edit_clocks_list->setAllColumnsShowFocus(true); edit_clocks_list->setItemMargin(5); + edit_clocks_list->setSorting(-1); edit_clocks_list->addColumn(tr("Start")); edit_clocks_list->addColumn(tr("End")); edit_clocks_list->addColumn(tr("Event")); From f83ea42a77e674145b6ee6653f4bf49a051ff14e Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Wed, 28 Aug 2019 13:47:18 -0400 Subject: [PATCH 34/35] Fixed typo in 'ChangeLog' --- ChangeLog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 1313a263..e09942a2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -19024,6 +19024,6 @@ 2019-08-26 Fred Gleason * Fixed a bug in the 'pypad_xmpad.py' PyPAD script that caused an infinite loop. -2019-08-26 Fred Gleason +2019-08-28 Fred Gleason * Added a statement to explicitly disable sorting of the events list in the 'List Clocks' dialog in rdlogmanager(1). From c47b54720e100ddb7446bc63745224c76040d56c Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Wed, 28 Aug 2019 15:27:54 -0400 Subject: [PATCH 35/35] 2019-08-28 Fred Gleason * Refactored the 'RDClock::insert()' method to calculate the ordinal position of the inserted event automatically. * Fixed a bug in rdlogmanager(1) that caused newly added events to be incorrectly sorted in the event list in the 'Edit Clock' dialog. --- ChangeLog | 10 +++++ lib/rdclock.cpp | 60 ++++++++++++------------------ lib/rdclock.h | 3 +- rdlogmanager/edit_clock.cpp | 49 ++++++++++-------------- rdlogmanager/rdlogmanager_cs.ts | 10 ++++- rdlogmanager/rdlogmanager_de.ts | 10 ++++- rdlogmanager/rdlogmanager_es.ts | 10 ++++- rdlogmanager/rdlogmanager_fr.ts | 12 ++++-- rdlogmanager/rdlogmanager_nb.ts | 10 ++++- rdlogmanager/rdlogmanager_nn.ts | 10 ++++- rdlogmanager/rdlogmanager_pt_BR.ts | 10 ++++- 11 files changed, 116 insertions(+), 78 deletions(-) diff --git a/ChangeLog b/ChangeLog index e09942a2..9d55dbec 100644 --- a/ChangeLog +++ b/ChangeLog @@ -19027,3 +19027,13 @@ 2019-08-28 Fred Gleason * Added a statement to explicitly disable sorting of the events list in the 'List Clocks' dialog in rdlogmanager(1). +2019-08-28 Fred Gleason + * Fixed a bug in rdlogmanager(1) that threw a segfault when + adding an event to the end of the event list in the 'Edit + Clock' dialog. +2019-08-28 Fred Gleason + * Refactored the 'RDClock::insert()' method to calculate the + ordinal position of the inserted event automatically. + * Fixed a bug in rdlogmanager(1) that caused newly added events + to be incorrectly sorted in the event list in the 'Edit Clock' + dialog. diff --git a/lib/rdclock.cpp b/lib/rdclock.cpp index c0d7c9b4..b64ab34c 100644 --- a/lib/rdclock.cpp +++ b/lib/rdclock.cpp @@ -226,27 +226,42 @@ bool RDClock::save() } -bool RDClock::insert(const QString &event_name,int line) +int RDClock::insert(const QString &event_name,const QTime &time,int len) { + int line=-1; + QString sql=QString("select NAME from EVENTS where ")+ "NAME=\""+RDEscapeString(event_name)+"\""; RDSqlQuery *q=new RDSqlQuery(sql); if(!q->first()) { delete q; - return false; + return -1; } delete q; - if(line>=size()) { - clock_events.push_back(new RDEventLine(clock_station)); + if((clock_events.size()==0)||(timestartTime())) { + line=0; + clock_events.insert(0,new RDEventLine(clock_station)); } else { - clock_events.insert(line,new RDEventLine(clock_station)); - // QList::iterator it=clock_events.begin()+line; - //clock_events.insert(it,1,RDEventLine(clock_station)); + for(int i=0;iclock_events.at(i)->startTime())&& + (timestartTime())) { + line=i+1; + clock_events.insert(line,new RDEventLine(clock_station)); + break; + } + } + if(line<0) { + line=clock_events.size(); + clock_events.push_back(new RDEventLine(clock_station)); + } } clock_events.at(line)->setName(event_name); + clock_events.at(line)->setStartTime(time); + clock_events.at(line)->setLength(len); clock_events.at(line)->load(); - return true; + + return line; } @@ -254,35 +269,6 @@ void RDClock::remove(int line) { delete clock_events[line]; clock_events.removeAt(line); - // std::vector::iterator it=clock_events.begin()+line; - // clock_events.erase(it,it+1); -} - - -void RDClock::move(int from_line,int to_line) -{ - int src_offset=0; - int dest_offset=1; - RDEventLine *srcline; - RDEventLine *destline; - - if(to_linename(),to_line+dest_offset); - if((to_line+1)>=size()) { - to_line=clock_events.size()-1; - dest_offset=0; - } - - if(((destline=eventLine(to_line+dest_offset))==NULL)|| - (srcline=eventLine(from_line+src_offset))==NULL) { - remove(to_line+dest_offset); - return; - } - *destline=*srcline; - remove(from_line+src_offset); } diff --git a/lib/rdclock.h b/lib/rdclock.h index 9bc47c4a..6cca5220 100644 --- a/lib/rdclock.h +++ b/lib/rdclock.h @@ -48,9 +48,8 @@ class RDClock int size() const; bool load(); bool save(); - bool insert(const QString &event_name,int line); + int insert(const QString &event_name,const QTime &start,int len); void remove(int line); - void move(int from_line,int to_line); bool validate(const QTime &start_time,int length,int except_line=-1); bool generateLog(int hour,const QString &logname,const QString &svc_name, QString *errors); diff --git a/rdlogmanager/edit_clock.cpp b/rdlogmanager/edit_clock.cpp index 41d708bc..50474a09 100644 --- a/rdlogmanager/edit_clock.cpp +++ b/rdlogmanager/edit_clock.cpp @@ -2,7 +2,7 @@ // // Edit Rivendell Log Clock // -// (C) Copyright 2002-2018 Fred Gleason +// (C) Copyright 2002-2019 Fred Gleason // // 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 @@ -289,26 +289,22 @@ void EditClock::addData() { int line=0; RDEventLine eventline(rda->station()); - - RDListViewItem *item=(RDListViewItem *)edit_clocks_list->selectedItem(); - if(item!=NULL) { - if(item->text(4).isEmpty()) { - line=edit_clock->size(); - } - else { - line=item->text(4).toInt(); - } - } EditEventLine *edit_eventline= new EditEventLine(&eventline,edit_clock,-1,this); if(edit_eventline->exec()<0) { return; } delete edit_eventline; - edit_clock->insert(eventline.name(),line); - edit_clock->eventLine(line)->setStartTime(eventline.startTime()); - edit_clock->eventLine(line)->setLength(eventline.length()); - edit_clock->eventLine(line)->load(); + if(line<0) { + line=edit_clock->size(); + } + line=edit_clock-> + insert(eventline.name(),eventline.startTime(),eventline.length()); + if(line<0) { + QMessageBox::warning(this,"RDLogManager - "+tr("Error"), + tr("That event does not exist.")); + return; + } edit_modified=true; RefreshList(line); } @@ -367,10 +363,13 @@ void EditClock::cloneData() return; } delete edit_eventline; - edit_clock->insert(eventline.name(),line); - edit_clock->eventLine(line)->setStartTime(eventline.startTime()); - edit_clock->eventLine(line)->setLength(eventline.length()); - edit_clock->eventLine(line)->load(); + line=edit_clock-> + insert(eventline.name(),eventline.startTime(),eventline.length()); + if(line<0) { + QMessageBox::warning(this,"RDLogManager - "+tr("Error"), + tr("That event does not exist.")); + return; + } edit_modified=true; RefreshList(line); } @@ -526,8 +525,8 @@ void EditClock::doubleClickedData(Q3ListViewItem *item,const QPoint &,int) void EditClock::colorData() { - QColor color=QColorDialog::getColor(edit_color_button->backgroundColor(), - this,"color_dialog"); + QColor color= + QColorDialog::getColor(edit_color_button->backgroundColor(),this); if(color.isValid()) { edit_color_button->setPalette(QPalette(color,backgroundColor())); } @@ -620,18 +619,10 @@ void EditClock::Save() void EditClock::RefreshList(int select_line) { UpdateClock(); - RDListViewItem *prev_item=(RDListViewItem *)edit_clocks_list->selectedItem(); - - if((prev_item!=NULL)&&(select_line>=0)) { - select_line=prev_item->text(4).toInt(); - } RDListViewItem *item; RDEventLine *eventline; edit_clocks_list->clear(); - item=new RDListViewItem(edit_clocks_list); - item->setText(2,tr("--- End of clock ---")); - item->setText(4,"-2"); for(int i=edit_clock->size()-1;i>=0;i--) { if((eventline=edit_clock->eventLine(i))!=NULL) { item=new RDListViewItem(edit_clocks_list); diff --git a/rdlogmanager/rdlogmanager_cs.ts b/rdlogmanager/rdlogmanager_cs.ts index af445208..31294d82 100644 --- a/rdlogmanager/rdlogmanager_cs.ts +++ b/rdlogmanager/rdlogmanager_cs.ts @@ -169,7 +169,7 @@ Chcete je uložit? --- End of clock --- - --- Konec hodin --- + --- Konec hodin --- Invalid Code @@ -208,6 +208,14 @@ Chcete je uložit? Are you sure you want to delete + + Error + + + + That event does not exist. + + EditEvent diff --git a/rdlogmanager/rdlogmanager_de.ts b/rdlogmanager/rdlogmanager_de.ts index fa85d2a2..2d06f001 100644 --- a/rdlogmanager/rdlogmanager_de.ts +++ b/rdlogmanager/rdlogmanager_de.ts @@ -169,7 +169,7 @@ Wollen Sie sie speichern? --- End of clock --- - --- Ende der Uhr --- + --- Ende der Uhr --- Invalid Code @@ -208,6 +208,14 @@ Wollen Sie sie speichern? Are you sure you want to delete + + Error + + + + That event does not exist. + + EditEvent diff --git a/rdlogmanager/rdlogmanager_es.ts b/rdlogmanager/rdlogmanager_es.ts index 5f8180bb..91c15bf4 100644 --- a/rdlogmanager/rdlogmanager_es.ts +++ b/rdlogmanager/rdlogmanager_es.ts @@ -149,7 +149,7 @@ Do you want to save? --- End of clock --- - --- Fin de la torta --- + --- Fin de la torta --- Invalid Code @@ -210,6 +210,14 @@ horario Are you sure you want to delete + + Error + + + + That event does not exist. + + EditEvent diff --git a/rdlogmanager/rdlogmanager_fr.ts b/rdlogmanager/rdlogmanager_fr.ts index e8ffb00d..e18e83bf 100644 --- a/rdlogmanager/rdlogmanager_fr.ts +++ b/rdlogmanager/rdlogmanager_fr.ts @@ -151,10 +151,6 @@ Do you want to save? Clock already exists! Overwrite? - - --- End of clock --- - - Invalid Code @@ -192,6 +188,14 @@ Do you want to save? Are you sure you want to delete + + Error + + + + That event does not exist. + + EditEvent diff --git a/rdlogmanager/rdlogmanager_nb.ts b/rdlogmanager/rdlogmanager_nb.ts index b09a5a1c..63c08ebc 100644 --- a/rdlogmanager/rdlogmanager_nb.ts +++ b/rdlogmanager/rdlogmanager_nb.ts @@ -170,7 +170,7 @@ Vil du lagra? --- End of clock --- - --- Slutt på klokka --- + --- Slutt på klokka --- Invalid Code @@ -209,6 +209,14 @@ Vil du lagra? Are you sure you want to delete + + Error + + + + That event does not exist. + + EditEvent diff --git a/rdlogmanager/rdlogmanager_nn.ts b/rdlogmanager/rdlogmanager_nn.ts index b09a5a1c..63c08ebc 100644 --- a/rdlogmanager/rdlogmanager_nn.ts +++ b/rdlogmanager/rdlogmanager_nn.ts @@ -170,7 +170,7 @@ Vil du lagra? --- End of clock --- - --- Slutt på klokka --- + --- Slutt på klokka --- Invalid Code @@ -209,6 +209,14 @@ Vil du lagra? Are you sure you want to delete + + Error + + + + That event does not exist. + + EditEvent diff --git a/rdlogmanager/rdlogmanager_pt_BR.ts b/rdlogmanager/rdlogmanager_pt_BR.ts index 5dc20915..6774a59b 100644 --- a/rdlogmanager/rdlogmanager_pt_BR.ts +++ b/rdlogmanager/rdlogmanager_pt_BR.ts @@ -175,7 +175,7 @@ Você quer salvar? --- End of clock --- - -- Fim do Relógio -- + -- Fim do Relógio -- Invalid Code @@ -210,6 +210,14 @@ Você quer salvar? Are you sure you want to delete + + Error + + + + That event does not exist. + + EditEvent