2017-03-29 Fred Gleason <fredg@paravelsystems.com>

* Added a 'CUTS.SHA1_HASH' field to the database.
	* Incremented the database version to 261.
	* Added a 'Sha1' column to the Cut List in 'rdlibrary/audio_cart.cpp'.
	* Added 'RDCut::sha1Hash()' and 'RDCut::setSha1Hash()' methods in
	'lib/rdcut.cpp' and 'lib/rdcut.h'.
	* Added a '--rehash=' directive to rddbcheck(8).
	* Added a 'Rehash' Web API call.
	* Added an 'RDRehash' class in 'lib/rdrehash.cpp' and 'lib/rdrehash.h'.
This commit is contained in:
Fred Gleason 2017-03-29 11:09:15 -04:00
parent bc2c441680
commit 5dc6fa92a9
50 changed files with 1124 additions and 24 deletions

1
.gitignore vendored
View File

@ -79,6 +79,7 @@ tests/reserve_carts_test
tests/sas_switch_torture
tests/sas_torture
tests/stringcode_test
tests/test_hash
tests/test_pam
tests/timer_test
tests/upload_test

View File

@ -15664,3 +15664,12 @@
* Added a 'WebAPI Timeout' control to the Edit User dialog in
'rdadmin/edit_user.cpp' and 'rdadmin/edit_user.h'.
* Implemented a 'CreateTicket' Web API call.
2017-03-29 Fred Gleason <fredg@paravelsystems.com>
* Added a 'CUTS.SHA1_HASH' field to the database.
* Incremented the database version to 261.
* Added a 'Sha1' column to the Cut List in 'rdlibrary/audio_cart.cpp'.
* Added 'RDCut::sha1Hash()' and 'RDCut::setSha1Hash()' methods in
'lib/rdcut.cpp' and 'lib/rdcut.h'.
* Added a '--rehash=' directive to rddbcheck(8).
* Added a 'Rehash' Web API call.
* Added an 'RDRehash' class in 'lib/rdrehash.cpp' and 'lib/rdrehash.h'.

View File

@ -2585,6 +2585,73 @@
</table>
</sect1>
<sect1>
<title>Rehash</title>
<subtitle>Generate a SHA-1 hash for a cut and write it to the database</subtitle>
<para>
Command Code: <code>RDXPORT_COMMAND_REHASH</code>
</para>
<para>
Required User Permissions: None
</para>
<table xml:id="ex.rehash" frame="all">
<title>Rehash Call Fields</title>
<tgroup cols="3" align="left" colsep="1" rowsep="1">
<colspec colname="FIELD NAME" />
<colspec colname="MEANING" />
<colspec colname="REMARKS" />
<thead>
<row>
<entry>
FIELD NAME
</entry>
<entry>
MEANING
</entry>
<entry>
REMARKS
</entry>
</row>
</thead>
<tbody>
<row>
<entry>
COMMAND
</entry>
<entry>
32
</entry>
<entry>
Mandatory
</entry>
</row>
<row>
<entry>
CART_NUMBER
</entry>
<entry>
Number of cart
</entry>
<entry>
Mandatory
</entry>
</row>
<row>
<entry>
CUT_NUMBER
</entry>
<entry>
Number of cut
</entry>
<entry>
Mandatory
</entry>
</row>
</tbody>
</tgroup>
</table>
</sect1>
<sect1>
<title>RemoveCart</title>
<subtitle>Remove a cart from the Library</subtitle>

View File

@ -14,6 +14,7 @@ DESCRIPTION char(64) Indexed
OUTCUE char(64) Indexed
ISRC char(12) International Standard Recording Code
ISCI char(32) ISCI Code
SHA1_HASH char(40)
LENGTH int(10) unsigned Overall length in ms.
ORIGIN_DATETIME datetime Date/Time when recorded
START_DATETIME datetime

View File

@ -142,6 +142,7 @@ dist_librd_la_SOURCES = dbversion.h\
rdgrid.cpp rdgrid.h\
rdgroup.cpp rdgroup.h\
rdgroup_list.cpp rdgroup_list.h\
rdhash.cpp rdhas.h\
rdhotkeys.cpp rdhotkeys.h\
rdhotkeylist.cpp rdhotkeylist.h\
rdidvalidator.cpp rdidvalidator.h\
@ -192,6 +193,7 @@ dist_librd_la_SOURCES = dbversion.h\
rdprofilesection.cpp rdprofilesection.h\
rdpushbutton.cpp rdpushbutton.h\
rdrecording.cpp rdrecording.h\
rdrehash.cpp rdrehash.h\
rdreplicator.cpp rdreplicator.h\
rdreport.cpp rdreport.h\
rdringbuffer.cpp rdringbuffer.h\
@ -307,6 +309,7 @@ nodist_librd_la_SOURCES = moc_rdadd_cart.cpp\
moc_rdschedcodes_dialog.cpp\
moc_rdsegmeter.cpp\
moc_rdsimpleplayer.cpp\
moc_rdrehash.cpp\
moc_rdslider.cpp\
moc_rdsocket.cpp\
moc_rdsound_panel.cpp\

View File

@ -24,7 +24,7 @@
/*
* Current Database Version
*/
#define RD_VERSION_DATABASE 260
#define RD_VERSION_DATABASE 261
#endif // DBVERSION_H

View File

@ -149,6 +149,7 @@ x11 {
SOURCES += rdgpioselector.cpp
SOURCES += rdgrid.cpp
SOURCES += rdgroup.cpp
SOURCES += rdhash.cpp
SOURCES += rdimport_audio.cpp
SOURCES += rdlist_groups.cpp
SOURCES += rdlist_logs.cpp
@ -160,6 +161,7 @@ x11 {
SOURCES += rdpasswd.cpp
SOURCES += rdplay_deck.cpp
SOURCES += rdrecording.cpp
SOURCES += rdrehash.cpp
SOURCES += rdsimpleplayer.cpp
SOURCES += rdsound_panel.cpp
SOURCES += rdstatus.cpp
@ -282,6 +284,7 @@ x11 {
HEADERS += rdgrid.h
HEADERS += rdgpio.h
HEADERS += rdgroup.h
HEADERS += rdhash.h
HEADERS += rdimport_audio.h
HEADERS += rdlist_groups.h
HEADERS += rdlist_logs.h
@ -294,6 +297,7 @@ x11 {
HEADERS += rdpasswd.h
HEADERS += rdplay_deck.h
HEADERS += rdrecording.h
HEADERS += rdrehash.h
HEADERS += rdsimpleplayer.h
HEADERS += rdsound_panel.h
HEADERS += rdstatus.h

View File

@ -2113,6 +2113,33 @@ Zkuste to, prosím, znovu!</translation>
<translation type="obsolete">Unbekanntes Audioformat</translation>
</message>
</context>
<context>
<name>RDRehash</name>
<message>
<source>OK</source>
<translation type="unfinished">OK</translation>
</message>
<message>
<source>Internal Error</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Invalid URL</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>RDXport service returned an error</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Invalid user or password</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Audio does not exist</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>RDReport</name>
<message>

View File

@ -2104,6 +2104,33 @@ bitte erneut versuchen!</translation>
<translation type="obsolete">Unbekanntes Audioformat</translation>
</message>
</context>
<context>
<name>RDRehash</name>
<message>
<source>OK</source>
<translation type="unfinished">OK</translation>
</message>
<message>
<source>Internal Error</source>
<translation type="unfinished">Interner Fehler</translation>
</message>
<message>
<source>Invalid URL</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>RDXport service returned an error</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Invalid user or password</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Audio does not exist</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>RDReport</name>
<message>

View File

@ -2106,6 +2106,33 @@ please try again!</source>
<translation type="obsolete">Formado desconocido</translation>
</message>
</context>
<context>
<name>RDRehash</name>
<message>
<source>OK</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Internal Error</source>
<translation type="unfinished">Error interno</translation>
</message>
<message>
<source>Invalid URL</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>RDXport service returned an error</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Invalid user or password</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Audio does not exist</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>RDReport</name>
<message>

View File

@ -1765,6 +1765,33 @@ please try again!</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>RDRehash</name>
<message>
<source>OK</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Internal Error</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Invalid URL</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>RDXport service returned an error</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Invalid user or password</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Audio does not exist</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>RDSoundPanel</name>
<message>

View File

@ -2054,6 +2054,33 @@ prøv ein gong til!</translation>
<translation type="obsolete">Last opp</translation>
</message>
</context>
<context>
<name>RDRehash</name>
<message>
<source>OK</source>
<translation type="unfinished">OK</translation>
</message>
<message>
<source>Internal Error</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Invalid URL</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>RDXport service returned an error</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Invalid user or password</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Audio does not exist</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>RDReport</name>
<message>

View File

@ -2054,6 +2054,33 @@ prøv ein gong til!</translation>
<translation type="obsolete">Last opp</translation>
</message>
</context>
<context>
<name>RDRehash</name>
<message>
<source>OK</source>
<translation type="unfinished">OK</translation>
</message>
<message>
<source>Internal Error</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Invalid URL</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>RDXport service returned an error</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Invalid user or password</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Audio does not exist</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>RDReport</name>
<message>

View File

@ -2053,6 +2053,33 @@ por favor, tente novamente!</translation>
<translation type="obsolete">Upload</translation>
</message>
</context>
<context>
<name>RDRehash</name>
<message>
<source>OK</source>
<translation type="unfinished">OK</translation>
</message>
<message>
<source>Internal Error</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Invalid URL</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>RDXport service returned an error</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Invalid user or password</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Audio does not exist</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>RDReport</name>
<message>

View File

@ -256,6 +256,19 @@ void RDCut::setIsci(const QString &isci) const
}
QString RDCut::sha1Hash() const
{
return RDGetSqlValue("CUTS","CUT_NAME",cut_name,"SHA1_HASH",cut_db).
toString();
}
void RDCut::setSha1Hash(const QString &str)
{
SetRow("SHA1_HASH",str);
}
unsigned RDCut::length() const
{
return RDGetSqlValue("CUTS","CUT_NAME",cut_name,"LENGTH",cut_db).
@ -1169,6 +1182,7 @@ bool RDCut::checkInRecording(const QString &stationname,RDSettings *settings,
QString sql;
RDSqlQuery *q;
int format;
QString hash;
switch(settings->format()) {
case RDSettings::MpegL2:

View File

@ -59,6 +59,8 @@ class RDCut
void setIsrc(const QString &isrc) const;
QString isci() const;
void setIsci(const QString &isci) const;
QString sha1Hash() const;
void setSha1Hash(const QString &str);
unsigned length() const;
void setLength(int length) const;
QDateTime originDatetime(bool *valid) const;

59
lib/rdhash.cpp Normal file
View File

@ -0,0 +1,59 @@
// rdhash.cpp
//
// Functions for generating hashes.
//
// (C) Copyright 2017 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 <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <openssl/sha.h>
#include "rdhash.h"
QString RDSha1Hash(const QString &filename,bool throttle)
{
QString ret;
SHA_CTX ctx;
int fd=-1;
int n;
char data[1024];
unsigned char md[SHA_DIGEST_LENGTH];
if((fd=open(filename,O_RDONLY))<0) {
return ret;
}
SHA1_Init(&ctx);
while((n=read(fd,data,1024))>0) {
SHA1_Update(&ctx,data,n);
if(throttle) {
usleep(1);
}
}
close(fd);
SHA1_Final(md,&ctx);
ret="";
for(int i=0;i<SHA_DIGEST_LENGTH;i++) {
ret+=QString().sprintf("%02x",0xff&md[i]);
}
return ret;
}

29
lib/rdhash.h Normal file
View File

@ -0,0 +1,29 @@
// rdhash.h
//
// Functions for generating hashes.
//
// (C) Copyright 2017 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 RDHASH_H
#define RDHASH_H
#include <qstring.h>
QString RDSha1Hash(const QString &filename,bool throttle=false);
#endif // RD_H

190
lib/rdrehash.cpp Normal file
View File

@ -0,0 +1,190 @@
// rdrehash.cpp
//
// Generate a SHA-1 hash of an audio file and write it to the database.
//
// (C) Copyright 2017 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 <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <math.h>
#include <curl/curl.h>
#include <qstringlist.h>
#include <rd.h>
#include <rdxport_interface.h>
#include <rdformpost.h>
#include <rdrehash.h>
size_t __RDRehashCallback(void *ptr,size_t size,size_t nmemb,void *userdata)
{
return size*nmemb;
}
RDRehash::RDRehash(RDStation *station,RDConfig *config,QObject *parent)
: QObject(parent)
{
conv_station=station;
conv_config=config;
conv_cart_number=0;
conv_cut_number=0;
}
void RDRehash::setCartNumber(unsigned cartnum)
{
conv_cart_number=cartnum;
}
void RDRehash::setCutNumber(unsigned cutnum)
{
conv_cut_number=cutnum;
}
RDRehash::ErrorCode RDRehash::runRehash(const QString &username,
const QString &password)
{
long response_code;
CURL *curl=NULL;
char url[1024];
CURLcode curl_err;
//
// Generate POST Data
//
QString post=QString().sprintf("COMMAND=%d&LOGIN_NAME=%s&PASSWORD=%s&CART_NUMBER=%u&CUT_NUMBER=%u",
RDXPORT_COMMAND_REHASH,
(const char *)RDFormPost::urlEncode(username),
(const char *)RDFormPost::urlEncode(password),
conv_cart_number,
conv_cut_number);
if((curl=curl_easy_init())==NULL) {
return RDRehash::ErrorInternal;
}
//
// Write out URL as a C string before passing to curl_easy_setopt(),
// otherwise some versions of LibCurl will throw a 'bad/illegal format'
// error.
//
strncpy(url,conv_station->webServiceUrl(conv_config),1024);
curl_easy_setopt(curl,CURLOPT_URL,url);
curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,__RDRehashCallback);
curl_easy_setopt(curl,CURLOPT_POST,1);
curl_easy_setopt(curl,CURLOPT_POSTFIELDS,(const char *)post);
curl_easy_setopt(curl,CURLOPT_TIMEOUT,RD_CURL_TIMEOUT);
switch(curl_err=curl_easy_perform(curl)) {
case CURLE_OK:
break;
case CURLE_UNSUPPORTED_PROTOCOL:
case CURLE_FAILED_INIT:
case CURLE_COULDNT_RESOLVE_PROXY:
case CURLE_PARTIAL_FILE:
case CURLE_HTTP_RETURNED_ERROR:
case CURLE_WRITE_ERROR:
case CURLE_OUT_OF_MEMORY:
case CURLE_OPERATION_TIMEDOUT:
case CURLE_HTTP_POST_ERROR:
curl_easy_cleanup(curl);
fprintf(stderr,"curl error: %d\n",curl_err);
return RDRehash::ErrorInternal;
case CURLE_URL_MALFORMAT:
case CURLE_COULDNT_RESOLVE_HOST:
case CURLE_COULDNT_CONNECT:
case 9: // CURLE_REMOTE_ACCESS_DENIED
curl_easy_cleanup(curl);
return RDRehash::ErrorUrlInvalid;
default:
curl_easy_cleanup(curl);
return RDRehash::ErrorService;
}
curl_easy_getinfo(curl,CURLINFO_RESPONSE_CODE,&response_code);
curl_easy_cleanup(curl);
switch(response_code) {
case 200:
break;
case 404:
return RDRehash::ErrorNoAudio;
default:
return RDRehash::ErrorService;
}
return RDRehash::ErrorOk;
}
QString RDRehash::errorText(RDRehash::ErrorCode err)
{
QString ret=QString().sprintf("Unknown Error [%u]",err);
switch(err) {
case RDRehash::ErrorOk:
ret=tr("OK");
break;
case RDRehash::ErrorInternal:
ret=tr("Internal Error");
break;
case RDRehash::ErrorUrlInvalid:
ret=tr("Invalid URL");
break;
case RDRehash::ErrorService:
ret=tr("RDXport service returned an error");
break;
case RDRehash::ErrorInvalidUser:
ret=tr("Invalid user or password");
break;
case RDRehash::ErrorNoAudio:
ret=tr("Audio does not exist");
break;
}
return ret;
}
RDRehash::ErrorCode RDRehash::rehash(RDStation *station,RDUser *user,
RDConfig *config,
unsigned cartnum,int cutnum)
{
RDRehash::ErrorCode err;
RDRehash *rehash=new RDRehash(station,config);
rehash->setCartNumber(cartnum);
rehash->setCutNumber(cutnum);
err=rehash->runRehash(user->name(),user->password());
delete rehash;
return err;
}

54
lib/rdrehash.h Normal file
View File

@ -0,0 +1,54 @@
// rdrehash.h
//
// Generate a SHA-1 hash of an audio file and write it to the database.
//
// (C) Copyright 2017 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 RDREHASH_H
#define RDREHASH_H
#include <qobject.h>
#include <rdconfig.h>
#include <rdstation.h>
#include <rduser.h>
class RDRehash : public QObject
{
Q_OBJECT;
public:
enum ErrorCode {ErrorOk=0,ErrorInternal=5,ErrorUrlInvalid=7,
ErrorService=8,ErrorInvalidUser=9,ErrorNoAudio=10};
RDRehash(RDStation *station,RDConfig *config,QObject *parent=0);
void setCartNumber(unsigned cartnum);
void setCutNumber(unsigned cutnum);
RDRehash::ErrorCode runRehash(const QString &username,
const QString &password);
static QString errorText(RDRehash::ErrorCode err);
static RDRehash::ErrorCode rehash(RDStation *station,RDUser *user,
RDConfig *config,
unsigned cartnum,int cutnum);
private:
RDStation *conv_station;
RDConfig *conv_config;
unsigned conv_cart_number;
unsigned conv_cut_number;
};
#endif // RDREHASH_H

View File

@ -52,6 +52,7 @@
#define RDXPORT_COMMAND_ADDLOG 29
#define RDXPORT_COMMAND_DELETELOG 30
#define RDXPORT_COMMAND_CREATETICKET 31
#define RDXPORT_COMMAND_REHASH 32
#endif // RDXPORT_INTERFACE_H

View File

@ -763,6 +763,7 @@ bool CreateDb(QString name,QString pwd)
ISRC char(12),\
ISCI char(32),\
LENGTH INT UNSIGNED,\
SHA1_HASH char(40),\
ORIGIN_DATETIME DATETIME,\
START_DATETIME DATETIME,\
END_DATETIME DATETIME,\
@ -8346,6 +8347,20 @@ int UpdateDb(int ver)
}
}
if(ver<261) {
sql=QString("alter table CUTS add column ")+
"SHA1_HASH char(40) after LENGTH";
if(!RunQuery(sql)) {
return false;
}
sql="create index SHA1_HASH_IDX on CUTS(SHA1_HASH)";
if(!RunQuery(sql)) {
return false;
}
}
//
// Update Version Field

View File

@ -43,6 +43,7 @@
#include <rddb.h>
#include <rdconf.h>
#include <rdhash.h>
#include <rdurl.h>
#include <rdwavefile.h>
#include <rdcut.h>
@ -2394,6 +2395,7 @@ void MainObject::CheckInRecording(QString cutname,CatchEvent *evt,
s->setBitRate(evt->bitrate());
s->setChannels(evt->channels());
cut->checkInRecording(catch_config->stationName(),s,msecs);
cut->setSha1Hash(RDSha1Hash(RDCut::pathName(cut->cutName())));
delete s;
cut->autoTrim(RDCut::AudioBoth,-threshold);
RDCart *cart=new RDCart(cut->cartNumber());

View File

@ -174,6 +174,9 @@ AudioCart::AudioCart(AudioControls *controls,RDCart *cart,QString *path,
rdcart_cut_list->addColumn(tr("NAME"));
rdcart_cut_list->setColumnAlignment(11,Qt::AlignLeft);
rdcart_cut_list->addColumn(tr("SHA1"));
rdcart_cut_list->setColumnAlignment(12,Qt::AlignLeft);
RefreshList();
//
@ -810,6 +813,12 @@ void AudioCart::RefreshList()
l->setText(10,tr("None"));
}
l->setText(11,q->value(9).toString());
if(q->value(23).toString().isEmpty()) {
l->setText(12,"["+tr("not available")+"]");
}
else {
l->setText(12,q->value(23).toString());
}
total_length+=q->value(3).toUInt();
pass++;
}
@ -908,6 +917,12 @@ void AudioCart::RefreshLine(RDListViewItem *item)
item->setText(10,tr("None"));
}
item->setText(11,q->value(9).toString());
if(q->value(23).toString().isEmpty()) {
item->setText(12,"["+tr("not available")+"]");
}
else {
item->setText(12,q->value(23).toString());
}
total_length+=q->value(3).toUInt();
}
if(q->size()>0) {

View File

@ -224,6 +224,14 @@ Do you still want to delete?</source>
<source>ORD</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>SHA1</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>not available</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>CdRipper</name>

View File

@ -224,6 +224,14 @@ Do you still want to delete?</source>
<source>ORD</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>SHA1</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>not available</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>CdRipper</name>

View File

@ -224,6 +224,14 @@ Do you still want to delete?</source>
<source>ORD</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>SHA1</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>not available</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>CdRipper</name>

View File

@ -202,6 +202,14 @@ Do you still want to delete?</source>
<source>ORD</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>SHA1</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>not available</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>CdRipper</name>

View File

@ -219,6 +219,14 @@ Do you still want to delete?</source>
<source>ORD</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>SHA1</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>not available</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>CdRipper</name>

View File

@ -219,6 +219,14 @@ Do you still want to delete?</source>
<source>ORD</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>SHA1</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>not available</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>CdRipper</name>

View File

@ -220,6 +220,14 @@ Do you still want to delete?</source>
<source>ORD</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>SHA1</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>not available</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>CdRipper</name>

View File

@ -30,6 +30,7 @@
#include <rd.h>
#include <rdconf.h>
#include <rdmixer.h>
#include <rdrehash.h>
#include <record_cut.h>
#include <globals.h>
@ -865,6 +866,8 @@ void RecordCut::recordUnloadedData(int card,int stream,unsigned len)
s->setChannels(rec_channels);
s->setFormat((RDSettings::Format)rec_format);
rec_cut->checkInRecording(rdstation_conf->name(),s,len);
RDRehash::rehash(rdstation_conf,lib_user,lib_config,rec_cut->cartNumber(),
rec_cut->cutNumber());
if(rec_trim_box->currentItem()==0) {
rec_cut->autoTrim(RDCut::AudioBoth,rdlibrary_conf->trimThreshold());
}

View File

@ -31,24 +31,25 @@ QString ValidateCutFields()
"DESCRIPTION,"+ // 02
"LENGTH,"+ // 03
"LAST_PLAY_DATETIME,"+ // 04
"PLAY_COUNTER,"+ // 03
"ORIGIN_DATETIME,"+ // 04
"ORIGIN_NAME,"+ // 05
"OUTCUE,"+ // 06
"CUT_NAME,"+ // 07
"LENGTH,"+ // 08
"EVERGREEN,"+ // 09
"START_DATETIME,"+ // 10
"END_DATETIME,"+ // 11
"START_DAYPART,"+ // 12
"END_DAYPART,"+ // 13
"MON,"+ // 14
"TUE,"+ // 15
"WED,"+ // 16
"THU,"+ // 17
"FRI,"+ // 18
"SAT,"+ // 19
"SUN "+ // 20
"PLAY_COUNTER,"+ // 05
"ORIGIN_DATETIME,"+ // 06
"ORIGIN_NAME,"+ // 07
"OUTCUE,"+ // 08
"CUT_NAME,"+ // 09
"LENGTH,"+ // 10
"EVERGREEN,"+ // 11
"START_DATETIME,"+ // 12
"END_DATETIME,"+ // 13
"START_DAYPART,"+ // 14
"END_DAYPART,"+ // 15
"MON,"+ // 16
"TUE,"+ // 17
"WED,"+ // 18
"THU,"+ // 19
"FRI,"+ // 20
"SAT,"+ // 21
"SUN,"+ // 22
"SHA1_HASH "+ // 23
"from CUTS";
return sql;

View File

@ -37,6 +37,7 @@
#include <rdlibrary_conf.h>
#include <rdedit_audio.h>
#include <rdimport_audio.h>
#include <rdrehash.h>
#include <rdwavedata.h>
#include <globals.h>
@ -1873,6 +1874,9 @@ void VoiceTracker::recordUnloadedData(int card,int stream,unsigned msecs)
if(!track_aborting) {
edit_track_cuts[1]->
checkInRecording(rdstation_conf->name(),edit_settings,msecs);
RDRehash::rehash(rdstation_conf,rduser,log_config,
edit_track_cuts[1]->cartNumber(),
edit_track_cuts[1]->cutNumber());
edit_track_cuts[1]->setSampleRate(rdsystem->sampleRate());
edit_track_cart->updateLength();
edit_track_cart->resetRotation();

View File

@ -38,6 +38,7 @@ noinst_PROGRAMS = audio_convert_test\
sas_switch_torture\
sas_torture\
stringcode_test\
test_hash\
test_pam\
timer_test\
upload_test\
@ -75,6 +76,9 @@ sas_torture_LDADD = @LIB_RDLIBS@ @LIBVORBIS@
dist_stringcode_test_SOURCES = stringcode_test.cpp stringcode_test.h
stringcode_test_LDADD = @LIB_RDLIBS@ @LIBVORBIS@
dist_test_hash_SOURCES = test_hash.cpp test_hash.h
test_hash_LDADD = @LIB_RDLIBS@ @LIBVORBIS@
dist_test_pam_SOURCES = test_pam.cpp test_pam.h
test_pam_LDADD = @LIB_RDLIBS@ @LIBVORBIS@

69
tests/test_hash.cpp Normal file
View File

@ -0,0 +1,69 @@
// test_hash.cpp
//
// Test SHA1 hash generation
//
// (C) Copyright 2017 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 <qapplication.h>
#include <rdcmd_switch.h>
#include <rdconfig.h>
#include <rdcut.h>
#include <rddb.h>
#include <rdhash.h>
#include "test_hash.h"
MainObject::MainObject(QObject *parent)
:QObject(parent)
{
QString filename="";
//
// Read Command Options
//
RDCmdSwitch *cmd=
new RDCmdSwitch(qApp->argc(),qApp->argv(),"test_hash",TEST_HASH_USAGE);
for(unsigned i=0;i<cmd->keys();i++) {
if(cmd->key(i)=="--filename") {
filename=cmd->value(i);
cmd->setProcessed(i,true);
}
}
if(filename.isEmpty()) {
fprintf(stderr,"test_hash: missing --filename\n");
exit(256);
}
QString hash=RDSha1Hash(filename);
if(hash.isEmpty()) {
fprintf(stderr,"test_hash: unable to open \"%s\"\n",
(const char *)filename);
exit(256);
}
printf("%s\n",(const char *)hash);
exit(0);
}
int main(int argc,char *argv[])
{
QApplication a(argc,argv,false);
new MainObject();
return a.exec();
}

36
tests/test_hash.h Normal file
View File

@ -0,0 +1,36 @@
// test_hash.h
//
// Test SHA1 hash generation
//
// (C) Copyright 2017 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 TEST_HASH_H
#define TEST_HASH_H
#include <rdcmd_switch.cpp>
#include <rdhash.h>
#define TEST_HASH_USAGE "[options]\n\nTest SHA1 has generation\n\n--filename=<file-name>\n The name of the file for which to generate a hash.\n\n"
class MainObject : public QObject
{
public:
MainObject(QObject *parent=0);
};
#endif // TEST_HASH_H

View File

@ -34,6 +34,7 @@
#include <rd.h>
#include <rddbcheck.h>
#include <rdcart.h>
#include <rdhash.h>
#include <rdlog.h>
#include <rdclock.h>
#include <rdcreate_log.h>
@ -81,6 +82,9 @@ MainObject::MainObject(QObject *parent)
if(cmd->key(i)=="--dump-cuts-dir") {
dump_cuts_dir=cmd->value(i);
}
if(cmd->key(i)=="--rehash") {
rehash=cmd->value(i);
}
}
if(check_yes&&check_no) {
fprintf(stderr,"rddbcheck: '--yes' and '--no' are mutually exclusive\n");
@ -171,6 +175,14 @@ MainObject::MainObject(QObject *parent)
delete q;
}
//
// Rehash
//
if(!rehash.isEmpty()) {
Rehash(rehash);
exit(0);
}
//
// Check for Orphaned Voice Tracks
//
@ -685,6 +697,95 @@ void MainObject::ValidateAudioLengths()
}
void MainObject::Rehash(const QString &arg)
{
QString sql;
QSqlQuery *q;
unsigned cartnum;
bool ok=false;
if(arg.lower()=="all") {
sql=QString("select NUMBER from CART where ")+
QString().sprintf("TYPE=%d ",RDCart::Audio)+
"order by NUMBER";
q=new QSqlQuery(sql);
while(q->next()) {
RehashCart(q->value(0).toUInt());
}
delete q;
return;
}
cartnum=arg.toUInt(&ok);
if(ok&&(cartnum>0)&&(cartnum<=RD_MAX_CART_NUMBER)) {
RehashCart(cartnum);
return;
}
RDCut *cut=new RDCut(arg);
if(cut->exists()) {
RehashCut(arg);
}
delete cut;
}
void MainObject::RehashCart(unsigned cartnum)
{
RDCart *cart=new RDCart(cartnum);
if(cart->exists()) {
if(cart->type()==RDCart::Audio) {
QString sql=QString("select CUT_NAME from CUTS where ")+
QString().sprintf("CART_NUMBER=%u ",cartnum)+
"order by CUT_NAME";
QSqlQuery *q=new QSqlQuery(sql);
while(q->next()) {
RehashCut(q->value(0).toString());
}
delete q;
}
}
else {
printf(" Cart %06u does not exist.\n",cartnum);
}
}
void MainObject::RehashCut(const QString &cutnum)
{
QString hash=RDSha1Hash(RDCut::pathName(cutnum),true);
if(hash.isEmpty()) {
printf(" Unable to generate hash for \"%s\"\n",
(const char *)RDCut::pathName(cutnum));
}
else {
RDCut *cut=new RDCut(cutnum);
if(cut->exists()) {
if(cut->sha1Hash().isEmpty()) {
cut->setSha1Hash(hash);
}
else {
if(cut->sha1Hash()!=hash) {
RDCart *cart=new RDCart(RDCut::cartNumber(cutnum));
printf(" Cut %d [%s] in cart %06u [%s] has inconsistent SHA1 hash. Correct? (y/N) ",
cut->cutNumber(),
(const char *)cut->description(),
cart->number(),
(const char *)cart->title());
fflush(NULL);
if(UserResponse()) {
cut->setSha1Hash(hash);
}
delete cart;
}
}
}
else {
printf(" Cut \"%s\" does not exist.\n",(const char *)cutnum);
}
delete cut;
}
}
void MainObject::SetCutLength(const QString &cutname,int len)
{
QString sql;

View File

@ -31,7 +31,7 @@
#include <rdcmd_switch.cpp>
#include <rdstation.h>
#define RDDBCHECK_USAGE "[options]\n\nCheck the Rivendell database and audio store for consistency\nand correctness.\n\n--yes\n Answer all questions with 'yes'\n\n--no\n Answer all questions with 'no'\n\n--user=<username>\n Connect using the Rivendell user <username> (default is \"user\").\n\n--orphan-group=<group-name>\n Move carts with missing/invalid GROUP information to the <group-name>\n group.\n\n--dump-cuts-dir=<dir-name>\n Move orphaned cut audio to the <dir-name> directory.\n"
#define RDDBCHECK_USAGE "[options]\n\nCheck the Rivendell database and audio store for consistency\nand correctness.\n\n--yes\n Answer all questions with 'yes'\n\n--no\n Answer all questions with 'no'\n\n--user=<username>\n Connect using the Rivendell user <username> (default is \"user\").\n\n--orphan-group=<group-name>\n Move carts with missing/invalid GROUP information to the <group-name>\n group.\n\n--dump-cuts-dir=<dir-name>\n Move orphaned cut audio to the <dir-name> directory.\n\n--rehash=<cartnum>/ALL"
//
// Global Variables
@ -54,6 +54,9 @@ class MainObject : public QObject
void CheckOrphanedCuts();
void CheckOrphanedAudio();
void ValidateAudioLengths();
void Rehash(const QString &arg);
void RehashCart(unsigned cartnum);
void RehashCut(const QString &cutnum);
void SetCutLength(const QString &cutname,int len);
void CleanTables(const QString &ext,QSqlQuery *table_q,QSqlQuery *name_q);
void CleanTables(const QString &ext,QSqlQuery *table_q);
@ -63,6 +66,7 @@ class MainObject : public QObject
bool check_no;
QString orphan_group_name;
QString dump_cuts_dir;
QString rehash;
RDStation *check_station;
RDUser *check_user;
};

View File

@ -33,6 +33,7 @@
#include <rd.h>
#include <rdconf.h>
#include <rdrehash.h>
#include <rdmaint.h>
#include <rdlibrary_conf.h>
#include <rdescape_string.h>
@ -127,14 +128,16 @@ void MainObject::RunSystemMaintenance()
QString sql;
RDSqlQuery *q;
sql="update VERSION set LAST_MAINT_DATETIME=now()";
q=new RDSqlQuery(sql);
delete q;
PurgeCuts();
PurgeLogs();
PurgeElr();
PurgeGpioEvents();
PurgeWebapiAuths();
sql="update VERSION set LAST_MAINT_DATETIME=now()";
q=new RDSqlQuery(sql);
delete q;
RehashCuts();
}
@ -292,6 +295,36 @@ void MainObject::PurgeWebapiAuths()
delete q;
}
void MainObject::RehashCuts()
{
QString sql;
RDSqlQuery *q;
RDRehash::ErrorCode err;
sql="select CUT_NAME from CUTS where SHA1_HASH is null limit 100";
q=new RDSqlQuery(sql);
while(q->next()) {
printf("CUT: %s\n",(const char *)q->value(0).toString());
if((err=RDRehash::rehash(maint_station,maint_user,maint_config,
RDCut::cartNumber(q->value(0).toString()),
RDCut::cutNumber(q->value(0).toString())))!=RDRehash::ErrorOk) {
maint_config->
log("rdmaint",
RDConfig::LogErr,QString().sprintf("failed to rehash cut %s [%s]",
(const char *)q->value(0).toString(),
(const char *)RDRehash::errorText(err)));
}
if(maint_verbose) {
fprintf(stderr,"rehashed cut \"%s\"\n",
(const char *)q->value(0).toString());
}
sleep(1);
}
delete q;
}
int main(int argc,char *argv[])
{

View File

@ -45,6 +45,7 @@ class MainObject : public QObject
void PurgeDropboxes();
void PurgeGpioEvents();
void PurgeWebapiAuths();
void RehashCuts();
RDConfig *maint_config;
bool maint_verbose;
bool maint_system;

View File

@ -198,6 +198,10 @@ void MainObject::Revert(int schema) const
case 260:
Revert260();
break;
case 261:
Revert261();
break;
}
}
@ -550,6 +554,19 @@ void MainObject::Revert260() const
}
void MainObject::Revert261() const
{
QString sql;
QSqlQuery *q;
sql=QString("alter table CUTS drop column SHA1_HASH");
q=new QSqlQuery(sql);
delete q;
SetVersion(260);
}
int MainObject::GetVersion() const
{
QString sql;
@ -592,7 +609,7 @@ int MainObject::MapSchema(const QString &ver)
version_map["2.13"]=255;
version_map["2.14"]=258;
version_map["2.15"]=259;
version_map["2.16"]=260;
version_map["2.16"]=261;
//
// Normalize String

View File

@ -56,6 +56,7 @@ class MainObject : public QObject
void Revert258() const;
void Revert259() const;
void Revert260() const;
void Revert261() const;
int GetVersion() const;
void SetVersion(int schema) const;
int MapSchema(const QString &ver);

View File

@ -39,6 +39,7 @@ dist_rdxport_cgi_SOURCES = audioinfo.cpp\
import.cpp\
logs.cpp\
rdxport.cpp rdxport.h\
rehash.cpp\
schedcodes.cpp\
services.cpp\
trimaudio.cpp

View File

@ -30,6 +30,7 @@
#include <rdsettings.h>
#include <rdconf.h>
#include <rdgroup.h>
#include <rdhash.h>
#include <rdlibrary_conf.h>
#include <rdxport.h>
@ -214,12 +215,15 @@ void Xport::Import()
resp_code=400;
break;
}
/*
delete conv;
delete settings;
delete conf;
delete cut;
delete cart;
*/
if(resp_code==200) {
cut->setSha1Hash(RDSha1Hash(RDCut::pathName(cut->cutName())));
printf("Content-type: application/xml\n");
printf("Status: %d\n",resp_code);
printf("\n");

View File

@ -264,6 +264,10 @@ Xport::Xport(QObject *parent)
ListServices();
break;
case RDXPORT_COMMAND_REHASH:
Rehash();
break;
default:
printf("Content-type: text/html\n\n");
printf("rdxport: missing/invalid command\n");

View File

@ -64,6 +64,7 @@ class Xport : public QObject
void DeleteLog();
void ListLogs();
void ListLog();
void Rehash();
void SaveLog();
void ListSchedCodes();
void AssignSchedCode();

65
web/rdxport/rehash.cpp Normal file
View File

@ -0,0 +1,65 @@
// rehash.cpp
//
// Rivendell web service portal -- Rehash service
//
// (C) Copyright 2017 Fred Gleason <fredg@paravelsystems.com>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <rdformpost.h>
#include <rdlog_line.h>
#include <rdweb.h>
#include <rduser.h>
#include <rdgroup.h>
#include <rdconf.h>
#include <rdescape_string.h>
#include <rdcart_search_text.h>
#include <rdhash.h>
#include <rdxport.h>
void Xport::Rehash()
{
int cart_number;
int cut_number;
//
// Verify Post
//
if(!xport_post->getValue("CART_NUMBER",&cart_number)) {
XmlExit("Missing CART_NUMBER",400);
}
if(!xport_post->getValue("CUT_NUMBER",&cut_number)) {
XmlExit("Missing CUT_NUMBER",400);
}
//
// Process Request
//
RDCut *cut=new RDCut(cart_number,cut_number);
if(!cut->exists()) {
delete cut;
XmlExit("No such cut",404);
}
cut->setSha1Hash(RDSha1Hash(RDCut::pathName(cart_number,cut_number)));
delete cut;
XmlExit("OK",200);
}

View File

@ -50,6 +50,7 @@ install-exec-am:
cp listlogs.html $(DESTDIR)@libexecdir@
cp listschedcodes.html $(DESTDIR)@libexecdir@
cp listservices.html $(DESTDIR)@libexecdir@
cp rehash.html $(DESTDIR)@libexecdir@
cp removecart.html $(DESTDIR)@libexecdir@
cp removecut.html $(DESTDIR)@libexecdir@
cp savelog.html $(DESTDIR)@libexecdir@
@ -86,6 +87,7 @@ uninstall-local:
rm -f $(DESTDIR)@libexecdir@/listlogs.html
rm -f $(DESTDIR)@libexecdir@/listschedcodes.html
rm -f $(DESTDIR)@libexecdir@/listservices.html
rm -f $(DESTDIR)@libexecdir@/rehash.html
rm -f $(DESTDIR)@libexecdir@/removecart.html
rm -f $(DESTDIR)@libexecdir@/removecut.html
rm -f $(DESTDIR)@libexecdir@/savelog.html
@ -121,6 +123,7 @@ EXTRA_DIST = addcart.html\
listlogs.html\
listschedcodes.html\
listservices.html\
rehash.html\
removecart.html\
removecut.html\
savelog.html\

37
web/tests/rehash.html Normal file
View File

@ -0,0 +1,37 @@
<html>
<head>
<title>Rivendell REHASH Service Test Harness</title>
<body>
<form action="/rd-bin/rdxport.cgi" method="post" enctype="multipart/form-data">
<input type="hidden" name="COMMAND" value="32">
<table cellpadding="0" cellspacing="2" border="0">
<tr>
<td align="right">LOGIN NAME:</td>
<td><input type="text" name="LOGIN_NAME" size="20" maxlength="255"></td>
</tr>
<tr>
<td align="right">PASSWORD:</td>
<td><input type="password" name="PASSWORD" size="20" maxlength="32"></td>
</tr>
<tr>
<td align="right">TICKET:</td>
<td><input type="text" name="TICKET" size="40" maxlength="40"></td>
</tr>
<tr>
<td align="right">CART NUMBER:</td>
<td><input type="text" name="CART_NUMBER" size="20" maxlength="6"></td>
</tr>
<tr>
<td align="right">CUT NUMBER:</td>
<td><input type="text" name="CUT_NUMBER" size="20" maxlength="3"></td>
</tr>
<tr>
<td colspan="2" align="right">&nbsp;</td>
</tr>
<tr>
<td colspan="2" align="right"><input type="submit" value="OK"></td>
</tr>
</table>
</form>
</body>
</html>