From e5a89ae8331c9eaa1871503ae5f87b70d56ba85f Mon Sep 17 00:00:00 2001 From: Fred Gleason <fredg@paravelsystems.com> Date: Mon, 23 Nov 2020 11:56:35 -0500 Subject: [PATCH] 2020-11-22 Fred Gleason <fredg@paravelsystems.com> * Added '--send-mail' and '--mail-per-file' options to rdimport(1). Signed-off-by: Fred Gleason <fredg@paravelsystems.com> --- ChangeLog | 2 + docs/manpages/rdimport.xml | 48 ++++++++ utils/rdimport/Makefile.am | 3 +- utils/rdimport/journal.cpp | 235 ++++++++++++++++++++++++++++++++++++ utils/rdimport/journal.h | 52 ++++++++ utils/rdimport/rdimport.cpp | 72 ++++++++--- utils/rdimport/rdimport.h | 6 + 7 files changed, 402 insertions(+), 16 deletions(-) create mode 100644 utils/rdimport/journal.cpp create mode 100644 utils/rdimport/journal.h diff --git a/ChangeLog b/ChangeLog index 130a1c10..94f0d09b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -20618,3 +20618,5 @@ Settings' dialog in rdadmin(1). * Added a 'Notification E-Mail Addresses' control to the 'Group' dialog in rdadmin(1). +2020-11-22 Fred Gleason <fredg@paravelsystems.com> + * Added '--send-mail' and '--mail-per-file' options to rdimport(1). diff --git a/docs/manpages/rdimport.xml b/docs/manpages/rdimport.xml index 337af65a..3f05704d 100644 --- a/docs/manpages/rdimport.xml +++ b/docs/manpages/rdimport.xml @@ -256,6 +256,24 @@ </listitem> </varlistentry> + <varlistentry> + <term> + <option>--mail-per-file</option> + </term> + <listitem> + <para> + Send an e-mail message for each file processed, rather than + a single message per run summarizing all actions taken. Implies + the <command>--send-mail</command> switch. + </para> + <para> + See the <command>--send-mail</command> switch (below) for more + details about generating e-mailed reports from + <command>rdimport</command><manvolnum>1</manvolnum>. + </para> + </listitem> + </varlistentry> + <varlistentry> <term> <option>--metadata-pattern=</option><replaceable>pattern</replaceable> @@ -547,6 +565,32 @@ </listitem> </varlistentry> + <varlistentry> + <term> + <option>--send-mail</option> + </term> + <listitem> + <para> + Send e-mail to the address(es) specified in the destination group's + <computeroutput>Notification E-Mail Addresses</computeroutput> + setting in <command>rdadmin</command><manvolnum>1</manvolnum> + summarizing the action(s) performed during the run. + Each invocation of + <command>rdimport</command><manvolnum>1</manvolnum> will + potentially generate + one message for all successful imports and another for all failed + imports (but see the <option>--mail-per-file</option> switch + (above) for a way to modify this behavior). + </para> + <para> + NOTE: Rivendell uses the system's + <command>sendmail</command><manvolnum>1</manvolnum> subsystem + for originating e-mail. For many modern e-mail setups, further + configuration of that subsystem may be necessary. + </para> + </listitem> + </varlistentry> + <varlistentry> <term> <option>--set-datetimes=</option><replaceable>start-datetime</replaceable>,<replaceable>end-datetime</replaceable> @@ -989,6 +1033,10 @@ <refsect1 id='see_also'><title>See Also</title> <para> + <citerefentry> + <refentrytitle>sendmail</refentrytitle><manvolnum>1</manvolnum> + </citerefentry> + <literal>,</literal> <citerefentry> <refentrytitle>rdexport</refentrytitle><manvolnum>1</manvolnum> </citerefentry> diff --git a/utils/rdimport/Makefile.am b/utils/rdimport/Makefile.am index 0fdfd8f4..ca5f6f57 100644 --- a/utils/rdimport/Makefile.am +++ b/utils/rdimport/Makefile.am @@ -27,7 +27,8 @@ moc_%.cpp: %.h bin_PROGRAMS = rdimport -dist_rdimport_SOURCES = markerset.cpp markerset.h\ +dist_rdimport_SOURCES = journal.cpp journal.h\ + markerset.cpp markerset.h\ rdimport.cpp rdimport.h nodist_rdimport_SOURCES = moc_rdimport.cpp diff --git a/utils/rdimport/journal.cpp b/utils/rdimport/journal.cpp new file mode 100644 index 00000000..92a01032 --- /dev/null +++ b/utils/rdimport/journal.cpp @@ -0,0 +1,235 @@ +// journal.cpp +// +// E-mail file importation actions +// +// (C) Copyright 2020 Fred Gleason <fredg@paravelsystems.com> +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public +// License along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include <rdapplication.h> +#include <rdgroup.h> +#include <rdsendmail.h> + +#include "journal.h" + +Journal::Journal(bool send_immediately) +{ + c_send_immediately=send_immediately; +} + + +void Journal::addSuccess(const QString &groupname,QString filename, + unsigned cartnum,const QString &title) +{ + QString errors; + QString subject; + QString body; + RDGroup *group=new RDGroup(groupname); + QStringList addrs= + group->notifyEmailAddress().split(",",QString::SkipEmptyParts); + + if(addrs.size()>0) { + filename=filename.split("/",QString::SkipEmptyParts).last(); + if(c_send_immediately) { + subject=QObject::tr("Rivendell import for file")+": "+filename; + body+=QObject::tr("Rivendell File Import Report")+"\n"; + body+="\n"; + body+=QObject::tr("Import Success")+"\n"; + body+="\n"; + body+=QObject::tr("Filename")+": "+filename+"\n"; + body+=QObject::tr("Submitted by")+": "; + if(rda->user()->emailAddress().isEmpty()) { + body+=rda->user()->name()+"\n"; + } + else { + body+=rda->user()->emailAddress()+"\n"; + } + body+=QObject::tr("Group")+": "+groupname+"\n"; + body+=QObject::tr("Cart Number")+QString().sprintf(": %06u",cartnum)+"\n"; + body+=QObject::tr("Cart Title")+": "+title+"\n"; + + if(!RDSendMail(&errors,subject,body,rda->system()->originEmailAddress(), + addrs)) { + rda->syslog(LOG_WARNING,"email send failed [%s]", + errors.toUtf8().constData()); + } + } + else { + c_good_groups.push_back(groupname); + c_good_filenames.push_back(filename); + c_good_cart_numbers.push_back(cartnum); + c_good_titles.push_back(title); + } + } + delete group; +} + + +void Journal::addFailure(const QString &groupname,QString filename, + const QString &err_msg) +{ + QString errors; + QString subject; + QString body; + RDGroup *group=new RDGroup(groupname); + QStringList addrs= + group->notifyEmailAddress().split(",",QString::SkipEmptyParts); + + if(addrs.size()>0) { + filename=filename.split("/",QString::SkipEmptyParts).last(); + if(c_send_immediately) { + subject=QObject::tr("Rivendell import FAILURE for file")+": "+filename; + body+=QObject::tr("Rivendell File Import Report")+"\n"; + body+="\n"; + body+=QObject::tr("IMPORT FAILED!")+"\n"; + body+="\n"; + body+=QObject::tr("Filename")+": "+filename+"\n"; + body+=QObject::tr("Submitted by")+": "; + if(rda->user()->emailAddress().isEmpty()) { + body+=rda->user()->name()+"\n"; + } + else { + body+=rda->user()->emailAddress()+"\n"; + } + body+=QObject::tr("Group")+": "+groupname+"\n"; + body+=QObject::tr("Reason")+": "+err_msg+"\n"; + + if(!RDSendMail(&errors,subject,body,rda->system()->originEmailAddress(), + addrs)) { + rda->syslog(LOG_WARNING,"email send failed [%s]", + errors.toUtf8().constData()); + } + } + else { + c_bad_groups.push_back(groupname); + c_bad_filenames.push_back(filename); + c_bad_errors.push_back(err_msg); + } + } + + delete group; +} + + +void Journal::sendAll() +{ + QStringList used_addrs; + + // + // Successful imports + // + used_addrs.clear(); + if(c_good_groups.size()>0) { + QMultiMap<QString,QString> grp_map=GroupsByAddress(c_good_groups); + for(QMap<QString,QString>::const_iterator it=grp_map.begin(); + it!=grp_map.end();it++) { + if(!used_addrs.contains(it.key())) { + QString errors; + QString subject; + QString body; + QString from_addr; + QStringList to_addrs; + + from_addr=rda->system()->originEmailAddress(); + to_addrs=it.key().split(",",QString::SkipEmptyParts); + subject=QObject::tr("Rivendell import report")+"\n"; + + body+=QObject::tr("Rivendell File Import Report")+"\n"; + body+="\n"; + body+=QObject::tr("-Group---- -Cart- -Title------------------------ -Filename-------------")+"\n"; + QStringList grps=grp_map.values(it.key()); + for(int i=0;i<grps.size();i++) { + for(int j=0;j<c_good_groups.size();j++) { + if(c_good_groups.at(j)==grps.at(i)) { + body+=QString().sprintf("%-10s %06u %-30s %-22s\n", + grps.at(i).left(10).toUtf8().constData(), + c_good_cart_numbers.at(j), + c_good_titles.at(j).left(30).toUtf8().constData(), + c_good_filenames.at(j).left(22).toUtf8().constData()); + } + } + } + if(!RDSendMail(&errors,subject,body,rda->system()->originEmailAddress(), + to_addrs)) { + rda->syslog(LOG_WARNING,"email send failed [%s]", + errors.toUtf8().constData()); + } + used_addrs.push_back(it.key()); + } + } + } + + // + // Failed imports + // + used_addrs.clear(); + if(c_bad_groups.size()>0) { + QMultiMap<QString,QString> grp_map=GroupsByAddress(c_bad_groups); + for(QMap<QString,QString>::const_iterator it=grp_map.begin(); + it!=grp_map.end();it++) { + if(!used_addrs.contains(it.key())) { + QString errors; + QString subject; + QString body; + QString from_addr; + QStringList to_addrs; + + from_addr=rda->system()->originEmailAddress(); + to_addrs=it.key().split(",",QString::SkipEmptyParts); + subject=QObject::tr("Rivendell import FAILURE report")+"\n"; + + body+=QObject::tr("Rivendell File Import FAILURE Report")+"\n"; + body+="\n"; + body+=QObject::tr("-Group---- -Reason------------------------------ -Filename-------------")+"\n"; + QStringList grps=grp_map.values(it.key()); + for(int i=0;i<grps.size();i++) { + for(int j=0;j<c_bad_groups.size();j++) { + if(c_bad_groups.at(j)==grps.at(i)) { + body+=QString().sprintf("%-10s %-37s %-22s\n", + grps.at(i).left(10).toUtf8().constData(), + c_bad_errors.at(j).left(37).toUtf8().constData(), + c_bad_filenames.at(j).left(22).toUtf8().constData()); + } + } + } + if(!RDSendMail(&errors,subject,body,rda->system()->originEmailAddress(), + to_addrs)) { + rda->syslog(LOG_WARNING,"email send failed [%s]", + errors.toUtf8().constData()); + } + used_addrs.push_back(it.key()); + } + } + } +} + + +QMultiMap<QString,QString> Journal::GroupsByAddress(QStringList groups) const +{ + RDGroup *grp; + QMultiMap<QString,QString> ret; + + for(int i=0;i<groups.size();i++) { + grp=new RDGroup(groups.at(i)); + if(!grp->notifyEmailAddress().isEmpty()) { + if(!ret.contains(grp->notifyEmailAddress(),grp->name())) { + ret.insert(grp->notifyEmailAddress(),grp->name()); + } + } + delete grp; + } + + return ret; +} diff --git a/utils/rdimport/journal.h b/utils/rdimport/journal.h new file mode 100644 index 00000000..dab59767 --- /dev/null +++ b/utils/rdimport/journal.h @@ -0,0 +1,52 @@ +// journal.h +// +// E-mail file importation actions +// +// (C) Copyright 2020 Fred Gleason <fredg@paravelsystems.com> +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public +// License along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#ifndef JOURNAL_H +#define JOURNAL_H + +#include <QList> +#include <QMultiMap> +#include <QString> +#include <QStringList> + +class Journal +{ + public: + Journal(bool send_immediately); + void addSuccess(const QString &groupname,QString filename, + unsigned cartnum,const QString &title); + void addFailure(const QString &groupname,QString filename, + const QString &err_msg); + void sendAll(); + + private: + QMultiMap<QString,QString> GroupsByAddress(QStringList groups) const; + bool c_send_immediately; + QStringList c_good_groups; + QStringList c_good_filenames; + QList<unsigned> c_good_cart_numbers; + QStringList c_good_titles; + QStringList c_bad_groups; + QStringList c_bad_filenames; + QStringList c_bad_errors; +}; + + +#endif // JOURNAL_H diff --git a/utils/rdimport/rdimport.cpp b/utils/rdimport/rdimport.cpp index 7baf9629..5b067ab5 100644 --- a/utils/rdimport/rdimport.cpp +++ b/utils/rdimport/rdimport.cpp @@ -99,6 +99,8 @@ MainObject::MainObject(QObject *parent) import_xml=false; import_to_mono=false; import_failed_imports=0; + import_send_mail=false; + import_mail_per_file=false; // // Open the Database @@ -445,6 +447,15 @@ MainObject::MainObject(QObject *parent) import_xml=true; rda->cmdSwitch()->setProcessed(i,true); } + if(rda->cmdSwitch()->key(i)=="--send-mail") { + import_send_mail=true; + rda->cmdSwitch()->setProcessed(i,true); + } + if(rda->cmdSwitch()->key(i)=="--mail-per-file") { + import_mail_per_file=true; + import_send_mail=true; + rda->cmdSwitch()->setProcessed(i,true); + } } // @@ -707,6 +718,17 @@ MainObject::MainObject(QObject *parent) else { Log(LOG_INFO,QString(" Broken format workarounds are DISABLED\n")); } + if(import_send_mail) { + if(import_mail_per_file) { + Log(LOG_INFO,QString(" E-mail report per file is ENABLED\n")); + } + else { + Log(LOG_INFO,QString(" Summary e-mail report is ENABLED\n")); + } + } + else { + Log(LOG_INFO,QString(" E-mail reporting is DISABLED\n")); + } if(import_create_dates) { Log(LOG_INFO,QString(" Import Create Dates mode is ON\n")); Log(LOG_INFO,QString().sprintf(" Import Create Start Date Offset = %d days\n",import_create_startdate_offset)); @@ -800,6 +822,11 @@ MainObject::MainObject(QObject *parent) } } + // + // Start the email journal + // + import_journal=new Journal(import_mail_per_file); + // // Setup Signal Handling // @@ -968,18 +995,19 @@ MainObject::Result MainObject::ImportFile(const QString &filename, Log(LOG_WARNING,QString().sprintf( " File \"%s\" is not readable or not a recognized format, skipping...\n", (const char *)RDGetBasePart(filename).toUtf8())); - delete wavefile; - delete wavedata; - delete effective_group; if(!import_run) { NormalExit(); } if(!import_temp_fix_filename.isEmpty()) { -// printf("Fixed Name: %s\n",(const char *)import_temp_fix_filename); QFile::remove(import_temp_fix_filename); import_temp_fix_filename=""; } import_failed_imports++; + import_journal->addFailure(effective_group->name(),filename, + tr("unknown/unrecognized file format")); + delete wavefile; + delete wavedata; + delete effective_group; return MainObject::FileBad; } Log(LOG_WARNING,QString().sprintf("success.\n")); @@ -989,9 +1017,6 @@ MainObject::Result MainObject::ImportFile(const QString &filename, Log(LOG_WARNING,QString().sprintf( " File \"%s\" is not readable or not a recognized format, skipping...\n", (const char *)RDGetBasePart(filename).toUtf8())); - delete wavefile; - delete wavedata; - delete effective_group; if(!import_run) { NormalExit(); } @@ -1000,6 +1025,11 @@ MainObject::Result MainObject::ImportFile(const QString &filename, import_temp_fix_filename=""; } import_failed_imports++; + import_journal->addFailure(effective_group->name(),filename, + tr("unknown/unrecognized file format")); + delete wavefile; + delete wavedata; + delete effective_group; return MainObject::FileBad; } } @@ -1042,10 +1072,12 @@ MainObject::Result MainObject::ImportFile(const QString &filename, " File \"%s\" has an invalid or out of range Cart Number, skipping...\n", (const char *)RDGetBasePart(filename).toUtf8())); wavefile->closeWave(); + import_failed_imports++; + import_journal->addFailure(effective_group->name(),filename, + tr("invalid/out-of-range cart number")); delete wavefile; delete wavedata; delete effective_group; - import_failed_imports++; return MainObject::FileBad; } } @@ -1056,9 +1088,6 @@ MainObject::Result MainObject::ImportFile(const QString &filename, Log(LOG_ERR,QString().sprintf("rdimport: no free carts available in specified group\n")); wavefile->closeWave(); import_failed_imports++; - delete wavefile; - delete wavedata; - delete effective_group; import_failed_imports++; if(import_drop_box) { if(!import_run) { @@ -1068,6 +1097,11 @@ MainObject::Result MainObject::ImportFile(const QString &filename, QFile::remove(import_temp_fix_filename); import_temp_fix_filename=""; } + import_journal->addFailure(effective_group->name(),filename, + tr("no free cart available in group")); + delete wavefile; + delete wavedata; + delete effective_group; return MainObject::NoCart; } exit(RDApplication::ExitImportFailed); @@ -1087,8 +1121,10 @@ MainObject::Result MainObject::ImportFile(const QString &filename, cart->addCut(import_format,import_bitrate,import_channels); if(cutnum<0) { Log(LOG_WARNING,QString().sprintf("rdimport: no free cuts available in cart %06u\n",*cartnum)); - delete cart; import_failed_imports++; + import_journal->addFailure(effective_group->name(),filename, + tr("no free cut available in cart")); + delete cart; return MainObject::NoCut; } RDCut *cut=new RDCut(*cartnum,cutnum); @@ -1149,9 +1185,6 @@ MainObject::Result MainObject::ImportFile(const QString &filename, delete cut; delete cart; wavefile->closeWave(); - delete wavefile; - delete wavedata; - delete effective_group; if(!import_run) { NormalExit(); } @@ -1160,6 +1193,11 @@ MainObject::Result MainObject::ImportFile(const QString &filename, import_temp_fix_filename=""; } import_failed_imports++; + import_journal->addFailure(effective_group->name(),filename, + tr("corrupt audio file")); + delete wavefile; + delete wavedata; + delete effective_group; return MainObject::FileBad; break; } @@ -1351,6 +1389,9 @@ MainObject::Result MainObject::ImportFile(const QString &filename, delete ll; } + import_journal-> + addSuccess(effective_group->name(),filename,*cartnum,cart->title()); + delete settings; delete conv; delete cut; @@ -2176,6 +2217,7 @@ void MainObject::Log(int prio,const QString &msg) const void MainObject::NormalExit() const { + import_journal->sendAll(); if(import_failed_imports>0) { exit(RDApplication::ExitImportFailed); } diff --git a/utils/rdimport/rdimport.h b/utils/rdimport/rdimport.h index 72e4a520..c9ea489a 100644 --- a/utils/rdimport/rdimport.h +++ b/utils/rdimport/rdimport.h @@ -28,6 +28,8 @@ #include <qsqldatabase.h> #include <qfileinfo.h> #include <qdatetime.h> +#include <QList> +#include <QStringList> #include <rdcart.h> #include <rdcut.h> @@ -36,6 +38,7 @@ #include <rdwavedata.h> #include <rdwavefile.h> +#include "journal.h" #include "markerset.h" #define RDIMPORT_TEMP_BASENAME "rdimp" @@ -113,6 +116,8 @@ class MainObject : public QObject int import_autotrim_level; int import_segue_level; int import_segue_length; + bool import_send_mail; + bool import_mail_per_file; unsigned import_cart_number; QString import_metadata_pattern; QString import_output_pattern; @@ -151,6 +156,7 @@ class MainObject : public QObject MarkerSet *import_segue_markers; MarkerSet *import_fadedown_marker; MarkerSet *import_fadeup_marker; + Journal *import_journal; };