KBlog Client Library
movabletype.cpp
00001 /* 00002 This file is part of the kblog library. 00003 00004 Copyright (c) 2004 Reinhold Kainhofer <reinhold@kainhofer.com> 00005 Copyright (c) 2006-2009 Christian Weilbach <christian_weilbach@web.de> 00006 Copyright (c) 2007-2008 Mike McQuaid <mike@mikemcquaid.com> 00007 00008 This library is free software; you can redistribute it and/or 00009 modify it under the terms of the GNU Library General Public 00010 License as published by the Free Software Foundation; either 00011 version 2 of the License, or (at your option) any later version. 00012 00013 This library is distributed in the hope that it will be useful, 00014 but WITHOUT ANY WARRANTY; without even the implied warranty of 00015 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00016 Library General Public License for more details. 00017 00018 You should have received a copy of the GNU Library General Public License 00019 along with this library; see the file COPYING.LIB. If not, write to 00020 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00021 Boston, MA 02110-1301, USA. 00022 */ 00023 00024 #include "movabletype.h" 00025 #include "movabletype_p.h" 00026 #include "blogpost.h" 00027 00028 #include <kxmlrpcclient/client.h> 00029 #include <kio/job.h> 00030 00031 #include <KDebug> 00032 #include <KLocale> 00033 #include <KDateTime> 00034 00035 #include <QtCore/QStringList> 00036 00037 using namespace KBlog; 00038 00039 MovableType::MovableType( const KUrl &server, QObject *parent ) 00040 : MetaWeblog( server, *new MovableTypePrivate, parent ) 00041 { 00042 kDebug(); 00043 } 00044 00045 MovableType::MovableType( const KUrl &server, MovableTypePrivate &dd, 00046 QObject *parent ) 00047 : MetaWeblog( server, dd, parent ) 00048 { 00049 kDebug(); 00050 } 00051 00052 MovableType::~MovableType() 00053 { 00054 kDebug(); 00055 } 00056 00057 QString MovableType::interfaceName() const 00058 { 00059 return QLatin1String( "Movable Type" ); 00060 } 00061 00062 void MovableType::listRecentPosts( int number ) 00063 { 00064 Q_D( MovableType ); 00065 kDebug(); 00066 QList<QVariant> args( d->defaultArgs( blogId() ) ); 00067 args << QVariant( number ); 00068 d->mXmlRpcClient->call( 00069 "metaWeblog.getRecentPosts", args, 00070 this, SLOT(slotListRecentPosts(QList<QVariant>,QVariant)), 00071 this, SLOT(slotError(int,QString,QVariant)), 00072 QVariant( number ) ); 00073 } 00074 00075 void MovableType::listTrackBackPings( KBlog::BlogPost *post ) 00076 { 00077 Q_D( MovableType ); 00078 kDebug(); 00079 QList<QVariant> args; 00080 args << QVariant( post->postId() ); 00081 unsigned int i = d->mCallCounter++; 00082 d->mCallMap[ i ] = post; 00083 d->mXmlRpcClient->call( 00084 "mt.getTrackbackPings", args, 00085 this, SLOT(slotListTrackbackPings(QList<QVariant>,QVariant)), 00086 this, SLOT(slotError(int,QString,QVariant)), 00087 QVariant( i ) ); 00088 } 00089 00090 void MovableType::fetchPost( BlogPost *post ) 00091 { 00092 Q_D( MovableType ); 00093 kDebug(); 00094 d->loadCategories(); 00095 if ( d->mCategoriesList.isEmpty() && post->categories( ).count() ) { 00096 d->mFetchPostCache << post; 00097 if ( d->mFetchPostCache.count() ) { 00098 // we are already trying to fetch another post, so we don't need to start 00099 // another listCategories() job 00100 return; 00101 } 00102 00103 connect( this, SIGNAL(listedCategories(QList<QMap<QString,QString> >)) 00104 , this, SLOT(slotTriggerFetchPost()) ); 00105 listCategories(); 00106 } else { 00107 MetaWeblog::fetchPost( post ); 00108 } 00109 } 00110 00111 void MovableType::createPost( BlogPost *post ) 00112 { 00113 // reimplemented because we do this: 00114 // http://comox.textdrive.com/pipermail/wp-testers/2005-July/000284.html 00115 kDebug(); 00116 Q_D( MovableType ); 00117 00118 // we need mCategoriesList to be loaded first, since we cannot use the post->categories() 00119 // names later, but we need to map them to categoryId of the blog 00120 d->loadCategories(); 00121 if(d->mCategoriesList.isEmpty()&&!post->categories().isEmpty()){ 00122 kDebug() << "No categories in the cache yet. Have to fetch them first."; 00123 d->mCreatePostCache << post; 00124 connect(this,SIGNAL(listedCategories(QList<QMap<QString,QString> >)), 00125 this,SLOT(slotTriggerCreatePost())); 00126 listCategories(); 00127 } 00128 else { 00129 bool publish = post->isPrivate(); 00130 // If we do setPostCategories() later than we disable publishing first. 00131 if( !post->categories().isEmpty() ){ 00132 post->setPrivate( true ); 00133 if ( d->mSilentCreationList.contains( post ) ) { 00134 kDebug()<< "Post already in mSilentCreationList, this *should* never happen!"; 00135 } else { 00136 d->mSilentCreationList << post; 00137 } 00138 } 00139 MetaWeblog::createPost( post ); 00140 // HACK: uuh this a bit ugly now... reenable the original publish argument, 00141 // since createPost should have parsed now 00142 post->setPrivate(publish); 00143 } 00144 } 00145 00146 void MovableType::modifyPost( BlogPost *post ) 00147 { 00148 // reimplemented because we do this: 00149 // http://comox.textdrive.com/pipermail/wp-testers/2005-July/000284.html 00150 kDebug(); 00151 Q_D( MovableType ); 00152 00153 // we need mCategoriesList to be loaded first, since we cannot use the post->categories() 00154 // names later, but we need to map them to categoryId of the blog 00155 d->loadCategories(); 00156 if(d->mCategoriesList.isEmpty() && !post->categories().isEmpty()){ 00157 kDebug() << "No categories in the cache yet. Have to fetch them first."; 00158 d->mModifyPostCache << post; 00159 connect(this,SIGNAL(listedCategories(QList<QMap<QString,QString> >)), 00160 this,SLOT(slotTriggerModifyPost())); 00161 listCategories(); 00162 } 00163 else { 00164 MetaWeblog::modifyPost( post ); 00165 } 00166 } 00167 00168 void MovableTypePrivate::slotTriggerCreatePost() 00169 { 00170 kDebug(); 00171 Q_Q( MovableType ); 00172 00173 q->disconnect(q,SIGNAL(listedCategories(QList<QMap<QString,QString> >)), 00174 q,SLOT(slotTriggerCreatePost())); 00175 // now we can recall createPost with the posts from the cache 00176 QList<BlogPost*>::Iterator it = mCreatePostCache.begin(); 00177 QList<BlogPost*>::Iterator end = mCreatePostCache.end(); 00178 for ( ; it!=end; it++ ) { 00179 q->createPost( *it ); 00180 } 00181 mCreatePostCache.clear(); 00182 } 00183 00184 void MovableTypePrivate::slotTriggerModifyPost() 00185 { 00186 kDebug(); 00187 Q_Q( MovableType ); 00188 00189 q->disconnect(q,SIGNAL(listedCategories(QList<QMap<QString,QString> >)), 00190 q,SLOT(slotTriggerModifyPost())); 00191 // now we can recall createPost with the posts from the cache 00192 QList<BlogPost*>::Iterator it = mModifyPostCache.begin(); 00193 QList<BlogPost*>::Iterator end = mModifyPostCache.end(); 00194 for ( ; it!=end; it++ ) { 00195 q->modifyPost( *it ); 00196 } 00197 mModifyPostCache.clear(); 00198 } 00199 00200 void MovableTypePrivate::slotTriggerFetchPost() 00201 { 00202 kDebug(); 00203 Q_Q( MovableType ); 00204 00205 q->disconnect( q,SIGNAL(listedCategories(QList<QMap<QString,QString> >)), 00206 q,SLOT(slotTriggerFetchPost()) ); 00207 QList<BlogPost*>::Iterator it = mFetchPostCache.begin(); 00208 QList<BlogPost*>::Iterator end = mFetchPostCache.end(); 00209 for ( ; it!=end; it++ ) { 00210 q->fetchPost( *it ); 00211 } 00212 mFetchPostCache.clear(); 00213 } 00214 00215 00216 MovableTypePrivate::MovableTypePrivate() 00217 { 00218 kDebug(); 00219 } 00220 00221 MovableTypePrivate::~MovableTypePrivate() 00222 { 00223 kDebug(); 00224 } 00225 00226 void MovableTypePrivate::slotCreatePost( const QList<QVariant> &result, const QVariant &id ) 00227 { 00228 Q_Q( MovableType ); 00229 // reimplement from Blogger1 to chainload the categories stuff before emit() 00230 kDebug(); 00231 KBlog::BlogPost *post = mCallMap[ id.toInt() ]; 00232 mCallMap.remove( id.toInt() ); 00233 00234 kDebug(); 00235 //array of structs containing ISO.8601 00236 // dateCreated, String userid, String postid, String content; 00237 kDebug () << "TOP:" << result[0].typeName(); 00238 if ( result[0].type() != QVariant::String && result[0].type() != QVariant::Int ) { 00239 kError() << "Could not read the postId, not a string or an integer."; 00240 emit q->errorPost( Blogger1::ParsingError, 00241 i18n( "Could not read the postId, not a string or an integer." ), 00242 post ); 00243 return; 00244 } 00245 QString serverID; 00246 if ( result[0].type() == QVariant::String ) { 00247 serverID = result[0].toString(); 00248 } 00249 if ( result[0].type() == QVariant::Int ) { 00250 serverID = QString( "%1" ).arg( result[0].toInt() ); 00251 } 00252 post->setPostId( serverID ); 00253 if ( mSilentCreationList.contains( post ) ) 00254 { 00255 // set the categories and publish afterwards 00256 setPostCategories( post, !post->isPrivate() ); 00257 } else { 00258 kDebug() << "emitting createdPost()" 00259 << "for title: \"" << post->title() 00260 << "\" server id: " << serverID; 00261 post->setStatus( KBlog::BlogPost::Created ); 00262 emit q->createdPost( post ); 00263 } 00264 } 00265 00266 void MovableTypePrivate::slotFetchPost( const QList<QVariant> &result, const QVariant &id ) 00267 { 00268 Q_Q( MovableType ); 00269 kDebug(); 00270 00271 KBlog::BlogPost *post = mCallMap[ id.toInt() ]; 00272 mCallMap.remove( id.toInt() ); 00273 00274 //array of structs containing ISO.8601 00275 // dateCreated, String userid, String postid, String content; 00276 kDebug () << "TOP:" << result[0].typeName(); 00277 if ( result[0].type() == QVariant::Map && readPostFromMap( post, result[0].toMap() ) ) { 00278 } else { 00279 kError() << "Could not fetch post out of the result from the server."; 00280 post->setError( i18n( "Could not fetch post out of the result from the server." ) ); 00281 post->setStatus( BlogPost::Error ); 00282 emit q->errorPost( Blogger1::ParsingError, 00283 i18n( "Could not fetch post out of the result from the server." ), post ); 00284 } 00285 if ( post->categories().isEmpty() ) { 00286 QList<QVariant> args( defaultArgs( post->postId() ) ); 00287 unsigned int i= mCallCounter++; 00288 mCallMap[ i ] = post; 00289 mXmlRpcClient->call( 00290 "mt.getPostCategories", args, 00291 q, SLOT(slotGetPostCategories(QList<QVariant>,QVariant)), 00292 q, SLOT(slotError(int,QString,QVariant)), 00293 QVariant( i ) ); 00294 } else { 00295 kDebug() << "Emitting fetchedPost()"; 00296 post->setStatus( KBlog::BlogPost::Fetched ); 00297 emit q->fetchedPost( post ); 00298 } 00299 } 00300 00301 void MovableTypePrivate::slotModifyPost( const QList<QVariant> &result, const QVariant &id ) 00302 { 00303 Q_Q( MovableType ); 00304 // reimplement from Blogger1 00305 kDebug(); 00306 KBlog::BlogPost *post = mCallMap[ id.toInt() ]; 00307 mCallMap.remove( id.toInt() ); 00308 00309 //array of structs containing ISO.8601 00310 // dateCreated, String userid, String postid, String content; 00311 kDebug() << "TOP:" << result[0].typeName(); 00312 if ( result[0].type() != QVariant::Bool && result[0].type() != QVariant::Int ) { 00313 kError() << "Could not read the result, not a boolean."; 00314 emit q->errorPost( Blogger1::ParsingError, 00315 i18n( "Could not read the result, not a boolean." ), 00316 post ); 00317 return; 00318 } 00319 if ( mSilentCreationList.contains( post ) ) { 00320 post->setStatus( KBlog::BlogPost::Created ); 00321 mSilentCreationList.removeOne( post ); 00322 emit q->createdPost( post ); 00323 } else { 00324 if( !post->categories().isEmpty() ){ 00325 setPostCategories( post, false ); 00326 } 00327 } 00328 } 00329 00330 void MovableTypePrivate::setPostCategories( BlogPost *post, bool publishAfterCategories ) 00331 { 00332 kDebug(); 00333 Q_Q( MovableType ); 00334 00335 unsigned int i = mCallCounter++; 00336 mCallMap[ i ] = post; 00337 mPublishAfterCategories[ i ] = publishAfterCategories; 00338 QList<QVariant> catList; 00339 QList<QVariant> args( defaultArgs( post->postId() ) ); 00340 00341 // map the categoryId of the server to the name 00342 QStringList categories = post->categories(); 00343 for( int j=0; j<categories.count(); j++ ){ 00344 for( int k=0; k<mCategoriesList.count(); k++ ){ 00345 if(mCategoriesList[k]["name"]==categories[j]){ 00346 kDebug() << "Matched category with name: " << categories[ j ] << " and id: " << mCategoriesList[ k ][ "categoryId" ]; 00347 QMap<QString,QVariant> category; 00348 //the first in the QStringList of post->categories() 00349 // is the primary category 00350 category["categoryId"]=mCategoriesList[k]["categoryId"].toInt(); 00351 catList<<QVariant( category ); 00352 break; 00353 } 00354 if(k==mCategoriesList.count()){ 00355 kDebug() << "Couldn't find categoryId for: " << categories[j]; 00356 } 00357 } 00358 } 00359 args<<QVariant( catList ); 00360 00361 mXmlRpcClient->call( 00362 "mt.setPostCategories", args, 00363 q, SLOT(slotSetPostCategories(QList<QVariant>,QVariant)), 00364 q, SLOT(slotError(int,QString,QVariant)), 00365 QVariant( i ) ); 00366 } 00367 00368 void MovableTypePrivate::slotGetPostCategories(const QList<QVariant>& result,const QVariant& id) 00369 { 00370 kDebug(); 00371 Q_Q( MovableType ); 00372 00373 int i = id.toInt(); 00374 BlogPost* post = mCallMap[ i ]; 00375 mCallMap.remove(i); 00376 00377 if ( result[ 0 ].type() != QVariant::List ) { 00378 kError() << "Could not read the result, not a list. Category fetching failed! We will still emit fetched post now."; 00379 emit q->errorPost( Blogger1::ParsingError, 00380 i18n( "Could not read the result - is not a list. Category fetching failed." ), post ); 00381 00382 post->setStatus( KBlog::BlogPost::Fetched ); 00383 emit q->fetchedPost( post ); 00384 } else { 00385 QList<QVariant> categoryList = result[ 0 ].toList(); 00386 QList<QString> newCatList; 00387 QList<QVariant>::ConstIterator it = categoryList.constBegin(); 00388 QList<QVariant>::ConstIterator end = categoryList.constEnd(); 00389 for ( ;it!=end;it++ ) { 00390 newCatList << ( *it ).toMap()[ "categoryName" ].toString(); 00391 } 00392 kDebug()<< "categories list: " << newCatList; 00393 post->setCategories( newCatList ); 00394 post->setStatus( KBlog::BlogPost::Fetched ); 00395 emit q->fetchedPost( post ); 00396 } 00397 } 00398 00399 void MovableTypePrivate::slotSetPostCategories(const QList<QVariant>& result,const QVariant& id) 00400 { 00401 kDebug(); 00402 Q_Q( MovableType ); 00403 00404 int i = id.toInt(); 00405 BlogPost* post = mCallMap[ i ]; 00406 bool publish = mPublishAfterCategories[ i ]; 00407 mCallMap.remove(i); 00408 mPublishAfterCategories.remove(i); 00409 00410 if ( result[0].type() != QVariant::Bool ) { 00411 kError() << "Could not read the result, not a boolean. Category setting failed! We will still publish if now if necessary. "; 00412 emit q->errorPost( Blogger1::ParsingError, 00413 i18n( "Could not read the result - is not a boolean value. Category setting failed. Will still publish now if necessary." ), 00414 post ); 00415 } 00416 // Finally publish now, if the post was meant to be published in the beginning. 00417 // The first boolean is necessary to only publish if the post is created, not 00418 // modified. 00419 if( publish && !post->isPrivate() ){ 00420 q->modifyPost( post ); 00421 } 00422 00423 // this is the end of the chain then 00424 if ( !publish ) { 00425 if ( mSilentCreationList.contains( post ) ) { 00426 kDebug() << "emitting createdPost() for title: \"" 00427 << post->title() << "\""; 00428 post->setStatus( KBlog::BlogPost::Created ); 00429 mSilentCreationList.removeOne( post ); 00430 emit q->createdPost( post ); 00431 } else { 00432 kDebug() << "emitting modifiedPost() for title: \"" 00433 << post->title() << "\""; 00434 post->setStatus( KBlog::BlogPost::Modified ); 00435 emit q->modifiedPost( post ); 00436 } 00437 } 00438 } 00439 00440 QList<QVariant> MovableTypePrivate::defaultArgs( const QString &id ) 00441 { 00442 Q_Q( MovableType ); 00443 QList<QVariant> args; 00444 if( !id.isEmpty() ) { 00445 args << QVariant( id ); 00446 } 00447 args << QVariant( q->username() ) 00448 << QVariant( q->password() ); 00449 return args; 00450 } 00451 00452 bool MovableTypePrivate::readPostFromMap( BlogPost *post, const QMap<QString, QVariant> &postInfo ) 00453 { 00454 00455 // FIXME: integrate error handling 00456 kDebug() << "readPostFromMap()"; 00457 if ( !post ) { 00458 return false; 00459 } 00460 QStringList mapkeys = postInfo.keys(); 00461 kDebug() << endl << "Keys:" << mapkeys.join( ", " ); 00462 kDebug() << endl; 00463 00464 KDateTime dt = 00465 KDateTime( postInfo["dateCreated"].toDateTime(), KDateTime::UTC ); 00466 if ( dt.isValid() && !dt.isNull() ) { 00467 post->setCreationDateTime( dt.toLocalZone() ); 00468 } 00469 00470 dt = 00471 KDateTime( postInfo["lastModified"].toDateTime(), KDateTime::UTC ); 00472 if ( dt.isValid() && !dt.isNull() ) { 00473 post->setModificationDateTime( dt.toLocalZone() ); 00474 } 00475 00476 post->setPostId( postInfo["postid"].toString().isEmpty() ? postInfo["postId"].toString() : 00477 postInfo["postid"].toString() ); 00478 00479 QString title( postInfo["title"].toString() ); 00480 QString description( postInfo["description"].toString() ); 00481 QStringList categoryIdList = postInfo["categories"].toStringList(); 00482 QStringList categories; 00483 // since the metaweblog definition is ambigious, we try different 00484 // category mappings 00485 for ( int i=0; i<categoryIdList.count(); i++ ) { 00486 for ( int k=0; k<mCategoriesList.count(); k++ ) { 00487 if ( mCategoriesList[ k ][ "name" ]==categoryIdList[ i ] ){ 00488 categories << mCategoriesList[ k ][ "name" ]; 00489 } else if ( mCategoriesList[ k ][ "categoryId" ]==categoryIdList[ i ]) { 00490 categories << mCategoriesList[ k ][ "name" ]; 00491 } 00492 } 00493 } 00494 00495 //TODO 2 new keys are: 00496 // String mt_convert_breaks, the value for the convert_breaks field 00497 post->setSlug( postInfo["wp_slug"].toString() ); 00498 post->setAdditionalContent( postInfo["mt_text_more"].toString() ); 00499 post->setTitle( title ); 00500 post->setContent( description ); 00501 post->setCommentAllowed( (bool)postInfo["mt_allow_comments"].toInt() ); 00502 post->setTrackBackAllowed( (bool)postInfo["mt_allow_pings"].toInt() ); 00503 post->setSummary( postInfo["mt_excerpt"].toString() ); 00504 post->setTags( postInfo["mt_keywords"].toStringList() ); 00505 post->setLink( postInfo["link"].toString() ); 00506 post->setPermaLink( postInfo["permaLink"].toString() ); 00507 QString postStatus = postInfo["post_status"].toString(); 00508 if( postStatus != "publish" && !postStatus.isEmpty() ){ 00514 post->setPrivate(true); 00515 } 00516 if ( !categories.isEmpty() ){ 00517 kDebug() << "Categories:" << categories; 00518 post->setCategories( categories ); 00519 } 00520 return true; 00521 } 00522 00523 void MovableTypePrivate::slotListTrackBackPings( 00524 const QList<QVariant> &result, const QVariant &id ) 00525 { 00526 Q_Q( MovableType ); 00527 kDebug() << "slotTrackbackPings()"; 00528 BlogPost *post = mCallMap[ id.toInt() ]; 00529 mCallMap.remove( id.toInt() ); 00530 QList<QMap<QString,QString> > trackBackList; 00531 if ( result[0].type() != QVariant::List ) { 00532 kError() << "Could not fetch list of trackback pings out of the" 00533 << "result from the server."; 00534 emit q->error( MovableType::ParsingError, 00535 i18n( "Could not fetch list of trackback pings out of the " 00536 "result from the server." ) ); 00537 return; 00538 } 00539 const QList<QVariant> trackBackReceived = result[0].toList(); 00540 QList<QVariant>::ConstIterator it = trackBackReceived.begin(); 00541 QList<QVariant>::ConstIterator end = trackBackReceived.end(); 00542 for ( ; it != end; ++it ) { 00543 QMap<QString,QString> tping; 00544 kDebug() << "MIDDLE:" << ( *it ).typeName(); 00545 const QMap<QString, QVariant> trackBackInfo = ( *it ).toMap(); 00546 tping[ "title" ] = trackBackInfo[ "pingTitle"].toString(); 00547 tping[ "url" ] = trackBackInfo[ "pingURL"].toString(); 00548 tping[ "ip" ] = trackBackInfo[ "pingIP"].toString(); 00549 trackBackList << tping; 00550 } 00551 kDebug() << "Emitting listedTrackBackPings()"; 00552 emit q->listedTrackBackPings( post, trackBackList ); 00553 } 00554 00555 bool MovableTypePrivate::readArgsFromPost( QList<QVariant> *args, const BlogPost &post ) 00556 { 00557 //TODO 2 new keys are: 00558 // String mt_convert_breaks, the value for the convert_breaks field 00559 // array mt_tb_ping_urls, the list of TrackBack ping URLs for this entry 00560 if ( !args ) { 00561 return false; 00562 } 00563 QMap<QString, QVariant> map; 00564 map["categories"] = post.categories(); 00565 map["description"] = post.content(); 00566 if( !post.additionalContent().isEmpty() ) 00567 map["mt_text_more"] = post.additionalContent(); 00568 map["title"] = post.title(); 00569 map["dateCreated"] = post.creationDateTime().dateTime().toUTC(); 00570 map["mt_allow_comments"] = (int)post.isCommentAllowed(); 00571 map["mt_allow_pings"] = (int)post.isTrackBackAllowed(); 00572 map["mt_excerpt"] = post.summary(); 00573 map["mt_keywords"] = post.tags().join(","); 00574 //map["mt_tb_ping_urls"] check for that, i think this should only be done on the server. 00575 *args << map; 00576 *args << QVariant( !post.isPrivate() ); 00577 return true; 00578 } 00579 00580 #include "movabletype.moc"
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Thu Aug 2 2012 15:24:08 by doxygen 1.7.5 written by Dimitri van Heesch, © 1997-2006
Documentation copyright © 1996-2012 The KDE developers.
Generated on Thu Aug 2 2012 15:24:08 by doxygen 1.7.5 written by Dimitri van Heesch, © 1997-2006
KDE's Doxygen guidelines are available online.