Make plasma libs build.
[amarok.git] / src / scriptmanager.cpp
blobc8aefb96464c16203ab1c5dfaea1f77758a2ba10
1 /***************************************************************************
2 * Copyright (C) 2004-2007 by Mark Kretschmann <markey@web.de> *
3 * 2005-2007 by Seb Ruiz <ruiz@kde.org> *
4 * 2006 by Alexandre Oliveira <aleprj@gmail.com> *
5 * 2006 by Martin Ellis <martin.ellis@kdemail.net> *
6 * 2007 by Leonardo Franchi <lfranchi@gmail.com> *
7 * *
8 * This program is free software; you can redistribute it and/or modify *
9 * it under the terms of the GNU General Public License as published by *
10 * the Free Software Foundation; either version 2 of the License, or *
11 * (at your option) any later version. *
12 * *
13 * This program is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License *
19 * along with this program; if not, write to the *
20 * Free Software Foundation, Inc., *
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
22 ***************************************************************************/
24 #define DEBUG_PREFIX "ScriptManager"
26 #include "scriptmanager.h"
28 #include "amarok.h"
29 #include "amarokconfig.h"
30 #include "debug.h"
31 #include "enginecontroller.h"
32 #include "metabundle.h"
33 #include "Process.h"
34 #include "ContextStatusBar.h"
36 #include <KAboutApplicationDialog>
37 #include <KAboutData>
38 #include <KApplication>
39 #include <KFileDialog>
40 #include <KIconLoader>
41 #include <KIO/NetAccess>
42 #include <KLocale>
43 #include <KMenu>
44 #include <KMessageBox>
45 #include <KProtocolManager>
46 #include <KPushButton>
47 #include <KRun>
48 #include <KStandardDirs>
49 #include <KTar>
50 #include <KTextEdit>
51 #include <KWindowSystem>
53 #include <QCheckBox>
54 #include <QDir>
55 #include <QFileInfo>
56 #include <QFont>
57 #include <QLabel>
58 #include <QPixmap>
59 #include <QSettings>
60 #include <QTextCodec>
61 #include <QTimer>
63 #include <sys/stat.h>
64 #include <sys/types.h>
66 namespace Amarok {
68 QString
69 proxyForUrl(const QString& url)
71 KUrl kurl( url );
73 QString proxy;
75 if ( KProtocolManager::proxyForUrl( kurl ) !=
76 QString::fromLatin1( "DIRECT" ) ) {
77 KProtocolManager::slaveProtocol ( kurl, proxy );
80 return proxy;
83 QString
84 proxyForProtocol(const QString& protocol)
86 return KProtocolManager::proxyFor( protocol );
92 ////////////////////////////////////////////////////////////////////////////////
93 // class AmarokScriptNewStuff
94 ////////////////////////////////////////////////////////////////////////////////
96 /**
97 * GHNS Customised Download implementation.
99 #if 0 //TODO: PORT to KNS2
100 class AmarokScriptNewStuff : public KNewStuff
102 public:
103 AmarokScriptNewStuff(const QString &type, QWidget *parentWidget=0)
104 : KNewStuff( type, parentWidget )
107 bool install( const QString& fileName )
109 return ScriptManager::instance()->slotInstallScript( fileName );
112 virtual bool createUploadFile( const QString& ) { return false; } //make compile on kde 3.5
114 #endif
116 ////////////////////////////////////////////////////////////////////////////////
117 // class ScriptManager
118 ////////////////////////////////////////////////////////////////////////////////
120 ScriptManager* ScriptManager::s_instance = 0;
123 ScriptManager::ScriptManager( QWidget *parent, const char *name )
124 : KDialog( parent )
125 , EngineObserver( EngineController::instance() )
126 , m_gui( new Ui::ScriptManagerBase() )
128 DEBUG_BLOCK
129 setObjectName( name );
130 setModal( false );
131 setButtons( Close );
132 setDefaultButton( Close );
133 showButtonSeparator( true );
136 s_instance = this;
138 kapp->setTopWidget( this );
139 setCaption( KDialog::makeStandardCaption( i18n( "Script Manager" ) ) );
141 // Gives the window a small title bar, and skips a taskbar entry
142 #ifdef Q_WS_X11
143 KWindowSystem::setType( winId(), NET::Utility );
144 KWindowSystem::setState( winId(), NET::SkipTaskbar );
145 #endif
147 QWidget* main = new QWidget( this );
148 m_gui->setupUi( main );
150 setMainWidget( main );
152 /// Category items
153 m_generalCategory = new QTreeWidgetItem( m_gui->treeWidget );
154 m_lyricsCategory = new QTreeWidgetItem( m_gui->treeWidget );
155 m_scoreCategory = new QTreeWidgetItem( m_gui->treeWidget );
156 m_transcodeCategory = new QTreeWidgetItem( m_gui->treeWidget );
157 m_contextCategory = new QTreeWidgetItem( m_gui->treeWidget );
159 m_generalCategory ->setText( 0, i18n( "General" ) );
160 m_lyricsCategory ->setText( 0, i18n( "Lyrics" ) );
161 m_scoreCategory ->setText( 0, i18n( "Score" ) );
162 m_transcodeCategory->setText( 0, i18n( "Transcoding" ) );
163 m_contextCategory ->setText( 0, i18n( "Context Browser" ) );
165 m_generalCategory ->setFlags( Qt::ItemIsEnabled );
166 m_lyricsCategory ->setFlags( Qt::ItemIsEnabled );
167 m_scoreCategory ->setFlags( Qt::ItemIsEnabled );
168 m_transcodeCategory->setFlags( Qt::ItemIsEnabled );
169 m_contextCategory ->setFlags( Qt::ItemIsEnabled );
171 m_generalCategory ->setIcon( 0, SmallIcon( Amarok::icon( "files" ) ) );
172 m_lyricsCategory ->setIcon( 0, SmallIcon( Amarok::icon( "files" ) ) );
173 m_scoreCategory ->setIcon( 0, SmallIcon( Amarok::icon( "files" ) ) );
174 m_transcodeCategory->setIcon( 0, SmallIcon( Amarok::icon( "files" ) ) );
175 m_contextCategory ->setIcon( 0, SmallIcon( Amarok::icon( "files" ) ) );
177 // Restore the open/closed state of the category items
178 KConfigGroup config = Amarok::config( "ScriptManager" );
179 m_generalCategory ->setExpanded( config.readEntry( "General category open", false ) );
180 m_lyricsCategory ->setExpanded( config.readEntry( "Lyrics category open", false ) );
181 m_scoreCategory ->setExpanded( config.readEntry( "Score category State", false ) );
182 m_transcodeCategory->setExpanded( config.readEntry( "Transcode category open", false ) );
183 m_contextCategory ->setExpanded( config.readEntry( "Context category open", false ) );
185 connect( m_gui->treeWidget, SIGNAL( currentItemChanged( QTreeWidgetItem*, QTreeWidgetItem* ) ), SLOT( slotCurrentChanged( QTreeWidgetItem* ) ) );
186 connect( m_gui->treeWidget, SIGNAL( itemDoubleClicked( QTreeWidgetItem*, int ) ), SLOT( slotRunScript() ) );
187 connect( m_gui->treeWidget, SIGNAL( customContextMenuRequested ( const QPoint& ) ), SLOT( slotShowContextMenu( const QPoint& ) ) );
189 connect( m_gui->installButton, SIGNAL( clicked() ), SLOT( slotInstallScript() ) );
190 connect( m_gui->retrieveButton, SIGNAL( clicked() ), SLOT( slotRetrieveScript() ) );
191 connect( m_gui->uninstallButton, SIGNAL( clicked() ), SLOT( slotUninstallScript() ) );
192 connect( m_gui->runButton, SIGNAL( clicked() ), SLOT( slotRunScript() ) );
193 connect( m_gui->stopButton, SIGNAL( clicked() ), SLOT( slotStopScript() ) );
194 connect( m_gui->configureButton, SIGNAL( clicked() ), SLOT( slotConfigureScript() ) );
195 connect( m_gui->aboutButton, SIGNAL( clicked() ), SLOT( slotAboutScript() ) );
197 m_gui->installButton ->setIcon( KIcon( Amarok::icon( "files" ) ) );
198 m_gui->retrieveButton ->setIcon( KIcon( Amarok::icon( "download" ) ) );
199 m_gui->uninstallButton->setIcon( KIcon( Amarok::icon( "remove" ) ) );
200 m_gui->runButton ->setIcon( KIcon( Amarok::icon( "play" ) ) );
201 m_gui->stopButton ->setIcon( KIcon( Amarok::icon( "stop" ) ) );
202 m_gui->configureButton->setIcon( KIcon( Amarok::icon( "configure" ) ) );
203 m_gui->aboutButton ->setIcon( KIcon( Amarok::icon( "info" ) ) );
205 QSize sz = sizeHint();
206 setMinimumSize( qMax( 350, sz.width() ), qMax( 250, sz.height() ) );
207 resize( sizeHint() );
209 //FIXME: contex tbrowser changes
210 // connect( this, SIGNAL(lyricsScriptChanged()), ContextBrowser::instance(), SLOT( lyricsScriptChanged() ) );
212 // Delay this call via eventloop, because it's a bit slow and would block
213 QTimer::singleShot( 0, this, SLOT( findScripts() ) );
217 ScriptManager::~ScriptManager()
219 DEBUG_BLOCK
221 QStringList runningScripts;
222 foreach( const QString &key, m_scripts.keys() ) {
223 if( m_scripts[key].process ) {
224 terminateProcess( &m_scripts[key].process );
225 runningScripts << key;
229 // Save config
230 KConfigGroup config = Amarok::config( "ScriptManager" );
231 config.writeEntry( "Running Scripts", runningScripts );
233 // Save the open/closed state of the category items
234 config.writeEntry( "General category open", m_generalCategory->isExpanded() );
235 config.writeEntry( "Lyrics category open", m_lyricsCategory->isExpanded() );
236 config.writeEntry( "Score category open", m_scoreCategory->isExpanded() );
237 config.writeEntry( "Transcode category open", m_transcodeCategory->isExpanded() );
238 config.writeEntry( "Context category open", m_contextCategory->isExpanded() );
239 s_instance = 0;
243 ////////////////////////////////////////////////////////////////////////////////
244 // public
245 ////////////////////////////////////////////////////////////////////////////////
247 bool
248 ScriptManager::runScript( const QString& name, bool silent )
250 if( !m_scripts.contains( name ) )
251 return false;
253 m_gui->treeWidget->setCurrentItem( m_scripts[name].li );
254 return slotRunScript( silent );
258 bool
259 ScriptManager::stopScript( const QString& name )
261 if( !m_scripts.contains( name ) )
262 return false;
264 m_gui->treeWidget->setCurrentItem( m_scripts[name].li );
265 slotStopScript();
267 return true;
271 QStringList
272 ScriptManager::listRunningScripts()
274 QStringList runningScripts;
275 foreach( const QString &key, m_scripts.keys() )
276 if( m_scripts[key].process )
277 runningScripts << key;
279 return runningScripts;
283 void
284 ScriptManager::customMenuClicked( const QString& message )
286 notifyScripts( "customMenuClicked: " + message );
290 QString
291 ScriptManager::specForScript( const QString& name )
293 if( !m_scripts.contains( name ) )
294 return QString();
295 QFileInfo info( m_scripts[name].url.path() );
296 const QString specPath = info.path() + '/' + info.completeBaseName() + ".spec";
298 return specPath;
302 void
303 ScriptManager::notifyFetchLyrics( const QString& artist, const QString& title )
305 const QString args = QUrl::toPercentEncoding( artist ) + ' ' + QUrl::toPercentEncoding( title );
306 notifyScripts( "fetchLyrics " + args );
310 void
311 ScriptManager::notifyFetchLyricsByUrl( const QString& url )
313 notifyScripts( "fetchLyricsByUrl " + url );
317 void ScriptManager::notifyTranscode( const QString& srcUrl, const QString& filetype )
319 notifyScripts( "transcode " + srcUrl + ' ' + filetype );
323 void
324 ScriptManager::requestNewScore( const QString &url, double prevscore, int playcount, int length, float percentage, const QString &reason )
326 const QString script = ensureScoreScriptRunning();
327 if( script.isNull() )
329 Amarok::ContextStatusBar::instance()->longMessage(
330 i18n( "No score scripts were found, or none of them worked. Automatic scoring will be disabled. Sorry." ),
331 KDE::StatusBar::Sorry );
332 return;
335 m_scripts[script].process->writeStdin(
336 QString( "requestNewScore %6 %1 %2 %3 %4 %5" )
337 .arg( prevscore )
338 .arg( playcount )
339 .arg( length )
340 .arg( percentage )
341 .arg( reason )
342 .arg( QString( QUrl::toPercentEncoding( url ) ) ) ); //last because it might have %s
345 ////////////////////////////////////////////////////////////////////////////////
346 // private slots
347 ////////////////////////////////////////////////////////////////////////////////
349 void
350 ScriptManager::findScripts() //SLOT
352 const QStringList allFiles = KGlobal::dirs()->findAllResources( "data", "amarok/scripts/*",KStandardDirs::Recursive );
354 // Add found scripts to treeWidget:
355 foreach( const QString &str, allFiles )
356 if( QFileInfo( str ).isExecutable() )
357 loadScript( str );
359 // Handle auto-run:
361 KConfigGroup config = Amarok::config( "ScriptManager" );
362 const QStringList runningScripts = config.readEntry( "Running Scripts", QStringList() );
364 foreach( const QString &str, runningScripts )
365 if( m_scripts.contains( str ) ) {
366 debug() << "Auto-running script: " << str;
367 m_gui->treeWidget->setCurrentItem( m_scripts[str].li );
368 slotRunScript();
371 //FIXME m_gui->treeWidget->setCurrentItem( m_gui->treeWidget->firstChild() );
372 slotCurrentChanged( m_gui->treeWidget->currentItem() );
376 void
377 ScriptManager::slotCurrentChanged( QTreeWidgetItem* item )
379 const bool isCategory = item == m_generalCategory ||
380 item == m_lyricsCategory ||
381 item == m_scoreCategory ||
382 item == m_transcodeCategory;
384 if( item && !isCategory ) {
385 const QString name = item->text( 0 );
386 m_gui->uninstallButton->setEnabled( true );
387 m_gui->runButton->setEnabled( !m_scripts[name].process );
388 m_gui->stopButton->setEnabled( m_scripts[name].process );
389 m_gui->configureButton->setEnabled( m_scripts[name].process );
390 m_gui->aboutButton->setEnabled( true );
392 else {
393 m_gui->uninstallButton->setEnabled( false );
394 m_gui->runButton->setEnabled( false );
395 m_gui->stopButton->setEnabled( false );
396 m_gui->configureButton->setEnabled( false );
397 m_gui->aboutButton->setEnabled( false );
402 bool
403 ScriptManager::slotInstallScript( const QString& path )
405 QString _path = path;
407 if( path.isNull() ) {
408 _path = KFileDialog::getOpenFileName( KUrl(),
409 "*.amarokscript.tar *.amarokscript.tar.bz2 *.amarokscript.tar.gz|"
410 + i18n( "Script Packages (*.amarokscript.tar, *.amarokscript.tar.bz2, *.amarokscript.tar.gz)" )
411 , this );
412 if( _path.isNull() ) return false;
415 KTar archive( _path );
416 if( !archive.open( QIODevice::ReadOnly ) ) {
417 KMessageBox::sorry( 0, i18n( "Could not read this package." ) );
418 return false;
421 QString destination = Amarok::saveLocation( "scripts/" );
422 const KArchiveDirectory* const archiveDir = archive.directory();
424 // Prevent installing a script that's already installed
425 const QString scriptFolder = destination + archiveDir->entries().first();
426 if( QFile::exists( scriptFolder ) ) {
427 KMessageBox::error( 0, i18n( "A script with the name '%1' is already installed. "
428 "Please uninstall it first.", archiveDir->entries().first() ) );
429 return false;
432 archiveDir->copyTo( destination );
433 m_installSuccess = false;
434 recurseInstall( archiveDir, destination );
436 if( m_installSuccess ) {
437 KMessageBox::information( 0, i18n( "Script successfully installed." ) );
438 return true;
440 else {
441 KMessageBox::sorry( 0, i18n( "<p>Script installation failed.</p>"
442 "<p>The package did not contain an executable file. "
443 "Please inform the package maintainer about this error.</p>" ) );
445 // Delete directory recursively
446 KIO::NetAccess::del( KUrl( scriptFolder ), 0 );
449 return false;
453 void
454 ScriptManager::recurseInstall( const KArchiveDirectory* archiveDir, const QString& destination )
456 const QStringList entries = archiveDir->entries();
458 foreach( const QString &entry, entries ) {
459 const KArchiveEntry* const archEntry = archiveDir->entry( entry );
461 if( archEntry->isDirectory() ) {
462 const KArchiveDirectory* const dir = static_cast<const KArchiveDirectory*>( archEntry );
463 recurseInstall( dir, destination + entry + '/' );
465 else {
466 ::chmod( QFile::encodeName( destination + entry ), archEntry->permissions() );
468 if( QFileInfo( destination + entry ).isExecutable() ) {
469 loadScript( destination + entry );
470 m_installSuccess = true;
477 void
478 ScriptManager::slotRetrieveScript()
480 #if 0 //FIXME: PORT To KNS2
481 // Delete KNewStuff's configuration entries. These entries reflect which scripts
482 // are already installed. As we cannot yet keep them in sync after uninstalling
483 // scripts, we deactivate the check marks entirely.
484 Amarok::config()->deleteGroup( "KNewStuffStatus" );
486 // we need this because KNewStuffGeneric's install function isn't clever enough
487 AmarokScriptNewStuff *kns = new AmarokScriptNewStuff( "amarok/script", this );
488 KNS::Engine *engine = new KNS::Engine( kns, "amarok/script", this );
489 KNS::DownloadDialog *d = new KNS::DownloadDialog( engine, this );
490 d->setType( "amarok/script" );
491 // you have to do this by hand when providing your own Engine
492 KNS::ProviderLoader *p = new KNS::ProviderLoader( this );
493 QObject::connect( p, SIGNAL( providersLoaded(Provider::List*) ), d, SLOT( slotProviders (Provider::List *) ) );
494 p->load( "amarok/script", "http://amarok.kde.org/knewstuff/amarokscripts-providers.xml" );
496 d->exec();
497 #endif
501 void
502 ScriptManager::slotUninstallScript()
504 const QString name = m_gui->treeWidget->currentItem()->text( 0 );
506 if( KMessageBox::warningContinueCancel( this, i18n( "Are you sure you want to uninstall the script '%1'?", name ), i18n("Uninstall Script"), KGuiItem( i18n("Uninstall") ) ) == KMessageBox::Cancel )
507 return;
509 if( m_scripts.find( name ) == m_scripts.end() )
510 return;
512 const QString directory = m_scripts[name].url.directory();
514 // Delete directory recursively
515 const KUrl url = KUrl( directory );
516 if( !KIO::NetAccess::del( url, 0 ) ) {
517 KMessageBox::sorry( 0, i18n( "<p>Could not uninstall this script.</p><p>The ScriptManager can only uninstall scripts which have been installed as packages.</p>" ) );
518 return;
521 QStringList keys;
523 // Find all scripts that were in the uninstalled folder
524 foreach( const QString &key, m_scripts.keys() )
525 if( m_scripts[key].url.directory() == directory )
526 keys << key;
528 // Terminate script processes, remove entries from script list
529 foreach( const QString &key, keys ) {
530 delete m_scripts[key].li;
531 terminateProcess( &m_scripts[key].process );
532 m_scripts.remove( key );
537 bool
538 ScriptManager::slotRunScript( bool silent )
540 if( !m_gui->runButton->isEnabled() ) return false;
542 QTreeWidgetItem* const li = m_gui->treeWidget->currentItem();
543 const QString name = li->text( 0 );
545 if( m_scripts[name].type == "lyrics" && lyricsScriptRunning() != QString::null ) {
546 if( !silent )
547 KMessageBox::sorry( 0, i18n( "Another lyrics script is already running. "
548 "You may only run one lyrics script at a time." ) );
549 return false;
552 if( m_scripts[name].type == "transcode" && transcodeScriptRunning() != QString::null ) {
553 if( !silent )
554 KMessageBox::sorry( 0, i18n( "Another transcode script is already running. "
555 "You may only run one transcode script at a time." ) );
556 return false;
559 // Don't start a script twice
560 if( m_scripts[name].process ) return false;
562 ProcIO* script = new ProcIO();
563 script->setOutputChannelMode( ProcIO::SeparateChannels );
564 const KUrl url = m_scripts[name].url;
565 *script << url.path();
566 script->setWorkingDirectory( Amarok::saveLocation( "scripts-data/" ) );
568 connect( script, SIGNAL( receivedStderr( Process* ) ), SLOT( slotReceivedStderr( Process* ) ) );
569 connect( script, SIGNAL( receivedStdout( Process* ) ), SLOT( slotReceivedStdout( Process* ) ) );
570 connect( script, SIGNAL( processExited( Process* ) ), SLOT( scriptFinished( Process* ) ) );
572 script->start( );
573 if( script->error() != ProcIO::FailedToStart )
575 if( m_scripts[name].type == "score" && !scoreScriptRunning().isNull() )
577 stopScript( scoreScriptRunning() );
578 m_gui->treeWidget->setCurrentItem( li );
580 AmarokConfig::setLastScoreScript( name );
582 else
584 if( !silent )
585 KMessageBox::sorry( 0, i18n( "<p>Could not start the script <i>%1</i>.</p>"
586 "<p>Please make sure that the file has execute (+x) permissions.</p>", name ) );
587 delete script;
588 return false;
591 li->setIcon( 0, SmallIcon( Amarok::icon( "play" ) ) );
592 debug() << "Running script: " << url.path();
594 m_scripts[name].process = script;
595 slotCurrentChanged( m_gui->treeWidget->currentItem() );
596 if( m_scripts[name].type == "lyrics" )
597 emit lyricsScriptChanged();
599 return true;
603 void
604 ScriptManager::slotStopScript()
606 QTreeWidgetItem* const li = m_gui->treeWidget->currentItem();
607 const QString name = li->text( 0 );
609 // Just a sanity check
610 if( m_scripts.find( name ) == m_scripts.end() )
611 return;
613 terminateProcess( &m_scripts[name].process );
614 m_scripts[name].log = QString::null;
615 slotCurrentChanged( m_gui->treeWidget->currentItem() );
617 li->setIcon( 0, QPixmap() );
621 void
622 ScriptManager::slotConfigureScript()
624 const QString name = m_gui->treeWidget->currentItem()->text( 0 );
625 if( !m_scripts[name].process ) return;
627 const KUrl url = m_scripts[name].url;
628 QDir::setCurrent( url.directory() );
630 m_scripts[name].process->writeStdin( QString("configure") );
634 void
635 ScriptManager::slotAboutScript()
637 const QString name = m_gui->treeWidget->currentItem()->text( 0 );
638 QFile readme( m_scripts[name].url.directory( KUrl::AppendTrailingSlash ) + "README" );
639 QFile license( m_scripts[name].url.directory( KUrl::AppendTrailingSlash) + "COPYING" );
641 if( !readme.open( QIODevice::ReadOnly ) ) {
642 KMessageBox::sorry( 0, i18n( "There is no information available for this script." ) );
643 return;
646 KAboutData aboutData( name.toLatin1(), 0, ki18n(name.toLatin1()), "1.0", ki18n(readme.readAll()) );
648 KAboutApplicationDialog* about = new KAboutApplicationDialog( &aboutData, this );
649 about->setButtons( KDialog::Ok );
650 about->setDefaultButton( KDialog::Ok );
652 kapp->setTopWidget( about );
653 about->setCaption( KDialog::makeStandardCaption( i18n( "About %1", name ) ) );
655 about->setInitialSize( QSize( 500, 350 ) );
656 about->show();
660 void
661 ScriptManager::slotShowContextMenu( const QPoint& pos )
663 QTreeWidgetItem* item = m_gui->treeWidget->itemAt( pos );
665 const bool isCategory = item == m_generalCategory ||
666 item == m_lyricsCategory ||
667 item == m_scoreCategory ||
668 item == m_transcodeCategory ||
669 item == m_contextCategory;
671 if( !item || isCategory ) return;
673 // Find the script entry in our map
674 QString key;
675 foreach( key, m_scripts.keys() )
676 if( m_scripts[key].li == item ) break;
678 enum { SHOW_LOG, EDIT };
679 KMenu menu;
680 menu.addTitle( i18n( "Debugging" ) );
681 QAction* logAction = menu.addAction( KIcon( Amarok::icon( "clock" ) ), i18n( "Show Output &Log" ) );
682 QAction* editAction = menu.addAction( KIcon( Amarok::icon( "edit" ) ), i18n( "&Edit" ) );
683 logAction->setData( SHOW_LOG );
684 editAction->setData( EDIT );
686 logAction->setEnabled( m_scripts[key].process != 0 );
688 QAction* choice = menu.exec( mapToGlobal( pos ) );
689 if( !choice ) return;
690 const int id = choice->data().toInt();
692 switch( id )
694 case EDIT:
695 KRun::runCommand( "kwrite " + m_scripts[key].url.path(), 0 );
696 break;
698 case SHOW_LOG:
699 QString line;
700 while( m_scripts[key].process->readln( line ) != -1 )
701 m_scripts[key].log += line;
703 KTextEdit* editor = new KTextEdit( m_scripts[key].log );
704 kapp->setTopWidget( editor );
705 editor->setWindowTitle( KDialog::makeStandardCaption( i18n( "Output Log for %1" ).arg( key ) ) );
706 editor->setReadOnly( true );
708 QFont font( "fixed" );
709 font.setFixedPitch( true );
710 font.setStyleHint( QFont::TypeWriter );
711 editor->setFont( font );
713 editor->resize( 500, 380 );
714 editor->show();
715 break;
720 /* This is just a workaround, some scripts crash for some people if stdout is not handled. */
721 void
722 ScriptManager::slotReceivedStdout( Process *process )
724 debug() << QString::fromLatin1( process->readAllStandardOutput() );
728 void
729 ScriptManager::slotReceivedStderr( Process* process )
731 // Look up script entry in our map
732 ScriptMap::Iterator it;
733 ScriptMap::Iterator end( m_scripts.end() );
734 for( it = m_scripts.begin(); it != end; ++it )
735 if( it.value().process == process ) break;
737 const QString text = QString::fromLatin1( process->readAllStandardError() );
738 error() << it.key() << ":\n" << text;
740 if( it.value().log.length() > 20000 )
741 it.value().log = "==== LOG TRUNCATED HERE ====\n";
742 it.value().log += text;
746 void
747 ScriptManager::scriptFinished( Process* process ) //SLOT
749 // Look up script entry in our map
750 ScriptMap::Iterator it;
751 ScriptMap::Iterator end( m_scripts.end() );
752 for( it = m_scripts.begin(); it != end; ++it )
753 if( it.value().process == process ) break;
755 // Check if there was an error on exit
756 if( process->error() != Process::Crashed && process->exitStatus() != 0 )
757 KMessageBox::detailedError( 0, i18n( "The script '%1' exited with error code: %2", it.key(), process->exitStatus() )
758 ,it.value().log );
760 // Destroy script process
761 delete it.value().process;
762 it.value().process = 0;
763 it.value().log.clear();
764 it.value().li->setIcon( 0, QPixmap() );
765 slotCurrentChanged( m_gui->treeWidget->currentItem() );
769 ////////////////////////////////////////////////////////////////////////////////
770 // private
771 ////////////////////////////////////////////////////////////////////////////////
773 QStringList
774 ScriptManager::scriptsOfType( const QString &type ) const
776 QStringList scripts;
777 foreach( const QString &key, m_scripts.keys() )
778 if( m_scripts[key].type == type )
779 scripts += key;
781 return scripts;
785 QString
786 ScriptManager::scriptRunningOfType( const QString &type ) const
788 foreach( const QString &key, m_scripts.keys() )
789 if( m_scripts[key].process && m_scripts[key].type == type )
790 return key;
792 return QString();
796 QString
797 ScriptManager::ensureScoreScriptRunning()
799 QString s = scoreScriptRunning();
800 if( !s.isNull() )
801 return s;
803 if( runScript( AmarokConfig::lastScoreScript(), true /*silent*/ ) )
804 return AmarokConfig::lastScoreScript();
806 const QString def = i18n( "Score" ) + ": " + "Default";
807 if( runScript( def, true ) )
808 return def;
810 const QStringList scripts = scoreScripts();
811 for( QStringList::const_iterator it = scripts.begin(), end = scripts.end(); it != end; ++it )
812 if( runScript( *it, true ) )
813 return *it;
815 return QString();
819 void
820 ScriptManager::terminateProcess( ProcIO** proc )
822 if( *proc ) {
823 (*proc)->kill(); // Sends SIGTERM
825 delete *proc;
826 *proc = 0;
831 void
832 ScriptManager::notifyScripts( const QString& message )
834 foreach( const ScriptItem &item, m_scripts ) {
835 ProcIO* const proc = item.process;
836 if( proc ) proc->writeStdin( message );
841 void
842 ScriptManager::loadScript( const QString& path )
844 if( !path.isEmpty() ) {
845 const KUrl url = KUrl( path );
846 QString name = url.fileName();
847 QString type = "generic";
849 // Read and parse .spec file, if exists
850 QFileInfo info( path );
851 QTreeWidgetItem* li = 0;
852 const QString specPath = info.path() + '/' + info.completeBaseName() + ".spec";
853 if( QFile::exists( specPath ) ) {
854 debug() << "Spec file found: " << specPath;
855 QSettings spec( specPath, QSettings::IniFormat );
856 if( spec.contains( "name" ) )
857 name = spec.value( "name" ).toString();
858 if( spec.contains( "type" ) ) {
859 type = spec.value( "type" ).toString();
860 if( type == "lyrics" ) {
861 li = new QTreeWidgetItem( m_lyricsCategory );
862 li->setText( 0, name );
864 if( type == "transcode" ) {
865 li = new QTreeWidgetItem( m_transcodeCategory );
866 li->setText( 0, name );
868 if( type == "score" ) {
869 li = new QTreeWidgetItem( m_scoreCategory );
870 li->setText( 0, name );
872 if( type == "context" ) {
873 li = new QTreeWidgetItem( m_contextCategory );
874 li->setText( 0, name );
879 if( !li ) {
880 li = new QTreeWidgetItem( m_generalCategory );
881 li->setText( 0, name );
884 li->setIcon( 0, QPixmap() );
886 ScriptItem item;
887 item.url = url;
888 item.type = type;
889 item.process = 0;
890 item.li = li;
892 m_scripts[name] = item;
893 debug() << "Loaded: " << name;
895 slotCurrentChanged( m_gui->treeWidget->currentItem() );
900 void
901 ScriptManager::engineStateChanged( Engine::State state, Engine::State /*oldState*/ )
903 switch( state )
905 case Engine::Empty:
906 notifyScripts( "engineStateChange: empty" );
907 break;
909 case Engine::Idle:
910 notifyScripts( "engineStateChange: idle" );
911 break;
913 case Engine::Paused:
914 notifyScripts( "engineStateChange: paused" );
915 break;
917 case Engine::Playing:
918 notifyScripts( "engineStateChange: playing" );
919 break;
924 void
925 ScriptManager::engineNewMetaData( const MetaBundle& /*bundle*/, bool /*trackChanged*/ )
927 notifyScripts( "trackChange" );
931 void
932 ScriptManager::engineVolumeChanged( int newVolume )
934 notifyScripts( "volumeChange: " + QString::number( newVolume ) );
938 #include "scriptmanager.moc"