Blokkal
an Extendable KDE Blogging Client
SourceForge.net Logo

blokkalpostentryqueue.cpp

00001 /***************************************************************************
00002  *   Copyright (C) 2006 - 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 "blokkalpostentryqueue.h"
00021 #include "blokkalpostentryqueue.moc"
00022 
00023 #include "blokkalentry.h"
00024 #include "blokkalblog.h"
00025 #include "blokkalaccount.h"
00026 #include "blokkalaccountmanager.h"
00027 #include "blokkalio/jobs.h"
00028 
00029 #include <kdebug.h>
00030 #include <klocale.h>
00031 #include <kstandarddirs.h>
00032 
00033 #include <QMap>
00034 #include <QTimer>
00035 #include <QDir>
00036 #include <QLinkedList>
00037 
00038 class Blokkal::PostEntryStatus::Private
00039 {
00040 public:
00041         Private( void ) {}
00042 
00043         Status status;
00044         int error;
00045         QString errorString;
00046         Io::EntryJob * job;
00047 
00048         long saveNumber;
00049 };
00050 
00051 Blokkal::PostEntryStatus::PostEntryStatus( void ) :
00052 d( new Private() )
00053 {
00054         d->status = StatusNormal;
00055         d->error = 0;
00056         d->errorString = QString::null;
00057         d->job = 0;
00058 
00059         d->saveNumber = -1;
00060 }
00061 
00062 Blokkal::PostEntryStatus::~PostEntryStatus( void )
00063 {
00064         delete d;
00065 }
00066 
00067 int Blokkal::PostEntryStatus::error( void ) const
00068 {
00069         return d->error;
00070 }
00071 
00072 QString Blokkal::PostEntryStatus::errorString( void ) const
00073 {
00074         return d->errorString;
00075 }
00076 
00077 Blokkal::PostEntryStatus::Status Blokkal::PostEntryStatus::status( void ) const
00078 {
00079         return d->status;
00080 }
00081 
00082 const Blokkal::Io::EntryJob * Blokkal::PostEntryStatus::job( void ) const
00083 {
00084         return d->job;
00085 }
00086 
00087 //--------------
00088 //PostEntryQueue
00089 //--------------
00090 class Blokkal::PostEntryQueuePrivate
00091 {
00092 public:
00093         PostEntryQueuePrivate( void ) :
00094         entryStatus( QMap<Blokkal::Entry*, PostEntryStatus*>() ),
00095         entryList( QLinkedList<Blokkal::Entry*>() ),
00096         //queueLock( TRUE ), // recursive mutex
00097         isProcessing( TRUE ),
00098         saveNumber( 0 ),
00099         isSyncing( TRUE ) {}
00100 
00101         Blokkal::PostEntryQueue instance;
00102 
00103         QMap<Blokkal::Entry*, PostEntryStatus*> entryStatus;
00104         QLinkedList<Blokkal::Entry*> entryList;
00105 
00106         //QMutex queueLock;
00107         bool isProcessing;
00108         long saveNumber;
00109         bool isSyncing;
00110 };
00111 
00112 K_GLOBAL_STATIC( Blokkal::PostEntryQueuePrivate, _bpeqp );
00113 
00114 Blokkal::PostEntryQueue::PostEntryQueue( void ):
00115 QObject( 0 )
00116 {
00117         QList< Blokkal::Account * > accounts = Blokkal::AccountManager::self()->accounts();
00118         if( !accounts.isEmpty() ) {
00119                 for( QList< Blokkal::Account * >::ConstIterator it = accounts.begin();
00120                      it != accounts.end();
00121                      ++it )
00122                 {
00123                         slotAccountAdded( *it );
00124                 }
00125         }
00126 
00127         connect( Blokkal::AccountManager::self(),
00128                  SIGNAL( accountRegistered( Blokkal::Account * ) ),
00129                  SLOT( slotAccountAdded( Blokkal::Account * ) ) );
00130 }
00131 
00132 Blokkal::PostEntryQueue::~PostEntryQueue( void )
00133 {
00134 }
00135 
00136 Blokkal::PostEntryQueue * Blokkal::PostEntryQueue::self( void )
00137 {
00138         return &_bpeqp->instance;
00139 }
00140 
00141 void Blokkal::PostEntryQueue::queue( Blokkal::Entry * entry )
00142 {
00143         if( !entry ) {
00144                 return;
00145         }
00146 
00147         entry->ref(); //the view might close after queing the entry
00148         
00149         //_bpeqp->queueLock.lock();
00150         connect( entry,
00151                  SIGNAL( entryDestroyed( Blokkal::Entry * ) ),
00152                  SLOT( slotEntryDestroyed( Blokkal::Entry * ) ) );
00153         _bpeqp->entryStatus.insert( entry, new PostEntryStatus() );
00154         _bpeqp->entryList.append( entry );
00155         //_bpeqp->queueLock.unlock();
00156 
00157         if( _bpeqp->isSyncing ) {
00158                 entry->saveAs( KStandardDirs::locateLocal( "appdata", QString::fromLatin1( "queuedentries/%1" ).arg( _bpeqp->saveNumber ) ) );
00159                 _bpeqp->entryStatus[entry]->d->saveNumber = _bpeqp->saveNumber;
00160                 _bpeqp->saveNumber++;
00161         }
00162         
00163         emit entryAdded( entry );
00164         
00165         QTimer::singleShot( 0, this, SLOT( processQueue( void ) ) );
00166 }
00167 
00168 bool Blokkal::PostEntryQueue::unqueue( Blokkal::Entry * entry, bool force )
00169 {
00170         //_bpeqp->queueLock.lock();
00171         bool removed = FALSE;
00172         bool contains = _bpeqp->entryList.contains( entry );
00173         long saveNumber = -1;
00174 
00175         if( contains ) {
00176                 if( _bpeqp->entryStatus.contains( entry) ) {
00177                         PostEntryStatus * status = _bpeqp->entryStatus[entry];
00178                         if( status->status() != PostEntryStatus::StatusPosting
00179                                 || force )
00180                         {
00181                                 saveNumber = status->d->saveNumber;
00182                                 delete status->d->job;
00183                                 delete status;
00184                                 _bpeqp->entryStatus.remove( entry );
00185                                 removed = TRUE;
00186                         }
00187                 }
00188                 else {
00189                         removed = TRUE;
00190                 }
00191 
00192                 if( removed ) {
00193                         _bpeqp->entryList.removeAll( entry );
00194                         disconnect( entry,
00195                                     SIGNAL( entryDestroyed( Blokkal::Entry* ) ),
00196                                     this,
00197                                     SLOT( slotEntryDestroyed( Blokkal::Entry* ) ) );
00198                 }
00199         }
00200         //_bpeqp->queueLock.unlock();
00201 
00202         if( removed ) {
00203                 if( _bpeqp->isSyncing && saveNumber >= 0 ) {
00204                         QDir entrydir( KStandardDirs::locateLocal( "appdata", QString::fromLatin1( "queuedentries/" ) ) );
00205                         QString fileName = QString::number( saveNumber );
00206                         if( !entrydir.remove( fileName ) ) {
00207                                 kWarning() << "could not delete entry file: " << fileName << endl;
00208                         }
00209                 }
00210 
00211                 if( isEmpty() ) {
00212                         _bpeqp->saveNumber = 0;
00213                 }
00214                 
00215                 emit entryRemoved( entry );
00216         }
00217 
00218         return removed;
00219 }
00220 
00221 int Blokkal::PostEntryQueue::count( void ) const
00222 {
00223         return _bpeqp->entryStatus.count();
00224 }
00225 
00226 bool Blokkal::PostEntryQueue::isEmpty( void ) const
00227 {
00228         return !count();
00229 }
00230 
00231 QLinkedList< Blokkal::Entry * > Blokkal::PostEntryQueue::entries( void ) const
00232 {
00233         return _bpeqp->entryList;
00234 }
00235 
00236 void Blokkal::PostEntryQueue::processQueue( void )
00237 {
00238         if( !_bpeqp->isProcessing || isEmpty() ) {
00239                 return;
00240         }
00241 
00242         //_bpeqp->queueLock.lock();
00243         QStringList postingAccounts;
00244         for( QLinkedList< Blokkal::Entry * >::Iterator it = _bpeqp->entryList.begin();
00245              it != _bpeqp->entryList.end();
00246              ++it )
00247         {
00248                 if( !(*it)->blog()->account()->isConnected() ) {
00249                         continue;
00250                 }
00251 
00252                 if( postingAccounts.contains( (*it)->blog()->account()->id() ) ) {
00253                         continue;
00254                 }
00255 
00256                 PostEntryStatus * entryStatus = _bpeqp->entryStatus[ *it ];
00257                 
00258                 if( entryStatus->status() != PostEntryStatus::StatusNormal ) {
00259                         if( entryStatus->status() == PostEntryStatus::StatusPosting
00260                             && !postingAccounts.contains( (*it)->blog()->account()->id() ) )
00261                         {
00262                                 postingAccounts.append( (*it)->blog()->account()->id() );
00263                         }
00264                         continue;
00265                 }
00266 
00267                 if( entryStatus->error() ) {
00268                         continue;
00269                 }
00270 
00271                 entryStatus->d->job = (*it)->createJob( Blokkal::Entry::PostJob );
00272                 if( entryStatus->d->job ) {
00273                         connect( entryStatus->d->job,
00274                                  SIGNAL( result( KJob * ) ),
00275                                  SLOT( slotJobFinished( KJob * ) ) );
00276                         entryStatus->d->status =  PostEntryStatus::StatusPosting;
00277                         emit entryStatusChanged( *it, entryStatus );
00278                         entryStatus->d->job->start();
00279                         break; //ensure that no entry is posted out of order
00280                 }
00281                 else {
00282                         //if we end up here, the entry implementation does not support posting
00283                         //entries. While this may seem braindead it may help
00284                         //when implementing new protocols, because not all methods need to be
00285                         //fully operational.
00286                         entryStatus->d->status =  PostEntryStatus::StatusError;
00287                         emit entryStatusChanged( *it, entryStatus );
00288                         //process next entry in queue
00289                         continue;
00290                 }
00291         }
00292         
00293         //_bpeqp->queueLock.unlock();
00294 }
00295 
00296 void Blokkal::PostEntryQueue::slotJobFinished( KJob * _job )
00297 {
00298         Io::EntryJob * job = dynamic_cast<Io::EntryJob *>( _job );
00299         if( !job ) {
00300                 kError() << "job is 0!" << endl;
00301                 return;
00302         }
00303 
00304         if( !_bpeqp->entryStatus.contains( job->entry() ) ) {
00305                 kError() << "job is belongs to unknown entry!" << endl;
00306                 return;
00307         }
00308 
00309         if( job->error() ) {
00310                 //_bpeqp->queueLock.lock();
00311                 PostEntryStatus * entryStatus = _bpeqp->entryStatus[ job->entry() ];
00312                 entryStatus->d->status = PostEntryStatus::StatusError;
00313                 entryStatus->d->errorString = job->errorString();
00314                 entryStatus->d->error = job->error();
00315                 entryStatus->d->job = 0;
00316                 //_bpeqp->queueLock.unlock();
00317 
00318                 emit entryStatusChanged( job->entry(), entryStatus );
00319                 emit entryFailing( job->entry() );
00320         }
00321         else {
00322                 PostEntryStatus * entryStatus = _bpeqp->entryStatus[ job->entry() ];
00323                 entryStatus->d->status = PostEntryStatus::StatusNormal;
00324                 entryStatus->d->job = 0;
00325 
00326                 job->entry()->setDirty( FALSE );
00327                 unqueue( job->entry() );
00328                 emit entryPosted( job->entry() );
00329                 job->entry()->deref();
00330         }
00331 
00332         QTimer::singleShot( 0, this, SLOT( processQueue( void ) ) );
00333 }
00334 
00335 void Blokkal::PostEntryQueue::slotConnectionsChanged( Blokkal::Account * account )
00336 {
00337         if( account->connectionStatus() == Blokkal::Account::Connected ) {
00338                 QTimer::singleShot( 0, this, SLOT( processQueue( void ) ) );
00339         }
00340 }
00341 
00342 void Blokkal::PostEntryQueue::slotAccountAdded( Blokkal::Account * account )
00343 {
00344         connect( account,
00345                  SIGNAL( connectionStatusChanged( Blokkal::Account * ) ),
00346                  SLOT(slotConnectionsChanged( Blokkal::Account * ) ) );
00347 }
00348 
00349 const Blokkal::PostEntryStatus * Blokkal::PostEntryQueue::entryStatus( Blokkal::Entry * entry ) const
00350 {
00351         if( _bpeqp->entryStatus.contains( entry ) ) {
00352                 return _bpeqp->entryStatus[ entry ];
00353         }
00354         return 0;
00355 }
00356 
00357 void Blokkal::PostEntryQueue::clearError( Blokkal::Entry * entry )
00358 {
00359         if( !entry ) {
00360                 return;
00361         }
00362         
00363         if( !_bpeqp->entryStatus.contains( entry ) ) {
00364                 return;
00365         }
00366 
00367         if( !_bpeqp->entryStatus[ entry ]->error() ) {
00368                 return;
00369         }
00370 
00371         //_bpeqp->queueLock.lock();
00372         PostEntryStatus * entryStatus = _bpeqp->entryStatus[ entry ];
00373         entryStatus->d->error = 0;
00374         entryStatus->d->errorString = QString::null;
00375         entryStatus->d->status = PostEntryStatus::StatusNormal;
00376         //_bpeqp->queueLock.unlock();
00377         emit entryStatusChanged( entry, entryStatus );
00378 
00379         QTimer::singleShot( 0, this, SLOT( processQueue( void ) ) );
00380 }
00381 
00382 void Blokkal::PostEntryQueue::slotEntryDestroyed( Blokkal::Entry * entry )
00383 {
00384         unqueue( entry, TRUE );
00385 }
00386 
00387 void Blokkal::PostEntryQueue::suspendProcessing( void )
00388 {
00389         _bpeqp->isProcessing = FALSE;
00390 }
00391 
00392 void Blokkal::PostEntryQueue::resumeProcessing( void )
00393 {
00394         if( _bpeqp->isProcessing ) {
00395                 return;
00396         }
00397 
00398         _bpeqp->isProcessing = TRUE;
00399 
00400         QTimer::singleShot( 0, this, SLOT( processQueue( void ) ) );
00401 }
00402 
00403 bool Blokkal::PostEntryQueue::isProcessing( void ) const
00404 {
00405         return _bpeqp->isProcessing;
00406 }
00407 
00408 void Blokkal::PostEntryQueue::restoreState( void )
00409 {
00410         QDir entrydir( KStandardDirs::locateLocal( "appdata", QString::fromLatin1( "queuedentries/" ) ) );
00411         QStringList files = entrydir.entryList( QDir::Files );
00412         if( files.isEmpty() ) {
00413                 return;
00414         }
00415 
00416         suspendProcessing();
00417 
00418         long minNumber = -1;
00419         long maxNumber = -1;
00420         
00421         for( QStringList::ConstIterator it = files.begin();
00422              it != files.end();
00423              ++it )
00424         {
00425                 bool ok;
00426                 long currentNumber = (*it).toLong( &ok );
00427                 if( !ok ) {
00428                         kWarning() << "encountered invalid file in queue dir: " << *it << endl;
00429                         continue;
00430                 }
00431                 
00432                 if( currentNumber < minNumber || minNumber < 0 ) {
00433                         minNumber = currentNumber;
00434                 }
00435 
00436                 if( currentNumber > maxNumber ) {
00437                         maxNumber = currentNumber;
00438                 }
00439         }
00440 
00441 
00442         _bpeqp->isSyncing = FALSE;
00443         for( long i = minNumber; i <= maxNumber; ++i ) {
00444                 if( !files.contains( QString::number( i ) ) ) {
00445                         continue;
00446                 }
00447                         
00448                 Entry * entry = Entry::open( entrydir.absolutePath() + QString::fromLatin1("/%1").arg( i ) , 0, FALSE );
00449                 if( entry ) {
00450                         entry->setDirty( TRUE );
00451                         queue( entry );
00452                         _bpeqp->entryStatus[entry]->d->saveNumber = i;
00453                 }
00454                 else {
00455                         kWarning() << "failed to restore entry file: " << i << endl;
00456                 }
00457         }
00458         _bpeqp->isSyncing = TRUE;
00459         _bpeqp->saveNumber = maxNumber + 1;
00460 
00461         resumeProcessing();
00462 }
00463 
00464 void Blokkal::PostEntryQueue::stopSyncing( void )
00465 {
00466         _bpeqp->isSyncing = FALSE;
00467 }