2023-05-062023-05-06 Fred Gleason <fredg@paravelsystems.com>

* Refactored the RSS reports in rdadmin(1) to include information on
	missing	items.
	* Modified the feed report in rdcastmanager to include information
	on missing items.

Signed-off-by: Fred Gleason <fredg@paravelsystems.com>
This commit is contained in:
Fred Gleason
2023-05-06 11:17:44 -04:00
parent 3cf2fe1ce4
commit 47d30f803e
20 changed files with 881 additions and 287 deletions

View File

@@ -136,7 +136,6 @@ dist_librd_la_SOURCES = dbversion.h\
rdexport_settings_dialog.cpp rdexport_settings_dialog.h\
rdfeed.cpp rdfeed.h\
rdfeedlistmodel.cpp rdfeedlistmodel.h\
rdfeedlistview.cpp rdfeedlistview.h\
rdfontengine.cpp rdfontengine.h\
rdformpost.cpp rdformpost.h\
rdflacdecode.cpp rdflacdecode.h\
@@ -348,7 +347,6 @@ nodist_librd_la_SOURCES = moc_rdadd_cart.cpp\
moc_rdexport_settings_dialog.cpp\
moc_rdfeed.cpp\
moc_rdfeedlistmodel.cpp\
moc_rdfeedlistview.cpp\
moc_rdframe.cpp\
moc_rdget_ath.cpp\
moc_rdgetpasswd.cpp\

View File

@@ -44,6 +44,7 @@
#include "rdwavefile.h"
#include "rdwebresult.h"
#include "rdxport_interface.h"
#include "rdxsltengine.h"
int __RDFeed_Debug_Callback(CURL *handle,curl_infotype type,char *data,
size_t size,void *userptr)
@@ -69,6 +70,8 @@ size_t __RDFeed_Download_Callback(char *ptr,size_t size,size_t nmemb,
}
RDFeed::RDFeed(const QString &keyname,RDConfig *config,QObject *parent)
: QObject(parent)
{
@@ -956,90 +959,6 @@ QString RDFeed::imageUrl(int img_id) const
}
bool RDFeed::downloadXml(QByteArray *xml,QString *err_msg)
{
long response_code;
CURL *curl=NULL;
CURLcode curl_err;
char curl_errorbuffer[CURL_ERROR_SIZE];
struct curl_httppost *first=NULL;
struct curl_httppost *last=NULL;
//
// Generate POST Data
//
curl_formadd(&first,&last,CURLFORM_PTRNAME,"COMMAND",
CURLFORM_COPYCONTENTS,
QString::asprintf("%u",RDXPORT_COMMAND_DOWNLOAD_RSS).toUtf8().
constData(),
CURLFORM_END);
curl_formadd(&first,&last,CURLFORM_PTRNAME,"LOGIN_NAME",
CURLFORM_COPYCONTENTS,rda->user()->name().toUtf8().constData(),
CURLFORM_END);
curl_formadd(&first,&last,CURLFORM_PTRNAME,"PASSWORD",
CURLFORM_COPYCONTENTS,
rda->user()->password().toUtf8().constData(),CURLFORM_END);
curl_formadd(&first,&last,CURLFORM_PTRNAME,"ID",
CURLFORM_COPYCONTENTS,
QString::asprintf("%u",feed_id).toUtf8().constData(),
CURLFORM_END);
//
// Set up the transfer
//
if((curl=curl_easy_init())==NULL) {
curl_formfree(first);
return false;
}
QStringList *err_msgs=SetupCurlLogging(curl);
curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,__RDFeed_Download_Callback);
feed_xml.clear();
curl_easy_setopt(curl,CURLOPT_WRITEDATA,xml);
curl_easy_setopt(curl,CURLOPT_HTTPPOST,first);
curl_easy_setopt(curl,CURLOPT_ERRORBUFFER,curl_errorbuffer);
curl_easy_setopt(curl,CURLOPT_USERAGENT,
rda->config()->userAgent().toUtf8().constData());
curl_easy_setopt(curl,CURLOPT_TIMEOUT,RD_CURL_TIMEOUT);
curl_easy_setopt(curl,CURLOPT_NOPROGRESS,1);
curl_easy_setopt(curl,CURLOPT_URL,
rda->station()->webServiceUrl(rda->config()).toUtf8().constData());
rda->syslog(LOG_DEBUG,"using web service URL: %s",
rda->station()->webServiceUrl(rda->config()).toUtf8().constData());
//
// Send it
//
if((curl_err=curl_easy_perform(curl))!=CURLE_OK) {
*err_msg=QString::fromUtf8(curl_errorbuffer);
curl_easy_cleanup(curl);
curl_formfree(first);
ProcessCurlLogging("RDFeed::postPodcast()",err_msgs);
return false;
}
//
// Clean up
//
curl_easy_getinfo(curl,CURLINFO_RESPONSE_CODE,&response_code);
curl_easy_cleanup(curl);
curl_formfree(first);
//
// Process the results
//
if((response_code<200)||(response_code>299)) {
*err_msg=tr("remote server returned unexpected response code")+
QString::asprintf(" %ld",response_code);
ProcessCurlLogging("RDFeed::postPodcast()",err_msgs);
return false;
}
delete err_msgs;
return true;
}
bool RDFeed::postXml(QString *err_msg)
{
long response_code;
@@ -1714,7 +1633,8 @@ unsigned RDFeed::postLog(const QString &logname,const QTime &start_time,
}
QString RDFeed::rssXml(QString *err_msg,const QDateTime &now,bool *ok)
QString RDFeed::rssXml(QString *err_msg,const QDateTime &now,bool *ok,
QList<unsigned> *active_cast_ids)
{
QString ret;
@@ -1791,7 +1711,8 @@ QString RDFeed::rssXml(QString *err_msg,const QDateTime &now,bool *ok)
//
// Render Channel XML
//
ret+=" <channel>\n";
ret+=QString::asprintf(" <channel rivendell:id=\"%u\">\n",
chan_q->value(18).toUInt());
ret+=ResolveChannelWildcards(channel_template,chan_q,now)+"\r\n";
//
@@ -1852,7 +1773,15 @@ QString RDFeed::rssXml(QString *err_msg,const QDateTime &now,bool *ok)
}
item_q=new RDSqlQuery(sql);
while(item_q->next()) {
ret+=" <item>\r\n";
if((active_cast_ids==NULL)||
(active_cast_ids->contains(item_q->value(14).toUInt()))) {
ret+=QString::
asprintf(" <item rivendell:id=\"%u\" rivendell:style=\"active-rounded-block\">\r\n",item_q->value(14).toUInt());
}
else {
ret+=QString::
asprintf(" <item rivendell:id=\"%u\" rivendell:style=\"missing-rounded-block\">\r\n",item_q->value(14).toUInt());
}
ret+=ResolveItemWildcards(item_template,item_q,chan_q);
ret+="\r\n";
ret+=" </item>\r\n";
@@ -1871,6 +1800,256 @@ QString RDFeed::rssXml(QString *err_msg,const QDateTime &now,bool *ok)
}
bool RDFeed::rssFrontXml(QByteArray *xml,QString *err_msg) const
{
long response_code;
CURL *curl=NULL;
CURLcode curl_err;
char curl_errorbuffer[CURL_ERROR_SIZE];
struct curl_httppost *first=NULL;
struct curl_httppost *last=NULL;
//
// Generate POST Data
//
curl_formadd(&first,&last,CURLFORM_PTRNAME,"COMMAND",
CURLFORM_COPYCONTENTS,
QString::asprintf("%u",RDXPORT_COMMAND_DOWNLOAD_RSS).toUtf8().
constData(),
CURLFORM_END);
curl_formadd(&first,&last,CURLFORM_PTRNAME,"LOGIN_NAME",
CURLFORM_COPYCONTENTS,rda->user()->name().toUtf8().constData(),
CURLFORM_END);
curl_formadd(&first,&last,CURLFORM_PTRNAME,"PASSWORD",
CURLFORM_COPYCONTENTS,
rda->user()->password().toUtf8().constData(),CURLFORM_END);
curl_formadd(&first,&last,CURLFORM_PTRNAME,"ID",
CURLFORM_COPYCONTENTS,
QString::asprintf("%u",feed_id).toUtf8().constData(),
CURLFORM_END);
//
// Set up the transfer
//
if((curl=curl_easy_init())==NULL) {
curl_formfree(first);
return false;
}
QStringList *err_msgs=SetupCurlLogging(curl);
curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,__RDFeed_Download_Callback);
xml->clear();
curl_easy_setopt(curl,CURLOPT_WRITEDATA,xml);
curl_easy_setopt(curl,CURLOPT_HTTPPOST,first);
curl_easy_setopt(curl,CURLOPT_ERRORBUFFER,curl_errorbuffer);
curl_easy_setopt(curl,CURLOPT_USERAGENT,
rda->config()->userAgent().toUtf8().constData());
curl_easy_setopt(curl,CURLOPT_TIMEOUT,RD_CURL_TIMEOUT);
curl_easy_setopt(curl,CURLOPT_NOPROGRESS,1);
curl_easy_setopt(curl,CURLOPT_URL,
rda->station()->webServiceUrl(rda->config()).toUtf8().constData());
rda->syslog(LOG_DEBUG,"using web service URL: %s",
rda->station()->webServiceUrl(rda->config()).toUtf8().constData());
//
// Send it
//
if((curl_err=curl_easy_perform(curl))!=CURLE_OK) {
*err_msg=QString::fromUtf8(curl_errorbuffer);
curl_easy_cleanup(curl);
curl_formfree(first);
ProcessCurlLogging("RDFeed::postPodcast()",err_msgs);
return false;
}
//
// Clean up
//
curl_easy_getinfo(curl,CURLINFO_RESPONSE_CODE,&response_code);
curl_easy_cleanup(curl);
curl_formfree(first);
//
// Process the results
//
if((response_code<200)||(response_code>299)) {
*err_msg=tr("remote server returned unexpected response code")+
QString::asprintf(" %ld",response_code);
ProcessCurlLogging("RDFeed::postPodcast()",err_msgs);
return false;
}
delete err_msgs;
return true;
}
bool RDFeed::rssBackXml(QByteArray *xml,QString *err_msg) const
{
CURL *curl=NULL;
CURLcode curl_err;
long response_code;
bool ret=false;
if((curl=curl_easy_init())==NULL) {
*err_msg=tr("Unable to initialize CURL");
ret=false;
}
else {
QByteArray src_xml;
curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,__RDFeed_Download_Callback);
curl_easy_setopt(curl,CURLOPT_WRITEDATA,xml);
curl_easy_setopt(curl,CURLOPT_USERAGENT,
rda->config()->userAgent().toUtf8().constData());
curl_easy_setopt(curl,CURLOPT_TIMEOUT,RD_CURL_TIMEOUT);
curl_easy_setopt(curl,CURLOPT_NOPROGRESS,1);
curl_easy_setopt(curl,CURLOPT_URL,
RDFeed::publicUrl(baseUrl(""),feed_keyname).
toUtf8().constData());
curl_err=curl_easy_perform(curl);
if((ret=(curl_err==CURLE_OK))) {
curl_easy_getinfo(curl,CURLINFO_RESPONSE_CODE,&response_code);
if((ret=(response_code>=200)||(response_code<300))) {
ret=true;
*err_msg=QObject::tr("Server returned result code")+
QString::asprintf(" %lu ",response_code)+*xml;
}
}
else {
*err_msg=QObject::tr("Curl error")+" ["+curl_easy_strerror(curl_err)+"].";
ret=false;
}
}
curl_easy_cleanup(curl);
return ret;
}
void RDFeed::activeCasts(QList<unsigned> *cast_ids)
{
cast_ids->clear();
QString sql=QString("select ")+
"`ID` "+ // 00
"from `PODCASTS` where "+
QString::asprintf("`FEED_ID`=%u && ",feed_id)+
QString::asprintf("`STATUS`=%u ",RDPodcast::StatusActive)+
"order by `ORIGIN_DATETIME` desc";
RDSqlQuery *q=new RDSqlQuery(sql);
while(q->next()) {
cast_ids->push_back(q->value(0).toUInt());
}
delete q;
}
bool RDFeed::frontActiveCasts(QList<unsigned> *cast_ids,QString *err_msg)
{
QByteArray xml;
bool ret=false;
QString text;
bool ok=false;
cast_ids->clear();
if(rssFrontXml(&xml,err_msg)) {
RDXsltEngine *xslt=
new RDXsltEngine("/usr/share/rivendell/rss-item-enclosures.xsl",this);
if(xslt->transform(&text,xml,err_msg)) {
QStringList f0=text.split("|",QString::SkipEmptyParts);
for(int i=0;i<f0.size();i++) {
QStringList f1=f0.at(i).split("/",QString::SkipEmptyParts);
QStringList f2=f1.last().split(".",QString::KeepEmptyParts);
if(f2.size()==2) {
QStringList f3=f2.first().split("_",QString::KeepEmptyParts);
if(f3.size()==2) {
cast_ids->push_back(f3.last().toUInt(&ok));
if(ok) {
ret=ok;
// OK, keep going
}
else {
// Integer conversion failed
*err_msg=QObject::tr("Internal error 1");
ret=false;
break;
}
}
else {
// f3: Split on '_' failed
*err_msg=QObject::tr("Internal error 2");
ret=false;
break;
}
}
else {
// f2: Split on '.' failed
*err_msg=QObject::tr("Internal error 3");
ret=false;
break;
}
}
}
delete xslt;
}
return ret;
}
bool RDFeed::backActiveCasts(QList<unsigned> *cast_ids,QString *err_msg)
{
QByteArray xml;
bool ret=false;
QString text;
bool ok=false;
cast_ids->clear();
if(rssBackXml(&xml,err_msg)) {
RDXsltEngine *xslt=
new RDXsltEngine("/usr/share/rivendell/rss-item-enclosures.xsl",
this);
if(xslt->transform(&text,xml,err_msg)) {
QStringList f0=text.split("|",QString::SkipEmptyParts);
for(int i=0;i<f0.size();i++) {
QStringList f1=f0.at(i).split("/",QString::SkipEmptyParts);
QStringList f2=f1.last().split(".",QString::KeepEmptyParts);
if(f2.size()==2) {
QStringList f3=f2.first().split("_",QString::KeepEmptyParts);
if(f3.size()==2) {
cast_ids->push_back(f3.last().toUInt(&ok));
if(ok) {
ret=ok;
// OK, keep going
}
else {
// Integer conversion failed
*err_msg=QObject::tr("Internal error 1");
ret=false;
break;
}
}
else {
// f3: Split on '_' failed
*err_msg=QObject::tr("Internal error 2");
ret=false;
break;
}
}
else {
// f2: Split on '.' failed
*err_msg=QObject::tr("Internal error 3");
ret=false;
break;
}
}
}
delete xslt;
}
return ret;
}
unsigned RDFeed::create(const QString &keyname,bool enable_users,
QString *err_msg)
{

View File

@@ -136,7 +136,6 @@ class RDFeed : public QObject
bool postPodcast(unsigned cast_id,QString *err_msg);
QString audioUrl(unsigned cast_id);
QString imageUrl(int img_id) const;
bool downloadXml(QByteArray *xml,QString *err_msg); // WebAPI Call
bool postXml(QString *err_msg); // WebAPI Call
bool postXmlConditional(const QString &caption,QWidget *widget);
bool removeRss(); // WebAPI Call
@@ -148,31 +147,19 @@ class RDFeed : public QObject
unsigned postLog(const QString &logname,const QTime &start_time,
bool stop_at_stop,int start_line,int end_line,
QString *err_msg);
QString rssXml(QString *err_msg,const QDateTime &now,bool *ok=NULL);
QString rssXml(QString *err_msg,const QDateTime &now,bool *ok,
QList<unsigned> *active_cast_ids=NULL);
bool rssFrontXml(QByteArray *xml,QString *err_msg) const; // WebAPI Call
bool rssBackXml(QByteArray *xml,QString *err_msg) const;// Public HTTP(S) Call
void activeCasts(QList<unsigned> *cast_ids);
bool frontActiveCasts(QList<unsigned> *cast_ids,QString *err_msg);
bool backActiveCasts(QList<unsigned> *cast_ids,QString *err_msg);
static unsigned create(const QString &keyname,bool enable_users,
QString *err_msg);
static QString imageFilename(int feed_id,int img_id,const QString &ext);
static QString publicUrl(const QString &base_url,const QString &keyname);
static QString itunesCategoryXml(const QString &category,
const QString &sub_category,int padding=0);
/*
static bool generateReport(const QString &feed_url,
const QString &stylesheet_pathname,
const QString &report_filename,
RDTempDirectory *tempdir,QString *err_msg);
*/
/*
static bool generateReport(const QByteArray &src_xml,
const QString &stylesheet_pathname,
const QString &report_filename,
RDTempDirectory *tempdir,QString *err_msg);
*/
/*
static bool generateReport(QString *output,
const QString &src_xml,
const QString &stylesheet_pathname,
QString *err_msg);
*/
signals:
void postProgressChanged(int step);

View File

@@ -1,131 +0,0 @@
// rdfeedlistview.cpp
//
// RDTableView widget for RSS feeds
//
// (C) Copyright 2021 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 <QDragEnterEvent>
#include <QHeaderView>
#include <QMessageBox>
#include "rdfeed.h"
#include "rdfeedlistmodel.h"
#include "rdfeedlistview.h"
#include "rdtextfile.h"
RDFeedListView::RDFeedListView(QWidget *parent)
:RDTableView(parent)
{
d_mouse_row=-1;
//
// Mouse menu
//
d_mouse_menu=new QMenu(this);
d_front_report_action=d_mouse_menu->
addAction(tr("Generate Front Report"),this,SLOT(generateFrontReportData()));
d_front_report_action->setCheckable(false);
d_back_report_action=d_mouse_menu->
addAction(tr("Generate Back Report"),this,SLOT(generateBackReportData()));
d_back_report_action->setCheckable(false);
connect(d_mouse_menu,SIGNAL(aboutToShow()),
this,SLOT(aboutToShowMenuData()));
//
// XSLT Engine (for feed reports)
//
d_xslt_engine=
new RDXsltEngine("/usr/share/rivendell/rdcastmanager-report.xsl",this);
}
RDFeedListView::~RDFeedListView()
{
delete d_xslt_engine;
}
void RDFeedListView::aboutToShowMenuData()
{
RDFeedListModel *mod=(RDFeedListModel *)model();
if((d_mouse_row<0)||(d_mouse_row>=mod->rowCount())) {
d_front_report_action->setEnabled(false);
d_back_report_action->setEnabled(false);
}
else {
d_front_report_action->setEnabled(true);
d_back_report_action->setEnabled(true);
}
}
void RDFeedListView::generateFrontReportData()
{
QString err_msg;
RDFeedListModel *m=(RDFeedListModel *)model();
QString keyname=m->data(m->index(d_mouse_row,0)).toString();
RDFeed *feed=new RDFeed(keyname,rda->config(),this);
QByteArray xml;
if(feed->downloadXml(&xml,&err_msg)) {
QString output_filename="report.html";
if(d_xslt_engine->transformXml(&output_filename,xml,&err_msg)) {
RDWebBrowser("file://"+output_filename);
}
else {
QMessageBox::warning(this,"RDAdmin - "+tr("Error"),err_msg);
}
}
else {
QMessageBox::warning(this,"RDAdmin - "+tr("Error"),err_msg);
}
}
void RDFeedListView::generateBackReportData()
{
QString err_msg;
RDFeedListModel *m=(RDFeedListModel *)model();
QString url=m->data(m->index(d_mouse_row,6)).toString();
QString output_filename="report.html";
if(d_xslt_engine->transformUrl(&output_filename,url,&err_msg)) {
RDWebBrowser("file://"+output_filename);
}
else {
QMessageBox::warning(this,"RDAdmin - "+tr("Error"),err_msg);
return;
}
}
void RDFeedListView::mousePressEvent(QMouseEvent *e)
{
if(e->button()==Qt::RightButton) {
d_mouse_row=indexAt(e->pos()).row();
if((d_mouse_row>=0)&&(d_mouse_row<model()->rowCount())) {
d_mouse_menu->popup(e->globalPos());
}
else {
d_mouse_row=-1;
}
}
QTableView::mousePressEvent(e);
}

View File

@@ -1,56 +0,0 @@
// rdfeedlistview.h
//
// RDTableView widget for RSS feeds
//
// (C) Copyright 2023 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 RDFEEDLISTVIEW_H
#define RDFEEDLISTVIEW_H
#include <QAction>
#include <QList>
#include <QMenu>
#include <rdtableview.h>
#include <rdtempdirectory.h>
#include "rdxsltengine.h"
class RDFeedListView : public RDTableView
{
Q_OBJECT
public:
RDFeedListView(QWidget *parent=0);
~RDFeedListView();
private slots:
void aboutToShowMenuData();
void generateFrontReportData();
void generateBackReportData();
protected:
void mousePressEvent(QMouseEvent *e);
private:
int d_mouse_row;
QMenu *d_mouse_menu;
QAction *d_front_report_action;
QAction *d_back_report_action;
RDXsltEngine *d_xslt_engine;
};
#endif // RDFEEDLISTVIEW_H

View File

@@ -2,7 +2,7 @@
//
// RSS schema definitions for Rivendell
//
// (C) Copyright 2020-2022 Fred Gleason <fredg@paravelsystems.com>
// (C) Copyright 2020-2023 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
@@ -57,13 +57,13 @@ RDRssSchemas::RDRssSchemas()
c_header_templates.push_back("");
// Rss202Schema
c_header_templates.push_back("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\">");
c_header_templates.push_back("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\" xmlns:rivendell=\"http://www.rivendellaudio.org/dtds/rivendell-0.1.dtd\">");
// AppleSchema
c_header_templates.push_back("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rss version=\"2.0\" xmlns:itunes=\"http://www.itunes.com/dtds/podcast-1.0.dtd\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\" xmlns:atom=\"http://www.w3.org/2005/Atom\" xmlns:superfeed=\"http://www.rivendellaudio.org/dtds/superfeed-0.1.dtd\" xmlns:direct=\"http://www.rivendellaudio.org/dtds/direct-0.1.dtd\">");
c_header_templates.push_back("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rss version=\"2.0\" xmlns:itunes=\"http://www.itunes.com/dtds/podcast-1.0.dtd\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\" xmlns:atom=\"http://www.w3.org/2005/Atom\" xmlns:superfeed=\"http://www.rivendellaudio.org/dtds/superfeed-0.1.dtd\" xmlns:direct=\"http://www.rivendellaudio.org/dtds/direct-0.1.dtd\" xmlns:rivendell=\"http://www.rivendellaudio.org/dtds/rivendell-0.1.dtd\">");
// AppleSuperfeedSchema
c_header_templates.push_back("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rss version=\"2.0\" xmlns:itunes=\"http://www.itunes.com/dtds/podcast-1.0.dtd\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\" xmlns:atom=\"http://www.w3.org/2005/Atom\" xmlns:superfeed=\"http://www.rivendellaudio.org/dtds/superfeed-0.1.dtd\" xmlns:direct=\"http://www.rivendellaudio.org/dtds/direct-0.1.dtd\">");
c_header_templates.push_back("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rss version=\"2.0\" xmlns:itunes=\"http://www.itunes.com/dtds/podcast-1.0.dtd\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\" xmlns:atom=\"http://www.w3.org/2005/Atom\" xmlns:superfeed=\"http://www.rivendellaudio.org/dtds/superfeed-0.1.dtd\" xmlns:direct=\"http://www.rivendellaudio.org/dtds/direct-0.1.dtd\" xmlns:rivendell=\"http://www.rivendellaudio.org/dtds/rivendell-0.1.dtd\">");
//
// Channel Templates