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 "ContextStatusBar.h"
19 #include <QApplication>
23 using Amarok::ContextStatusBar
;
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
);
190 for( JobList::ConstIterator it
= m_jobs
.begin(), end
= m_jobs
.end(); it
!= end
; ++it
)
191 if ( name
== (*it
)->name() ) {
192 thread
->runJob( (*it
) );
196 // this thread is done
203 debug() << "Threads in pool: " << m_threads
.count();
205 // for( ThreadList::Iterator it = m_threads.begin(), end = m_threads.end(); it != end; ++it )
206 // if ( (*it)->readyForTrash() ) {
207 // m_threads.remove( it );
209 // break; // only delete 1 thread every 5 minutes
213 case OverrideCursorEventType
:
214 // we have to do this for the PlaylistLoader case, as Qt uses the same
215 // function for drag and drop operations.
216 QApplication::setOverrideCursor( Qt::BusyCursor
);
219 case RestoreOverrideCursorEventType
:
220 // we have to do this for the PlaylistLoader case, as Qt uses the same
221 // function for drag and drop operations.
222 QApplication::restoreOverrideCursor();
233 /// @class ThreadManager::Thread
235 ThreadManager::Thread::Thread()
239 ThreadManager::Thread::~Thread()
241 Q_ASSERT( isFinished() );
245 ThreadManager::Thread::runJob( Job
*job
)
247 job
->m_thread
= this;
249 if ( job
->isAborted() )
250 QApplication::postEvent( ThreadManager::instance(), new JobCompletedEvent( job
) );
256 start( Thread::IdlePriority
);
258 QApplication::postEvent(
259 ThreadManager::instance(),
260 new QEvent( QEvent::Type( ThreadManager::OverrideCursorEventType
) ) );
265 ThreadManager::Thread::run()
271 //keep this first, before anything that uses the database, or SQLite may error out
272 if ( AmarokConfig::databaseEngine().toInt() == DbConnection::sqlite
)
273 CollectionDB::instance()->releasePreviousConnection( this );
277 m_job
->m_aborted
|= !m_job
->doJob();
278 QApplication::postEvent( ThreadManager::instance(), new JobCompletedEvent( m_job
) );
281 // almost always the thread doesn't finish until after the
282 // above event is already finished processing
287 /// @class ProgressEvent
288 /// @short Used by ThreadManager::Job internally
290 class ProgressEvent
: public QEvent
{
292 static const int ProgressEventType
= 30303;
293 ProgressEvent( int progress
)
294 : QEvent( Type( ProgressEventType
) )
295 , progress( progress
) {}
302 /// @class ThreadManager::Job
304 ThreadManager::Job::Job( const char *name
)
305 : QEvent( Type( ThreadManager::JobEventType
) )
309 , m_progressDone( 0 )
310 , m_totalSteps( 1 ) // no divide by zero
313 ThreadManager::Job::~Job()
315 /*if( m_thread->running() && m_thread->job() == this )
316 warning() << "Deleting a job before its thread has finished with it!\n";*/
320 ThreadManager::Job::setProgressTotalSteps( uint steps
)
323 warning() << "You can't set steps to 0!\n";
327 m_totalSteps
= steps
;
329 QApplication::postEvent( this, new ProgressEvent( -1 ) );
333 ThreadManager::Job::setProgress( uint steps
)
335 m_progressDone
= steps
;
337 uint newPercent
= uint( (100 * steps
) / m_totalSteps
);
339 if ( newPercent
!= m_percentDone
) {
340 m_percentDone
= newPercent
;
341 QApplication::postEvent( this, new ProgressEvent( newPercent
) );
346 ThreadManager::Job::setStatus( const QString
&status
)
350 QApplication::postEvent( this, new ProgressEvent( -2 ) );
354 ThreadManager::Job::incrementProgress()
356 setProgress( m_progressDone
+ 1 );
360 ThreadManager::Job::customEvent( QEvent
*e
)
362 int progress
= static_cast<ProgressEvent
*>(e
)->progress
;
367 ContextStatusBar::instance()->setProgressStatus( this, m_status
);
371 ContextStatusBar::instance()->newProgressOperation( this )
372 .setDescription( m_description
)
373 .setAbortSlot( this, SLOT(abort()) )
378 ContextStatusBar::instance()->setProgress( this, progress
);
384 ThreadManager::DependentJob::DependentJob( QObject
*dependent
, const char *name
)
386 , m_dependent( dependent
)
388 Q_ASSERT( dependent
!= this );
389 connect( dependent
, SIGNAL(destroyed()), SLOT(abort()) );
391 QApplication::postEvent( dependent
, new QEvent( Type( JobStartedEventType
) ) );
395 ThreadManager::DependentJob::completeJob()
397 //synchronous, so we don't get deleted twice
398 QApplication::sendEvent( m_dependent
, this );
401 #include "threadmanager.moc"
402 #undef QT_FATAL_ASSERT //enable-final