Rivendellaudio/rdrepld/citadelxds.cpp
Fred Gleason e305344e6b 2018-02-04 Fred Gleason <fredg@paravelsystems.com>
* Refactored the RDAudioConvert class to use RDApplication.
	* Converted rdcatchd(8) to use RDApplication.
2018-02-04 06:18:52 -05:00

465 lines
13 KiB
C++

// citadelxds.cpp
//
// Replicator implementation for the Citadel XDS Portal
//
// (C) Copyright 2010,2016-2018 Fred Gleason <fredg@paravelsystems.com>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <qfileinfo.h>
#include <qdatetime.h>
#include <rdapplication.h>
#include <rdaudioconvert.h>
#include <rdcart.h>
#include <rdcut.h>
#include <rdconf.h>
#include <rddb.h>
#include <rddelete.h>
#include <rdescape_string.h>
#include <rdstringlist.h>
#include <rdtempdirectory.h>
#include <rdupload.h>
#include "citadelxds.h"
#define RD_MAX_CART_NUMBER 999999
CitadelXds::CitadelXds(ReplConfig *repl_config)
: ReplFactory(repl_config)
{
QString sql;
RDSqlQuery *q;
sql="select LAST_ISCI_XREFERENCE from VERSION";
q=new RDSqlQuery(sql);
if(q->first()) {
xds_isci_datetime=q->value(0).toDateTime();
}
delete q;
}
void CitadelXds::startProcess()
{
CheckIsciXreference();
CheckCarts();
}
bool CitadelXds::processCart(const unsigned cartnum)
{
QString sql;
RDSqlQuery *q;
bool ret=false;
sql=QString().sprintf("select FILENAME from ISCI_XREFERENCE \
where (CART_NUMBER=%u)&&(LATEST_DATE>=now())&&\
((TYPE=\"R\")||(TYPE=\"B\"))",cartnum);
q=new RDSqlQuery(sql);
if(q->first()) {
ret=PostCut(RDCut::cutName(cartnum,1),q->value(0).toString());
}
delete q;
return ret;
}
void CitadelXds::CheckIsciXreference()
{
QString sql;
RDSqlQuery *q;
QFileInfo *fi=new QFileInfo(rda->system()->isciXreferencePath());
if(fi->exists()) {
if(fi->lastModified()>xds_isci_datetime) {
if(LoadIsciXreference(rda->system()->isciXreferencePath())) {
sql="update VERSION set LAST_ISCI_XREFERENCE=now()";
q=new RDSqlQuery(sql);
delete q;
xds_isci_datetime=QDateTime(QDate::currentDate(),QTime::currentTime());
PurgeCuts();
}
}
}
else {
rda->config()->log("rdrepld",RDConfig::LogErr,
QString().sprintf("unable to load ISCI cross reference file \"%s\"",
(const char *)rda->system()->isciXreferencePath()));
}
delete fi;
}
bool CitadelXds::LoadIsciXreference(const QString &filename)
{
FILE *f=NULL;
char line[1024];
QString sql;
RDSqlQuery *q;
RDStringList fields;
unsigned cartnum;
QStringList datelist;
QDate date;
bool ok=false;
unsigned linenum=3;
if((f=fopen(filename,"r"))==NULL) {
rda->config()->log("rdrepld",RDConfig::LogErr,
QString().sprintf("unable to load ISCI cross reference file \"%s\" [%s]",
(const char *)rda->system()->isciXreferencePath(),
strerror(errno)));
return false;
}
//
// Purge Old Data
//
sql="delete from ISCI_XREFERENCE";
q=new RDSqlQuery(sql);
delete q;
//
// Skip Header
//
fgets(line,1024,f);
fgets(line,1024,f);
//
// Load Records
//
while(fgets(line,1024,f)!=NULL) {
fields=fields.split(",",line,"\"");
if(fields.size()==9) {
for(unsigned i=0;i<fields.size();i++) {
fields[i]=fields[i].replace("\"","").stripWhiteSpace();
}
cartnum=fields[3].right(fields[3].length()-1).toUInt(&ok);
if(ok&&(cartnum<=RD_MAX_CART_NUMBER)) {
datelist=datelist.split("/",fields[6]);
if(datelist.size()==3) {
date.setYMD(datelist[2].toInt()+2000,datelist[0].toInt(),
datelist[1].toInt());
if(ValidateFilename(fields[8])) {
if(date.isValid()) {
sql=QString("insert into ISCI_XREFERENCE set ")+
"CART_NUMBER="+QString().sprintf("%u",cartnum)+","+
"ISCI=\""+RDEscapeString(fields[4])+"\","+
"FILENAME=\""+RDEscapeString(fields[8])+"\","+
"LATEST_DATE=\""+date.toString("yyyy/MM/dd")+"\","+
"TYPE=\""+RDEscapeString(fields[0])+"\","+
"ADVERTISER_NAME=\""+RDEscapeString(fields[1])+"\","+
"PRODUCT_NAME=\""+RDEscapeString(fields[2])+"\","+
"CREATIVE_TITLE=\""+RDEscapeString(fields[5])+"\","+
"REGION_NAME=\""+RDEscapeString(fields[7])+"\"";
q=new RDSqlQuery(sql);
delete q;
}
else {
rda->config()->log("rdrepld",RDConfig::LogWarning,QString().
sprintf("invalid date in line %d of \"%s\"",
linenum,(const char *)filename));
}
}
else {
rda->config()->log("rdrepld",RDConfig::LogWarning,QString().
sprintf("invalid FILENAME field \"%s\" in line %d of \"%s\"",
(const char *)fields[8],linenum,(const char *)filename));
}
}
else {
rda->config()->log("rdrepld",RDConfig::LogWarning,QString().
sprintf("invalid date in line %d of \"%s\"",
linenum,(const char *)filename));
}
}
else {
rda->config()->log("rdrepld",RDConfig::LogDebug,QString().
sprintf("missing/invalid cart number in line %d of \"%s\"",
linenum,(const char *)filename));
}
}
else {
rda->config()->log("rdrepld",RDConfig::LogWarning,QString().
sprintf("line %d malformed in \"%s\"",
linenum,(const char *)filename));
}
linenum++;
}
//
// Clean Up
//
rda->config()->log("rdrepld",RDConfig::LogInfo,
QString().sprintf("loaded ISCI cross reference file \"%s\"",
(const char *)rda->system()->isciXreferencePath()));
fclose(f);
return true;
}
bool CitadelXds::ValidateFilename(const QString &filename)
{
bool ret=true;
//
// List of illegal characters taken from 'Illegal Characters4.doc'
// from Citadel
//
ret=ret&&(filename.find(" ")<0);
ret=ret&&(filename.find("\"")<0);
ret=ret&&(filename.find("%")<0);
ret=ret&&(filename.find("*")<0);
ret=ret&&(filename.find("+")<0);
ret=ret&&(filename.find("/")<0);
ret=ret&&(filename.find(":")<0);
ret=ret&&(filename.find(";")<0);
ret=ret&&(filename.find("<")<0);
ret=ret&&(filename.find("=")<0);
ret=ret&&(filename.find(">")<0);
ret=ret&&(filename.find("?")<0);
ret=ret&&(filename.find("@")<0);
ret=ret&&(filename.find("[")<0);
ret=ret&&(filename.find("\\")<0);
ret=ret&&(filename.find("]")<0);
ret=ret&&(filename.find("^")<0);
ret=ret&&(filename.find("{")<0);
ret=ret&&(filename.find("|")<0);
ret=ret&&(filename.find("}")<0);
return ret;
}
void CitadelXds::CheckCarts()
{
QString sql;
RDSqlQuery *q;
RDSqlQuery *q1;
RDSqlQuery *q2;
QString now=QDateTime(QDate::currentDate(),QTime::currentTime()).addDays(-6).
toString("yyyy-MM-dd hh:mm:ss");
//
// Generate Update List
//
sql="select CART_NUMBER,FILENAME from ISCI_XREFERENCE \
where (LATEST_DATE>=now())&&((TYPE=\"R\")||(TYPE=\"B\"))";
q=new RDSqlQuery(sql);
while(q->next()) {
sql=QString().sprintf("select REPL_CART_STATE.ID from \
REPL_CART_STATE left join CUTS \
on REPL_CART_STATE.CART_NUMBER=CUTS.CART_NUMBER \
where (CUTS.ORIGIN_DATETIME<REPL_CART_STATE.ITEM_DATETIME)&&\
(REPL_CART_STATE.REPLICATOR_NAME=\"%s\")&&\
(REPL_CART_STATE.CART_NUMBER=%u)&&\
(REPL_CART_STATE.POSTED_FILENAME=\"%s\")&&\
(REPL_CART_STATE.ITEM_DATETIME>\"%s\")&&\
(REPL_CART_STATE.REPOST=\"N\")",
(const char *)RDEscapeString(config()->name()),
q->value(0).toUInt(),
(const char *)RDEscapeString(q->value(1).toString()),
(const char *)now);
q1=new RDSqlQuery(sql);
if(!q1->first()) {
if(PostCut(RDCut::cutName(q->value(0).toUInt(),1),
q->value(1).toString())) {
sql=QString().sprintf("select ID from REPL_CART_STATE where \
(REPLICATOR_NAME=\"%s\")&&\
(CART_NUMBER=%u)&&\
(POSTED_FILENAME=\"%s\")",
(const char *)RDEscapeString(config()->name()),
q->value(0).toUInt(),
(const char *)RDEscapeString(q->value(1).
toString()));
q2=new RDSqlQuery(sql);
if(q2->first()) {
sql=QString().sprintf("update REPL_CART_STATE set\
ITEM_DATETIME=now(),\
REPOST=\"N\" where \
(REPLICATOR_NAME=\"%s\")&&\
(CART_NUMBER=%u)&&\
(POSTED_FILENAME=\"%s\")",
(const char *)RDEscapeString(config()->name()),
q->value(0).toUInt(),
(const char *)RDEscapeString(q->value(1).
toString()));
}
else {
sql=QString().sprintf("insert into REPL_CART_STATE set \
ITEM_DATETIME=now(),\
REPOST=\"N\",\
REPLICATOR_NAME=\"%s\",\
CART_NUMBER=%u,\
POSTED_FILENAME=\"%s\"",
(const char *)RDEscapeString(config()->name()),
q->value(0).toUInt(),
(const char *)RDEscapeString(q->value(1).
toString()));
}
delete q2;
q2=new RDSqlQuery(sql);
delete q2;
}
}
delete q1;
}
delete q;
}
bool CitadelXds::PostCut(const QString &cutname,const QString &filename)
{
//
// Export File
//
RDAudioConvert::ErrorCode conv_err;
RDUpload::ErrorCode upload_err;
float speed_ratio=1.0;
RDCut *cut=new RDCut(cutname);
if(!cut->exists()) {
delete cut;
return false;
}
if(cut->length()==0) {
delete cut;
return true;
}
RDCart *cart=new RDCart(cut->cartNumber());
if(cart->enforceLength()) {
speed_ratio=(float)cut->length()/(float)cart->forcedLength();
}
RDSettings *settings=new RDSettings();
QString tempfile=RDTempDirectory::basePath()+"/"+filename;
RDAudioConvert *conv=new RDAudioConvert();
conv->setSourceFile(RDCut::pathName(cutname));
conv->setDestinationFile(tempfile);
conv->setRange(cut->startPoint(),cut->endPoint());
conv->setSpeedRatio(speed_ratio);
settings->setFormat(config()->format());
settings->setChannels(config()->channels());
settings->setSampleRate(config()->sampleRate());
settings->setBitRate(config()->bitRate());
settings->setQuality(config()->quality());
settings->setNormalizationLevel(config()->normalizeLevel()/1000);
conv->setDestinationSettings(settings);
delete cart;
delete cut;
switch(conv_err=conv->convert()) {
case RDAudioConvert::ErrorOk:
break;
default:
rda->config()->log("rdrepld",RDConfig::LogErr,
QString().sprintf("CitadelXds: audio conversion failed: %s, cutname: %s",
(const char *)RDAudioConvert::errorText(conv_err),
(const char *)cutname));
delete conv;
delete settings;
return false;
}
delete conv;
delete settings;
//
// Upload File
//
RDUpload *upload=new RDUpload(rda->config());
upload->setSourceFile(tempfile);
upload->setDestinationUrl(config()->url()+"/"+filename);
switch(upload_err=upload->runUpload(config()->urlUsername(),
config()->urlPassword(),
rda->config()->logXloadDebugData())) {
case RDUpload::ErrorOk:
break;
default:
rda->config()->log("rdrepld",RDConfig::LogErr,
QString().sprintf("CitadelXds: audio upload failed: %s",
(const char *)RDUpload::errorText(upload_err)));
unlink(tempfile);
delete upload;
return false;
}
unlink(tempfile);
delete upload;
rda->config()->log("rdrepld",RDConfig::LogInfo,
QString().sprintf("CitadelXds: uploaded cut %s to %s/%s",
(const char *)cutname,
(const char *)config()->url(),
(const char *)filename));
return true;
}
void CitadelXds::PurgeCuts()
{
QString sql;
RDSqlQuery *q;
RDSqlQuery *q1;
RDSqlQuery *q2;
RDDelete *conv;
RDDelete::ErrorCode conv_err;
sql=QString().sprintf("select ID,POSTED_FILENAME from REPL_CART_STATE \
where REPLICATOR_NAME=\"%s\"",
(const char *)RDEscapeString(config()->name()));
q=new RDSqlQuery(sql);
while(q->next()) {
sql=QString().
sprintf("select ID from ISCI_XREFERENCE where FILENAME=\"%s\"",
(const char *)RDEscapeString(q->value(1).toString()));
q1=new RDSqlQuery(sql);
if(!q1->first()) {
QString path=config()->url();
if(path.right(1)!="/") {
path+="/";
}
QUrl url(path+q->value(1).toString());
conv=new RDDelete(rda->config());
conv->setTargetUrl(url);
if((conv_err=conv->runDelete(config()->urlUsername(),
config()->urlPassword(),
rda->config()->logXloadDebugData()))==
RDDelete::ErrorOk) {
sql=QString().sprintf("delete from REPL_CART_STATE where ID=%d",
q->value(0).toInt());
q2=new RDSqlQuery(sql);
delete q2;
rda->config()->log("rdrepld",RDConfig::LogInfo,
QString().sprintf("purged \"%s\" for replicator \"%s\"",
(const char *)url.toString(),
(const char *)config()->name()));
}
else {
rda->config()->log("rdrepld",RDConfig::LogErr,
QString().sprintf("unable to delete \"%s\" for replicator \"%s\" [%s]",
(const char *)url.toString(),
(const char *)config()->name(),
(const char *)RDDelete::errorText(conv_err)));
}
delete conv;
}
delete q1;
}
delete q;
}