Blokkal
an Extendable KDE Blogging Client
SourceForge.net Logo

xmlrpcjob.cpp

00001 /***************************************************************************
00002  *   Copyright (C) 2007 - 2008 by Martin Mueller                           *
00003  *   orvio@orvio.de                                                        *
00004  *                                                                         *
00005  *   This program is free software; you can redistribute it and/or modify  *
00006  *   it under the terms of the GNU General Public License as published by  *
00007  *   the Free Software Foundation; either version 2 of the License, or     *
00008  *   (at your option) any later version.                                   *
00009  *                                                                         *
00010  *   This program is distributed in the hope that it will be useful,       *
00011  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
00012  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
00013  *   GNU General Public License for more details.                          *
00014  *                                                                         *
00015  *   You should have received a copy of the GNU General Public License     *
00016  *   along with this program; if not, write to the                         *
00017  *   Free Software Foundation, Inc.,                                       *
00018  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
00019  ***************************************************************************/
00020 #include "xmlrpcjob.h"
00021 #include "xmlrpcjob.moc"
00022 
00023 #include "util.h"
00024 
00025 #include <kdebug.h>
00026 #include <kcodecs.h>
00027 #include <klocale.h>
00028 #include <kurl.h>
00029 #include <kio/job.h>
00030 
00031 #include <QDomElement>
00032 #include <QVariant>
00033 #include <QDateTime>
00034 
00035 class Blokkal::Io::XmlRpcJob::Private {
00036 public:
00037         Private( void ) :
00038         responseBuffer( QByteArray() ),
00039         response( QVariant() ),
00040         responseType( ResponseError ){}
00041 
00042         QByteArray responseBuffer;
00043         QVariant response;
00044         ResponseType responseType;
00045 };
00046 
00047 Blokkal::Io::XmlRpcJob::XmlRpcJob( const KUrl & url,
00048                                    const QString & methodName,
00049                                    const QList< QVariant > & params,
00050                                    unsigned int workArounds,
00051                                    bool showProgressInfo ) :
00052 Job(),
00053 d( new Private() )
00054 {
00055         QByteArray postData;
00056 
00057         //generate xml-rpc call data
00058         QDomDocument callDocument;
00059         callDocument.appendChild( callDocument.createProcessingInstruction( "xml", "version=\"1.0\"" ) );
00060 
00061         QDomElement methodElement = callDocument.createElement( "methodCall" );
00062         callDocument.appendChild( methodElement );
00063 
00064         QDomElement currentElement = callDocument.createElement( "methodName" );
00065         currentElement.appendChild( callDocument.createTextNode( methodName ) );
00066         methodElement.appendChild( currentElement );
00067 
00068         QDomElement paramsElement = callDocument.createElement( "params" );
00069         methodElement.appendChild( paramsElement );
00070         for( QList< QVariant >::ConstIterator it = params.begin();
00071              it != params.end();
00072              ++it )
00073         {
00074                 QDomElement paramElement = callDocument.createElement( "param" );
00075                 paramsElement.appendChild( paramElement );
00076 
00077                 encodeData( paramElement, *it, workArounds );
00078         }
00079 
00080         QTextStream stream( &postData, QIODevice::WriteOnly );
00081         callDocument.save( stream, 3 );
00082 
00083         //kDebug() << postData << endl;
00084 
00085         KIO::TransferJob * job = KIO::http_post( KUrl( url ),
00086                                                  postData,
00087                                                  showProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo );
00088 
00089         job->addMetaData( "UserAgent", "Blokkal/IO XML-RPC Job" );
00090         job->addMetaData( "content-type", "Content-Type: text/xml; charset=utf-8" );
00091         job->addMetaData( "ConnectTimeout", "300" );
00092 
00093         connect( job,
00094                  SIGNAL( data( KIO::Job *, const QByteArray & ) ),
00095                  SLOT( slotDataArrived( KIO::Job *, const QByteArray & ) ) );
00096         addSubjob( job );
00097 }
00098 
00099 Blokkal::Io::XmlRpcJob::~XmlRpcJob( void )
00100 {
00101         delete d;
00102 }
00103 
00104 void Blokkal::Io::XmlRpcJob::slotDataArrived( KIO::Job * job, const QByteArray & data )
00105 {
00106         if( subjobs().isEmpty() || job != subjobs().first() ) {
00107                 return;
00108         }
00109         
00110         d->responseBuffer.append( data );
00111 }
00112 
00113 void Blokkal::Io::XmlRpcJob::jobFinished( void )
00114 {
00115         //kDebug() << d->responseBuffer<< endl;
00116         if( error() ) {
00117                 d->responseType = ResponseError;
00118                 return;
00119         }
00120         
00121         QString errorText;
00122         QDomDocument responseDocument;
00123         if( responseDocument.setContent( d->responseBuffer, &errorText ) ) {
00124                 QDomNode rootNode = responseDocument.firstChild();
00125                 for( ; !rootNode.isNull(); rootNode = rootNode.nextSibling() ) {
00126                         if( rootNode.isElement() ) {
00127                                 break;
00128                         }
00129                 }
00130                 
00131                 if( rootNode.firstChild().nodeName() == "params" ) {
00132                         //handle response
00133                         QDomNode valueNode = rootNode.firstChild().firstChild().firstChild();
00134                         d->response = decodeData( valueNode.toElement() );
00135                         d->responseType = ResponseSuccess;
00136                 }
00137                 else if( rootNode.firstChild().nodeName() == "fault" ) {
00138                         //handle fault
00139                         setError( -1 );
00140                         d->response = decodeData( rootNode.firstChild().firstChild().toElement() );
00141                         if( d->response.type() == QVariant::Map ) {
00142                                 setErrorText( ( ( d->response.toMap() )["faultString"] ).toString() );
00143                         }
00144                         d->responseType = ResponseFault;
00145                 }
00146                 else {
00147                         setError( -1 );
00148                         setErrorText( i18n( "Could not parse response." ) );
00149                         d->responseType = ResponseError;
00150                 }
00151         }
00152         else {
00153                 setError( -1 );
00154                 setErrorText( errorText );
00155                 d->responseType = ResponseError;
00156         }
00157         
00158 }
00159 
00160 const QVariant & Blokkal::Io::XmlRpcJob::response( void ) const
00161 {
00162         return d->response;
00163 }
00164 
00165 Blokkal::Io::XmlRpcJob::ResponseType Blokkal::Io::XmlRpcJob::responseType( void ) const
00166 {
00167         return d->responseType;
00168 }
00169 
00170 void Blokkal::Io::XmlRpcJob::encodeData( QDomElement & containerElement, QVariant data, unsigned int workArounds )
00171 {
00172         QDomDocument callDocument = containerElement.ownerDocument();
00173         QDomElement valueElement = callDocument.createElement( "value" );
00174         containerElement.appendChild( valueElement );
00175         QDomElement currentElement;
00176         switch ( data.type() )  {
00177         case QVariant::Int:
00178                 currentElement = callDocument.createElement( "int" );
00179                 valueElement.appendChild( currentElement );
00180                 currentElement.appendChild( callDocument.createTextNode( data.toString() ) );
00181                 break;
00182         case QVariant::Bool:
00183                 currentElement = callDocument.createElement( "boolean" );
00184                 valueElement.appendChild( currentElement );
00185                 currentElement.appendChild( callDocument.createTextNode( data.toBool() ? "1" : "0" ) );
00186                 break;
00187         case QVariant::String:
00188                 currentElement = callDocument.createElement( "string" );
00189                 valueElement.appendChild( currentElement );
00190                 currentElement.appendChild( callDocument.createTextNode( data.toString() ) );
00191                 break;
00192         case QVariant::Double:
00193                 currentElement = callDocument.createElement( "double" );
00194                 valueElement.appendChild( currentElement );
00195                 currentElement.appendChild( callDocument.createTextNode( data.toString() ) );
00196                 break;
00197         case QVariant::DateTime:
00198                 currentElement = callDocument.createElement( "dateTime.iso8601" );
00199                 valueElement.appendChild( currentElement );
00200                 if( workArounds & WordPressDateWorkAround  ) {
00201                         currentElement.appendChild( callDocument.createTextNode( data.toDateTime().toString( Qt::ISODate ).remove( '-' ) ) );
00202                 }
00203                 else {
00204                         currentElement.appendChild( callDocument.createTextNode( data.toDateTime().toString( Qt::ISODate ) ) );
00205                 }
00206                 break;
00207         case QVariant::ByteArray:
00208                 currentElement = callDocument.createElement( "base64" );
00209                 valueElement.appendChild( currentElement );
00210                 currentElement.appendChild( callDocument.createTextNode( KCodecs::base64Encode( data.toByteArray() ) ) );
00211                 break;
00212         case QVariant::Map:
00213                 {
00214                         currentElement = callDocument.createElement( "struct" );
00215                         valueElement.appendChild( currentElement );
00216                         const QMap<QString, QVariant> mapData = data.toMap();
00217                         for( QMap<QString, QVariant>::ConstIterator it = mapData.begin();
00218                                         it != mapData.end();
00219                                         ++it )
00220                         {
00221                                 QDomElement memberElement = callDocument.createElement( "member" );
00222                                 currentElement.appendChild( memberElement );
00223                                 QDomElement nameElement = callDocument.createElement( "name" );
00224                                 memberElement.appendChild( nameElement );
00225                                 nameElement.appendChild( callDocument.createTextNode( it.key() ) );
00226                                 encodeData( memberElement, it.value(), workArounds );
00227                         }
00228                 }
00229                 break;
00230         case QVariant::List:
00231         case QVariant::StringList:
00232                 {
00233                         currentElement = callDocument.createElement( "array" );
00234                         valueElement.appendChild( currentElement );
00235                         QDomElement dataElement = callDocument.createElement( "data" );
00236                         currentElement.appendChild( dataElement );
00237                         const QList<QVariant> arrayData = data.toList();
00238                         for( QList<QVariant>::ConstIterator it = arrayData.begin();
00239                                         it != arrayData.end();
00240                                         ++it )
00241                         {
00242                                 encodeData( dataElement, *it, workArounds );
00243                         }
00244                 }
00245                 break;
00246         default:
00247                 kError() << "encountered invalid parameter type: " << data.type() << endl;
00248                 break;
00249         }
00250 }
00251 
00252 QVariant Blokkal::Io::XmlRpcJob::decodeData( const QDomElement & valueElement )
00253 {
00254         Q_ASSERT( valueElement.nodeName() == "value" );
00255 
00256         QDomElement typeElement = valueElement.firstChild().toElement();
00257         QString type = typeElement.nodeName();
00258         if( type == "int" || type == "i4" ) {
00259                 return QVariant( typeElement.text().toInt() );
00260         }
00261 
00262         if ( type == "boolean" ) {
00263                 if ( typeElement.text().toLower() == "true" || typeElement.text() == "1" ) {
00264                         return QVariant( TRUE );
00265                 }
00266                 else {
00267                         return QVariant( FALSE );
00268                 }
00269         }
00270 
00271         if( type == "double" ) {
00272                 return QVariant( typeElement.text().toDouble() );
00273         }
00274 
00275         if ( type == "dateTime.iso8601" ) {
00276                 if( typeElement.text().contains( '-' ) ) {
00277                         return QVariant( QDateTime::fromString( typeElement.text(), Qt::ISODate ) );
00278                 }
00279                 else {
00280                         return QVariant( QDateTime::fromString( typeElement.text().insert( 6, '-' ).insert( 4, '-' ), Qt::ISODate ) );
00281                 }
00282         }
00283 
00284         if ( type == "base64" ) {
00285                 return QVariant( KCodecs::base64Decode( typeElement.text().toLatin1() ) );
00286         }
00287 
00288         if( type == "struct" ) {
00289                 QMap<QString, QVariant> map;
00290                 QDomNode memberNode = typeElement.firstChild();
00291                 while ( !memberNode.isNull() ) {
00292                         const QString name = memberNode.namedItem( "name" ).toElement().text();
00293                         const QVariant value = decodeData( memberNode.namedItem( "value" ).toElement() );
00294                         map[ name ] = value;
00295                         memberNode = memberNode.nextSibling();
00296                 }
00297                 return QVariant( map );
00298         }
00299 
00300         if ( type == "array" ) {
00301                 QList<QVariant> list;
00302                 QDomNode valueNode = typeElement.firstChild().firstChild();
00303                 while ( !valueNode.isNull() ) {
00304                         list.append( decodeData( valueNode.toElement() ));
00305                         valueNode = valueNode.nextSibling();
00306                 }
00307                 return QVariant( list );
00308         }
00309 
00310         //no recognised type specified
00311         //maybe none specified and first child is a text node
00312         //type string is default
00313         if( type == "string"  ||
00314                         !valueElement.firstChild().toText().isNull() )
00315         {
00316                 return QVariant( Util::decodeUtf8Data( valueElement.text().toUtf8() ) );
00317         }
00318 
00319         //a type name was specified but we could not recognise it
00320         kError() << "encountered invalid type: " << type << endl;
00321         kError() << "returning invalid QVariant" << endl;
00322         return QVariant();
00323 }