diff --git a/.gitignore b/.gitignore index eb92770a..f6f400d8 100644 --- a/.gitignore +++ b/.gitignore @@ -110,6 +110,7 @@ tests/dateparse_test tests/db_charset_test tests/delete_test tests/download_test +tests/feed_image_test tests/getpids_test tests/log_unlink_test tests/mcast_recv_test diff --git a/ChangeLog b/ChangeLog index 97dd95d2..d3231754 100644 --- a/ChangeLog +++ b/ChangeLog @@ -19834,3 +19834,6 @@ * Fixed a regression in rdairplay(1) that caused two events to be started by a single spacebar tap if a SoundPanel event had been played previously. +2020-03-18 Fred Gleason + * Added a 'FEED_IMAGES' table to the database. + * Added 'feed_image_test' in 'tests/'. diff --git a/docs/tables/Makefile.am b/docs/tables/Makefile.am index 84f6a48e..ceeff0e9 100644 --- a/docs/tables/Makefile.am +++ b/docs/tables/Makefile.am @@ -50,6 +50,7 @@ EXTRA_DIST = audio_cards.txt\ events.txt\ extended_panel_names.txt\ extended_panels.txt\ + feed_images.txt\ feed_perms.txt\ feeds.txt\ gpio_events.txt\ diff --git a/docs/tables/feed_images.txt b/docs/tables/feed_images.txt new file mode 100644 index 00000000..d826c0c6 --- /dev/null +++ b/docs/tables/feed_images.txt @@ -0,0 +1,15 @@ + FEED_IMAGES Table Layout for Rivendell + +The FEED_IMAGES table holds binary images used by podcast feeds. + + +FIELD NAME TYPE REMARKS +--------------------------------------------------------------- +ID int(10) unsigned Primary key, auto increment +FEED_ID int(10) unsigned From FEEDS.ID +FEED_KEY_NAME varchar(8) From FEEDS.KEY_NAME +WIDTH int(11) signed Pixels +HEIGHT int(11) signed Pixels +DEPTH int(11) signed Bits/pixel +DESCRIPTION text +DATA mediumblob diff --git a/lib/dbversion.h b/lib/dbversion.h index 86e53ad7..d2d544d6 100644 --- a/lib/dbversion.h +++ b/lib/dbversion.h @@ -24,7 +24,7 @@ /* * Current Database Version */ -#define RD_VERSION_DATABASE 319 +#define RD_VERSION_DATABASE 320 #endif // DBVERSION_H diff --git a/lib/rdescape_string.cpp b/lib/rdescape_string.cpp index 655922cf..32aa9afd 100644 --- a/lib/rdescape_string.cpp +++ b/lib/rdescape_string.cpp @@ -2,7 +2,7 @@ // // Escape non-valid characters in a string. // -// (C) Copyright 2002-2005,2016-2017 Fred Gleason +// (C) Copyright 2002-2020 Fred Gleason // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 as @@ -113,3 +113,21 @@ QString RDEscapeShellString(QString str) { return "\""+str.replace("$","\\$")+"\""; } + + +QString RDEscapeBlob(const QByteArray &data) +{ + return RDEscapeBlob(data.constData(),data.length()); +} + + +QString RDEscapeBlob(const char *data,size_t len) +{ + QString ret="x'"; + + for(unsigned i=0;i -#include - #ifndef RDESCAPE_STRING_H #define RDESCAPE_STRING_H +#include +#include +#include + QString RDCheckDateTime(const QTime &time, const QString &format); QString RDCheckDateTime(const QDateTime &datetime, const QString &format); QString RDCheckDateTime(const QDate &date, const QString &format); QString RDEscapeString(const QString &str); QString RDEscapeShellString(QString str); +QString RDEscapeBlob(const QByteArray &data); +QString RDEscapeBlob(const char *data,size_t len); #endif // RDESCAPE_STRING_H diff --git a/tests/Makefile.am b/tests/Makefile.am index 18684612..445019a6 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -36,6 +36,7 @@ noinst_PROGRAMS = audio_convert_test\ db_charset_test\ delete_test\ download_test\ + feed_image_test\ getpids_test\ log_unlink_test\ mcast_recv_test\ @@ -81,6 +82,9 @@ delete_test_LDADD = @LIB_RDLIBS@ @LIBVORBIS@ @QT4_LIBS@ @MUSICBRAINZ_LIBS@ -lQt3 dist_download_test_SOURCES = download_test.cpp download_test.h download_test_LDADD = @LIB_RDLIBS@ @LIBVORBIS@ @QT4_LIBS@ @MUSICBRAINZ_LIBS@ -lQt3Support +dist_feed_image_test_SOURCES = feed_image_test.cpp feed_image_test.h +feed_image_test_LDADD = @LIB_RDLIBS@ @LIBVORBIS@ @QT4_LIBS@ @MUSICBRAINZ_LIBS@ -lQt3Support + dist_getpids_test_SOURCES = getpids_test.cpp getpids_test.h getpids_test_LDADD = @LIB_RDLIBS@ @LIBVORBIS@ @QT4_LIBS@ @MUSICBRAINZ_LIBS@ -lQt3Support diff --git a/tests/feed_image_test.cpp b/tests/feed_image_test.cpp new file mode 100644 index 00000000..0be7d9b1 --- /dev/null +++ b/tests/feed_image_test.cpp @@ -0,0 +1,290 @@ +// feed_image_test.cpp +// +// Test Rivendell image storage +// +// (C) Copyright 2010-2020 Fred Gleason +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public +// License along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "feed_image_test.h" + +MainObject::MainObject(QObject *parent) + :QObject(parent) +{ + QString err_msg; + QString key_name; + bool ok=false; + MainObject::Command command=MainObject::None; + test_image_id=-1; + + // + // Open the Database + // + rda=new RDApplication("feed_image_test","feed_image_test",FEED_IMAGE_TEST_USAGE,this); + if(!rda->open(&err_msg)) { + fprintf(stderr,"feed_image_test: %s\n",(const char *)err_msg); + exit(1); + } + + // + // Read Command Options + // + for(unsigned i=0;icmdSwitch()->keys();i++) { + if(rda->cmdSwitch()->key(i)=="--feed") { + key_name=rda->cmdSwitch()->value(i); + rda->cmdSwitch()->setProcessed(i,true); + } + if(rda->cmdSwitch()->key(i)=="--filename") { + test_filename=rda->cmdSwitch()->value(i); + rda->cmdSwitch()->setProcessed(i,true); + } + if(rda->cmdSwitch()->key(i)=="--description") { + test_description=rda->cmdSwitch()->value(i); + rda->cmdSwitch()->setProcessed(i,true); + } + if(rda->cmdSwitch()->key(i)=="--image-id") { + test_image_id=rda->cmdSwitch()->value(i).toInt(&ok); + if((!ok)||(test_image_id<0)) { + fprintf(stderr,"feed_image_test: invalid --image-id\n"); + exit(1); + } + rda->cmdSwitch()->setProcessed(i,true); + } + if(rda->cmdSwitch()->key(i)=="--list") { + if(command!=MainObject::None) { + fprintf(stderr, + "feed_image_test: --list, --pop and --push are mutually exclusive\n"); + exit(1); + } + command=MainObject::List; + rda->cmdSwitch()->setProcessed(i,true); + } + if(rda->cmdSwitch()->key(i)=="--pop") { + if(command!=MainObject::None) { + fprintf(stderr, + "feed_image_test: --list, --pop and --push are mutually exclusive\n"); + exit(1); + } + command=MainObject::Pop; + rda->cmdSwitch()->setProcessed(i,true); + } + if(rda->cmdSwitch()->key(i)=="--push") { + if(command!=MainObject::None) { + fprintf(stderr, + "feed_image_test: --list, --pop and --push are mutually exclusive\n"); + exit(1); + } + command=MainObject::Push; + rda->cmdSwitch()->setProcessed(i,true); + } + if(!rda->cmdSwitch()->processed(i)) { + fprintf(stderr,"feed_image_test: unknown command option \"%s\"\n", + (const char *)rda->cmdSwitch()->key(i)); + exit(2); + } + } + + // + // Sanity Checks + // + if(command==MainObject::None) { + fprintf(stderr,"feed_image_test: you must specify --list, --pop or --push\n"); + exit(1); + } + + + + + // + // Get the feed data + // + + // + // Dispatch + // + switch(command) { + case MainObject::List: + if(key_name.isEmpty()) { + fprintf(stderr,"feed_image_test: you must specify --feed=\n"); + exit(1); + } + test_feed=new RDFeed(key_name,rda->config(),this); + if(!test_feed->exists()) { + fprintf(stderr,"feed_image_test: no such feed\n"); + exit(1); + } + RunList(); + break; + + case MainObject::Pop: + if(test_image_id<0) { + fprintf(stderr,"feed_image_test: you must specify --image-id\n"); + exit(1); + } + if(test_filename.isEmpty()) { + fprintf(stderr, + "feed_image_test: you must specify --filename=\n"); + exit(1); + } + RunPop(); + break; + + case MainObject::Push: + if(key_name.isEmpty()) { + fprintf(stderr,"feed_image_test: you must specify --feed=\n"); + exit(1); + } + test_feed=new RDFeed(key_name,rda->config(),this); + if(!test_feed->exists()) { + fprintf(stderr,"feed_image_test: no such feed\n"); + exit(1); + } + if(test_filename.isEmpty()) { + fprintf(stderr, + "feed_image_test: you must specify --filename=\n"); + exit(1); + } + if(test_description.isEmpty()) { + fprintf(stderr, + "feed_image_test: you must specify --description=\n"); + exit(1); + } + RunPush(); + break; + + case MainObject::None: + break; + } + + exit(0); +} + + +void MainObject::RunList() +{ + QString sql; + RDSqlQuery *q=NULL; + + sql=QString("select ")+ + "ID,"+ // 00 + "WIDTH,"+ // 01 + "HEIGHT,"+ // 02 + "DEPTH,"+ // 03 + "DESCRIPTION "+ // 04 + "from FEED_IMAGES where "+ + "FEED_KEY_NAME=\""+RDEscapeString(test_feed->keyName())+"\""; + q=new RDSqlQuery(sql); + while(q->next()) { + printf("ID: %u\n",q->value(0).toUInt()); + printf(" Description: %s\n",q->value(4).toString().toUtf8().constData()); + printf(" Dimensions: %dx%dx%d\n",q->value(1).toInt(),q->value(2).toInt(), + q->value(3).toInt()); + printf("\n"); + } + delete q; +} + + +void MainObject::RunPush() +{ + QString sql; + + // + // Load the image + // + QFile file(test_filename); + if(!file.open(QIODevice::ReadOnly)) { + fprintf(stderr,"feed_image_test: unable to open image file [%s]\n", + strerror(errno)); + exit(1); + } + QByteArray data=file.readAll(); + file.close(); + + // + // Validate the image + // + QImage *img=new QImage(); + if(!img->loadFromData(data)) { + fprintf(stderr,"feed_image_test: invalid image file\n"); + exit(1); + } + printf("Image is %dx%dx%d\n",img->width(),img->height(),img->depth()); + + // + // Write it to the DB + // + sql=QString("insert into FEED_IMAGES set ")+ + QString().sprintf("FEED_ID=%u,",test_feed->id())+ + "FEED_KEY_NAME=\""+RDEscapeString(test_feed->keyName())+"\","+ + QString().sprintf("WIDTH=%d,",img->width())+ + QString().sprintf("HEIGHT=%d,",img->height())+ + QString().sprintf("DEPTH=%d,",img->depth())+ + "DESCRIPTION=\""+RDEscapeString(test_description)+"\","+ + "DATA="+RDEscapeBlob(data); + RDSqlQuery::apply(sql); +} + + +void MainObject::RunPop() +{ + QString sql; + RDSqlQuery *q=NULL; + QByteArray data; + FILE *f=NULL; + + sql=QString("select DATA from FEED_IMAGES where ")+ + QString().sprintf("ID=%u",test_image_id); + q=new RDSqlQuery(sql); + if(q->first()) { + if((f=fopen(test_filename.toUtf8(),"w"))==NULL) { + fprintf(stderr,"feed_image_test: unable to open \"%s\" [%s]\n", + test_filename.toUtf8().constData(),strerror(errno)); + exit(1); + } + data=q->value(0).toByteArray(); + if(fwrite(data.constData(),1,data.size(),f)<0) { + fprintf(stderr,"feed_image_test: unable to write to \"%s\" [%s]\n", + test_filename.toUtf8().constData(),strerror(errno)); + exit(1); + } + } + else { + fprintf(stderr,"feed_image_test: no such image\n"); + exit(1); + } + delete q; +} + + +int main(int argc,char *argv[]) +{ + QApplication a(argc,argv,false); + new MainObject(); + return a.exec(); +} diff --git a/tests/feed_image_test.h b/tests/feed_image_test.h new file mode 100644 index 00000000..24fec331 --- /dev/null +++ b/tests/feed_image_test.h @@ -0,0 +1,48 @@ +// feed_image_test.h +// +// Test Rivendell image storage. +// +// (C) Copyright 2010-2020 Fred Gleason +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public +// License along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#ifndef FEED_IMAGE_TEST_H +#define FEED_IMAGE_TEST_H + +#include + +#include + +#define FEED_IMAGE_TEST_USAGE "[options]\n\nTest the Rivendell binary image routines\n\nOptions are:\n--push | --pop | --list\n\n--description=\n\n--name=\n\n--feed=\n\n--image-id=\n\n" + +class MainObject : public QObject +{ + public: + enum Command {None=0,List=1,Push=2,Pop=3}; + MainObject(QObject *parent=0); + + private: + void RunList(); + void RunPush(); + void RunPop(); + QString test_filename; + QString test_description; + QString test_name; + RDFeed *test_feed; + int test_image_id; +}; + + +#endif // FEED_IMAGE_TEST_H diff --git a/utils/rddbmgr/revertschema.cpp b/utils/rddbmgr/revertschema.cpp index e5682185..f657dc36 100644 --- a/utils/rddbmgr/revertschema.cpp +++ b/utils/rddbmgr/revertschema.cpp @@ -41,6 +41,15 @@ bool MainObject::RevertSchema(int cur_schema,int set_schema,QString *err_msg) // NEW SCHEMA REVERSIONS GO HERE... + // + // Revert 320 + // + if((cur_schema==320)&&(set_schemacur_schema)) { + sql=QString("create table FEED_IMAGES (")+ + "ID int unsigned primary key,"+ + "FEED_ID int unsigned not null,"+ + "FEED_KEY_NAME varchar(8) not null,"+ + "WIDTH int not null,"+\ + "HEIGHT int not null,"+ + "DEPTH int not null,"+ + "DESCRIPTION text,"+ + "DATA mediumblob not null,"+ + "index FEED_ID_IDX (FEED_ID),"+ + "index FEED_KEY_NAME_IDX (FEED_KEY_NAME))"; + if(!RDSqlQuery::apply(sql,err_msg)) { + return false; + } + + WriteSchemaVersion(++cur_schema); + } + + // NEW SCHEMA UPDATES GO HERE...