1 // Author: Max Howell (C) Copyright 2004
2 // (c) 2005 Jeff Mitchell <kde-dev@emailgoeshere.com>
3 // See COPYING file that comes with this distribution
6 // the asserts we use in this module prevent crashes, so best to abort the application if they fail
7 #define QT_FATAL_ASSERT
8 #define DEBUG_PREFIX "ThreadManager"
10 #include "threadmanager.h"
12 #include "amarokconfig.h"
13 #include "collectiondb.h"
15 #include "statusbar.h"
19 #include <QApplication>
23 using Amarok::StatusBar
;
26 class ThreadManager::JobCompletedEvent
: public QEvent
29 static const int JobCompletedEventType
= 1321;
30 JobCompletedEvent( Job
*j
): QEvent( Type( JobCompletedEventType
) ), job( j
) { }
34 ThreadManager::ThreadManager()
36 startTimer( 5 * 60 * 1000 ); // prunes the thread pool every 5 minutes
39 ThreadManager::~ThreadManager()
43 for( ThreadList::Iterator it
= m_threads
.begin(), end
= m_threads
.end(); it
!= end
; ++it
)
46 // we don't delete the thread's job as amarok is gone
47 // and the Job dtor may expect amarok to be there etc.
48 if ( (*it
)->job() && (*it
)->job()->name() == QByteArray( "INotify" ) )
50 debug() << "Forcibly terminating INotify thread...\n";
56 if( (*it
)->job() && (*it
)->job()->name() )
57 debug() << "Waiting on thread " << (*it
)->job()->name() << "...\n";
59 debug() << "Waiting on thread...\n";
65 ThreadManager::jobCount( const QByteArray
&name
)
69 for( JobList::Iterator it
= m_jobs
.begin(), end
= m_jobs
.end(); it
!= end
; ++it
)
70 if ( name
== (*it
)->name() )
77 ThreadManager::queueJob( Job
*job
)
84 // this list contains all pending and running jobs
87 const uint count
= jobCount( job
->name() );
90 gimmeThread()->runJob( job
);
96 ThreadManager::queueJobs( const JobList
&jobs
)
100 if ( jobs
.isEmpty() )
105 const QByteArray name
= jobs
.front()->name();
106 const int count
= jobCount( name
);
108 if ( count
== jobs
.count() )
109 gimmeThread()->runJob( jobs
.front() );
115 ThreadManager::onlyOneJob( Job
*job
)
119 const QByteArray name
= job
->name();
121 // first cause all current jobs with this name to be aborted
122 abortAllJobsNamed( name
);
124 // now queue this job.
125 // if there is a running Job of its type this one will be
126 // started when that one returns to the GUI thread.
129 // if there weren't any jobs of this type running, we must
131 if ( jobCount( name
) == 1 )
132 gimmeThread()->runJob( job
);
136 ThreadManager::abortAllJobsNamed( const QByteArray
&name
)
142 for( JobList::Iterator it
= m_jobs
.begin(), end
= m_jobs
.end(); it
!= end
; ++it
)
143 if ( name
== (*it
)->name() ) {
151 ThreadManager::Thread
*
152 ThreadManager::gimmeThread()
154 for( ThreadList::ConstIterator it
= m_threads
.begin(), end
= m_threads
.end(); it
!= end
; ++it
)
155 if ( !(*it
)->isRunning() && (*it
)->job() == 0 )
158 Thread
*thread
= new Thread
;
164 ThreadManager::event( QEvent
*e
)
168 case JobCompletedEvent::JobCompletedEventType
: {
169 Job
*job
= static_cast<JobCompletedEvent
*>( e
)->job
;
170 DebugStream d
= debug() << "Job ";
171 const QByteArray name
= job
->name();
172 Thread
*thread
= job
->m_thread
;
174 QApplication::postEvent(
175 ThreadManager::instance(),
176 new QEvent( QEvent::Type( ThreadManager::RestoreOverrideCursorEventType
) ) );
178 if ( !job
->isAborted() ) {
184 m_jobs
.remove( job
);
188 d
<< ". Jobs pending: " << jobCount( name
);
191 for( JobList::ConstIterator it
= m_jobs
.begin(), end
= m_jobs
.end(); it
!= end
; ++it
)
192 if ( name
== (*it
)->name() ) {
193 thread
->runJob( (*it
) );
197 // this thread is done
204 debug() << "Threads in pool: " << m_threads
.count();
206 // for( ThreadList::Iterator it = m_threads.begin(), end = m_threads.end(); it != end; ++it )
207 // if ( (*it)->readyForTrash() ) {
208 // m_threads.remove( it );
210 // break; // only delete 1 thread every 5 minutes
214 case OverrideCursorEventType
:
215 // we have to do this for the PlaylistLoader case, as Qt uses the same
216 // function for drag and drop operations.
217 QApplication::setOverrideCursor( Qt::BusyCursor
);
220 case RestoreOverrideCursorEventType
:
221 // we have to do this for the PlaylistLoader case, as Qt uses the same
222 // function for drag and drop operations.
223 QApplication::restoreOverrideCursor();
234 /// @class ThreadManager::Thread
236 ThreadManager::Thread::Thread()
240 ThreadManager::Thread::~Thread()
242 Q_ASSERT( isFinished() );
246 ThreadManager::Thread::runJob( Job
*job
)
248 job
->m_thread
= this;
250 if ( job
->isAborted() )
251 QApplication::postEvent( ThreadManager::instance(), new JobCompletedEvent( job
) );
257 start( Thread::IdlePriority
);
259 QApplication::postEvent(
260 ThreadManager::instance(),
261 new QEvent( QEvent::Type( ThreadManager::OverrideCursorEventType
) ) );
266 ThreadManager::Thread::run()
272 //keep this first, before anything that uses the database, or SQLite may error out
273 if ( AmarokConfig::databaseEngine().toInt() == DbConnection::sqlite
)
274 CollectionDB::instance()->releasePreviousConnection( this );
278 m_job
->m_aborted
|= !m_job
->doJob();
279 QApplication::postEvent( ThreadManager::instance(), new JobCompletedEvent( m_job
) );
282 // almost always the thread doesn't finish until after the
283 // above event is already finished processing
288 /// @class ProgressEvent
289 /// @short Used by ThreadManager::Job internally
291 class ProgressEvent
: public QEvent
{
293 static const int ProgressEventType
= 30303;
294 ProgressEvent( int progress
)
295 : QEvent( Type( ProgressEventType
) )
296 , progress( progress
) {}
303 /// @class ThreadManager::Job
305 ThreadManager::Job::Job( const char *name
)
306 : QEvent( Type( ThreadManager::JobEventType
) )
310 , m_progressDone( 0 )
311 , m_totalSteps( 1 ) // no divide by zero
314 ThreadManager::Job::~Job()
316 /*if( m_thread->running() && m_thread->job() == this )
317 warning() << "Deleting a job before its thread has finished with it!\n";*/
321 ThreadManager::Job::setProgressTotalSteps( uint steps
)
324 warning() << "You can't set steps to 0!\n";
328 m_totalSteps
= steps
;
330 QApplication::postEvent( this, new ProgressEvent( -1 ) );
334 ThreadManager::Job::setProgress( uint steps
)
336 m_progressDone
= steps
;
338 uint newPercent
= uint( (100 * steps
) / m_totalSteps
);
340 if ( newPercent
!= m_percentDone
) {
341 m_percentDone
= newPercent
;
342 QApplication::postEvent( this, new ProgressEvent( newPercent
) );
347 ThreadManager::Job::setStatus( const QString
&status
)
351 QApplication::postEvent( this, new ProgressEvent( -2 ) );
355 ThreadManager::Job::incrementProgress()
357 setProgress( m_progressDone
+ 1 );
361 ThreadManager::Job::customEvent( QEvent
*e
)
363 int progress
= static_cast<ProgressEvent
*>(e
)->progress
;
368 StatusBar::instance()->setProgressStatus( this, m_status
);
372 StatusBar::instance()->newProgressOperation( this )
373 .setDescription( m_description
)
374 .setAbortSlot( this, SLOT(abort()) )
379 StatusBar::instance()->setProgress( this, progress
);
385 ThreadManager::DependentJob::DependentJob( QObject
*dependent
, const char *name
)
387 , m_dependent( dependent
)
389 Q_ASSERT( dependent
!= this );
390 connect( dependent
, SIGNAL(destroyed()), SLOT(abort()) );
392 QApplication::postEvent( dependent
, new QEvent( Type( JobStartedEventType
) ) );
396 ThreadManager::DependentJob::completeJob()
398 //synchronous, so we don't get deleted twice
399 QApplication::sendEvent( m_dependent
, this );
402 #include "threadmanager.moc"
403 #undef QT_FATAL_ASSERT //enable-final