2018-07-25 Fred Gleason <fredg@paravelsystems.com>

* Fixed a bug in the 'RDEscapeString' function that caused corruption
	in UTF-8 strings.
	* Added a UTF-8 complaint MySQL driver in 'drivers/qt3-mysql-utf/'.
This commit is contained in:
Fred Gleason 2018-07-25 07:44:48 -04:00
parent c722e4fe09
commit 15f21fb4ec
16 changed files with 1503 additions and 62 deletions

View File

@ -17214,3 +17214,7 @@
* Cleaned up RDConf calls to ensure UTF-8 compatibility.
2018-07-23 Fred Gleason <fredg@paravelsystems.com>
* Fixed a buffer overflow vulnerability in the 'RDWaveFile' class.
2018-07-25 Fred Gleason <fredg@paravelsystems.com>
* Fixed a bug in the 'RDEscapeString' function that caused corruption
in UTF-8 strings.
* Added a UTF-8 complaint MySQL driver in 'drivers/qt3-mysql-utf/'.

View File

@ -38,6 +38,7 @@ SUBDIRS = icons\
conf\
debian\
docs\
drivers\
xdg\
cae\
importers\

View File

@ -24,7 +24,8 @@ Hostname=localhost
Loginname=rduser
Password=letmein
Database=Rivendell
Driver=QMYSQL3
Driver=QMYSQL3 ; Stock Latin1-capable driver supplied by TrollTech
;Driver=QNYSQLUTF8 ; UTF-8-capable driver packaged within Rivendell
; The following three settings control the attributes of new DB tables
; created by Rivendell.

View File

@ -213,6 +213,11 @@ AC_CHECK_HEADER(security/pam_appl.h,[],[AC_MSG_ERROR([*** PAM not found ***])])
#
AC_CHECK_HEADER(soundtouch/SoundTouch.h,[],[AC_MSG_ERROR([*** SoundTouch not found ***])])
#
# Check for MySQL client libraries
#
MYSQL_CLIENT()
#
# Check for FLAC
#
@ -495,6 +500,8 @@ AC_CONFIG_FILES([rivendell.spec \
rivendell-suse \
rdrepld-suse \
conf/rd-bin.conf \
drivers/Makefile \
drivers/qt3-mysql-utf/Makefile \
icons/Makefile \
helpers/Makefile \
lib/rdpaths.h \

40
drivers/Makefile.am Normal file
View File

@ -0,0 +1,40 @@
## automake.am
##
## Makefile.am for rivendell/drivers
##
## (C) Copyright 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 as
## published by the Free Software Foundation; either version 2 of
## the License, or (at your option) any later version.
##
## 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.
##
## Use automake to process this into a Makefile.in
SUBDIRS = qt3-mysql-utf
EXTRA_DIST = drivers.pro
CLEANFILES = *~\
*.idb\
*ilk\
*.obj\
*.pdb\
*.qm\
moc_*
MAINTAINERCLEANFILES = *~\
*.tar.gz\
aclocal.m4\
configure\
Makefile.in\
moc_*

22
drivers/drivers.pro Normal file
View File

@ -0,0 +1,22 @@
# drivers.pro
#
# The drivers/ QMake project file for Rivendell
#
# (C) Copyright 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.
TEMPLATE = subdirs
SUBDIRS += qt3-mysql-utf

View File

@ -0,0 +1,103 @@
THE Q PUBLIC LICENSE
version 1.0
Copyright (C) 1999-2000 Trolltech AS, Norway.
Everyone is permitted to copy and
distribute this license document.
The intent of this license is to establish freedom to share and change the
software regulated by this license under the open source model.
This license applies to any software containing a notice placed by the
copyright holder saying that it may be distributed under the terms of
the Q Public License version 1.0. Such software is herein referred to as
the Software. This license covers modification and distribution of the
Software, use of third-party application programs based on the Software,
and development of free software which uses the Software.
Granted Rights
1. You are granted the non-exclusive rights set forth in this license
provided you agree to and comply with any and all conditions in this
license. Whole or partial distribution of the Software, or software
items that link with the Software, in any form signifies acceptance of
this license.
2. You may copy and distribute the Software in unmodified form provided
that the entire package, including - but not restricted to - copyright,
trademark notices and disclaimers, as released by the initial developer
of the Software, is distributed.
3. You may make modifications to the Software and distribute your
modifications, in a form that is separate from the Software, such as
patches. The following restrictions apply to modifications:
a. Modifications must not alter or remove any copyright notices in
the Software.
b. When modifications to the Software are released under this
license, a non-exclusive royalty-free right is granted to the
initial developer of the Software to distribute your modification
in future versions of the Software provided such versions remain
available under these terms in addition to any other license(s) of
the initial developer.
4. You may distribute machine-executable forms of the Software or
machine-executable forms of modified versions of the Software, provided
that you meet these restrictions:
a. You must include this license document in the distribution.
b. You must ensure that all recipients of the machine-executable forms
are also able to receive the complete machine-readable source code
to the distributed Software, including all modifications, without
any charge beyond the costs of data transfer, and place prominent
notices in the distribution explaining this.
c. You must ensure that all modifications included in the
machine-executable forms are available under the terms of this
license.
5. You may use the original or modified versions of the Software to
compile, link and run application programs legally developed by you
or by others.
6. You may develop application programs, reusable components and other
software items that link with the original or modified versions of the
Software. These items, when distributed, are subject to the following
requirements:
a. You must ensure that all recipients of machine-executable forms of
these items are also able to receive and use the complete
machine-readable source code to the items without any charge
beyond the costs of data transfer.
b. You must explicitly license all recipients of your items to use
and re-distribute original and modified versions of the items in
both machine-executable and source code forms. The recipients must
be able to do so without any charges whatsoever, and they must be
able to re-distribute to anyone they choose.
c. If the items are not available to the general public, and the
initial developer of the Software requests a copy of the items,
then you must supply one.
Limitations of Liability
In no event shall the initial developers or copyright holders be liable
for any damages whatsoever, including - but not restricted to - lost
revenue or profits or other direct, indirect, special, incidental or
consequential damages, even if they have been advised of the possibility
of such damages, except to the extent invariable law, if any, provides
otherwise.
No Warranty
The Software and this license document are provided AS IS with NO WARRANTY
OF ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE.
Choice of Law
This license is governed by the Laws of Norway. Disputes shall be settled
by Oslo City Court.

View File

@ -0,0 +1,63 @@
## automake.am
##
## Makefile.am for rivendell/drivers/qt3-mysql-utf
##
## by Fred Gleason <fredg@paravelsystems.com>
## (C) Copyright 2002-2007,2016 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.
##
## Use automake to process this into a Makefile.in
##
AM_CPPFLAGS = -Wall -DPREFIX=\"$(prefix)\" -DQTDIR=\"@QT_DIR@\" @QT_CXXFLAGS@ @MYSQL_CLIENT_CFLAGS@
LIBS = @QT_LIBS@
MOC = @QT_MOC@
# The dependency for qt's Meta Object Compiler (moc)
moc_%.cpp: %.h
$(MOC) $< -o $@
install-exec-local:
mkdir -p $(DESTDIR)$(QTDIR)/plugins/sqldrivers
cp .libs/libqmysqlutf.so.0.0.0 $(DESTDIR)$(QTDIR)/plugins/sqldrivers/libqmysqlutf.so
uninstall-exec:
rm -f $(DESTDIR)$(QTDIR)/plugins/sqldrivers/libqmysqlutf.so
lib_LTLIBRARIES = libqmysqlutf.la
dist_libqmysqlutf_la_SOURCES = qsqlextension.cpp qsqlextension.h\
qt3-mysql-utf.cpp qt3-mysql-utf.h
libqmysqlutf_la_LIBADD = @MYSQL_CLIENT_LIBS@
libqmysqlutf_la_LDFLAGS = -rpath `pwd`
EXTRA_DIST = LICENSE.QPL\
qt3-mysql-utf.pro
CLEANFILES = *~\
*.idb\
*.ilk\
*.lib\
*.obj\
*.pdb\
*.qm\
moc_*
DISTCLEAN = Makefile
MAINTAINERCLEANFILES = *~\
Makefile\
Makefile.in\
moc_*

View File

@ -0,0 +1,169 @@
/****************************************************************************
**
** Implementation of the QSqlExtension class
**
** Created : 2002-06-03
**
** Copyright (C) 2005-2008 Trolltech ASA. All rights reserved.
**
** This file is part of the sql module of the Qt GUI Toolkit.
**
** This file may be used under the terms of the GNU General
** Public License versions 2.0 or 3.0 as published by the Free
** Software Foundation and appearing in the files LICENSE.GPL2
** and LICENSE.GPL3 included in the packaging of this file.
** Alternatively you may (at your option) use any later version
** of the GNU General Public License if such license has been
** publicly approved by Trolltech ASA (or its successors, if any)
** and the KDE Free Qt Foundation.
**
** Please review the following information to ensure GNU General
** Public Licensing requirements will be met:
** http://trolltech.com/products/qt/licenses/licensing/opensource/.
** If you are unsure which license is appropriate for your use, please
** review the following information:
** http://trolltech.com/products/qt/licenses/licensing/licensingoverview
** or contact the sales department at sales@trolltech.com.
**
** This file may be used under the terms of the Q Public License as
** defined by Trolltech ASA and appearing in the file LICENSE.QPL
** included in the packaging of this file. Licensees holding valid Qt
** Commercial licenses may use this file in accordance with the Qt
** Commercial License Agreement provided with the Software.
**
** This file is provided "AS IS" with NO WARRANTY OF ANY KIND,
** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted
** herein.
**
**********************************************************************/
#include "qsqlextension.h"
#ifndef QT_NO_SQL
QSqlExtension::QSqlExtension()
: bindm( BindByPosition ), bindCount( 0 )
{
}
QSqlExtension::~QSqlExtension()
{
}
bool QSqlExtension::prepare( const QString& /*query*/ )
{
return FALSE;
}
bool QSqlExtension::exec()
{
return FALSE;
}
void QSqlExtension::bindValue( const QString& placeholder, const QVariant& val, QSql::ParameterType tp )
{
bindm = BindByName;
// if the index has already been set when doing emulated named
// bindings - don't reset it
if ( index.contains( (int)values.count() ) ) {
index[ (int)values.count() ] = placeholder;
}
values[ placeholder ] = Param( val, tp );
}
void QSqlExtension::bindValue( int pos, const QVariant& val, QSql::ParameterType tp )
{
bindm = BindByPosition;
index[ pos ] = QString::number( pos );
QString nm = QString::number( pos );
values[ nm ] = Param( val, tp );
}
void QSqlExtension::addBindValue( const QVariant& val, QSql::ParameterType tp )
{
bindm = BindByPosition;
bindValue( bindCount++, val, tp );
}
void QSqlExtension::clearValues()
{
values.clear();
bindCount = 0;
}
void QSqlExtension::resetBindCount()
{
bindCount = 0;
}
void QSqlExtension::clearIndex()
{
index.clear();
holders.clear();
}
void QSqlExtension::clear()
{
clearValues();
clearIndex();
}
QVariant QSqlExtension::parameterValue( const QString& holder )
{
return values[ holder ].value;
}
QVariant QSqlExtension::parameterValue( int pos )
{
return values[ index[ pos ] ].value;
}
QVariant QSqlExtension::boundValue( const QString& holder ) const
{
return values[ holder ].value;
}
QVariant QSqlExtension::boundValue( int pos ) const
{
return values[ index[ pos ] ].value;
}
QMap<QString, QVariant> QSqlExtension::boundValues() const
{
QMap<QString, Param>::ConstIterator it;
QMap<QString, QVariant> m;
if ( bindm == BindByName ) {
for ( it = values.begin(); it != values.end(); ++it )
m.insert( it.key(), it.data().value );
} else {
QString key, tmp, fmt;
fmt.sprintf( "%%0%dd", QString::number( values.count()-1 ).length() );
for ( it = values.begin(); it != values.end(); ++it ) {
tmp.sprintf( fmt.ascii(), it.key().toInt() );
m.insert( tmp, it.data().value );
}
}
return m;
}
QSqlExtension::BindMethod QSqlExtension::bindMethod()
{
return bindm;
}
QSqlDriverExtension::QSqlDriverExtension()
{
}
QSqlDriverExtension::~QSqlDriverExtension()
{
}
QSqlOpenExtension::QSqlOpenExtension()
{
}
QSqlOpenExtension::~QSqlOpenExtension()
{
}
#endif

View File

@ -0,0 +1,148 @@
/****************************************************************************
**
** Definition of the QSqlExtension class
**
** Created : 2002-06-03
**
** Copyright (C) 2005-2008 Trolltech ASA. All rights reserved.
**
** This file is part of the sql module of the Qt GUI Toolkit.
**
** This file may be used under the terms of the GNU General
** Public License versions 2.0 or 3.0 as published by the Free
** Software Foundation and appearing in the file COPYING
** included in the packaging of this file.
** Alternatively you may (at your option) use any later version
** of the GNU General Public License if such license has been
** publicly approved by Trolltech ASA (or its successors, if any)
** and the KDE Free Qt Foundation.
**
** Please review the following information to ensure GNU General
** Public Licensing requirements will be met:
** http://trolltech.com/products/qt/licenses/licensing/opensource/.
** If you are unsure which license is appropriate for your use, please
** review the following information:
** http://trolltech.com/products/qt/licenses/licensing/licensingoverview
** or contact the sales department at sales@trolltech.com.
**
** This file may be used under the terms of the Q Public License as
** defined by Trolltech ASA and appearing in the file LICENSE.QPL
** included in the packaging of this file. Licensees holding valid Qt
** Commercial licenses may use this file in accordance with the Qt
** Commercial License Agreement provided with the Software.
**
** This file is provided "AS IS" with NO WARRANTY OF ANY KIND,
** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted
** herein.
**
**********************************************************************/
#ifndef QSQLEXTENSION_H
#define QSQLEXTENSION_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists for the convenience
// of other Qt classes. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
//
#ifndef QT_H
#include "qmap.h"
#include "qvaluevector.h"
#include "qstring.h"
#include "qvariant.h"
#include "qsql.h"
#endif // QT_H
#ifndef QT_NO_SQL
#if !defined( QT_MODULE_SQL ) || defined( QT_LICENSE_PROFESSIONAL )
#define QM_EXPORT_SQL
#define QM_TEMPLATE_EXTERN_SQL
#else
#define QM_EXPORT_SQL Q_EXPORT
#define QM_TEMPLATE_EXTERN_SQL Q_TEMPLATE_EXTERN
#endif
struct Param {
Param( const QVariant& v = QVariant(), QSql::ParameterType t = QSql::In ): value( v ), typ( t ) {}
QVariant value;
QSql::ParameterType typ;
Q_DUMMY_COMPARISON_OPERATOR(Param)
};
struct Holder {
Holder( const QString& hldr = QString::null, int pos = -1 ): holderName( hldr ), holderPos( pos ) {}
bool operator==( const Holder& h ) const { return h.holderPos == holderPos && h.holderName == holderName; }
bool operator!=( const Holder& h ) const { return h.holderPos != holderPos || h.holderName != holderName; }
QString holderName;
int holderPos;
};
#define Q_DEFINED_QSQLEXTENSION
#include "qwinexport.h"
class QM_EXPORT_SQL QSqlExtension {
public:
QSqlExtension();
virtual ~QSqlExtension();
virtual bool prepare( const QString& query );
virtual bool exec();
virtual void bindValue( const QString& holder, const QVariant& value, QSql::ParameterType = QSql::In );
virtual void bindValue( int pos, const QVariant& value, QSql::ParameterType = QSql::In );
virtual void addBindValue( const QVariant& value, QSql::ParameterType = QSql::In );
virtual QVariant parameterValue( const QString& holder );
virtual QVariant parameterValue( int pos );
QVariant boundValue( const QString& holder ) const;
QVariant boundValue( int pos ) const;
QMap<QString, QVariant> boundValues() const;
void clear();
void clearValues();
void clearIndex();
void resetBindCount();
enum BindMethod { BindByPosition, BindByName };
BindMethod bindMethod(); // ### 4.0: make this const
BindMethod bindm;
int bindCount;
QMap<int, QString> index;
typedef QMap<QString, Param> ValueMap;
ValueMap values;
// convenience container for QSqlQuery
// to map holders <-> positions
typedef QValueVector<Holder> HolderVector;
HolderVector holders;
};
class QM_EXPORT_SQL QSqlDriverExtension
{
public:
QSqlDriverExtension();
virtual ~QSqlDriverExtension();
virtual bool isOpen() const = 0;
};
class QM_EXPORT_SQL QSqlOpenExtension
{
public:
QSqlOpenExtension();
virtual ~QSqlOpenExtension();
virtual bool open( const QString& db,
const QString& user,
const QString& password,
const QString& host,
int port,
const QString& connOpts ) = 0;
};
#endif
#endif

View File

@ -0,0 +1,781 @@
// qt3-mysql-utf.cpp
//
// Qt3 SQL plug-in for MySQL with Unicode UTF-8 support
// (C) Copyright 2018 Fred Gleason <fredg@paravelsystems.com>
//
// Based on the stock Qt3 MySQL driver
// Copyright (C) 1992-2008 Trolltech ASA. All rights reserved.
//
// This file may be used under the terms of the Q Public License as
// defined by Trolltech ASA and appearing in the file LICENSE.QPL
// included in the packaging of this file. Licensees holding valid Qt
// Commercial licenses may use this file in accordance with the Qt
// Commercial License Agreement provided with the Software.
//
// 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 "qt3-mysql-utf.h"
#include "qsqlextension.h"
#include <qdatetime.h>
#include <qvaluevector.h>
#include <qsqlrecord.h>
#define QMYSQLUTF_DRIVER_NAME "QMYSQLUTF8"
QPtrDict<QSqlOpenExtension> *qSqlOpenExtDict();
static int qMySqlConnectionCount = 0;
static bool qMySqlInitHandledByUser = FALSE;
class QMYSQLUTFOpenExtension : public QSqlOpenExtension
{
public:
QMYSQLUTFOpenExtension( QMYSQLUTFDriver *dri )
: QSqlOpenExtension(), driver(dri) {}
~QMYSQLUTFOpenExtension() {}
bool open( const QString& db,
const QString& user,
const QString& password,
const QString& host,
int port,
const QString& connOpts );
private:
QMYSQLUTFDriver *driver;
};
bool QMYSQLUTFOpenExtension::open( const QString& db,
const QString& user,
const QString& password,
const QString& host,
int port,
const QString& connOpts )
{
return driver->open( db, user, password, host, port, connOpts );
}
class QMYSQLUTFDriverPrivate
{
public:
QMYSQLUTFDriverPrivate() : mysql(0) {}
MYSQL* mysql;
};
class QMYSQLUTFResultPrivate : public QMYSQLUTFDriverPrivate
{
public:
QMYSQLUTFResultPrivate() : QMYSQLUTFDriverPrivate(), result(0) {}
MYSQL_RES* result;
MYSQL_ROW row;
QValueVector<QVariant::Type> fieldTypes;
};
QSqlError qMakeError( const QString& err, int type, const QMYSQLUTFDriverPrivate* p )
{
return QSqlError(QMYSQLUTF_DRIVER_NAME ": " + err, QString(mysql_error( p->mysql )), type, mysql_errno( p->mysql ));
}
QVariant::Type qDecodeMYSQLType( int mysqltype, uint flags )
{
QVariant::Type type;
switch ( mysqltype ) {
case FIELD_TYPE_TINY :
case FIELD_TYPE_SHORT :
case FIELD_TYPE_LONG :
case FIELD_TYPE_INT24 :
type = (flags & UNSIGNED_FLAG) ? QVariant::UInt : QVariant::Int;
break;
case FIELD_TYPE_YEAR :
type = QVariant::Int;
break;
case FIELD_TYPE_LONGLONG :
type = (flags & UNSIGNED_FLAG) ? QVariant::ULongLong : QVariant::LongLong;
break;
case FIELD_TYPE_DECIMAL :
case FIELD_TYPE_FLOAT :
case FIELD_TYPE_DOUBLE :
type = QVariant::Double;
break;
case FIELD_TYPE_DATE :
type = QVariant::Date;
break;
case FIELD_TYPE_TIME :
type = QVariant::Time;
break;
case FIELD_TYPE_DATETIME :
case FIELD_TYPE_TIMESTAMP :
type = QVariant::DateTime;
break;
case FIELD_TYPE_BLOB :
case FIELD_TYPE_TINY_BLOB :
case FIELD_TYPE_MEDIUM_BLOB :
case FIELD_TYPE_LONG_BLOB :
type = (flags & BINARY_FLAG) ? QVariant::ByteArray : QVariant::CString;
break;
default:
case FIELD_TYPE_ENUM :
case FIELD_TYPE_SET :
case FIELD_TYPE_STRING :
case FIELD_TYPE_VAR_STRING :
type = QVariant::String;
break;
}
return type;
}
QMYSQLUTFResult::QMYSQLUTFResult( const QMYSQLUTFDriver* db )
: QSqlResult( db )
{
d = new QMYSQLUTFResultPrivate();
d->mysql = db->d->mysql;
}
QMYSQLUTFResult::~QMYSQLUTFResult()
{
cleanup();
delete d;
}
MYSQL_RES* QMYSQLUTFResult::result()
{
return d->result;
}
void QMYSQLUTFResult::cleanup()
{
if ( d->result ) {
mysql_free_result( d->result );
}
d->result = NULL;
d->row = NULL;
setAt( -1 );
setActive( FALSE );
}
bool QMYSQLUTFResult::fetch( int i )
{
if ( isForwardOnly() ) { // fake a forward seek
if ( at() < i ) {
int x = i - at();
while ( --x && fetchNext() );
return fetchNext();
} else {
return FALSE;
}
}
if ( at() == i )
return TRUE;
mysql_data_seek( d->result, i );
d->row = mysql_fetch_row( d->result );
if ( !d->row )
return FALSE;
setAt( i );
return TRUE;
}
bool QMYSQLUTFResult::fetchNext()
{
d->row = mysql_fetch_row( d->result );
if ( !d->row )
return FALSE;
setAt( at() + 1 );
return TRUE;
}
bool QMYSQLUTFResult::fetchLast()
{
if ( isForwardOnly() ) { // fake this since MySQL can't seek on forward only queries
bool success = fetchNext(); // did we move at all?
while ( fetchNext() );
return success;
}
my_ulonglong numRows = mysql_num_rows( d->result );
if ( !numRows )
return FALSE;
return fetch( numRows - 1 );
}
bool QMYSQLUTFResult::fetchFirst()
{
if ( isForwardOnly() ) // again, fake it
return fetchNext();
return fetch( 0 );
}
QVariant QMYSQLUTFResult::data( int field )
{
if ( !isSelect() || field >= (int) d->fieldTypes.count() ) {
qWarning( "QMYSQLUTFResult::data: column %d out of range", field );
return QVariant();
}
QString val( d->row[field] );
switch ( d->fieldTypes.at( field ) ) {
case QVariant::LongLong:
return QVariant( val.toLongLong() );
case QVariant::ULongLong:
return QVariant( val.toULongLong() );
case QVariant::Int:
return QVariant( val.toInt() );
case QVariant::UInt:
return QVariant( val.toUInt() );
case QVariant::Double:
return QVariant( val.toDouble() );
case QVariant::Date:
if ( val.isEmpty() ) {
return QVariant( QDate() );
} else {
return QVariant( QDate::fromString( val, Qt::ISODate ) );
}
case QVariant::Time:
if ( val.isEmpty() ) {
return QVariant( QTime() );
} else {
return QVariant( QTime::fromString( val, Qt::ISODate ) );
}
case QVariant::DateTime:
if ( val.isEmpty() )
return QVariant( QDateTime() );
if ( val.length() == 14u )
// TIMESTAMPS have the format yyyyMMddhhmmss
val.insert(4, "-").insert(7, "-").insert(10, 'T').insert(13, ':').insert(16, ':');
return QVariant( QDateTime::fromString( val, Qt::ISODate ) );
case QVariant::ByteArray: {
unsigned long* fl = mysql_fetch_lengths( d->result );
QByteArray ba;
ba.duplicate( d->row[field], fl[field] );
return QVariant( ba );
}
default:
case QVariant::String:
case QVariant::CString:
return QVariant(QString::fromUtf8(d->row[field] ));
}
#ifdef QT_CHECK_RANGE
qWarning("QMYSQLUTFResult::data: unknown data type");
#endif
return QVariant();
}
bool QMYSQLUTFResult::isNull( int field )
{
if ( d->row[field] == NULL )
return TRUE;
return FALSE;
}
bool QMYSQLUTFResult::reset ( const QString& query )
{
if ( !driver() )
return FALSE;
if ( !driver()-> isOpen() || driver()->isOpenError() )
return FALSE;
cleanup();
QCString encQuery = query.utf8();
if ( mysql_real_query( d->mysql, encQuery, encQuery.length() ) ) {
setLastError( qMakeError("Unable to execute query", QSqlError::Statement, d ) );
return FALSE;
}
if ( isForwardOnly() ) {
if ( isActive() || isValid() ) // have to empty the results from previous query
fetchLast();
d->result = mysql_use_result( d->mysql );
} else {
d->result = mysql_store_result( d->mysql );
}
if ( !d->result && mysql_field_count( d->mysql ) > 0 ) {
setLastError( qMakeError( "Unable to store result", QSqlError::Statement, d ) );
return FALSE;
}
int numFields = mysql_field_count( d->mysql );
setSelect( !( numFields == 0) );
d->fieldTypes.resize( numFields );
if ( isSelect() ) {
for( int i = 0; i < numFields; i++) {
MYSQL_FIELD* field = mysql_fetch_field_direct( d->result, i );
if ( field->type == FIELD_TYPE_DECIMAL )
d->fieldTypes[i] = QVariant::String;
else
d->fieldTypes[i] = qDecodeMYSQLType( field->type, field->flags );
}
}
setActive( TRUE );
return TRUE;
}
int QMYSQLUTFResult::size()
{
return isSelect() ? (int)mysql_num_rows( d->result ) : -1;
}
int QMYSQLUTFResult::numRowsAffected()
{
return (int)mysql_affected_rows( d->mysql );
}
/////////////////////////////////////////////////////////
static void qServerEnd()
{
#ifndef Q_NO_MYSQL_EMBEDDED
# if MYSQL_VERSION_ID >= 40000
mysql_server_end();
# endif // MYSQL_VERSION_ID
#endif // Q_NO_MYSQL_EMBEDDED
}
static void qServerInit()
{
#ifndef Q_NO_MYSQL_EMBEDDED
# if MYSQL_VERSION_ID >= 40000
if ( qMySqlInitHandledByUser || qMySqlConnectionCount > 1 )
return;
// this should only be called once
// has no effect on client/server library
// but is vital for the embedded lib
if ( mysql_server_init( 0, 0, 0 ) ) {
# ifdef QT_CHECK_RANGE
qWarning( "QMYSQLUTFDriver::qServerInit: unable to start server." );
# endif
}
# endif // MYSQL_VERSION_ID
#endif // Q_NO_MYSQL_EMBEDDED
}
QMYSQLUTFDriver::QMYSQLUTFDriver( QObject * parent, const char * name )
: QSqlDriver( parent, name ? name : QMYSQLUTF_DRIVER_NAME )
{
init();
qServerInit();
}
/*!
Create a driver instance with an already open connection handle.
*/
QMYSQLUTFDriver::QMYSQLUTFDriver( MYSQL * con, QObject * parent, const char * name )
: QSqlDriver( parent, name ? name : QMYSQLUTF_DRIVER_NAME )
{
init();
if ( con ) {
d->mysql = (MYSQL *) con;
setOpen( TRUE );
setOpenError( FALSE );
if (qMySqlConnectionCount == 1)
qMySqlInitHandledByUser = TRUE;
} else {
qServerInit();
}
}
void QMYSQLUTFDriver::init()
{
// qSqlOpenExtDict()->insert( this, new QMYSQLUTFOpenExtension(this) );
d = new QMYSQLUTFDriverPrivate();
d->mysql = 0;
qMySqlConnectionCount++;
}
QMYSQLUTFDriver::~QMYSQLUTFDriver()
{
qMySqlConnectionCount--;
if (qMySqlConnectionCount == 0 && !qMySqlInitHandledByUser)
qServerEnd();
delete d;
if ( !qSqlOpenExtDict()->isEmpty() ) {
QSqlOpenExtension *ext = qSqlOpenExtDict()->take( this );
delete ext;
}
}
bool QMYSQLUTFDriver::hasFeature( DriverFeature f ) const
{
switch ( f ) {
case Transactions:
// CLIENT_TRANSACTION should be defined in all recent mysql client libs > 3.23.34
#ifdef CLIENT_TRANSACTIONS
if ( d->mysql ) {
if ( ( d->mysql->server_capabilities & CLIENT_TRANSACTIONS ) == CLIENT_TRANSACTIONS )
return TRUE;
}
#endif
return FALSE;
case QuerySize:
return TRUE;
case BLOB:
return TRUE;
case Unicode:
return TRUE;
default:
return FALSE;
}
}
bool QMYSQLUTFDriver::open( const QString& db,
const QString& user,
const QString& password,
const QString& host,
int port)
{
return open(db,user,password,host,port,"");
}
bool QMYSQLUTFDriver::open( const QString& db,
const QString& user,
const QString& password,
const QString& host,
int port,
const QString& connOpts )
{
if ( isOpen() )
close();
unsigned int optionFlags = 0;
QStringList raw = QStringList::split( ';', connOpts );
QStringList opts;
QStringList::ConstIterator it;
// extract the real options from the string
for ( it = raw.begin(); it != raw.end(); ++it ) {
QString tmp( *it );
int idx;
if ( (idx = tmp.find( '=' )) != -1 ) {
QString val( tmp.mid( idx + 1 ) );
val.simplifyWhiteSpace();
if ( val == "TRUE" || val == "1" )
opts << tmp.left( idx );
else
qWarning( "QMYSQLUTFDriver::open: Illegal connect option value '%s'", tmp.latin1() );
} else {
opts << tmp;
}
}
for ( it = opts.begin(); it != opts.end(); ++it ) {
QString opt( (*it).upper() );
if ( opt == "CLIENT_COMPRESS" )
optionFlags |= CLIENT_COMPRESS;
else if ( opt == "CLIENT_FOUND_ROWS" )
optionFlags |= CLIENT_FOUND_ROWS;
else if ( opt == "CLIENT_IGNORE_SPACE" )
optionFlags |= CLIENT_IGNORE_SPACE;
else if ( opt == "CLIENT_INTERACTIVE" )
optionFlags |= CLIENT_INTERACTIVE;
else if ( opt == "CLIENT_NO_SCHEMA" )
optionFlags |= CLIENT_NO_SCHEMA;
else if ( opt == "CLIENT_ODBC" )
optionFlags |= CLIENT_ODBC;
else if ( opt == "CLIENT_SSL" )
optionFlags |= CLIENT_SSL;
else
qWarning( "QMYSQLUTFDriver::open: Unknown connect option '%s'", (*it).latin1() );
}
if ( (d->mysql = mysql_init((MYSQL*) 0)) &&
mysql_real_connect( d->mysql,
host,
user,
password,
db.isNull() ? QString("") : db,
(port > -1) ? port : 0,
NULL,
optionFlags ) )
{
if ( !db.isEmpty() && mysql_select_db( d->mysql, db )) {
setLastError( qMakeError("Unable open database '" + db + "'", QSqlError::Connection, d ) );
mysql_close( d->mysql );
setOpenError( TRUE );
return FALSE;
}
} else {
setLastError( qMakeError( "Unable to connect", QSqlError::Connection, d ) );
mysql_close( d->mysql );
setOpenError( TRUE );
return FALSE;
}
setOpen( TRUE );
setOpenError( FALSE );
return TRUE;
}
void QMYSQLUTFDriver::close()
{
if ( isOpen() ) {
mysql_close( d->mysql );
setOpen( FALSE );
setOpenError( FALSE );
}
}
QSqlQuery QMYSQLUTFDriver::createQuery() const
{
return QSqlQuery( new QMYSQLUTFResult( this ) );
}
QStringList QMYSQLUTFDriver::tables( const QString& typeName ) const
{
QStringList tl;
if ( !isOpen() )
return tl;
if ( !typeName.isEmpty() && !(typeName.toInt() & (int)QSql::Tables) )
return tl;
MYSQL_RES* tableRes = mysql_list_tables( d->mysql, NULL );
MYSQL_ROW row;
int i = 0;
while ( tableRes && TRUE ) {
mysql_data_seek( tableRes, i );
row = mysql_fetch_row( tableRes );
if ( !row )
break;
tl.append( QString(row[0]) );
i++;
}
mysql_free_result( tableRes );
return tl;
}
QSqlIndex QMYSQLUTFDriver::primaryIndex( const QString& tablename ) const
{
QSqlIndex idx;
if ( !isOpen() )
return idx;
QSqlQuery i = createQuery();
QString stmt( "show index from %1;" );
QSqlRecord fil = record( tablename );
i.exec( stmt.arg( tablename ) );
while ( i.isActive() && i.next() ) {
if ( i.value(2).toString() == "PRIMARY" ) {
idx.append( *fil.field( i.value(4).toString() ) );
idx.setCursorName( i.value(0).toString() );
idx.setName( i.value(2).toString() );
}
}
return idx;
}
QSqlRecord QMYSQLUTFDriver::record( const QString& tablename ) const
{
QSqlRecord fil;
if ( !isOpen() )
return fil;
MYSQL_RES* r = mysql_list_fields( d->mysql, tablename.local8Bit().data(), 0);
if ( !r ) {
return fil;
}
MYSQL_FIELD* field;
while ( (field = mysql_fetch_field( r ))) {
QSqlField f ( QString( field->name ) , qDecodeMYSQLType( (int)field->type, field->flags ) );
fil.append ( f );
}
mysql_free_result( r );
return fil;
}
QSqlRecord QMYSQLUTFDriver::record( const QSqlQuery& query ) const
{
QSqlRecord fil;
if ( !isOpen() )
return fil;
if ( query.isActive() && query.isSelect() && query.driver() == this ) {
QMYSQLUTFResult* result = (QMYSQLUTFResult*)query.result();
QMYSQLUTFResultPrivate* p = result->d;
if ( !mysql_errno( p->mysql ) ) {
for ( ;; ) {
MYSQL_FIELD* f = mysql_fetch_field( p->result );
if ( f ) {
QSqlField fi( QString((const char*)f->name), qDecodeMYSQLType( f->type, f->flags ) );
fil.append( fi );
} else
break;
}
}
mysql_field_seek( p->result, 0 );
}
return fil;
}
QSqlRecordInfo QMYSQLUTFDriver::recordInfo( const QString& tablename ) const
{
QSqlRecordInfo info;
if ( !isOpen() )
return info;
MYSQL_RES* r = mysql_list_fields( d->mysql, tablename.local8Bit().data(), 0);
if ( !r ) {
return info;
}
MYSQL_FIELD* field;
while ( (field = mysql_fetch_field( r ))) {
info.append ( QSqlFieldInfo( QString( field->name ),
qDecodeMYSQLType( (int)field->type, field->flags ),
IS_NOT_NULL( field->flags ),
(int)field->length,
(int)field->decimals,
QString( field->def ),
(int)field->type ) );
}
mysql_free_result( r );
return info;
}
QSqlRecordInfo QMYSQLUTFDriver::recordInfo( const QSqlQuery& query ) const
{
QSqlRecordInfo info;
if ( !isOpen() )
return info;
if ( query.isActive() && query.isSelect() && query.driver() == this ) {
QMYSQLUTFResult* result = (QMYSQLUTFResult*)query.result();
QMYSQLUTFResultPrivate* p = result->d;
if ( !mysql_errno( p->mysql ) ) {
for ( ;; ) {
MYSQL_FIELD* field = mysql_fetch_field( p->result );
if ( field ) {
info.append ( QSqlFieldInfo( QString( field->name ),
qDecodeMYSQLType( (int)field->type, field->flags ),
IS_NOT_NULL( field->flags ),
(int)field->length,
(int)field->decimals,
QVariant(),
(int)field->type ) );
} else
break;
}
}
mysql_field_seek( p->result, 0 );
}
return info;
}
MYSQL* QMYSQLUTFDriver::mysql()
{
return d->mysql;
}
bool QMYSQLUTFDriver::beginTransaction()
{
#ifndef CLIENT_TRANSACTIONS
return FALSE;
#endif
if ( !isOpen() ) {
#ifdef QT_CHECK_RANGE
qWarning( "QMYSQLUTFDriver::beginTransaction: Database not open" );
#endif
return FALSE;
}
if ( mysql_query( d->mysql, "BEGIN WORK" ) ) {
setLastError( qMakeError("Unable to begin transaction", QSqlError::Statement, d ) );
return FALSE;
}
return TRUE;
}
bool QMYSQLUTFDriver::commitTransaction()
{
#ifndef CLIENT_TRANSACTIONS
return FALSE;
#endif
if ( !isOpen() ) {
#ifdef QT_CHECK_RANGE
qWarning( "QMYSQLUTFDriver::commitTransaction: Database not open" );
#endif
return FALSE;
}
if ( mysql_query( d->mysql, "COMMIT" ) ) {
setLastError( qMakeError("Unable to commit transaction", QSqlError::Statement, d ) );
return FALSE;
}
return TRUE;
}
bool QMYSQLUTFDriver::rollbackTransaction()
{
#ifndef CLIENT_TRANSACTIONS
return FALSE;
#endif
if ( !isOpen() ) {
#ifdef QT_CHECK_RANGE
qWarning( "QMYSQLUTFDriver::rollbackTransaction: Database not open" );
#endif
return FALSE;
}
if ( mysql_query( d->mysql, "ROLLBACK" ) ) {
setLastError( qMakeError("Unable to rollback transaction", QSqlError::Statement, d ) );
return FALSE;
}
return TRUE;
}
QString QMYSQLUTFDriver::formatValue( const QSqlField* field, bool trimStrings ) const
{
QString r;
if ( field->isNull() ) {
r = nullText();
} else {
switch( field->type() ) {
case QVariant::ByteArray: {
const QByteArray ba = field->value().toByteArray();
// buffer has to be at least length*2+1 bytes
char* buffer = new char[ ba.size() * 2 + 1 ];
/*uint escapedSize =*/ mysql_escape_string( buffer, ba.data(), ba.size() );
r.append("'").append(buffer).append("'");
delete[] buffer;
}
break;
case QVariant::String:
case QVariant::CString: {
// Escape '\' characters
r = QSqlDriver::formatValue( field );
r.replace( "\\", "\\\\" );
break;
}
default:
r = QSqlDriver::formatValue( field, trimStrings );
}
}
return r;
}
QMYSQLUTFDriverPlugin::QMYSQLUTFDriverPlugin()
: QSqlDriverPlugin()
{
}
QSqlDriver* QMYSQLUTFDriverPlugin::create( const QString &name )
{
if ( name == "QMYSQLUTF8" ) {
QMYSQLUTFDriver* driver = new QMYSQLUTFDriver();
return driver;
}
return 0;
}
QStringList QMYSQLUTFDriverPlugin::keys() const
{
QStringList l;
l << "QMYSQLUTF8";
return l;
}
Q_EXPORT_PLUGIN( QMYSQLUTFDriverPlugin )

View File

@ -0,0 +1,115 @@
// qt3-mysql-utf.h
//
// Qt3 SQL plug-in for MySQL with Unicode UTF-8 support
// (C) Copyright 2018 Fred Gleason <fredg@paravelsystems.com>
//
// Based on the stock Qt3 MySQL driver
// Copyright (C) 1992-2008 Trolltech ASA. All rights reserved.
//
// 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 QT3_MYSQL_UTF_H
#define QT3_MYSQL_UTF_H
#include <qsqldriver.h>
#include <qsqldriverplugin.h>
#include <qsqlresult.h>
#include <qsqlfield.h>
#include <qsqlindex.h>
#include <mysql/mysql.h>
#define Q_EXPORT_SQLDRIVER_MYSQL
class QMYSQLUTFDriverPlugin : public QSqlDriverPlugin
{
public:
QMYSQLUTFDriverPlugin();
QSqlDriver* create( const QString & );
QStringList keys() const;
};
class QMYSQLUTFDriverPrivate;
class QMYSQLUTFResultPrivate;
class QMYSQLUTFDriver;
class QSqlRecordInfo;
class QMYSQLUTFResult : public QSqlResult
{
friend class QMYSQLUTFDriver;
public:
QMYSQLUTFResult( const QMYSQLUTFDriver* db );
~QMYSQLUTFResult();
MYSQL_RES* result();
protected:
void cleanup();
bool fetch( int i );
bool fetchNext();
bool fetchLast();
bool fetchFirst();
QVariant data( int field );
bool isNull( int field );
bool reset ( const QString& query );
int size();
int numRowsAffected();
private:
QMYSQLUTFResultPrivate* d;
};
class Q_EXPORT_SQLDRIVER_MYSQL QMYSQLUTFDriver : public QSqlDriver
{
friend class QMYSQLUTFResult;
public:
QMYSQLUTFDriver( QObject * parent=0, const char * name=0 );
QMYSQLUTFDriver( MYSQL * con, QObject * parent=0, const char * name=0 );
~QMYSQLUTFDriver();
bool hasFeature( DriverFeature f ) const;
bool open( const QString & db,
const QString & user = QString::null,
const QString & password = QString::null,
const QString & host = QString::null,
int port = -1 );
void close();
QSqlQuery createQuery() const;
QStringList tables( const QString& user ) const;
QSqlIndex primaryIndex( const QString& tablename ) const;
QSqlRecord record( const QString& tablename ) const;
QSqlRecord record( const QSqlQuery& query ) const;
QSqlRecordInfo recordInfo( const QString& tablename ) const;
QSqlRecordInfo recordInfo( const QSqlQuery& query ) const;
QString formatValue( const QSqlField* field,
bool trimStrings ) const;
MYSQL* mysql();
// ### remove me for 4.0
bool open( const QString& db,
const QString& user,
const QString& password,
const QString& host,
int port,
const QString& connOpts );
protected:
bool beginTransaction();
bool commitTransaction();
bool rollbackTransaction();
private:
void init();
QMYSQLUTFDriverPrivate* d;
};
#endif // QT3_MYSQL_UTF_H

View File

@ -0,0 +1,26 @@
# qt3-mysql-utf.pro
#
# The QMake project file for qt3-mysql-utf
#
# (C) Copyright 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.
TEMPLATE = plugin
SOURCES += qsqlextension.cpp
SOURCES += qt3-mysql-utf.cpp
HEADERS += qsqlextension.h
HEADERS += qt3-mysql-utf.h

View File

@ -76,74 +76,32 @@ QString RDCheckDateTime(QDate const &date, QString const &format)
QString RDEscapeString(QString const &str)
{
QString orig=str;
QString res;
for(unsigned i=0;i<str.length();i++) {
switch(((const char *)str)[i]) {
case '(':
res+=QString("\\\(");
break;
QChar c=orig.ref(i);
switch(c) {
case '"':
res+=QString("\\\"");
break;
case ')':
res+=QString("\\)");
break;
case '`':
res+=QString("\\`");
break;
case '{':
res+=QString("\\\{");
break;
case '\'':
res+=QString("\\\'");
break;
case '"':
res+=QString("\\\"");
break;
case '\\':
res+=QString("\\");
res+=QString("\\");
break;
case '`':
res+=QString("\\`");
break;
case '[':
res+=QString("\\\[");
break;
case '\'':
res+=QString("\\\'");
break;
case '\\':
res+=QString("\\");
res+=QString("\\");
break;
case '?':
res+=QString("\\\?");
break;
case ' ':
res+=QString("\\ ");
break;
case '&':
res+=QString("\\&");
break;
case ';':
res+=QString("\\;");
break;
case '<':
res+=QString("\\<");
break;
case '>':
res+=QString("\\>");
break;
case '|':
res+=QString("\\|");
break;
default:
res+=((const char *)str)[i];
break;
default:
res+=c;
break;
}
}

View File

@ -916,6 +916,7 @@ void EditCart::okData()
rdcart_cart->setPublisher(rdcart_controls.publisher_edit->text());
rdcart_cart->setConductor(rdcart_controls.conductor_edit->text());
rdcart_cart->setComposer(rdcart_controls.composer_edit->text());
printf("USERDEF: %s\n",(const char *)rdcart_controls.user_defined_edit->text());
rdcart_cart->setUserDefined(rdcart_controls.user_defined_edit->text());
rdcart_cart->
setUsageCode((RDCart::UsageCode)rdcart_usage_box->currentItem());

View File

@ -108,6 +108,7 @@ make
%install
rm -rf $RPM_BUILD_ROOT
make install DESTDIR=$RPM_BUILD_ROOT
rm -f $RPM_BUILD_ROOT/@LOCAL_PREFIX@/@RD_LIB_PATH@/libqmysqlutf*
mkdir $RPM_BUILD_ROOT/.qt
touch $RPM_BUILD_ROOT/.qt/qt
rm -f $RPM_BUILD_ROOT/lib/security/pam_rd.la
@ -257,6 +258,7 @@ rm -rf $RPM_BUILD_ROOT
%files
%defattr(-,root,root)
@LOCAL_PREFIX@/@RD_LIB_PATH@/rivendell/*.rlm
@LOCAL_PREFIX@/@RD_LIB_PATH@/qt-3.3/plugins/sqldrivers/libqmysqlutf.so
@LOCAL_PREFIX@/bin/rdadmin
@LOCAL_PREFIX@/bin/rdairplay
@LOCAL_PREFIX@/bin/rdpanel